├── .babelignore
├── .babelrc
├── .env
├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .gitignore
├── Makefile
├── README.md
├── bin
├── ls-dynamodb-tables
├── ls-ec2
├── ls-rds
└── slack-cloudwatch-chart
├── circle.yml
├── dist
├── cli.js
├── cloudwatch.js
├── dynamodb.js
├── ec2.js
├── gen-chart.js
├── index.js
├── ls-ec2.js
├── ls-rds.js
├── metrics.js
├── phantom-api.js
├── print-names.js
├── print-stats.js
├── proc-gen-chart.js
├── render.js
├── time.js
└── upload.js
├── gulpfile.js
├── index.js
├── lib
└── modules.js
├── package.json
├── src
├── cli.js
├── cloudwatch.js
├── dynamodb.js
├── gen-chart.js
├── index.js
├── ls-ec2.js
├── ls-rds.js
├── metrics.js
├── phantom-api.js
├── print-stats.js
├── proc-gen-chart.js
├── render.js
├── time.js
└── upload.js
└── test
├── dynamodb.js
├── ls-ec2.js
├── metrics.js
└── time.js
/.babelignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmtk75/aws-cloudwatch-chart-slack/3e8fe816b2a685b70ab8512fd01d098f80e32fbd/.babelignore
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"],
3 |
4 | "plugins": [
5 | "transform-flow-strip-types"
6 | ],
7 |
8 | "env": {
9 | "test": {
10 | "plugins": [
11 | "babel-plugin-espower"
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | nvm use 4
2 | PATH=`pwd`/node_modules/.bin:$PATH
3 | #. .e/bin/activate
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/types/*.js
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "jsx": true,
4 | "modules": true
5 | },
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "parser": "babel-eslint",
11 | "rules": {
12 | "quotes": [2, "double"],
13 | "strict": [2, "never"],
14 | "babel/generator-star-spacing": 1,
15 | "babel/new-cap": [1, {"capIsNewExceptions": ["List", "Set"]}],
16 | "babel/object-shorthand": 1,
17 | //"babel/arrow-parens": [2, "as-needed"],
18 | "babel/no-await-in-loop": 1
19 | },
20 | "plugins": [
21 | "babel"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/.*
3 | .*/dist/.*
4 |
5 | [include]
6 |
7 | [libs]
8 | lib
9 |
10 | [options]
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .e
2 | .DS_Store
3 | .*.swp
4 | node_modules
5 | .vagrant
6 | #dist
7 | credentials
8 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | describe-rds:
2 | aws rds describe-db-instances
3 |
4 |
5 | .e/bin/aws-shell: .e/bin/pip
6 | .e/bin/pip install aws-shell
7 |
8 | .e/bin/pip:
9 | virtualenv .e
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # README [](https://circleci.com/gh/tmtk75/aws-cloudwatch-chart-slack)
2 | This module is a chart renderer and uploader to Slack.
3 | It's easy to share charts of CloudWatch on Slack.
4 | You can render charts for datapoints of CloudWatch, and can upload chart images to channels of Slack.
5 |
6 |
7 |
8 | ## Getting Started
9 | Tested with node-4.3.1 and npm-2.14.12.
10 | ```
11 | $ npm install [--no-spin]
12 | ```
13 | NOTE: v0.1.2 doesn't work with npm-3.x due to [here](https://github.com/tmtk75/aws-cloudwatch-chart-slack/blob/v0.1.2/src/gen-chart.js#L136)
14 |
15 | Set four environment variables.
16 | ```
17 | export AWS_DEFAULT_REGION=ap-northeast-1 │~
18 | export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
19 | export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
20 | export SLACK_API_TOKEN=bbbb-xxxxxxxxxx-yyyyyyyyyy-zzzzzzzzzzz-aaaaaaaaaa
21 | ```
22 |
23 | Try this, of course change channel name as you have.
24 | ```
25 | SLACK_CHANNEL_NAME=#api-test ./bin/slack-cloudwatch-chart
26 | ```
27 |
28 | A few seconds later, You can see a chart on the channel.
29 |
30 |
31 | ## Hubot Integration
32 | Please add the below snippet.
33 | ```coffee-script
34 | chart = require "aws-cloudwatch-chart-slack"
35 | module.exports = (robot) ->
36 | robot.respond /cloudwatch (.+)/i, (msg) ->
37 | [id, params...] = msg.match[1].split(" ").map (e) -> e.trim()
38 | console.log "cloudwatch: #{id}"
39 | console.log "message.room: #{msg.message.room}"
40 |
41 | chart.slack.post "#{msg.room}", [id, params...], (err, file)->
42 | if (err)
43 | console.error err.stack
44 | msg.send err.message
45 | ```
46 |
47 |
48 | For arguments and available options, see [here](https://github.com/tmtk76/aws-cloudwatch-chart-slack/blob/master/bin/slack-cloudwatch-chart#L1://github.com/tmtk75/aws-cloudwatch-chart-slack/blob/master/bin/slack-cloudwatch-chart#L5).
49 |
50 |
51 | ## How to give arguments
52 | By default, metric is `CPUUtilization` and namespace is `AWS/EC2`.
53 | ```
54 | cloudwatch i-12345678
55 | ```
56 |
57 | You can give metric and namespace at 2nd and 3rd arguments.
58 | ```
59 | cloudwatch main-db FreeableMemory AWS/RDS
60 | ```
61 |
62 | Multiple IDs can be given seperated with `,`.
63 | ```
64 | cloudwatch i-xyza5678,i-abcd1234
65 | ```
66 |
67 | `--region` is to specify a AWS region.
68 | ```
69 | cloudwatch i-abcd4567 --region us-west-2
70 | ```
71 |
72 | `--statistics` has `Maximum`, `Minimum`, `Average`, `Sum` and `SampleCount`.
73 | ```
74 | cloudwatch i-abcd1234 --statistics Maximum
75 | ```
76 |
77 | Duration and period also can be given with `--duration` and `period` options.
78 | ```
79 | cloudwatch i-abcd1234 --duration 3days --period 1hour
80 | ```
81 |
82 | Regarding AWS/EC2, you can filter EC2 instances with some tags.
83 | Next example is that `site` is `dev` and `role` is `webapp` or `db`.
84 | ```
85 | cloudwatch "tag:site=dev,role=webapp|db"
86 | ```
87 |
88 |
89 | ## Development
90 | ```
91 | $ gulp
92 | ```
93 | The gulp default task is to complie watching change of sources.
94 | `src/*.js` are compiled and saved under `dist`.
95 |
96 | ```
97 | $ npm test
98 | or
99 | $ gulp test
100 | ```
101 | The 1st one is to run test once, the 2nd one watches change of sources.
102 |
103 | ```
104 | $ npm run lint
105 | ```
106 | Linting with ESLint.
107 |
108 | ```
109 | $ npm run typecheck
110 | ```
111 | Run type check with [flow](http://flowtype.org/).
112 |
113 |
114 | ### How it works
115 | ```
116 | dist/index.js
117 | |
118 | v
119 | dist/render.js Generate a png file
120 | |
121 | v
122 | dist/print-stats.js Retrieve stats with aws-sdk CloudWatch.
123 | |
124 | | stdin
125 | v
126 | spawn: dist/gen-chart.js Generate a .js file for c3 and a .html file.
127 | | Load the .html file with phantomjs and render a chart as .png
128 | | filename
129 | |
130 | +<--+
131 | |
132 | v
133 | dist/upload.js Read file.
134 | | Upload it to Slack with a REST API.
135 | |
136 | v
137 | unlink the file
138 | ```
139 |
140 |
141 | ## Sub modules
142 | ### Print statistics
143 | Print stats using aws-sdk. Environment variables for AWS are referred.
144 | ```
145 | $ node ./dist/print-stats.js [options]
146 | [{"Namespace":"AWS/EC2","InstanceId":"i-003bb906","Label":"CPUUtilization","Respon...
147 | ```
148 |
149 | ### Generating chart image in .png
150 | Generate a png image and show the path.
151 | ```
152 | $ cat | phantomjs ./dist/gen-chart.js
153 | ./.97516-1454216914841.png
154 | ```
155 |
156 | ### Render
157 | `render.js` calls `print-stats.js` and `gen-chart.js`.
158 | ```
159 | $ node dist/render.js
160 | ./.97516-1454216914841.png
161 | ```
162 |
163 | ### Upload to a channel of Slack
164 | ```
165 | $ node dist/upload.js ./.97516-1454216914841.png
166 | { ok: true,
167 | file:
168 | ...
169 | ```
170 |
171 |
172 | ## How to debug for rendered charts
173 | You can prevent removing temporary files with two options.
174 | ```
175 | $ node dist/render.js i-003bb906 --filename a.png --keep-html --keep-js
176 | a.png
177 | ```
178 |
179 | The options preserve temporary files `a.png.html` and `a.png.js`.
180 | You can open the html file and see the chart rendered by c3.js.
181 | ```
182 | $ open a.png.html
183 | ```
184 |
185 | ## Contribution
186 | 1. Fork me ()
187 | 1. Create your feature branch (git checkout -b my-new-feature)
188 | 1. Commit your changes (git commit -am 'Add some feature')
189 | 1. Pass `npm run typecheck`
190 | 1. Pass `npm run lint`
191 | 1. Add test cases for your changes
192 | 1. Pass `npm test`
193 | 1. Push to the branch (git push origin my-new-feature)
194 | 1. Create your new pull request
195 |
196 |
197 | ## License
198 |
199 | [MIT License](http://opensource.org/licenses/MIT)
200 |
201 |
--------------------------------------------------------------------------------
/bin/ls-dynamodb-tables:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | sort_key=${1-Name}
3 | aws dynamodb list-tables \
4 | --output table \
5 | --query 'TableNames'
6 |
--------------------------------------------------------------------------------
/bin/ls-ec2:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | sort_key=${1-Name}
3 | aws ec2 describe-instances \
4 | --output table \
5 | --filter Name=instance-state-name,Values=running \
6 | --query '
7 | sort_by(
8 | Reservations[].Instances[],
9 | &(Tags[?Key==`'$sort_key'`]|[0].Value)
10 | )[]
11 | .[
12 | Tags[?Key==`Name`]|[0].Value,
13 | InstanceType,
14 | InstanceId,
15 | PublicIpAddress,
16 | PrivateIpAddress,
17 | Placement.AvailabilityZone
18 | ]'
19 |
--------------------------------------------------------------------------------
/bin/ls-rds:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | sort_key=${1-Name}
3 | aws rds describe-db-instances \
4 | --output table \
5 | --query '
6 | DBInstances[]
7 | .[
8 | DBInstanceIdentifier,
9 | DBInstanceClass,
10 | AllocatedStorage,
11 | MultiAZ
12 | ]'
13 |
--------------------------------------------------------------------------------
/bin/slack-cloudwatch-chart:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | usage() {
3 | name=$(basename $0)
4 | cat<&2
5 | Usage: ${name} [options] [metric-name] [namespace]
6 |
7 | Timeseries chart renderer retrieving from CloudWatch.
8 | metrics-name is CPUUtilization, namespace is AWS/EC2 by default.
9 |
10 | Options:
11 | --region AWS region name e.g) us-west-1
12 | --duration Duration e.g) 3days, 1h, 30minutes
13 | --period Period e.g) 30minutes
14 | --statistics Statistics Maximum,Minimum,Average,Sum,SampleCount
15 |
16 | --width Chart width e.g) 800
17 | --height Chart height e.g) 300
18 | --max Y axis max
19 | --min Y axis min
20 | --grid-x Show x grid
21 | --grid-y Show y grid
22 | --utc X axis in UTC
23 | --bytes Y axis in bytes instead of megabytes
24 | --x-label Label for x
25 |
26 | --keep-html Disable to remove temporary .html file
27 | --keep-js Disable to remove temporary .js file
28 | --filename,-f Generated filename
29 | --base64 Output as base64
30 | --without-image Disable genrating image
31 |
32 | Examples:
33 | ${name} i-0935c127
34 | ${name} i-0935c127,i-003bb900
35 | ${name} i-0935c127 DiskReadOps AWS/EC2
36 |
37 | ${name} test-db cpu AWS/RDS
38 | ${name} test-db CPUUtilization AWS/RDS
39 |
40 | EOF
41 | }
42 |
43 | if [ "$1" == "" ]; then
44 | usage
45 | exit 1
46 | fi
47 |
48 | cli_path="$(cd ${0%/*}/..; pwd)"/aws-cloudwatch-chart-slack/dist/cli.js
49 | if [ -e $cli_path ]; then
50 | node $cli_path $*
51 | else
52 | node dist/cli.js $*
53 | fi
54 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 0.12
4 |
5 |
--------------------------------------------------------------------------------
/dist/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | var _index = require("./index.js");
5 |
6 | var channel_name = process.env.SLACK_CHANNEL_NAME || "#api-test";
7 | _index.slack.post(channel_name, process.argv.slice(2), function (err, file) {
8 | if (err) {
9 | console.error(err.stack);
10 | return;
11 | }
12 | console.log(file.thumb_80);
13 | });
--------------------------------------------------------------------------------
/dist/cloudwatch.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _awsSdk = require("aws-sdk");
12 |
13 | var _awsSdk2 = _interopRequireDefault(_awsSdk);
14 |
15 | var _time = require("./time.js");
16 |
17 | var _time2 = _interopRequireDefault(_time);
18 |
19 | var _moment = require("moment");
20 |
21 | var _moment2 = _interopRequireDefault(_moment);
22 |
23 | var _metrics = require("./metrics.js");
24 |
25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26 |
27 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
28 |
29 | var CloudWatch = function () {
30 | function CloudWatch() {
31 | _classCallCheck(this, CloudWatch);
32 | }
33 |
34 | _createClass(CloudWatch, [{
35 | key: "endTime",
36 |
37 |
38 | /** */
39 | value: function endTime(d) {
40 | this._endTime = d;
41 | return this;
42 | }
43 |
44 | /** */
45 |
46 | }, {
47 | key: "duration",
48 | value: function duration(d) {
49 | this._duration = d;
50 | return this;
51 | }
52 |
53 | /** */
54 |
55 | }, {
56 | key: "period",
57 | value: function period(p) {
58 | this._period = p;
59 | return this;
60 | }
61 |
62 | /** */
63 |
64 | }, {
65 | key: "statistics",
66 | value: function statistics(name) {
67 | this._statistics = name;
68 | return this;
69 | }
70 |
71 | /** */
72 |
73 | }, {
74 | key: "metricStatistics",
75 | value: function metricStatistics(namespace, instanceID, metricName) {
76 | var dimName = (0, _metrics.nsToDimName)(namespace);
77 | var metric = (0, _metrics.searchMetric)(namespace, metricName);
78 | var sep = _time2.default.toSEP(this._duration, this._endTime);
79 | if (this._period) {
80 | sep.Period = this._period;
81 | }
82 | if (this._statistics) {
83 | metric.Statistics = [this._statistics];
84 | }
85 |
86 | var params = _extends({}, sep, metric, {
87 | Namespace: namespace,
88 | Dimensions: [{
89 | Name: dimName,
90 | Value: instanceID
91 | }]
92 | });
93 |
94 | //process.stderr.write(JSON.stringify(params));
95 | var cloudwatch = new _awsSdk2.default.CloudWatch();
96 | return new Promise(function (resolve, reject) {
97 | return cloudwatch.getMetricStatistics(params, function (err, data) {
98 | return err ? reject(err) : resolve([sep, data]);
99 | });
100 | });
101 | }
102 | }]);
103 |
104 | return CloudWatch;
105 | }();
106 |
107 | exports.default = CloudWatch;
--------------------------------------------------------------------------------
/dist/dynamodb.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.mimic = mimic;
7 | exports.toY = toY;
8 |
9 | var _metrics = require("./metrics.js");
10 |
11 | var m = _interopRequireWildcard(_metrics);
12 |
13 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
14 |
15 | /**
16 | * Returns true if stat is exactly in a condition.
17 | */
18 | function mimic(stat) {
19 | return stat.Namespace === "AWS/DynamoDB" && stat.Period === 60 && (stat.Label === "ConsumedReadCapacityUnits" || stat.Label === "ConsumedWriteCapacityUnits") && stat.Datapoints[0].Sum !== undefined;
20 | }
21 | function toY(e) {
22 | var bytes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
23 |
24 | return m.toY(e, bytes) / 60;
25 | }
26 |
27 | exports.default = {
28 | mimic: mimic,
29 | toY: toY
30 | };
--------------------------------------------------------------------------------
/dist/ec2.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var AWS = require("aws-sdk");
4 |
5 | var params = {
6 | //DryRun: true || false,
7 | //Filters: [
8 | // {
9 | // Name: 'STRING_VALUE',
10 | // Values: [
11 | // 'STRING_VALUE',
12 | // /* more items */
13 | // ]
14 | // },
15 | // /* more items */
16 | //],
17 | InstanceIds: ["i-003bb906", "i-0935c111", "i-1c41aa18"]
18 | };
19 |
20 | //MaxResults: 0,
21 | //NextToken: 'STRING_VALUE'
22 | AWS.config.update({ region: "ap-northeast-1" });
23 | var ec2 = new AWS.EC2();
24 | ec2.describeInstances(params, function (err, data) {
25 | if (err) console.log(err, err.stack); // an error occurred
26 | else console.log(JSON.stringify(data)); // successful response
27 | });
--------------------------------------------------------------------------------
/dist/gen-chart.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _phantomApi = require("./phantom-api.js");
4 |
5 | var _moment = require("moment");
6 |
7 | var _moment2 = _interopRequireDefault(_moment);
8 |
9 | var _metrics = require("./metrics.js");
10 |
11 | var _dynamodb = require("./dynamodb.js");
12 |
13 | var _dynamodb2 = _interopRequireDefault(_dynamodb);
14 |
15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16 |
17 | var argv = require("minimist")(_phantomApi.system.args.slice(1), {
18 | string: ["filename", "width", "height", "max", "min", "node_modules_path", "x-label", "format", "bindto", "point-r"],
19 | boolean: ["base64", "keep-html", "keep-js", "grid-x", "grid-y", "utc", "bytes", "without-image"],
20 | alias: {
21 | f: "filename"
22 | },
23 | default: {
24 | width: 800,
25 | height: 300,
26 | node_modules_path: "./node_modules",
27 | format: "png",
28 | "x-tick-count": 120,
29 | "x-tick-culling-max": 10,
30 | "bindto": "container",
31 | "point-r": 2.5
32 | }
33 | });
34 |
35 | try {
36 | (function () {
37 | var stats_data = JSON.parse(_phantomApi.system.stdin.read());
38 | var repre = stats_data[0];
39 | var MetricName = repre.Label || "";
40 | var Namespace = repre.Namespace || "";
41 | var sort = function sort(datapoints) {
42 | return datapoints.sort(function (a, b) {
43 | return a.Timestamp.localeCompare(b.Timestamp);
44 | });
45 | };
46 | var yData = stats_data.map(function (stats) {
47 | if (stats.Datapoints.length < 2) {
48 | throw new Error("Number of datapoints is less than 2 for " + MetricName + " of " + stats.InstanceId + ". There is a possibility InstanceId was wrong. " + JSON.stringify(stats));
49 | }
50 | var b = _dynamodb2.default.mimic(stats);
51 | return [stats[(0, _metrics.nsToDimName)(Namespace)]].concat(sort(stats.Datapoints).map(function (e) {
52 | return b ? _dynamodb2.default.toY(e) : (0, _metrics.toY)(e, argv.bytes);
53 | }));
54 | });
55 | var textLabelX = (0, _metrics.to_axis_x_label_text)(repre, argv.utc);
56 |
57 | var data = {
58 | _meta: { StartTime: repre.StartTime, EndTime: repre.EndTime, UTC: argv.utc },
59 | bindto: "#" + argv.bindto,
60 | data: {
61 | x: "x",
62 | columns: [["x"].concat(sort(repre.Datapoints).map(function (e) {
63 | return _moment2.default.utc(e["Timestamp"]).valueOf();
64 | }))].concat(yData)
65 | },
66 | transition: {
67 | duration: null },
68 | size: {
69 | width: argv.width - 16, // heuristic adjustments
70 | height: argv.height - 16
71 | },
72 | axis: {
73 | y: {
74 | max: argv.max ? parseInt(argv.max) : (0, _metrics.toMax)(repre),
75 | min: argv.min ? parseInt(argv.min) : (0, _metrics.toMin)(repre),
76 | padding: { top: 0, bottom: 0 },
77 | label: {
78 | text: Namespace + " " + MetricName + " " + (0, _metrics.toAxisYLabel)(repre, argv.bytes),
79 | position: "outer-middle"
80 | }
81 | },
82 | x: {
83 | type: "timeseries",
84 | tick: {
85 | count: argv["x-tick-count"],
86 | culling: {
87 | max: argv["x-tick-culling-max"]
88 | },
89 | _format: "%Y-%m-%dT%H:%M:%S",
90 | format: "%H:%M"
91 | },
92 | //padding: {left: 0, right: 0},
93 | label: {
94 | text: argv["x-label"] || textLabelX,
95 | position: "outer-center"
96 | },
97 | localtime: !argv.utc
98 | }
99 | },
100 | grid: {
101 | x: {
102 | show: argv["grid-x"]
103 | },
104 | y: {
105 | show: argv["grid-y"]
106 | }
107 | },
108 | point: {
109 | r: argv["point-r"]
110 | }
111 | };
112 |
113 | render(argv, data);
114 | })();
115 | } catch (ex) {
116 | _phantomApi.system.stderr.write(ex.stack);
117 | _phantomApi.system.stderr.write("\n");
118 | phantom.exit(1);
119 | }
120 |
121 | /*
122 | * Rendering
123 | */
124 | function render(argv, data) {
125 | var page = _phantomApi.webpage.create();
126 | page.onConsoleMessage = function (msg) {
127 | return console.log(msg);
128 | };
129 | page.viewportSize = {
130 | width: argv.width ? parseInt(argv.width) : page.viewportSize.width,
131 | height: argv.height ? parseInt(argv.height) : page.viewportSize.height
132 | };
133 | //console.log(JSON.stringify(page.viewportSize))
134 |
135 | var suffix = argv.filename || "." + _phantomApi.system.pid + "-" + new Date().getTime();
136 | var tmp_html = "./" + suffix + ".html";
137 | var tmp_js = "./" + suffix + ".js";
138 | var filename = argv.filename || "./" + suffix + ".png";
139 | var node_modules_path = argv.node_modules_path;
140 |
141 | var now = (0, _moment2.default)().format("YYYY-MM-DD HH:mm:ss Z");
142 | _phantomApi.fs.write(tmp_js, "\n // Generated at " + now + "\n var data = " + JSON.stringify(data) + ";\n data.axis.y.tick = {format: d3.format(',')};\n c3.generate(data);\n ");
143 | _phantomApi.fs.write(tmp_html, "\n \n \n \n \n \n \n \n \n \n \n ");
144 |
145 | page.open(tmp_html, function (status) {
146 | //console.error(JSON.stringify(argv))
147 | if (!argv["without-image"]) {
148 | page.render(filename, { format: argv.format });
149 | _phantomApi.system.stdout.write(filename);
150 | } else if (argv.base64) {
151 | _phantomApi.system.stdout.write(page.renderBase64(argv.format));
152 | }
153 | if (!argv["keep-html"]) {
154 | _phantomApi.fs.remove(tmp_html);
155 | }
156 | if (!argv["keep-js"]) {
157 | _phantomApi.fs.remove(tmp_js);
158 | }
159 | phantom.exit();
160 | });
161 | }
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
5 |
6 | var _fs = require("fs");
7 |
8 | var _fs2 = _interopRequireDefault(_fs);
9 |
10 | var _render = require("./render.js");
11 |
12 | var _upload = require("./upload.js");
13 |
14 | var _lsEc = require("./ls-ec2.js");
15 |
16 | var _procGenChart = require("./proc-gen-chart.js");
17 |
18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19 |
20 | function unlink(path) {
21 | return new Promise(function (resolve, reject) {
22 | return _fs2.default.unlink(path, function (err) {
23 | return err ? reject(err) : resolve(path);
24 | });
25 | });
26 | }
27 |
28 | function post(channel, args, callback) {
29 | var cb_ok = callback || function (err, data) {
30 | return console.log(data);
31 | };
32 | var cb_err = callback || function (err, data) {
33 | return console.error(err);
34 | };
35 | return (0, _render.render)(args).then(function (path) {
36 | return Promise.all([(0, _upload.upload)(channel, path), unlink(path)]);
37 | }).then(function (_ref) {
38 | var _ref2 = _slicedToArray(_ref, 2),
39 | file = _ref2[0].file,
40 | path = _ref2[1];
41 |
42 | return cb_ok(null, file);
43 | }).catch(function (err) {
44 | return cb_err(err);
45 | });
46 | }
47 |
48 | module.exports = {
49 | slack: {
50 | post: post
51 | },
52 | ls_ec2: _lsEc.ls_ec2,
53 | render: _render.render,
54 | proc_gen_chart: _procGenChart.proc_gen_chart
55 | };
--------------------------------------------------------------------------------
/dist/ls-ec2.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
8 |
9 | exports.to_filters = to_filters;
10 | exports.tag_string_to_filters = tag_string_to_filters;
11 | exports.ls_ec2 = ls_ec2;
12 |
13 | var _awsSdk = require("aws-sdk");
14 |
15 | var _awsSdk2 = _interopRequireDefault(_awsSdk);
16 |
17 | var _immutable = require("immutable");
18 |
19 | var _immutable2 = _interopRequireDefault(_immutable);
20 |
21 | var _minimist = require("minimist");
22 |
23 | var _minimist2 = _interopRequireDefault(_minimist);
24 |
25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26 |
27 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
28 |
29 | /** */
30 | function describe_instances() {
31 | var filters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
32 |
33 | //AWS.config.update({region: "ap-northeast-1"})
34 | var ec2 = new _awsSdk2.default.EC2();
35 | var params = {
36 | Filters: [{ Name: "instance-state-name", Values: ["running"] }].concat(_toConsumableArray(filters))
37 | };
38 | return new Promise(function (resolve, reject) {
39 | return ec2.describeInstances(params, function (err, data) {
40 | return err ? reject(err) : resolve(data);
41 | });
42 | }).then(function (r) {
43 | return r.Reservations.map(function (e) {
44 | return e.Instances[0];
45 | });
46 | }).then(function (r) {
47 | return r.map(function (e) {
48 | return {
49 | InstanceId: e.InstanceId,
50 | InstanceType: e.InstanceType,
51 | Name: (_immutable2.default.List(e.Tags).find(function (e) {
52 | return e.Key === "Name";
53 | }) || {}).Value
54 | };
55 | });
56 | });
57 | }
58 |
59 | /**
60 | * in: tag:site=dev,role=webapp|db
61 | *
62 | * out: [
63 | * {Name: "tag:site", Values: ["dev"]},
64 | * {Name: "tag:role", Values: ["webapp", "db"]},
65 | * ]
66 | */
67 | function to_filters(s) {
68 | return s.trim().split(",").map(function (e) {
69 | return e.trim().split("=");
70 | }).map(function (_ref) {
71 | var _ref2 = _slicedToArray(_ref, 2),
72 | k = _ref2[0],
73 | v = _ref2[1];
74 |
75 | return { Name: "tag:" + k, Values: [].concat(_toConsumableArray(v.split("|"))) };
76 | });
77 | }
78 |
79 | function tag_string_to_filters(s) {
80 | if (!s) return [];
81 | var a = s.match("^tag:(.*)");
82 | return a ? to_filters(a[1]) : [];
83 | }
84 |
85 | function ls_ec2(f) {
86 | return describe_instances(to_filters(f)).then(function (e) {
87 | return e.map(function (s) {
88 | return s.InstanceId;
89 | });
90 | }).then(function (e) {
91 | return e.join(",");
92 | });
93 | }
94 |
95 | if (require.main === module) {
96 | var argv = (0, _minimist2.default)(process.argv.slice(2));
97 | _awsSdk2.default.config.update({ region: argv.region || process.env.AWS_DEFAULT_REGION || "ap-northeast-1" });
98 | describe_instances(tag_string_to_filters(argv._[0])).then(function (r) {
99 | return console.log(JSON.stringify(r, null, 2));
100 | }).catch(function (err) {
101 | return console.error(err.stack);
102 | });
103 | }
--------------------------------------------------------------------------------
/dist/ls-rds.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.ls_rds = ls_rds;
7 |
8 | var _awsSdk = require("aws-sdk");
9 |
10 | var _awsSdk2 = _interopRequireDefault(_awsSdk);
11 |
12 | var _immutable = require("immutable");
13 |
14 | var _immutable2 = _interopRequireDefault(_immutable);
15 |
16 | var _lsEc = require("./ls-ec2.js");
17 |
18 | var ec2 = _interopRequireWildcard(_lsEc);
19 |
20 | var _minimist = require("minimist");
21 |
22 | var _minimist2 = _interopRequireDefault(_minimist);
23 |
24 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
25 |
26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
27 |
28 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
29 |
30 | /** */
31 | function describe_db_instances() {
32 | var filters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
33 |
34 | //AWS.config.update({region: "ap-northeast-1"})
35 | var rds = new _awsSdk2.default.RDS();
36 | var params = {
37 | Filters: [].concat(_toConsumableArray(filters))
38 | };
39 | return new Promise(function (resolve, reject) {
40 | return rds.describeDBInstances(params, function (err, data) {
41 | return err ? reject(err) : resolve(data);
42 | });
43 | }).then(function (r) {
44 | return r;
45 | });
46 | }
47 |
48 | function ls_rds(f) {
49 | return describe_db_instances(ec2.to_filters(f)).then(function (e) {
50 | return e.map(function (s) {
51 | return s.InstanceId;
52 | });
53 | }).then(function (e) {
54 | return e.join(",");
55 | });
56 | }
57 |
58 | if (require.main === module) {
59 | var argv = (0, _minimist2.default)(process.argv.slice(2));
60 | _awsSdk2.default.config.update({ region: argv.region || process.env.AWS_DEFAULT_REGION || "ap-northeast-1" });
61 | describe_db_instances(ec2.tag_string_to_filters(argv._[0])).then(function (r) {
62 | return console.log(JSON.stringify(r, null, 2));
63 | }).catch(function (err) {
64 | return console.error(err.stack);
65 | });
66 | }
--------------------------------------------------------------------------------
/dist/metrics.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.METRICS = undefined;
7 |
8 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
9 |
10 | exports.searchMetric = searchMetric;
11 | exports.nsToDimName = nsToDimName;
12 | exports.toMax = toMax;
13 | exports.toMin = toMin;
14 | exports.toAxisYLabel = toAxisYLabel;
15 | exports.toY = toY;
16 | exports.find_stat_name = find_stat_name;
17 | exports.calc_period = calc_period;
18 | exports.to_axis_x_label_text = to_axis_x_label_text;
19 |
20 | var _immutable = require("immutable");
21 |
22 | var _immutable2 = _interopRequireDefault(_immutable);
23 |
24 | var _moment = require("moment");
25 |
26 | var _moment2 = _interopRequireDefault(_moment);
27 |
28 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
29 |
30 | var metricsRDS = [{ MetricName: "BinLogDiskUsage", Statistics: ["Maximum"] }, { MetricName: "CPUUtilization", Statistics: ["Average"] }, { MetricName: "DatabaseConnections", Statistics: ["Maximum"] }, { MetricName: "DiskQueueDepth", Statistics: ["Maximum"] }, { MetricName: "FreeStorageSpace", Statistics: ["Minimum"] }, { MetricName: "FreeableMemory", Statistics: ["Minimum"] }, { MetricName: "NetworkReceiveThroughput", Statistics: ["Maximum"] }, { MetricName: "NetworkTransmitThroughput", Statistics: ["Maximum"] }, { MetricName: "ReadIOPS", Statistics: ["Maximum"] }, { MetricName: "ReadLatency", Statistics: ["Maximum"] }, { MetricName: "ReadThroughput", Statistics: ["Maximum"] }, { MetricName: "SwapUsage", Statistics: ["Maximum"] }, { MetricName: "WriteIOPS", Statistics: ["Maximum"] }, { MetricName: "WriteLatency", Statistics: ["Maximum"] }, { MetricName: "WriteThroughput", Statistics: ["Maximum"] }];
31 |
32 | var metricsEC2 = [{ MetricName: "CPUCreditUsage", Statistics: ["Maximum"] }, { MetricName: "CPUCreditBalance", Statistics: ["Maximum"] }, { MetricName: "CPUUtilization", Statistics: ["Average"] }, { MetricName: "DiskReadOps", Statistics: ["Maximum"] }, { MetricName: "DiskWriteOps", Statistics: ["Maximum"] }, { MetricName: "DiskReadBytes", Statistics: ["Maximum"] }, { MetricName: "DiskWriteBytes", Statistics: ["Maximum"] }, { MetricName: "NetworkIn", Statistics: ["Maximum"] }, { MetricName: "NetworkOut", Statistics: ["Maximum"] }, { MetricName: "StatusCheckFailed", Statistics: ["Maximum"] }, { MetricName: "StatusCheckFailed_Instance", Statistics: ["Maximum"] }, { MetricName: "StatusCheckFailed_System", Statistics: ["Maximum"] }];
33 |
34 | var metricsDynamoDB = [{ MetricName: "ConsumedReadCapacityUnits", Statistics: ["Sum"] }, { MetricName: "ConsumedWriteCapacityUnits", Statistics: ["Sum"] }, { MetricName: "ProvisionedReadCapacityUnits", Statistics: ["Maximum"] }, { MetricName: "ProvisionedWriteCapacityUnits", Statistics: ["Maximum"] }, { MetricName: "ConditionalCheckFailedRequests", Statistics: ["Maximum"] }, { MetricName: "OnlineIndexConsumedWriteCapacity", Statistics: ["Maximum"] }, { MetricName: "OnlineIndexPercentageProgress", Statistics: ["Maximum"] }, { MetricName: "OnlineIndexThrottleEvents", Statistics: ["Maximum"] }, { MetricName: "ReturnedItemCount", Statistics: ["Maximum"] }, { MetricName: "SuccessfulRequestLatency", Statistics: ["Maximum"] }, { MetricName: "SystemErrors", Statistics: ["Maximum"] }, { MetricName: "ThrottledRequests", Statistics: ["Maximum"] }, { MetricName: "UserErrors", Statistics: ["Maximum"] }, { MetricName: "WriteThrottleEvents", Statistics: ["Maximum"] }, { MetricName: "ReadThrottleEvents", Statistics: ["Sum"] }];
35 |
36 | "Seconds | Microseconds | Milliseconds | Bytes | Kilobytes | Megabytes | Gigabytes | Terabytes | Bits | Kilobits | Megabits | Gigabits | Terabits | Percent | Count | Bytes/Second | Kilobytes/Second | Megabytes/Second | Gigabytes/Second | Terabytes/Second | Bits/Second | Kilobits/Second | Megabits/Second | Gigabits/Second | Terabits/Second | Count/Second | None";
37 | "Minimum | Maximum | Sum | Average | SampleCount";
38 |
39 | var METRICS = exports.METRICS = {
40 | "AWS/EC2": metricsEC2,
41 | "AWS/RDS": metricsRDS,
42 | "AWS/DynamoDB": metricsDynamoDB
43 | };
44 |
45 | function searchMetric(ns, metricName) {
46 | var a = METRICS[ns].filter(function (_ref) {
47 | var n = _ref.MetricName;
48 | return n.match(new RegExp(metricName, "i"));
49 | });
50 | return a[0];
51 | }
52 |
53 | function nsToDimName(ns) {
54 | return {
55 | "AWS/RDS": "DBInstanceIdentifier",
56 | "AWS/EC2": "InstanceId",
57 | "AWS/DynamoDB": "TableName"
58 | }[ns];
59 | }
60 |
61 | function toMax(metrics) {
62 | if (!metrics.Datapoints[0]) {
63 | return null;
64 | }
65 | if (metrics.Datapoints[0].Unit === "Percent") {
66 | return 100.0;
67 | }
68 | return null;
69 | }
70 |
71 | function toMin(metrics) {
72 | if (!metrics.Datapoints[0]) {
73 | return null;
74 | }
75 | if (metrics.Datapoints[0].Unit === "Percent") {
76 | return 0.0;
77 | }
78 | return null;
79 | }
80 |
81 | function toAxisYLabel(metrics, bytes) {
82 | if (metrics.Datapoints[0].Unit === "Bytes" && !bytes) {
83 | return "Megabytes";
84 | }
85 | return metrics.Datapoints[0].Unit;
86 | }
87 |
88 | function toY(metric, bytes) {
89 | var e = metric["Maximum"] || metric["Average"] || metric["Minimum"] || metric["Sum"] || metric["SampleCount"];
90 | if (metric.Unit === "Bytes" && !bytes) {
91 | return e / (1024 * 1024); // Megabytes
92 | }
93 | return e || 0;
94 | }
95 |
96 | var _stats = _immutable2.default.Set(["Maximum", "Average", "Minimum", "Sum", "SampleCount"]);
97 | function find_stat_name(datapoints) {
98 | if (!(datapoints && datapoints.length > 0)) return null;
99 | var dp = datapoints[0];
100 | return _immutable2.default.List(Object.keys(dp)).find(function (e) {
101 | return _stats.has(e);
102 | });
103 | }
104 |
105 | function calc_period(datapoints) {
106 | var measurement = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "minutes";
107 |
108 | if (!(datapoints && datapoints.length > 1)) return null;
109 |
110 | var _datapoints$sort = datapoints.sort(function (a, b) {
111 | return a.Timestamp.localeCompare(b.Timestamp);
112 | }),
113 | _datapoints$sort2 = _slicedToArray(_datapoints$sort, 2),
114 | a = _datapoints$sort2[0],
115 | b = _datapoints$sort2[1];
116 |
117 | return (0, _moment2.default)(b.Timestamp).diff((0, _moment2.default)(a.Timestamp), measurement);
118 | }
119 |
120 | function to_axis_x_label_text(stats, utc) {
121 | var et = (0, _moment2.default)(stats.EndTime);
122 | var diff = (0, _moment2.default)(stats.StartTime).diff(et);
123 | var tz = utc ? "UTC" : new Date().getTimezoneOffset() / 60 + "h";
124 |
125 | var ago = _moment2.default.duration(diff).humanize();
126 | var from = (utc ? et.utc() : et).format("YYYY-MM-DD HH:mm");
127 | return find_stat_name(stats.Datapoints) + " every " + calc_period(stats.Datapoints) + "minutes from " + from + " (tz:" + tz + ") to " + ago + " ago";
128 | }
129 |
130 | //
131 | if (require.main === module) {
132 | console.log(JSON.stringify(METRICS, null, 2));
133 | }
--------------------------------------------------------------------------------
/dist/phantom-api.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.webpage = exports.fs = exports.system = undefined;
7 |
8 | var _system = require("system");
9 |
10 | var _system2 = _interopRequireDefault(_system);
11 |
12 | var _fs = require("fs");
13 |
14 | var _fs2 = _interopRequireDefault(_fs);
15 |
16 | var _webpage = require("webpage");
17 |
18 | var _webpage2 = _interopRequireDefault(_webpage);
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | exports.system = _system2.default;
23 | exports.fs = _fs2.default;
24 | exports.webpage = _webpage2.default;
--------------------------------------------------------------------------------
/dist/print-names.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
4 |
5 | var _immutable = require("immutable");
6 |
7 | var _immutable2 = _interopRequireDefault(_immutable);
8 |
9 | var _cloudwatch = require("./cloudwatch.js");
10 |
11 | var _cloudwatch2 = _interopRequireDefault(_cloudwatch);
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | var argv = require("minimist")(process.argv.slice(2));
16 | var region = argv.region || process.env.AWS_DEFAULT_REGION || "ap-northeast-1";
17 | var namespace = argv.namespace || "AWS/EC2";
18 |
19 | _cloudwatch2.default.region(region).names().then(function (r) {
20 | return r.Metrics;
21 | }).then(function (r) {
22 | return [r.map(function (e) {
23 | return e.MetricName;
24 | }), r.map(function (_ref) {
25 | var _ref$Dimensions = _slicedToArray(_ref.Dimensions, 1);
26 |
27 | var d = _ref$Dimensions[0];
28 | return d;
29 | }).filter(function (e) {
30 | return e;
31 | })
32 | //.filter(d => d.Name === "DBInstanceIdentifier") // declared in params
33 | .map(function (d) {
34 | return d ? d.Value : null;
35 | })];
36 | }).then(function (_ref2) {
37 | var _ref3 = _slicedToArray(_ref2, 2);
38 |
39 | var a = _ref3[0];
40 | var b = _ref3[1];
41 | return [_immutable2.default.Set(a), _immutable2.default.Set(b)];
42 | }).then(function (_ref4) {
43 | var _ref5 = _slicedToArray(_ref4, 2);
44 |
45 | var a = _ref5[0];
46 | var b = _ref5[1];
47 | return [a.sort()];
48 | }). /*b.sort()*/then(function (r) {
49 | return console.log(JSON.stringify(r));
50 | }).catch(function (err) {
51 | return console.log(err);
52 | });
--------------------------------------------------------------------------------
/dist/print-stats.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4 |
5 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
6 |
7 | var _awsSdk = require("aws-sdk");
8 |
9 | var _awsSdk2 = _interopRequireDefault(_awsSdk);
10 |
11 | var _cloudwatch = require("./cloudwatch.js");
12 |
13 | var _cloudwatch2 = _interopRequireDefault(_cloudwatch);
14 |
15 | var _metrics = require("./metrics.js");
16 |
17 | var _time = require("./time.js");
18 |
19 | var _lsEc = require("./ls-ec2.js");
20 |
21 | var _path = require("path");
22 |
23 | var _path2 = _interopRequireDefault(_path);
24 |
25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26 |
27 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
28 |
29 | function print_stats(argv) {
30 | //let [ instID, metricName = 'CPUUtilization', ns = 'AWS/EC2'] = argv._; // flow doesn't support
31 | var instIDs = argv._[0];
32 | var metricName = argv._[1] || "CPUUtilization";
33 | var ns = argv._[2] || "AWS/EC2";
34 | var dimName = (0, _metrics.nsToDimName)(ns);
35 | var region = argv.region || process.env.AWS_DEFAULT_REGION || "ap-northeast-1";
36 | var period = (0, _time.toSeconds)(argv.period || "30minutes");
37 | var stats = argv.statistics;
38 |
39 | if (!instIDs) {
40 | //throw new Error("instanceID is missing")
41 | return Promise.reject(new Error("InstanceId is missing"));
42 | }
43 |
44 | _awsSdk2.default.config.update({ region: region });
45 |
46 | var watch = function watch(instanceID) {
47 | return new _cloudwatch2.default().endTime(argv["end-time"]).duration(argv.duration || "1day").period(period).statistics(stats).metricStatistics(ns, instanceID, metricName).then(function (_ref) {
48 | var _ref2 = _slicedToArray(_ref, 2),
49 | sep = _ref2[0],
50 | data = _ref2[1];
51 |
52 | return _extends(_defineProperty({
53 | Namespace: ns
54 | }, dimName, instanceID), sep, data);
55 | });
56 | };
57 |
58 | var a = instIDs.match("^tag:(.*)");
59 | if (a && ns === "AWS/RDS") return Promise.reject(new Error("filters is not supported AWS/RDS"));
60 |
61 | var p = a ? (0, _lsEc.ls_ec2)(a[1]) : Promise.resolve(instIDs);
62 | return p.then(function (s) {
63 | return s.split(",");
64 | }).then(function (s) {
65 | return Promise.all(s.map(function (e) {
66 | return watch(e.trim());
67 | }));
68 | });
69 | }
70 |
71 | module.exports = {
72 | print_stats: print_stats
73 | };
74 |
75 | if (require.main === module) {
76 | print_stats(require("minimist")(process.argv.slice(2))).then(function (r) {
77 | return process.stdout.write(JSON.stringify(r));
78 | }).catch(function (err) {
79 | //process.stderr.write(JSON.stringify(err, null, 2));
80 | console.error(err);
81 | process.exit(1);
82 | });
83 | }
--------------------------------------------------------------------------------
/dist/proc-gen-chart.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.proc_gen_chart = proc_gen_chart;
7 |
8 | var _child_process = require("child_process");
9 |
10 | var _path = require("path");
11 |
12 | var _path2 = _interopRequireDefault(_path);
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | // workaround: prod.stdin.write doesn't pass for flow check
17 | function write_to_stdin(proc, obj) {
18 | proc.stdin.write(JSON.stringify(obj));
19 | proc.stdin.end();
20 | }
21 |
22 | function proc_gen_chart(args) {
23 | return function (r) {
24 | return new Promise(function (resolve, reject) {
25 | var cmd = _path2.default.join(__dirname, "../node_modules/.bin/", "phantomjs");
26 | var js = _path2.default.join(__dirname, "gen-chart.js");
27 | var nmp = _path2.default.join(__dirname, "../node_modules");
28 | var p = (0, _child_process.spawn)(cmd, [js, "--node_modules_path", nmp].concat(args));
29 | write_to_stdin(p, r);
30 | var buf = "";
31 | var errbuf = "";
32 | p.stdout.on("data", function (data) {
33 | return buf += "" + data;
34 | }); // filename: String
35 | p.stderr.on("data", function (data) {
36 | return errbuf += "" + data;
37 | });
38 | p.on("close", function (code) {
39 | return code == 0 ? resolve(buf) : reject(new Error(errbuf));
40 | });
41 | });
42 | };
43 | }
--------------------------------------------------------------------------------
/dist/render.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _minimist = require("minimist");
4 |
5 | var _minimist2 = _interopRequireDefault(_minimist);
6 |
7 | var _printStats = require("./print-stats.js");
8 |
9 | var _procGenChart = require("./proc-gen-chart.js");
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | function render(args) {
14 | return (0, _printStats.print_stats)((0, _minimist2.default)(args)).then((0, _procGenChart.proc_gen_chart)(args));
15 | }
16 |
17 |
18 | module.exports = {
19 | render: render
20 | };
21 |
22 | if (require.main === module) {
23 | render(process.argv.slice(2)).then(function (r) {
24 | return console.log(r);
25 | }).catch(function (err) {
26 | return console.error(err.stack);
27 | });
28 | }
--------------------------------------------------------------------------------
/dist/time.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
8 |
9 | exports.toSEP = toSEP;
10 | exports.toSeconds = toSeconds;
11 |
12 | var _moment = require("moment");
13 |
14 | var _moment2 = _interopRequireDefault(_moment);
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17 |
18 | /*
19 | * Start End Period
20 | */
21 | function toSEP() {
22 | var reltime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "1day";
23 | var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
24 |
25 | var a = reltime.match(/([0-9]+) *(.*)/);
26 | if (!a) {
27 | throw new Error(reltime);
28 | }
29 |
30 | var _a = _slicedToArray(a, 3),
31 | _ = _a[0],
32 | n = _a[1],
33 | m = _a[2];
34 |
35 | var endTime = end ? (0, _moment2.default)(end) : (0, _moment2.default)();
36 | //console.error(endTime);
37 | var duration = _moment2.default.duration(parseInt(n), m);
38 | var startTime = endTime.clone().subtract(duration);
39 | var period = 60 * 30;
40 |
41 | return {
42 | EndTime: endTime.toDate(),
43 | StartTime: startTime.toDate(),
44 | Period: period
45 | };
46 | }
47 |
48 | function toSeconds(period) {
49 | var a = period.match(/([0-9]+)(.+)/);
50 | if (!a) {
51 | return NaN;
52 | }
53 |
54 | var _a2 = _slicedToArray(a, 3),
55 | _ = _a2[0],
56 | n = _a2[1],
57 | u = _a2[2];
58 |
59 | return _moment2.default.duration(parseInt(n), u).asSeconds();
60 | }
61 |
62 | exports.default = {
63 | toSEP: toSEP,
64 | toSeconds: toSeconds
65 | };
--------------------------------------------------------------------------------
/dist/upload.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | var fs = require("fs");
5 | var fetch = require("node-fetch");
6 | var FormData = require("form-data");
7 |
8 | function upload(channel, path) {
9 | return new Promise(function (resolve, reject) {
10 | var form = new FormData();
11 | form.append("channels", channel);
12 | form.append("token", process.env.SLACK_API_TOKEN);
13 | //form.append("file", fs.createReadStream(path)); //NOTE: This became to fail somehow suddenly... https://github.com/form-data/form-data#notes
14 | var buf = fs.readFileSync(path);
15 | form.append("file", buf, {
16 | filename: path,
17 | contentType: "image/png",
18 | knownLength: buf.length
19 | });
20 |
21 | return fetch("https://slack.com/api/files.upload", { method: "POST", body: form }).then(function (res) {
22 | return (
23 | /*
24 | * {ok: true, file: {...}} See https://api.slack.com/types/file
25 | */
26 | res.json()
27 | );
28 | }).then(function (e) {
29 | return e.ok ? resolve(e) : reject(new Error(JSON.stringify(e)));
30 | }).catch(function (err) {
31 | return reject(err);
32 | });
33 | });
34 | }
35 |
36 | module.exports = {
37 | upload: upload
38 | };
39 |
40 | if (require.main === module) {
41 | var channel = process.env.SLACK_CHANNEL_NAME || "#api-test";
42 | upload(channel, process.argv[2]).then(function (json) {
43 | return console.log(json);
44 | }).catch(function (err) {
45 | return console.error(err);
46 | });
47 | }
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var babel = require('gulp-babel');
3 | var src_dir = './src/*.js'
4 |
5 | gulp.task('default', ['compile', 'compile:watch'])
6 | gulp.task('test', ['mocha:env', 'mocha', 'mocha:watch'])
7 | gulp.task('lint', ['eslint', 'eslint:watch'])
8 |
9 | /** */
10 | gulp.task('compile', function () {
11 | gulp.src(src_dir)
12 | .pipe(babel())
13 | .pipe(gulp.dest('dist'));
14 | });
15 |
16 | gulp.task('compile:watch', function(){
17 | gulp.watch(src_dir, ['compile']);
18 | });
19 |
20 | /** */
21 | gulp.task('minify', function() {
22 | var uglify= require('gulp-uglify');
23 | gulp.src(['./dist/**/*.js'])
24 | .pipe(uglify())
25 | .pipe(gulp.dest('./build'))
26 | });
27 |
28 | /** */
29 | gulp.task('mocha', function() {
30 | var mocha = require('gulp-mocha');
31 | var gutil = require('gulp-util');
32 | gulp.src(['test/*.js'], {read: false})
33 | .pipe(mocha({reporter: 'list', require: ["babel-core/register"]}))
34 | .on('error', gutil.log);
35 | });
36 |
37 | gulp.task('mocha:watch', function() {
38 | gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']);
39 | });
40 |
41 | gulp.task('mocha:env', function () {
42 | var env = require('gulp-env');
43 | env({vars: {BABEL_ENV: "test"}})
44 | });
45 |
46 | /** */
47 | gulp.task('eslint', function() {
48 | var eslint = require('gulp-eslint');
49 | gulp.src(["src/**/*.js", "test/**/*.js"])
50 | .pipe(eslint({useEslintrc: true}))
51 | .pipe(eslint.format())
52 | .pipe(eslint.failAfterError());
53 | });
54 |
55 | gulp.task('eslint:watch', function() {
56 | gulp.watch(['src/**/*.js', "test/**/*.js"], ['lint']);
57 | });
58 |
59 | /** */
60 | //gulp.task('typecheck', function() {
61 | // var flow = require("gulp-flowtype");
62 | // return gulp.src('./src/*.js')
63 | // .pipe(flow({
64 | // all: false,
65 | // weak: false,
66 | // declarations: './lib',
67 | // killFlow: true,
68 | // beep: true,
69 | // abort: false
70 | // }))
71 | //});
72 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./dist/index.js");
2 |
--------------------------------------------------------------------------------
/lib/modules.js:
--------------------------------------------------------------------------------
1 |
2 | declare module child_process {
3 | declare function spawn(a: string, args: Array): Process;
4 | }
5 |
6 | declare module "form-data" {
7 | declare class exports {
8 | append(key: string, val: any): void;
9 | }
10 | }
11 |
12 | declare module "node-fetch" {
13 | declare function exports(path: string, options: Object): Promise;
14 | }
15 |
16 | declare module "aws-sdk" {
17 | declare class CloudWatch {
18 | listMetrics(params: Object, callback: function): void;
19 | getMetricStatistics(params: Object, callback: function): void;
20 | }
21 | declare class EC2 {
22 | describeInstances(params: Object, callback: function): void;
23 | }
24 | declare class RDS {
25 | describeDBInstances(params: Object, callback: function): void;
26 | }
27 | declare class AWSConfig {
28 | update(conf: Object): void;
29 | }
30 | declare var config: AWSConfig;
31 | }
32 |
33 | // ignore
34 | declare module "moment" {
35 | declare function exports(): any;
36 | }
37 |
38 | declare module "minimist" {
39 | declare function exports(): any;
40 | }
41 |
42 | declare module "immutable" {
43 | declare class List {
44 | static (vals: Array): List;
45 | find(predicate: (value: T) => any): T;
46 | }
47 | declare class Set {
48 | static (vals: Array): Set;
49 | has(e: any): boolean;
50 | }
51 | }
52 |
53 | declare class Phantom {
54 | exit(i: number): void;
55 | exit(): void;
56 | }
57 |
58 | declare var phantom: Phantom;
59 |
60 | declare var require: {(s: string): any, main: Object}
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-cloudwatch-chart-slack",
3 | "version": "0.1.3",
4 | "description": "Chart Renderer for AWS CloudWatch Datapoints and Uploader to Slack",
5 | "keywords": ["AWS", "CloudWatch", "chart", "upload", "Slack"],
6 | "main": "index.js",
7 | "engines": {
8 | "node": "~4",
9 | "npm": "~2"
10 | },
11 | "scripts": {
12 | "build": "gulp compile",
13 | "test": "BABEL_ENV=test mocha --require babel-core/register --recursive",
14 | "lint": "gulp eslint",
15 | "typecheck": "flow check"
16 | },
17 | "author": "tmtk75",
18 | "license": "MIT",
19 | "dependencies": {
20 | "aws-sdk": "^2.2.30",
21 | "c3": "^0.4.11-rc4",
22 | "d3": "^3.5.17",
23 | "form-data": "^1.0.0-rc4",
24 | "immutable": "^3.7.6",
25 | "minimist": "^1.2.0",
26 | "moment": "^2.11.1",
27 | "node-fetch": "^1.3.3",
28 | "phantomjs": "^2.1.7"
29 | },
30 | "devDependencies": {
31 | "babel-core": "^6.3.21",
32 | "babel-eslint": "^5.0.0-beta6",
33 | "babel-loader": "^6.2.0",
34 | "babel-plugin-espower": "^2.1.0",
35 | "babel-plugin-transform-flow-strip-types": "^6.4.0",
36 | "babel-preset-es2015": "^6.3.13",
37 | "babel-preset-stage-0": "^6.3.13",
38 | "eslint": "^1.10.3",
39 | "eslint-plugin-babel": "^3.0.0",
40 | "gulp": "^3.9.0",
41 | "gulp-babel": "^6.1.1",
42 | "gulp-env": "^0.4.0",
43 | "gulp-eslint": "^1.1.1",
44 | "gulp-mocha": "^2.2.0",
45 | "gulp-uglify": "^1.5.1",
46 | "gulp-util": "^3.0.7",
47 | "mocha": "^2.3.4",
48 | "power-assert": "^1.2.0"
49 | },
50 | "directories": {
51 | "bin": "./bin"
52 | },
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/tmtk75/aws-cloudwatch-chart-slack"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import {slack} from "./index.js"
3 |
4 | const channel_name = process.env.SLACK_CHANNEL_NAME || "#api-test"
5 | slack.post(channel_name, process.argv.slice(2), (err, file) => {
6 | if (err) {
7 | console.error(err.stack);
8 | return
9 | }
10 | console.log(file.thumb_80);
11 | })
12 |
--------------------------------------------------------------------------------
/src/cloudwatch.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import AWS from "aws-sdk"
3 | import time from "./time.js"
4 | import moment from "moment"
5 | import {
6 | nsToDimName,
7 | searchMetric,
8 | } from "./metrics.js"
9 |
10 | class CloudWatch {
11 | _endTime: string;
12 | _duration: string;
13 | _period: number;
14 | _statistics: string;
15 |
16 | /** */
17 | endTime(d: string): CloudWatch {
18 | this._endTime = d;
19 | return this;
20 | }
21 |
22 | /** */
23 | duration(d: string): CloudWatch {
24 | this._duration = d;
25 | return this;
26 | }
27 |
28 | /** */
29 | period(p: number): CloudWatch {
30 | this._period = p;
31 | return this;
32 | }
33 |
34 | /** */
35 | statistics(name: string): CloudWatch {
36 | this._statistics = name;
37 | return this;
38 | }
39 |
40 | /** */
41 | metricStatistics(namespace: string, instanceID: string, metricName: string): Promise {
42 | let dimName = nsToDimName(namespace);
43 | let metric = searchMetric(namespace, metricName);
44 | let sep = time.toSEP(this._duration, this._endTime);
45 | if (this._period) {
46 | sep.Period = this._period;
47 | }
48 | if (this._statistics) {
49 | metric.Statistics = [ this._statistics ]
50 | }
51 |
52 | let params = {
53 | ...sep,
54 | ...metric,
55 | Namespace: namespace,
56 | Dimensions: [
57 | {
58 | Name: dimName,
59 | Value: instanceID,
60 | },
61 | ],
62 | };
63 |
64 | //process.stderr.write(JSON.stringify(params));
65 | let cloudwatch = new AWS.CloudWatch();
66 | return new Promise((resolve, reject) =>
67 | cloudwatch.getMetricStatistics(params, (err, data) => err ? reject(err) : resolve([sep, data]))
68 | )
69 | }
70 | }
71 |
72 | export default CloudWatch;
73 |
74 |
--------------------------------------------------------------------------------
/src/dynamodb.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as m from "./metrics.js"
3 |
4 | /**
5 | * Returns true if stat is exactly in a condition.
6 | */
7 | export function mimic(stat: Object): boolean {
8 | return stat.Namespace === "AWS/DynamoDB"
9 | && stat.Period === 60
10 | && (stat.Label === "ConsumedReadCapacityUnits" || stat.Label === "ConsumedWriteCapacityUnits")
11 | && (stat.Datapoints[0].Sum !== undefined)
12 | }
13 |
14 | export function toY(e: Object, bytes: boolean = false): number {
15 | return m.toY(e, bytes) / 60;
16 | }
17 |
18 | export default {
19 | mimic,
20 | toY,
21 | }
22 |
--------------------------------------------------------------------------------
/src/gen-chart.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import {system, fs, webpage} from "./phantom-api.js"
3 | import moment from "moment"
4 | import {toMax, toMin, toAxisYLabel, toY, nsToDimName, to_axis_x_label_text} from "./metrics.js"
5 | import dynamodb from "./dynamodb.js"
6 |
7 | const argv = require("minimist")(system.args.slice(1), {
8 | string: ["filename", "width", "height", "max", "min", "node_modules_path",
9 | "x-label", "format", "bindto", "point-r"],
10 | boolean: ["base64", "keep-html", "keep-js", "grid-x", "grid-y", "utc", "bytes", "without-image"],
11 | alias: {
12 | f: "filename",
13 | },
14 | default: {
15 | width: 800,
16 | height: 300,
17 | node_modules_path: "./node_modules",
18 | format: "png",
19 | "x-tick-count": 120,
20 | "x-tick-culling-max": 10,
21 | "bindto": "container",
22 | "point-r": 2.5,
23 | }
24 | });
25 |
26 | try {
27 | const stats_data = JSON.parse(system.stdin.read())
28 | const repre = stats_data[0]
29 | const MetricName = repre.Label || ""
30 | const Namespace = repre.Namespace || ""
31 | const sort = (datapoints) => datapoints.sort((a, b) => a.Timestamp.localeCompare(b.Timestamp))
32 | const yData = stats_data.map(stats => {
33 | if (stats.Datapoints.length < 2) {
34 | throw new Error(`Number of datapoints is less than 2 for ${MetricName} of ${stats.InstanceId}. There is a possibility InstanceId was wrong. ${JSON.stringify(stats)}`)
35 | }
36 | let b = dynamodb.mimic(stats)
37 | return [stats[nsToDimName(Namespace)]].concat(sort(stats.Datapoints)
38 | .map(e => b ? dynamodb.toY(e) : toY(e, argv.bytes)))
39 | })
40 | const textLabelX = to_axis_x_label_text(repre, argv.utc)
41 |
42 | const data = {
43 | _meta: {StartTime: repre.StartTime, EndTime: repre.EndTime, UTC: argv.utc},
44 | bindto: `#${argv.bindto}`,
45 | data: {
46 | x: "x",
47 | columns: [
48 | ["x"].concat(sort(repre.Datapoints).map(e => moment.utc(e["Timestamp"]).valueOf())),
49 | ].concat(yData),
50 | //colors: {
51 | // [axisXLabel]: (Namespace === "AWS/EC2" ? "#f58536" : null),
52 | //},
53 | },
54 | transition: {
55 | duration: null, //
56 | },
57 | size: {
58 | width: argv.width - 16, // heuristic adjustments
59 | height: argv.height - 16,
60 | },
61 | axis: {
62 | y: {
63 | max: (argv.max ? parseInt(argv.max) : toMax(repre)),
64 | min: (argv.min ? parseInt(argv.min) : toMin(repre)),
65 | padding: {top: 0, bottom: 0},
66 | label: {
67 | text: `${Namespace} ${MetricName} ${toAxisYLabel(repre, argv.bytes)}`,
68 | position: "outer-middle",
69 | },
70 | //tick: {
71 | // format: d3.format('$,'),
72 | //}
73 | },
74 | x: {
75 | type: "timeseries",
76 | tick: {
77 | count: argv["x-tick-count"],
78 | culling: {
79 | max: argv["x-tick-culling-max"],
80 | },
81 | _format: "%Y-%m-%dT%H:%M:%S",
82 | format: "%H:%M",
83 | },
84 | //padding: {left: 0, right: 0},
85 | label: {
86 | text: (argv["x-label"] || textLabelX),
87 | position: "outer-center",
88 | },
89 | localtime: !argv.utc,
90 | },
91 | },
92 | grid: {
93 | x: {
94 | show: argv["grid-x"],
95 | },
96 | y: {
97 | show: argv["grid-y"],
98 | }
99 | },
100 | point: {
101 | r: argv["point-r"],
102 | },
103 | }
104 |
105 | render(argv, data);
106 | } catch (ex) {
107 | system.stderr.write(ex.stack);
108 | system.stderr.write("\n");
109 | phantom.exit(1);
110 | }
111 |
112 | /*
113 | * Rendering
114 | */
115 | function render(argv: Object, data: Object): void {
116 | const page = webpage.create()
117 | page.onConsoleMessage = (msg) => console.log(msg)
118 | page.viewportSize = {
119 | width: argv.width ? parseInt(argv.width) : page.viewportSize.width,
120 | height: argv.height ? parseInt(argv.height) : page.viewportSize.height,
121 | }
122 | //console.log(JSON.stringify(page.viewportSize))
123 |
124 | const suffix = argv.filename || `.${system.pid}-${new Date().getTime()}`
125 | const tmp_html = `./${suffix}.html`
126 | const tmp_js = `./${suffix}.js`
127 | const filename = argv.filename || `./${suffix}.png`
128 | const node_modules_path = argv.node_modules_path;
129 |
130 | const now = moment().format("YYYY-MM-DD HH:mm:ss Z")
131 | fs.write(tmp_js, `
132 | // Generated at ${now}
133 | var data = ${JSON.stringify(data)};
134 | data.axis.y.tick = {format: d3.format(',')};
135 | c3.generate(data);
136 | `)
137 | fs.write(tmp_html, `
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | `)
149 |
150 | page.open(tmp_html, (status) => {
151 | //console.error(JSON.stringify(argv))
152 | if (!argv["without-image"]) {
153 | page.render(filename, {format: argv.format});
154 | system.stdout.write(filename)
155 | } else if (argv.base64) {
156 | system.stdout.write(page.renderBase64(argv.format))
157 | }
158 | if (!argv["keep-html"]) {
159 | fs.remove(tmp_html);
160 | }
161 | if (!argv["keep-js"]) {
162 | fs.remove(tmp_js);
163 | }
164 | phantom.exit()
165 | })
166 | }
167 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // @flow
3 | import fs from "fs"
4 | import {render} from "./render.js"
5 | import {upload} from "./upload.js"
6 | import {ls_ec2} from "./ls-ec2.js"
7 | import {proc_gen_chart} from "./proc-gen-chart.js"
8 |
9 | function unlink(path: string): Promise {
10 | return new Promise((resolve, reject) => fs.unlink(path, (err) => err ? reject(err) : resolve(path)))
11 | }
12 |
13 | function post(channel: string, args: Array, callback: Function): Promise {
14 | let cb_ok = callback || ((err, data) => console.log(data))
15 | let cb_err = callback || ((err, data) => console.error(err))
16 | return render(args)
17 | .then(path => Promise.all([
18 | upload(channel, path),
19 | unlink(path)
20 | ]))
21 | .then(([{file}, path]) => cb_ok(null, file))
22 | .catch(err => cb_err(err))
23 | }
24 |
25 | module.exports = {
26 | slack: {
27 | post,
28 | },
29 | ls_ec2,
30 | render,
31 | proc_gen_chart,
32 | }
33 |
--------------------------------------------------------------------------------
/src/ls-ec2.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import AWS from "aws-sdk"
3 | import I from "immutable"
4 |
5 | /** */
6 | function describe_instances(filters: Array