├── .gitignore ├── History.md ├── package.json ├── Readme.md ├── LICENSE ├── test └── index.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 1.1.0 / 2015-10-15 2 | 3 | * Support increment type(fake) metric 4 | 5 | 1.0.0 / 2015-10-14 6 | ================== 7 | 8 | * Basic falcon initialization and data push 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-falcon", 3 | "description": "open-falcon nodejs client", 4 | "repository": "CatTail/node-open-falcon", 5 | "version": "1.3.4", 6 | "keywords": [], 7 | "files": [ 8 | "index.js" 9 | ], 10 | "license": "MIT", 11 | "dependencies": { 12 | "debug": "^2.2.0", 13 | "request": "^2.65.0" 14 | }, 15 | "devDependencies": { 16 | "mocha": "^2.3.3", 17 | "should": "^7.1.0" 18 | }, 19 | "scripts": { 20 | "test": "./node_modules/.bin/mocha --require should test" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # node-falcon 2 | 3 | > open-falcon nodejs client 4 | 5 | see [open-falcon data push section](http://book.open-falcon.com/zh/usage/data-push.html) 6 | 7 | ## Installation 8 | 9 | npm install --save open-falcon 10 | 11 | ## Usage 12 | 13 | ```js 14 | 'use strict'; 15 | let Falcon = require('open-falcon'); 16 | Falcon.init('http://127.0.0.1:6060'); 17 | // report memory usage every minutes 18 | let falcon = new Falcon(); 19 | setInterval(function() { 20 | let usage = process.memoryUsage(); 21 | falcon 22 | .step(20) 23 | .tag('type', 'memory') 24 | .gauge('node.mem.rss', usage.rss) 25 | .gauge('node.mem.total', usage.heapTotal) 26 | .gauge('node.mem.used', usage.heapUsed); 27 | }, 1000 * 60); 28 | ``` 29 | 30 | ## TODO 31 | 32 | * Global waiting queue rather than every instance manage it's own metric queue 33 | 34 | ## License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (C) 2015 CatTail 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var Falcon = require('..') 3 | var debug = require('debug')('open-falcon:test') 4 | 5 | describe('open-falcon', function() { 6 | this.timeout(50000) 7 | 8 | var api = 'http://localhost:6060' 9 | var project = 'projectName' 10 | Falcon.init(api, project) 11 | 12 | it('should initialize api and project', function(done) { 13 | var falcon = new Falcon({ 14 | step: 0, 15 | }) 16 | 17 | var metric = 'metricName' 18 | var value = 100 19 | falcon.gauge(metric, value) 20 | 21 | getClientRequestBody(6060, function(queue) { 22 | queue[0].tags.should.equal('project='+project) 23 | queue[0].metric.should.equal(metric) 24 | queue[0].value.should.equal(value) 25 | done() 26 | }) 27 | }) 28 | 29 | it('should support GAUGE and COUNTER counterType', function(done) { 30 | var falcon = new Falcon({ 31 | step: 0, 32 | }) 33 | 34 | var metric = 'metricName' 35 | var value = 100 36 | falcon.gauge(metric, value) 37 | 38 | getClientRequestBody(6060, function(queue) { 39 | queue[0].counterType.should.equal('GAUGE') 40 | 41 | falcon.counter(metric, value) 42 | getClientRequestBody(6060, function(queue) { 43 | queue[0].counterType.should.equal('COUNTER') 44 | done() 45 | }) 46 | }) 47 | }) 48 | 49 | it('should support fake INCREMENT counterType', function(done) { 50 | var falcon = new Falcon() 51 | 52 | var metric = 'metricName' 53 | falcon 54 | .step(1) 55 | .increment(metric) 56 | .increment(metric) 57 | 58 | setTimeout(function() { 59 | falcon.increment(metric) 60 | }, 1000) 61 | 62 | getClientRequestBody(6060, function(queue) { 63 | queue[0].counterType.should.equal('GAUGE') 64 | queue[0].value.should.equal(2) 65 | 66 | falcon.destroy() 67 | done() 68 | }) 69 | }) 70 | 71 | it('should support multiple INCREMENT on single falcon instance', function(done) { 72 | var falcon = new Falcon() 73 | 74 | var metric1 = 'metricName1' 75 | var metric2 = 'metricName2' 76 | falcon 77 | .step(1) 78 | .increment(metric1) 79 | .increment(metric2) 80 | .increment(metric1) 81 | 82 | getClientRequestBody(6060, function(queue) { 83 | queue[0].counterType.should.equal('GAUGE') 84 | queue[0].value.should.equal(2) 85 | queue[1].counterType.should.equal('GAUGE') 86 | queue[1].value.should.equal(1) 87 | 88 | done() 89 | }) 90 | }) 91 | 92 | it('should tag metric', function(done) { 93 | var falcon = new Falcon({ 94 | step: 0, 95 | }) 96 | 97 | var metric = 'metricName' 98 | var value = 100 99 | falcon 100 | .tag('key', 'value') 101 | .gauge(metric, value) 102 | 103 | getClientRequestBody(6060, function(queue) { 104 | queue[0].tags.should.equal('project='+project+','+'key=value') 105 | done() 106 | }) 107 | }) 108 | 109 | it('should allow change step', function(done) { 110 | var falcon = new Falcon({ 111 | step: 0, 112 | }) 113 | 114 | var metric = 'metricName' 115 | var value = 100 116 | falcon 117 | .step(1) 118 | .gauge(metric, value) 119 | 120 | getClientRequestBody(6060, function(queue) { 121 | queue[0].step.should.equal(1) 122 | done() 123 | }) 124 | }) 125 | }) 126 | 127 | var http = require('http') 128 | function getClientRequestBody(port, callback) { 129 | var server = http.createServer(function(req, res) { 130 | if(req.method === "POST"){ 131 | var data = '' 132 | 133 | req.on('data', function(chunk){ 134 | data += chunk 135 | }) 136 | req.on('end', function(){ 137 | end() 138 | callback(JSON.parse(data)) 139 | }) 140 | } else { 141 | end() 142 | } 143 | 144 | function end() { 145 | res.end() 146 | server.close() 147 | } 148 | }) 149 | server.listen(port) 150 | } 151 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var os = require('os') 4 | var http = require('http') 5 | var request = require('request') 6 | var debug = require('debug')('open-falcon:index') 7 | 8 | function getTrimedHostname() { 9 | var hostname = os.hostname() 10 | if (hostname.split('.').length) { 11 | return hostname.split('.')[0] 12 | } 13 | return hostname 14 | } 15 | 16 | /** 17 | * var Falcon = require('open-falcon') 18 | * Falcon.init('http://localhost:6060', 'exampleProject') 19 | * 20 | * var falcon1 = new Falcon({ 21 | * tags: 'tagkey=tagvalue', 22 | * }) 23 | * 24 | * falcon1 25 | * .step(60) 26 | * .tag('anotherkey', 'anothervalue') 27 | * .gauge('metricName', 100) 28 | * 29 | * var falcon2 = new Falcon() 30 | * falcon2.counter('metricName2', 20) 31 | * 32 | * var falcon3 = new Falcon() 33 | * falcon3 34 | * .step(20) 35 | * .increment('incrementMetric1') 36 | * .increment('incrementMetric2') 37 | */ 38 | 39 | /** 40 | * options 41 | * step: optional, integer, seconds, default 60 42 | * tags: optional 43 | */ 44 | function Falcon(options) { 45 | debug('constructor', options) 46 | options = options || {} 47 | 48 | this._lock = false 49 | this._queue = [] 50 | this._increment = {} 51 | 52 | // step trigger autoFlush 53 | this.step(options.step === undefined ? Falcon.DEFAULT_STEP : options.step) 54 | if (Falcon.PROJECT) { 55 | this.tag('project', Falcon.PROJECT) 56 | } 57 | this._handler = options.handler || Falcon.DEFAULT_HANDLER 58 | if (options.tags) { 59 | this.tag(options.tags) 60 | } 61 | } 62 | 63 | /** 64 | * api: Falcon push api 65 | * options 66 | * project: project name, set as project= tag 67 | */ 68 | Falcon.DEFAULT_STEP = 60 69 | Falcon.DEFAULT_COUNTER_TYPE = 'GAUGE' 70 | Falcon.ENDPOINT = getTrimedHostname() 71 | 72 | /** 73 | * @param {string} api 74 | * @param {string} project project name 75 | * @param {Function} handler optional request handler 76 | */ 77 | Falcon.init = function(api, project, handler) { 78 | debug('init', api, project) 79 | Falcon.API = api 80 | Falcon.PROJECT = project 81 | Falcon.DEFAULT_HANDLER = handler || function noop(){} 82 | return Falcon 83 | } 84 | 85 | Falcon.prototype.destroy = function() { 86 | debug('destroy') 87 | clearTimeout(this._timeout) 88 | } 89 | 90 | Falcon.prototype.tag = function(key, value) { 91 | debug('tag', key, value) 92 | if (this._lock) { 93 | this.emit('error', new Error('Falcon locked, can\'t alter tag')) 94 | return this 95 | } 96 | 97 | if (!this._tags) { 98 | this._tags = '' 99 | } else { 100 | this._tags = this._tags + ',' 101 | } 102 | if (arguments.length === 2) { 103 | this._tags = this._tags + key + '=' + value 104 | } else { 105 | this._tags = this._tags + key 106 | } 107 | return this 108 | } 109 | 110 | Falcon.prototype.step = function(step) { 111 | debug('step', step) 112 | if (this._lock) { 113 | this.emit('error', new Error('Falcon locked, can\'t alter step')) 114 | return this 115 | } 116 | 117 | this._step = step 118 | // autoFlush timeout should change everytime step changes 119 | this.autoFlush() 120 | return this 121 | } 122 | 123 | Falcon.prototype.gauge = function(metric, value, options) { 124 | debug('gauge', metric, value, options) 125 | options = options || {} 126 | options.counterType = 'GAUGE' 127 | this.push(metric, value, options) 128 | return this 129 | } 130 | 131 | Falcon.prototype.counter = function(metric, value, options) { 132 | debug('counter', metric, value, options) 133 | options = options || {} 134 | options.counterType = 'COUNTER' 135 | this.push(metric, value, options) 136 | return this 137 | } 138 | 139 | Falcon.prototype.increment = function(metric, options) { 140 | debug('increment', metric, options) 141 | options = options || {} 142 | options.counterType = 'INCREMENT' 143 | this.push(metric, 1, options) 144 | return this 145 | } 146 | 147 | /** 148 | * @private 149 | */ 150 | Falcon.prototype.now = function() { 151 | return Math.floor(Date.now() / 1000) 152 | } 153 | 154 | /** 155 | * @private 156 | */ 157 | Falcon.prototype.createItem = function(metric, value, options) { 158 | options.tags = options.tags ? (','+options.tags) : '' 159 | 160 | var endpoint = options.endpoint || Falcon.ENDPOINT 161 | var timestamp = this.now() 162 | var step = options.step || this._step 163 | var counterType = options.counterType 164 | var tags = this._tags + options.tags 165 | 166 | if (counterType === 'INCREMENT') { 167 | // reset fake counterType 168 | counterType = 'GAUGE' 169 | } 170 | 171 | return { 172 | metric: metric, 173 | value: value, 174 | endpoint: endpoint, 175 | timestamp: timestamp, 176 | step: step, 177 | counterType: counterType, 178 | tags: tags, 179 | } 180 | } 181 | 182 | /** 183 | * @private 184 | */ 185 | Falcon.prototype.push = function(metric, value, options) { 186 | var counterType = options.counterType || Falcon.DEFAULT_COUNTER_TYPE 187 | if (counterType === 'GAUGE' || counterType === 'COUNTER') { 188 | this._queue.push(this.createItem(metric, value, options)) 189 | } else if (counterType === 'INCREMENT') { 190 | if (!this._increment[metric]) { 191 | this._increment[metric] = this.createItem(metric, value, options) 192 | } else { 193 | this._increment[metric].value = this._increment[metric].value + 1 194 | } 195 | } 196 | this.checkAndFlush() 197 | return this 198 | } 199 | 200 | /** 201 | * @private 202 | */ 203 | Falcon.prototype.autoFlush = function() { 204 | debug('autoFlush') 205 | clearTimeout(this._timeout) 206 | this.checkAndFlush() 207 | 208 | // if step equals to zero, reset it to 1 209 | var step = this._step || 1 210 | var self = this 211 | this._timeout = setTimeout(function() { 212 | self.autoFlush() 213 | }, step * 1000) 214 | } 215 | 216 | /** 217 | * @private 218 | */ 219 | Falcon.prototype.checkAndFlush = function() { 220 | debug('checkAndFlush') 221 | var timestamp = this.now() 222 | for (var metric in this._increment) { 223 | var increment = this._increment[metric] 224 | if (timestamp - increment.timestamp >= this._step) { 225 | this._queue.push(increment) 226 | // reset increment rather than delete it 227 | this._increment[metric] = this.createItem(increment.metric, 0, {counterType: 'INCREMENT'}) 228 | } 229 | } 230 | 231 | if (this._queue.length) { 232 | if (timestamp - this._queue[0].timestamp >= this._step) { 233 | this.flush() 234 | } 235 | } 236 | } 237 | 238 | /** 239 | * @private 240 | */ 241 | Falcon.prototype.flush = function() { 242 | debug('flush', this._queue) 243 | var queue = this._queue 244 | if (!queue.length) { 245 | return 246 | } 247 | this._queue = [] 248 | 249 | var self = this 250 | request.post({ 251 | url: Falcon.API, 252 | body: JSON.stringify(queue), 253 | }, this._handler) 254 | .on('error', this._handler) 255 | } 256 | 257 | module.exports = Falcon 258 | --------------------------------------------------------------------------------