From b05cac7d5c6ff9f5abb9d431350a9557df6d2592 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 08:57:54 +0100 Subject: [PATCH 01/12] Add core metrics plugin --- lib/plugins/metrics/metrics.js | 40 +++++++++++++++++++++++++++++ lib/plugins/metrics/metrics.test.js | 34 ++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 lib/plugins/metrics/metrics.js create mode 100644 lib/plugins/metrics/metrics.test.js diff --git a/lib/plugins/metrics/metrics.js b/lib/plugins/metrics/metrics.js new file mode 100644 index 000000000..f5be29d97 --- /dev/null +++ b/lib/plugins/metrics/metrics.js @@ -0,0 +1,40 @@ +'use strict'; + +class Metrics { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + + this.commands = { + metrics: { + usage: 'Show metrics for a specific function', + lifecycleEvents: [ + 'metrics', + ], + options: { + function: { + usage: 'The function name', + required: true, + shortcut: 'f', + }, + stage: { + usage: 'Stage of the service', + shortcut: 's', + }, + region: { + usage: 'Region of the service', + shortcut: 'r', + }, + startTime: { + usage: 'Start time for the metrics retrieval', + }, + endTime: { + usage: 'End time for the metrics retrieval', + }, + }, + }, + }; + } +} + +module.exports = Metrics; diff --git a/lib/plugins/metrics/metrics.test.js b/lib/plugins/metrics/metrics.test.js new file mode 100644 index 000000000..b80323535 --- /dev/null +++ b/lib/plugins/metrics/metrics.test.js @@ -0,0 +1,34 @@ +'use strict'; + +const expect = require('chai').expect; +const Metrics = require('./metrics'); +const Serverless = require('../../Serverless'); + +describe('Metrics', () => { + let metrics; + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + const options = {}; + metrics = new Metrics(serverless, options); + }); + + describe('#constructor()', () => { + it('should have the command "metrics"', () => { + // eslint-disable-next-line no-unused-expressions + expect(metrics.commands.metrics).to.not.be.undefined; + }); + + it('should have a lifecycle events "metrics"', () => { + expect(metrics.commands.metrics.lifecycleEvents).to.deep.equal([ + 'metrics', + ]); + }); + + it('should have a required option "function"', () => { + // eslint-disable-next-line no-unused-expressions + expect(metrics.commands.metrics.options.function.required).to.be.true; + }); + }); +}); From 83900b362b3198e71fc18de6d1ec8ca10a13065a Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 10:33:57 +0100 Subject: [PATCH 02/12] Add AwsMetrics plugin --- lib/plugins/aws/metrics/awsMetrics.js | 210 +++++++++++++++ lib/plugins/aws/metrics/awsMetrics.test.js | 297 +++++++++++++++++++++ 2 files changed, 507 insertions(+) create mode 100644 lib/plugins/aws/metrics/awsMetrics.js create mode 100644 lib/plugins/aws/metrics/awsMetrics.test.js diff --git a/lib/plugins/aws/metrics/awsMetrics.js b/lib/plugins/aws/metrics/awsMetrics.js new file mode 100644 index 000000000..defb7b156 --- /dev/null +++ b/lib/plugins/aws/metrics/awsMetrics.js @@ -0,0 +1,210 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const chalk = require('chalk'); +const _ = require('lodash'); +const moment = require('moment'); +const validate = require('../lib/validate'); + +class AwsMetrics { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('aws'); + + this.commands = { + metrics: { + lifecycleEvents: [ + 'metrics', + ], + options: { + period: { + usage: 'The granularity, in seconds, of the returned data points', + shortcut: 'p', + }, + }, + }, + }; + + Object.assign(this, validate); + + this.hooks = { + 'metrics:metrics': () => BbPromise.bind(this) + .then(this.extendedValidate) + .then(this.getMetrics) + .then(this.showMetrics), + }; + } + + extendedValidate() { + this.validate(); + + // validate function exists in service + this.options.function = this.serverless.service.getFunction(this.options.function).name; + + const today = new Date(); + let yesterday = new Date(); + yesterday = yesterday.setDate(yesterday.getDate() - 1); + yesterday = new Date(yesterday); + + this.options.startTime = this.options.startTime || yesterday; + this.options.endTime = this.options.endTime || today; + this.options.period = this.options.period || 60 * 60 * 24; // (1 day) + + return BbPromise.resolve(); + } + + getMetrics() { + const FunctionName = this.options.function; + const StartTime = this.options.startTime; + const EndTime = this.options.endTime; + const Period = this.options.period; + const Namespace = 'AWS/Lambda'; + + const promises = []; + + // get invocations + const invocationsPromise = this.provider.request('CloudWatch', + 'getMetricStatistics', + { + StartTime, + EndTime, + MetricName: 'Invocations', + Namespace, + Period, + Dimensions: [ + { + Name: 'FunctionName', + Value: FunctionName, + }, + ], + Statistics: [ + 'Sum', + ], + Unit: 'Count', + }, + this.options.stage, + this.options.region + ); + // get throttles + const throttlesPromise = this.provider.request('CloudWatch', + 'getMetricStatistics', + { + StartTime, + EndTime, + MetricName: 'Throttles', + Namespace, + Period, + Dimensions: [ + { + Name: 'FunctionName', + Value: FunctionName, + }, + ], + Statistics: [ + 'Sum', + ], + Unit: 'Count', + }, + this.options.stage, + this.options.region + ); + // get errors + const errorsPromise = this.provider.request('CloudWatch', + 'getMetricStatistics', + { + StartTime, + EndTime, + MetricName: 'Errors', + Namespace, + Period, + Dimensions: [ + { + Name: 'FunctionName', + Value: FunctionName, + }, + ], + Statistics: [ + 'Sum', + ], + Unit: 'Count', + }, + this.options.stage, + this.options.region + ); + // get avg. duration + const avgDurationPromise = this.provider.request('CloudWatch', + 'getMetricStatistics', + { + StartTime, + EndTime, + MetricName: 'Duration', + Namespace, + Period, + Dimensions: [ + { + Name: 'FunctionName', + Value: FunctionName, + }, + ], + Statistics: [ + 'Average', + ], + Unit: 'Milliseconds', + }, + this.options.stage, + this.options.region + ); + + // push all promises to the array which will be used to resolve those + promises.push(invocationsPromise); + promises.push(throttlesPromise); + promises.push(errorsPromise); + promises.push(avgDurationPromise); + + return BbPromise.all(promises).then((metrics) => metrics); + } + + showMetrics(metrics) { + let message = ''; + + message += `${chalk.yellow.underline(this.options.function)}\n`; + + const formattedStartTime = moment(this.options.startTime).format('LLL'); + const formattedEndTime = moment(this.options.endTime).format('LLL'); + message += `${formattedStartTime} - ${formattedEndTime}\n\n`; + + if (metrics && metrics.length > 0) { + _.forEach(metrics, (metric) => { + if (metric.Label === 'Invocations') { + const datapoints = metric.Datapoints; + const invocations = datapoints + .reduce((previous, datapoint) => previous + datapoint.Sum, 0); + message += `${chalk.yellow('Invocations:', invocations, '\n')}`; + } else if (metric.Label === 'Throttles') { + const datapoints = metric.Datapoints; + const throttles = datapoints + .reduce((previous, datapoint) => previous + datapoint.Sum, 0); + message += `${chalk.yellow('Throttles:', throttles, '\n')}`; + } else if (metric.Label === 'Errors') { + const datapoints = metric.Datapoints; + const errors = datapoints + .reduce((previous, datapoint) => previous + datapoint.Sum, 0); + message += `${chalk.yellow('Errors:', errors, '\n')}`; + } else { + const datapoints = metric.Datapoints; + const duration = datapoints + .reduce((previous, datapoint) => previous + datapoint.Average, 0); + const formattedRoundedAvgDuration = `${Math.round(duration * 100) / 100}ms`; + message += `${chalk.yellow('Duration (avg.):', formattedRoundedAvgDuration)}`; + } + }); + } else { + message += `${chalk.yellow('There are no metrics to show for these options')}`; + } + this.serverless.cli.consoleLog(message); + return BbPromise.resolve(message); + } +} + +module.exports = AwsMetrics; diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js new file mode 100644 index 000000000..66efff68a --- /dev/null +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -0,0 +1,297 @@ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const BbPromise = require('bluebird'); +const AwsProvider = require('../provider/awsProvider'); +const AwsMetrics = require('./awsMetrics'); +const Serverless = require('../../../Serverless'); +const CLI = require('../../../classes/CLI'); +const chalk = require('chalk'); + +describe('AwsMetrics', () => { + let awsMetrics; + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + serverless.cli = new CLI(serverless); + serverless.setProvider('aws', new AwsProvider(serverless)); + const options = { + stage: 'dev', + region: 'us-east-1', + }; + awsMetrics = new AwsMetrics(serverless, options); + }); + + describe('#constructor()', () => { + it('should set the serverless instance to this.serverless', () => { + expect(awsMetrics.serverless).to.deep.equal(serverless); + }); + + it('should set the passed in options to this.options', () => { + expect(awsMetrics.options).to.deep.equal({ stage: 'dev', region: 'us-east-1' }); + }); + + it('should set the provider variable to the AwsProvider instance', () => + expect(awsMetrics.provider).to.be.instanceof(AwsProvider)); + + it('should have the command "metrics"', () => { + // eslint-disable-next-line no-unused-expressions + expect(awsMetrics.commands.metrics).to.not.be.undefined; + }); + + it('should have a lifecycle events "metrics"', () => { + expect(awsMetrics.commands.metrics.lifecycleEvents).to.deep.equal([ + 'metrics', + ]); + }); + + it('should have the option "period"', () => { + expect(awsMetrics.commands.metrics.options.period).to.not.equal(undefined); + }); + + it('should have a "metrics:metrics" hook', () => { + // eslint-disable-next-line no-unused-expressions + expect(awsMetrics.hooks['metrics:metrics']).to.not.be.undefined; + }); + + it('should run promise chain in order for "metrics:metrics" hook', () => { + const extendedValidateStub = sinon + .stub(awsMetrics, 'extendedValidate').returns(BbPromise.resolve()); + const getMetricsStub = sinon + .stub(awsMetrics, 'getMetrics').returns(BbPromise.resolve()); + const showMetricsStub = sinon + .stub(awsMetrics, 'showMetrics').returns(BbPromise.resolve()); + + return awsMetrics.hooks['metrics:metrics']().then(() => { + expect(extendedValidateStub.calledOnce).to.equal(true); + expect(getMetricsStub.calledAfter(extendedValidateStub)).to.equal(true); + expect(showMetricsStub.calledAfter(getMetricsStub)).to.equal(true); + + awsMetrics.extendedValidate.restore(); + awsMetrics.getMetrics.restore(); + awsMetrics.showMetrics.restore(); + }); + }); + }); + + describe('#extendedValidate()', () => { + let validateStub; + + beforeEach(() => { + awsMetrics.serverless.service.functions = { + function1: {}, + }; + awsMetrics.serverless.service.service = 'my-service'; + awsMetrics.options.function = 'function1'; + validateStub = sinon + .stub(awsMetrics, 'validate').returns(BbPromise.resolve); + }); + + afterEach(() => { + awsMetrics.validate.restore(); + }); + + it('should call the shared validate() function', () => + awsMetrics.extendedValidate().then(() => { + expect(validateStub.calledOnce).to.equal(true); + }) + ); + + it('should set the startTime to a default value if not provided', () => { + awsMetrics.options.startTime = null; + + return awsMetrics.extendedValidate().then(() => { + expect(String(awsMetrics.options.startTime)).to.have.length.above(0); + }); + }); + + it('should set the startTime to the provided value', () => { + awsMetrics.options.startTime = '1970-01-01'; + + return awsMetrics.extendedValidate().then(() => { + expect(awsMetrics.options.startTime).to.equal('1970-01-01'); + }); + }); + + it('should set the endTime to a default value if not provided', () => { + awsMetrics.options.endTime = null; + + return awsMetrics.extendedValidate().then(() => { + expect(String(awsMetrics.options.endTime)).to.have.length.above(0); + }); + }); + + it('should set the endTime to the provided value', () => { + awsMetrics.options.endTime = '1970-01-02'; + + return awsMetrics.extendedValidate().then(() => { + expect(awsMetrics.options.endTime).to.equal('1970-01-02'); + }); + }); + + it('should set the period to 86400 if not provided', () => { + awsMetrics.options.period = null; + + return awsMetrics.extendedValidate().then(() => { + expect(awsMetrics.options.period).to.equal(86400); + }); + }); + + it('should set the period to the provided value', () => { + awsMetrics.options.period = 4711; + + return awsMetrics.extendedValidate().then(() => { + expect(awsMetrics.options.period).to.equal(4711); + }); + }); + }); + + describe('#getMetrics()', () => { + let requestStub; + + beforeEach(() => { + awsMetrics.options.function = 'function1'; + awsMetrics.options.startTime = '1970-01-01'; + awsMetrics.options.endTime = '1970-01-02'; + awsMetrics.options.period = '4711'; + requestStub = sinon.stub(awsMetrics.provider, 'request'); + }); + + afterEach(() => { + awsMetrics.provider.request.restore(); + }); + + it('should should gather metrics for the function', () => { + // invocations + requestStub.onCall(0).returns( + BbPromise.resolve({ + ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755' }, + Label: 'Invocations', + Datapoints: [], + }) + ); + // throttles + requestStub.onCall(1).returns( + BbPromise.resolve({ + ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2' }, + Label: 'Throttles', + Datapoints: [], + }) + ); + // errors + requestStub.onCall(2).returns( + BbPromise.resolve({ + ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b' }, + Label: 'Errors', + Datapoints: [], + }) + ); + // duration + requestStub.onCall(3).returns( + BbPromise.resolve({ + ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164' }, + Label: 'Duration', + Datapoints: [], + }) + ); + + const expectedResult = [ + { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755' }, + Label: 'Invocations', + Datapoints: [], + }, + { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2' }, + Label: 'Throttles', + Datapoints: [], + }, + { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b' }, + Label: 'Errors', + Datapoints: [], + }, + { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164' }, + Label: 'Duration', + Datapoints: [], + }, + ]; + + return awsMetrics.getMetrics().then((result) => { + expect(result).to.deep.equal(expectedResult); + }); + }); + }); + + describe('#showMetrics()', () => { + let consoleLogStub; + + beforeEach(() => { + awsMetrics.options.function = 'function1'; + awsMetrics.options.startTime = '1970-01-01'; + awsMetrics.options.endTime = '1970-01-02'; + consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns(); + }); + + afterEach(() => { + serverless.cli.consoleLog.restore(); + }); + + it('should display all metrics for the given function', () => { + const metrics = [ + { + ResponseMetadata: { + RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755', + }, + Label: 'Invocations', + Datapoints: [{ Sum: 12 }, { Sum: 8 }], + }, + { + ResponseMetadata: { + RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2', + }, + Label: 'Throttles', + Datapoints: [{ Sum: 15 }, { Sum: 15 }], + }, + { + ResponseMetadata: { + RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b', + }, + Label: 'Errors', + Datapoints: [{ Sum: 0 }], + }, + { + ResponseMetadata: { + RequestId: '1f63db14-b569-11e6-8501-d98a275ce164', + }, + Label: 'Duration', + Datapoints: [{ Average: 1000 }], + }, + ]; + + let expectedMessage = ''; + expectedMessage += `${chalk.yellow.underline(awsMetrics.options.function)}\n`; + expectedMessage += 'January 1, 1970 12:00 AM - January 2, 1970 12:00 AM\n\n'; + expectedMessage += `${chalk.yellow('Invocations: 20 \n')}`; + expectedMessage += `${chalk.yellow('Throttles: 30 \n')}`; + expectedMessage += `${chalk.yellow('Errors: 0 \n')}`; + expectedMessage += `${chalk.yellow('Duration (avg.): 1000ms')}`; + + return awsMetrics.showMetrics(metrics).then((message) => { + expect(consoleLogStub.calledOnce).to.equal(true); + expect(message).to.equal(expectedMessage); + }); + }); + + it('should resolve with an error message if no metrics are available', () => { + let expectedMessage = ''; + expectedMessage += `${chalk.yellow.underline(awsMetrics.options.function)}\n`; + expectedMessage += 'January 1, 1970 12:00 AM - January 2, 1970 12:00 AM\n\n'; + expectedMessage += `${chalk.yellow('There are no metrics to show for these options')}`; + + return awsMetrics.showMetrics().then((message) => { + expect(consoleLogStub.calledOnce).to.equal(true); + expect(message).to.equal(expectedMessage); + }); + }); + }); +}); From ba27669d0d9e294702587ea0830e26d296f48b65 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 11:06:00 +0100 Subject: [PATCH 03/12] Add plugins to Plugins.json file --- lib/plugins/Plugins.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 7b7f0cc46..72617327e 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -7,6 +7,7 @@ "./invoke/invoke.js", "./info/info.js", "./logs/logs.js", + "./metrics/metrics.js", "./remove/remove.js", "./rollback/index.js", "./slstats/slstats.js", @@ -15,6 +16,7 @@ "./aws/invoke/index.js", "./aws/info/index.js", "./aws/logs/index.js", + "./aws/metrics/awsMetrics.js", "./aws/remove/index.js", "./aws/rollback/index.js", "./aws/deploy/compile/functions/index.js", From 2f4b510d81e9aabbb77f84f8cfde13e4047dc135 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 12:31:19 +0100 Subject: [PATCH 04/12] Add documentation --- docs/README.md | 1 + docs/providers/aws/cli-reference/info.md | 4 +- docs/providers/aws/cli-reference/metrics.md | 48 ++++++++++++++++++++ docs/providers/aws/cli-reference/remove.md | 2 +- docs/providers/aws/cli-reference/rollback.md | 2 +- docs/providers/aws/cli-reference/slstats.md | 2 +- 6 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 docs/providers/aws/cli-reference/metrics.md diff --git a/docs/README.md b/docs/README.md index bd56b4620..7570562de 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,6 +52,7 @@ The Serverless Framework allows you to deploy auto-scaling, pay-per-execution, e
  • Deploy
  • Invoke
  • Logs
  • +
  • Setup
  • Info
  • Rollback
  • Remove
  • diff --git a/docs/providers/aws/cli-reference/info.md b/docs/providers/aws/cli-reference/info.md index f066a2ac8..22eeac745 100644 --- a/docs/providers/aws/cli-reference/info.md +++ b/docs/providers/aws/cli-reference/info.md @@ -1,7 +1,7 @@ @@ -76,4 +76,4 @@ CreateThumbnailsLambdaFunctionArn: arn:aws:lambda:us-east-1:377024778620:functio TakeScreenshotLambdaFunctionArn: arn:aws:lambda:us-east-1:377024778620:function:lambda-screenshots-dev-takeScreenshot ServiceEndpoint: https://12341jc801.execute-api.us-east-1.amazonaws.com/dev ServerlessDeploymentBucketName: lambda-screenshots-dev-serverlessdeploymentbucket-15b7pkc04f98a -``` \ No newline at end of file +``` diff --git a/docs/providers/aws/cli-reference/metrics.md b/docs/providers/aws/cli-reference/metrics.md new file mode 100644 index 000000000..372f17b75 --- /dev/null +++ b/docs/providers/aws/cli-reference/metrics.md @@ -0,0 +1,48 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/metrics) + + +# Metrics + +Lets you watch the metrics of a specific function. + +```bash +serverless metrics --function hello +``` + +## Options + +- `--function` or `-f` The function you want to fetch the metrics for. **Required** +- `--stage` or `-s` The stage you want to view the function metrics for. If not provided, the plugin will use the default stage listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `dev` stage. +- `--region` or `-r` The region you want to view the function metrics for. If not provided, the plugin will use the default region listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `us-east-1` region. +- `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20` or `1469705761`). Must be a valid ISO 8601 format. +- `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Must be a valid ISO 8601 format. +- `--period` The granularity, in seconds, of the returned data points (defaults to 86400 (1 day)). + +## Examples + +**Note:** There's a small lag between invoking the function and actually having access to the metrics. It takes a few seconds for the metrics to show up right after invoking the function. + +### See all metrics of the last 24h + +```bash +serverless metrics --function hello +``` + +Displays all metrics for the last 24h. + +### See metrics for a specific timespan + +```bash +serverless metrics --function hello --startTime 1970-01-01 --endTime 1970-01-02 +``` + +Displays all metrics for the time between January 1, 1970 and January 2, 1970. diff --git a/docs/providers/aws/cli-reference/remove.md b/docs/providers/aws/cli-reference/remove.md index 57e8b954e..0e7e3e457 100644 --- a/docs/providers/aws/cli-reference/remove.md +++ b/docs/providers/aws/cli-reference/remove.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/cli-reference/rollback.md b/docs/providers/aws/cli-reference/rollback.md index 33a5e7a04..4dd4b1c0c 100644 --- a/docs/providers/aws/cli-reference/rollback.md +++ b/docs/providers/aws/cli-reference/rollback.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/cli-reference/slstats.md b/docs/providers/aws/cli-reference/slstats.md index 0665f014d..2671f8eea 100644 --- a/docs/providers/aws/cli-reference/slstats.md +++ b/docs/providers/aws/cli-reference/slstats.md @@ -1,7 +1,7 @@ From 80b12f626b38cdbe13539be8c6420165d72512e0 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 15:01:21 +0100 Subject: [PATCH 05/12] Add automatic Period setting and remove period option --- docs/providers/aws/cli-reference/metrics.md | 1 - lib/plugins/aws/metrics/awsMetrics.js | 19 ++---------- lib/plugins/aws/metrics/awsMetrics.test.js | 32 --------------------- 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/docs/providers/aws/cli-reference/metrics.md b/docs/providers/aws/cli-reference/metrics.md index 372f17b75..ed4f51a73 100644 --- a/docs/providers/aws/cli-reference/metrics.md +++ b/docs/providers/aws/cli-reference/metrics.md @@ -25,7 +25,6 @@ serverless metrics --function hello - `--region` or `-r` The region you want to view the function metrics for. If not provided, the plugin will use the default region listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `us-east-1` region. - `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20` or `1469705761`). Must be a valid ISO 8601 format. - `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Must be a valid ISO 8601 format. -- `--period` The granularity, in seconds, of the returned data points (defaults to 86400 (1 day)). ## Examples diff --git a/lib/plugins/aws/metrics/awsMetrics.js b/lib/plugins/aws/metrics/awsMetrics.js index defb7b156..f974cf0d1 100644 --- a/lib/plugins/aws/metrics/awsMetrics.js +++ b/lib/plugins/aws/metrics/awsMetrics.js @@ -12,20 +12,6 @@ class AwsMetrics { this.options = options; this.provider = this.serverless.getProvider('aws'); - this.commands = { - metrics: { - lifecycleEvents: [ - 'metrics', - ], - options: { - period: { - usage: 'The granularity, in seconds, of the returned data points', - shortcut: 'p', - }, - }, - }, - }; - Object.assign(this, validate); this.hooks = { @@ -49,7 +35,6 @@ class AwsMetrics { this.options.startTime = this.options.startTime || yesterday; this.options.endTime = this.options.endTime || today; - this.options.period = this.options.period || 60 * 60 * 24; // (1 day) return BbPromise.resolve(); } @@ -58,9 +43,11 @@ class AwsMetrics { const FunctionName = this.options.function; const StartTime = this.options.startTime; const EndTime = this.options.endTime; - const Period = this.options.period; const Namespace = 'AWS/Lambda'; + const hoursDiff = Math.abs(EndTime - StartTime) / 36e5; + const Period = (hoursDiff > 24) ? 3600 * 24 : 3600; + const promises = []; // get invocations diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js index 66efff68a..22a271b41 100644 --- a/lib/plugins/aws/metrics/awsMetrics.test.js +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -36,21 +36,6 @@ describe('AwsMetrics', () => { it('should set the provider variable to the AwsProvider instance', () => expect(awsMetrics.provider).to.be.instanceof(AwsProvider)); - it('should have the command "metrics"', () => { - // eslint-disable-next-line no-unused-expressions - expect(awsMetrics.commands.metrics).to.not.be.undefined; - }); - - it('should have a lifecycle events "metrics"', () => { - expect(awsMetrics.commands.metrics.lifecycleEvents).to.deep.equal([ - 'metrics', - ]); - }); - - it('should have the option "period"', () => { - expect(awsMetrics.commands.metrics.options.period).to.not.equal(undefined); - }); - it('should have a "metrics:metrics" hook', () => { // eslint-disable-next-line no-unused-expressions expect(awsMetrics.hooks['metrics:metrics']).to.not.be.undefined; @@ -130,22 +115,6 @@ describe('AwsMetrics', () => { expect(awsMetrics.options.endTime).to.equal('1970-01-02'); }); }); - - it('should set the period to 86400 if not provided', () => { - awsMetrics.options.period = null; - - return awsMetrics.extendedValidate().then(() => { - expect(awsMetrics.options.period).to.equal(86400); - }); - }); - - it('should set the period to the provided value', () => { - awsMetrics.options.period = 4711; - - return awsMetrics.extendedValidate().then(() => { - expect(awsMetrics.options.period).to.equal(4711); - }); - }); }); describe('#getMetrics()', () => { @@ -155,7 +124,6 @@ describe('AwsMetrics', () => { awsMetrics.options.function = 'function1'; awsMetrics.options.startTime = '1970-01-01'; awsMetrics.options.endTime = '1970-01-02'; - awsMetrics.options.period = '4711'; requestStub = sinon.stub(awsMetrics.provider, 'request'); }); From f249fa192a4506c94c4b675700fb57501f2d1f4c Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 29 Nov 2016 15:01:42 +0100 Subject: [PATCH 06/12] Fix typo in test description --- lib/plugins/metrics/metrics.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/metrics/metrics.test.js b/lib/plugins/metrics/metrics.test.js index b80323535..7ad7afb97 100644 --- a/lib/plugins/metrics/metrics.test.js +++ b/lib/plugins/metrics/metrics.test.js @@ -20,7 +20,7 @@ describe('Metrics', () => { expect(metrics.commands.metrics).to.not.be.undefined; }); - it('should have a lifecycle events "metrics"', () => { + it('should have a lifecycle event "metrics"', () => { expect(metrics.commands.metrics.lifecycleEvents).to.deep.equal([ 'metrics', ]); From 628a55e6b517d31852cd6e85312d626f97a8ca39 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 1 Dec 2016 09:30:01 +0100 Subject: [PATCH 07/12] Add translation of human friendly startTime syntax --- lib/plugins/aws/metrics/awsMetrics.js | 14 +++++++++++++- lib/plugins/aws/metrics/awsMetrics.test.js | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/metrics/awsMetrics.js b/lib/plugins/aws/metrics/awsMetrics.js index f974cf0d1..3d515c22d 100644 --- a/lib/plugins/aws/metrics/awsMetrics.js +++ b/lib/plugins/aws/metrics/awsMetrics.js @@ -33,7 +33,19 @@ class AwsMetrics { yesterday = yesterday.setDate(yesterday.getDate() - 1); yesterday = new Date(yesterday); - this.options.startTime = this.options.startTime || yesterday; + if (this.options.startTime) { + const since = (['m', 'h', 'd'] + .indexOf(this.options.startTime[this.options.startTime.length - 1]) !== -1); + if (since) { + this.options.startTime = moment().subtract(this.options + .startTime.replace(/\D/g, ''), this.options + .startTime.replace(/\d/g, '')).valueOf(); + this.options.startTime = new Date(this.options.startTime); + } + } else { + this.options.startTime = yesterday; + } + this.options.endTime = this.options.endTime || today; return BbPromise.resolve(); diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js index 22a271b41..1d26a8015 100644 --- a/lib/plugins/aws/metrics/awsMetrics.test.js +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -100,6 +100,28 @@ describe('AwsMetrics', () => { }); }); + it('should translate human friendly syntax (e.g. 24h) for startTime', () => { + awsMetrics.options.startTime = '24h'; // 24 hours ago + + let yesterday = new Date(); + yesterday = yesterday.setDate(yesterday.getDate() - 1); + yesterday = new Date(yesterday); + const yesterdaysYear = yesterday.getFullYear(); + const yesterdaysMonth = yesterday.getMonth() + 1; + const yesterdaysDay = yesterday.getDate(); + const yesterdaysDate = `${yesterdaysYear}-${yesterdaysMonth}-${yesterdaysDay}`; + + return awsMetrics.extendedValidate().then(() => { + const translatedStartTime = awsMetrics.options.startTime; + const translatedYear = translatedStartTime.getFullYear(); + const translatedMonth = translatedStartTime.getMonth() + 1; + const translatedDay = translatedStartTime.getDate(); + const translatedDate = `${translatedYear}-${translatedMonth}-${translatedDay}`; + + expect(translatedDate).to.equal(yesterdaysDate); + }); + }); + it('should set the endTime to a default value if not provided', () => { awsMetrics.options.endTime = null; From 45b6695bd645e03d7405e270bcc3f6d933ec30e3 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 1 Dec 2016 09:36:21 +0100 Subject: [PATCH 08/12] Update docs about startTime and endTime --- docs/providers/aws/cli-reference/metrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/aws/cli-reference/metrics.md b/docs/providers/aws/cli-reference/metrics.md index ed4f51a73..aaaef58fa 100644 --- a/docs/providers/aws/cli-reference/metrics.md +++ b/docs/providers/aws/cli-reference/metrics.md @@ -23,8 +23,8 @@ serverless metrics --function hello - `--function` or `-f` The function you want to fetch the metrics for. **Required** - `--stage` or `-s` The stage you want to view the function metrics for. If not provided, the plugin will use the default stage listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `dev` stage. - `--region` or `-r` The region you want to view the function metrics for. If not provided, the plugin will use the default region listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `us-east-1` region. -- `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20` or `1469705761`). Must be a valid ISO 8601 format. -- `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Must be a valid ISO 8601 format. +- `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20`, `1469705761`, `30m` (30 minutes ago), `2h` (2 days ago) or `3d` (3 days ago)). Date formats should be written in ISO 8601. Defaults to yesterday (24h ago). +- `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Date formats should be written in ISO 8601. Defaults to today (now). ## Examples From 5994360fb3db73323d40ec388f26601e1ee1b8b8 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 1 Dec 2016 10:09:14 +0100 Subject: [PATCH 09/12] Update tests for default value setting of startTime and endTime --- lib/plugins/aws/metrics/awsMetrics.test.js | 34 +++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js index 1d26a8015..4eb7ac3a4 100644 --- a/lib/plugins/aws/metrics/awsMetrics.test.js +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -84,11 +84,25 @@ describe('AwsMetrics', () => { }) ); - it('should set the startTime to a default value if not provided', () => { + it('should set the startTime to yesterday as the default value if not provided', () => { awsMetrics.options.startTime = null; + let yesterday = new Date(); + yesterday = yesterday.setDate(yesterday.getDate() - 1); + yesterday = new Date(yesterday); + const yesterdaysYear = yesterday.getFullYear(); + const yesterdaysMonth = yesterday.getMonth() + 1; + const yesterdaysDay = yesterday.getDate(); + const yesterdaysDate = `${yesterdaysYear}-${yesterdaysMonth}-${yesterdaysDay}`; + return awsMetrics.extendedValidate().then(() => { - expect(String(awsMetrics.options.startTime)).to.have.length.above(0); + const defaultsStartTime = awsMetrics.options.startTime; + const defaultsYear = defaultsStartTime.getFullYear(); + const defaultsMonth = defaultsStartTime.getMonth() + 1; + const defaultsDay = defaultsStartTime.getDate(); + const defaultsDate = `${defaultsYear}-${defaultsMonth}-${defaultsDay}`; + + expect(defaultsDate).to.equal(yesterdaysDate); }); }); @@ -122,11 +136,23 @@ describe('AwsMetrics', () => { }); }); - it('should set the endTime to a default value if not provided', () => { + it('should set the endTime to today as the default value if not provided', () => { awsMetrics.options.endTime = null; + const today = new Date(); + const todaysYear = today.getFullYear(); + const todaysMonth = today.getMonth() + 1; + const todaysDay = today.getDate(); + const todaysDate = `${todaysYear}-${todaysMonth}-${todaysDay}`; + return awsMetrics.extendedValidate().then(() => { - expect(String(awsMetrics.options.endTime)).to.have.length.above(0); + const defaultsStartTime = awsMetrics.options.endTime; + const defaultsYear = defaultsStartTime.getFullYear(); + const defaultsMonth = defaultsStartTime.getMonth() + 1; + const defaultsDay = defaultsStartTime.getDate(); + const defaultsDate = `${defaultsYear}-${defaultsMonth}-${defaultsDay}`; + + expect(defaultsDate).to.equal(todaysDate); }); }); From d0740787a662705bf904d6e1cd27dec4f54bf875 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 1 Dec 2016 10:10:12 +0100 Subject: [PATCH 10/12] Update documentation for default valued of startTime and endTime --- docs/providers/aws/cli-reference/metrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/aws/cli-reference/metrics.md b/docs/providers/aws/cli-reference/metrics.md index aaaef58fa..5cd708acb 100644 --- a/docs/providers/aws/cli-reference/metrics.md +++ b/docs/providers/aws/cli-reference/metrics.md @@ -23,8 +23,8 @@ serverless metrics --function hello - `--function` or `-f` The function you want to fetch the metrics for. **Required** - `--stage` or `-s` The stage you want to view the function metrics for. If not provided, the plugin will use the default stage listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `dev` stage. - `--region` or `-r` The region you want to view the function metrics for. If not provided, the plugin will use the default region listed in `serverless.yml`. If that doesn't exist either it'll just fetch the metrics from the `us-east-1` region. -- `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20`, `1469705761`, `30m` (30 minutes ago), `2h` (2 days ago) or `3d` (3 days ago)). Date formats should be written in ISO 8601. Defaults to yesterday (24h ago). -- `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Date formats should be written in ISO 8601. Defaults to today (now). +- `--startTime` A specific unit in time to start fetching metrics from (ie: `2010-10-20`, `1469705761`, `30m` (30 minutes ago), `2h` (2 days ago) or `3d` (3 days ago)). Date formats should be written in ISO 8601. Defaults to 24h ago. +- `--endTime` A specific unit in time to end fetching metrics from (ie: `2010-10-21` or `1469705761`). Date formats should be written in ISO 8601. Defaults to now. ## Examples From 5e4ad45594f3b96647a91d4d2db5cb349cc5a9f0 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 1 Dec 2016 13:34:24 +0100 Subject: [PATCH 11/12] Fix date parsing bug --- lib/plugins/aws/metrics/awsMetrics.js | 5 ++++- lib/plugins/aws/metrics/awsMetrics.test.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/metrics/awsMetrics.js b/lib/plugins/aws/metrics/awsMetrics.js index 3d515c22d..f4e47f0dd 100644 --- a/lib/plugins/aws/metrics/awsMetrics.js +++ b/lib/plugins/aws/metrics/awsMetrics.js @@ -40,7 +40,6 @@ class AwsMetrics { this.options.startTime = moment().subtract(this.options .startTime.replace(/\D/g, ''), this.options .startTime.replace(/\d/g, '')).valueOf(); - this.options.startTime = new Date(this.options.startTime); } } else { this.options.startTime = yesterday; @@ -48,6 +47,10 @@ class AwsMetrics { this.options.endTime = this.options.endTime || today; + // finally create a new date object + this.options.startTime = new Date(this.options.startTime); + this.options.endTime = new Date(this.options.endTime); + return BbPromise.resolve(); } diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js index 4eb7ac3a4..d6a30f084 100644 --- a/lib/plugins/aws/metrics/awsMetrics.test.js +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -110,7 +110,10 @@ describe('AwsMetrics', () => { awsMetrics.options.startTime = '1970-01-01'; return awsMetrics.extendedValidate().then(() => { - expect(awsMetrics.options.startTime).to.equal('1970-01-01'); + const startTime = awsMetrics.options.startTime.toISOString(); + const expectedStartTime = new Date('1970-01-01').toISOString(); + + expect(startTime).to.equal(expectedStartTime); }); }); @@ -157,10 +160,13 @@ describe('AwsMetrics', () => { }); it('should set the endTime to the provided value', () => { - awsMetrics.options.endTime = '1970-01-02'; + awsMetrics.options.endTime = '1970-01-01'; return awsMetrics.extendedValidate().then(() => { - expect(awsMetrics.options.endTime).to.equal('1970-01-02'); + const endTime = awsMetrics.options.endTime.toISOString(); + const expectedEndTime = new Date('1970-01-01').toISOString(); + + expect(endTime).to.equal(expectedEndTime); }); }); }); From 031841ec17957a18840bccd1350d01112be0202e Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 2 Dec 2016 07:06:22 +0100 Subject: [PATCH 12/12] Updates according to PR review --- docs/README.md | 2 +- lib/plugins/metrics/metrics.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 7570562de..2bd9c799f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ The Serverless Framework allows you to deploy auto-scaling, pay-per-execution, e
  • Deploy
  • Invoke
  • Logs
  • -
  • Setup
  • +
  • Metrics
  • Info
  • Rollback
  • Remove
  • diff --git a/lib/plugins/metrics/metrics.js b/lib/plugins/metrics/metrics.js index f5be29d97..3cc54bf4b 100644 --- a/lib/plugins/metrics/metrics.js +++ b/lib/plugins/metrics/metrics.js @@ -26,10 +26,10 @@ class Metrics { shortcut: 'r', }, startTime: { - usage: 'Start time for the metrics retrieval', + usage: 'Start time for the metrics retrieval (e.g. 1970-01-01)', }, endTime: { - usage: 'End time for the metrics retrieval', + usage: 'End time for the metrics retrieval (e.g. 1970-01-01)', }, }, },