Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.

Commit 51e3756

Browse files
committed
Initial commit
First working version up and ready.
1 parent d55330e commit 51e3756

3 files changed

Lines changed: 304 additions & 2 deletions

File tree

README.md

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,71 @@
1-
# aws-loop-timer
2-
Logging Node.js loop times to CloudWatch.
1+
> Logging Node.js loop times to CloudWatch.
2+
3+
Table of Contents: [What does this package do?](#what-does-this-package-do), [How to use?](#how-to-use), [Debugging](#debugging), [Notes on AWS CloudWatch](#notes-on-aws-cloudwatch), [Contribution](#contribution)
4+
5+
6+
# What does this package do?
7+
8+
The *aws-loop-timer* measures loop times (or cycle times) within your [Node.js](https://nodejs.org) applications and logs them using [AWS CloudWatch](https://aws.amazon.com/cloudwatch). With CloudWatch, you can easily monitor those metrics with alarms, dashboards etc.
9+
10+
# How to use?
11+
12+
## Install
13+
14+
Like always:
15+
16+
```
17+
npm i aws-loop-timer --save
18+
```
19+
20+
## Require
21+
22+
When requiring you need to provide your AWS credentials and region, and you also define the `namespace`:
23+
24+
```
25+
var namespace = 'my-namespace'; // this can be any string and probably should be unique per project
26+
27+
var Timer = require('aws-loop-timer')('eu-central-1', 'AWS-KEY', 'AWS-SECRET', namespace);
28+
```
29+
30+
Just to be clear: `my-namespace` can be any string you deem okay, and your AWS credentials need be authorized to access the CloudWatch region your trying to write to.
31+
32+
## Init
33+
34+
First, you need to spin up a timer. When doing so, you can assign the timer a `name`, and a `pulse`.
35+
36+
**name**: Okay, so the `name` will also be the name of the metric on CloudWatch. This means, this should be unique for every loop you want to measure. Whereas the `namespace` when requiring the module could be the same throughout the project.
37+
38+
**pulse**: The `pulse` is the number of seconds how often your timer pushed data to CloudWatch. E.g. if you set it to `5`, loop times will be averaged over five seconds and this average will be uploaded only. If you don't set a pulse, i.e. set it to `0`, each loop time will be uploaded immediately.
39+
40+
**You probably should set a pulse, because CloudWatch only allows 150 uploads per second!** For more info on that topic, read below.
41+
42+
```
43+
var name = 'name-of-the-timer'; // this will be your metric's name
44+
var pulse = 5; // this means data will be pushed to CloudWatch every five seconds
45+
46+
var timer = Timer.getTimer(name, pulse);
47+
```
48+
49+
## Measure
50+
51+
Finally we can measure loop times! This is now fairly simple with the object `timer` we created above.
52+
53+
1. Start the timer: `timer.start();`
54+
1. End it: `timer.end();`
55+
56+
That's it.
57+
58+
# Debugging
59+
60+
Note that this package never throws an error, it only logs error to the console. This is because this logger should not break your application logic.
61+
62+
# Notes on AWS CloudWatch
63+
64+
Some additional notes on the `pulse` setting. You have to know that [AWS CloudWatch only allows to upload 150 times per second](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html). There are two ways to mitigate this if you're in danger of exceeding this limit:
65+
66+
1. Upload several metrics at once. This package currently does not do that (would be a good [contribution](#contribution)), it only uploads one loop time per request to CloudWatch. One could theoretically piggy-ride on the existing pulse to store all loop times in memory and upload an array of all measurements with a single request. Then it's only important to respect the 40 KB POST data limit on uploads.
67+
1. Alternatively, and this is done here, you can already calculate an average of the loop times and only push this one.
68+
69+
# Contribution
70+
71+
Please do.

index.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// dependencies
2+
3+
var os = require('os');
4+
var _ = require('underscore');
5+
var AWS = require('aws-sdk');
6+
var CloudWatch;
7+
8+
var namespace = 'empty';
9+
10+
///////////////
11+
// Prototype //
12+
///////////////
13+
14+
function Timer(name, pulse) {
15+
16+
// validate
17+
if (!(this instanceof Timer)) return new Timer('unnamed', 0);
18+
if (!_.isString(name)) return console.error('Name must be a string');
19+
if (_.isUndefined(pulse)) pulse = 0;
20+
if (!_.isNumber(pulse) || _.isNaN(pulse) || pulse < 0)
21+
return console.error('Pulse must be positive integer');
22+
23+
// set
24+
this.name = name;
25+
this.pulse = pulse;
26+
27+
// init
28+
this.startDate = null;
29+
this.timerAverageCounter = 0;
30+
this.timerAverageSum = 0;
31+
32+
return this;
33+
}
34+
35+
////////////////
36+
// CloudWatch //
37+
////////////////
38+
39+
/**
40+
* pretty-prints the result of the timer
41+
* @param {String} name name of the timer
42+
* @param {Number} cycletime Cycletime (or average cycletime) in seconds
43+
* @param {Boolean} isAverageValue defines whether this is an average value
44+
*/
45+
function print(name, cycletime, isAverageValue) {
46+
var log = '⏱ ' + name + ' on ' + os.hostname() + ': ';
47+
log += isAverageValue ? 'Taking' : 'Took';
48+
log += ' ' + cycletime + ' s';
49+
log += isAverageValue ? ' on average' : '';
50+
51+
console.log(log);
52+
}
53+
54+
/**
55+
* uploads a measurement to AWS CloudWatch
56+
* @param {String} metric name of the metric
57+
* @param {Number} value value of the metric
58+
*/
59+
function uploadMetricToCloudWatch(metric, value) {
60+
61+
var params = {
62+
MetricData: [
63+
{
64+
MetricName: metric,
65+
Timestamp: new Date(),
66+
Unit: 'Seconds',
67+
Value: value,
68+
},
69+
],
70+
Namespace: 'Cycletime/' + namespace,
71+
};
72+
73+
CloudWatch.putMetricData(params, function (err) {
74+
if (err) console.error('Could not upload to CloudWatch: ', JSON.stringify(err));
75+
});
76+
77+
}
78+
79+
/////////////////////
80+
// Pulse Functions //
81+
/////////////////////
82+
83+
/**
84+
* update temp variables to calculate pulse
85+
* @param {Number} cycletime the cycletime of a single loop
86+
*/
87+
Timer.prototype.updatePulse = function (cycletime) {
88+
89+
this.timerAverageCounter++;
90+
this.timerAverageSum += cycletime;
91+
92+
};
93+
94+
/**
95+
* regular functions which calculates overage over pulse interval
96+
* calls itself with defined pulse timeout
97+
*/
98+
Timer.prototype.pushPulseToCloudWatch = function () {
99+
var _this = this;
100+
setTimeout(function () {
101+
102+
// calc
103+
var averageCycletime = _this.timerAverageSum / _this.timerAverageCounter;
104+
105+
// inform
106+
if (!_.isNaN(averageCycletime)) {
107+
print(_this.name, averageCycletime, true);
108+
uploadMetricToCloudWatch(_this.name, averageCycletime);
109+
}
110+
111+
// reset
112+
_this.timerAverageCounter = 0;
113+
_this.timerAverageSum = 0;
114+
115+
// do it again
116+
_this.pushPulseToCloudWatch();
117+
}, this.pulse * 1000);
118+
};
119+
120+
//////////////////////
121+
// Public Functions //
122+
//////////////////////
123+
124+
/**
125+
* returns a timer object
126+
* @param {String} name the name of the timer
127+
* @param {Number} pulse the pulse in seconds, is optional
128+
* @return {[type]} [description]
129+
*/
130+
function getTimer(name, pulse) {
131+
132+
if (_.isUndefined(pulse)) pulse = 0;
133+
134+
var timer = new Timer(name, pulse);
135+
if (timer.pulse > 0) timer.pushPulseToCloudWatch();
136+
return timer;
137+
138+
}
139+
140+
/**
141+
* starts timer and return timer object, is the only function exposed on require
142+
*/
143+
Timer.prototype.start = function () {
144+
145+
this.startDate = Date.now();
146+
147+
};
148+
149+
/**
150+
* ends the timer
151+
* @return {Number} cycletime of timer in seconds
152+
*/
153+
Timer.prototype.end = function () {
154+
if (_.isNull(this.startDate)) return;
155+
156+
// calc
157+
var cycletime = (Date.now() - this.startDate) / 1000;
158+
this.startDate = null;
159+
160+
// propagate cycletime via pulse
161+
if (!_.isNaN(cycletime)) {
162+
if (this.pulse > 0) this.updatePulse(cycletime);
163+
if (this.pulse === 0) uploadMetricToCloudWatch(this.name, cycletime);
164+
}
165+
166+
// inform
167+
print(this.name, cycletime, false);
168+
return cycletime;
169+
};
170+
171+
////////////
172+
// Export //
173+
////////////
174+
175+
/**
176+
* on require AWS credentials are stored and the constructor method is returned
177+
* @param {String} region AWS Credentials: Region of CloudWatch, defaults to 'eu-central-1'
178+
* @param {String} key AWS Credentials: Key
179+
* @param {String} secret AWS Credentials: Token
180+
* @param {String} ns Namespace for all Metrics
181+
* @return {Object} object with the constructor method startTimer()
182+
*/
183+
module.exports = function (region, key, secret, ns) {
184+
185+
if (_.isNull(region)) region = 'eu-central-1';
186+
187+
AWS.config.region = region;
188+
AWS.config.accessKeyId = key;
189+
AWS.config.secretAccessKey = secret;
190+
191+
CloudWatch = new AWS.CloudWatch();
192+
193+
namespace = ns;
194+
195+
return {
196+
getTimer: getTimer,
197+
};
198+
199+
};

package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "aws-loop-timer",
3+
"version": "0.0.1",
4+
"description": "Logging Node.js loop times to CloudWatch",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/parcelLab/aws-loop-timer.git"
12+
},
13+
"keywords": [
14+
"aws",
15+
"cloudwatch",
16+
"awscloudwatch",
17+
"looptime",
18+
"cycletime",
19+
"loop",
20+
"cycle",
21+
"timer",
22+
"performance"
23+
],
24+
"author": "Julian Krenge",
25+
"license": "MIT",
26+
"bugs": {
27+
"url": "https://github.com/parcelLab/aws-loop-timer/issues"
28+
},
29+
"homepage": "https://github.com/parcelLab/aws-loop-timer#readme",
30+
"dependencies": {
31+
"aws-sdk": "^2.4.9",
32+
"underscore": "^1.8.3"
33+
}
34+
}

0 commit comments

Comments
 (0)