UNPKG

swagger-stats

Version:

API Telemetry and APM. Trace API calls and Monitor API performance, health and usage statistics in Node.js Microservices, based on express routes and Swagger (Open API) specification

193 lines (143 loc) 6.34 kB
/** * Created by sv2 on 2/18/17. * Timeline Statistics */ 'use strict'; var util = require('util'); var debug = require('debug')('sws:timeline'); var swsUtil = require('./swsUtil'); var swsReqResStats = require('./swsReqResStats'); function swsTimeline() { // Options this.options = null; // Timeline Settings this.settings = { bucket_duration: 60000, // Timeline bucket duration in milliseconds bucket_current: 0, // Current Timeline bucket ID length: 60 // Timeline length - number of buckets to keep }; // Timeline of req / res statistics, one entry per minute for past 60 minutes // Hash by timestamp divided by settings.bucket_duration, so we can match finished response to bucket this.data = {}; this.startTime = process.hrtime(); this.startUsage = process.cpuUsage(); // average memory usage values on time interval this.memorySum = process.memoryUsage(); this.memoryMeasurements = 1; // current max event loop lag this.lag = 0; } swsTimeline.prototype.getStats = function(reqresdata) { return { settings: this.settings, data: this.data }; }; // Create empty timeline going back 60 minutes swsTimeline.prototype.initialize = function (swsOptions) { this.options = swsOptions; var curr = Date.now(); if( swsUtil.supportedOptions.timelineBucketDuration in swsOptions ) { this.settings.bucket_duration = swsOptions[swsUtil.supportedOptions.timelineBucketDuration]; } var timelineid = Math.floor(curr / this.settings.bucket_duration ); this.settings.bucket_current = timelineid; for (var i = 0; i < this.settings.length; i++) { this.openTimelineBucket(timelineid); timelineid--; } }; // Update timeline and stats per tick swsTimeline.prototype.tick = function (ts,totalElapsedSec) { var timelineid = Math.floor( ts / this.settings.bucket_duration ); this.settings.bucket_current = timelineid; var currBucket = this.getTimelineBucket(timelineid); this.expireTimelineBucket(timelineid - this.settings.length); // Update rates in timeline, only in current bucket var currBucketElapsedSec = (ts - timelineid*this.settings.bucket_duration)/1000; currBucket.stats.updateRates(currBucketElapsedSec); // Update sys stats in current bucket var cpuPercent = swsUtil.swsCPUUsagePct(this.startTime, this.startUsage); currBucket.sys.cpu = cpuPercent; this.updateMemoryUsage(process.memoryUsage()); this.setMemoryStats(currBucket); let start = process.hrtime(); setImmediate(this.setMaxEvenLoopLag,start,this); }; swsTimeline.prototype.setMaxEvenLoopLag = function (start, dest) { const delta = process.hrtime(start); const nanosec = delta[0] * 1e9 + delta[1]; const mseconds = nanosec / 1e6; if( mseconds > dest.lag ){ dest.lag = mseconds; } } swsTimeline.prototype.getTimelineBucket = function (timelineid) { if( (timelineid>0) && (!(timelineid in this.data)) ) { // Open new bucket this.openTimelineBucket(timelineid); // Close previous bucket this.closeTimelineBucket(timelineid-1); } return this.data[timelineid]; }; swsTimeline.prototype.openTimelineBucket = function(timelineid) { // Open new bucket this.data[timelineid] = { stats: new swsReqResStats(this.options.apdexThreshold), sys: { rss:0, heapTotal:0, heapUsed:0, external:0, cpu: 0} }; }; // TODO Carry over rates, SYS from prev bucket - so it would be aways 0 on refresh with short buckets swsTimeline.prototype.closeTimelineBucket = function(timelineid) { if( !(timelineid in this.data) ) return; // Close bucket // update rates in previous timeline bucket: it becomes closed this.data[timelineid].stats.updateRates(this.settings.bucket_duration/1000); // Update sys stats var cpuPercent = swsUtil.swsCPUUsagePct(this.startTime, this.startUsage); this.data[timelineid].sys.cpu = cpuPercent; //debug('CPU: %s on %d', cpuPercent.toFixed(4), timelineid); var currMem = process.memoryUsage(); this.updateMemoryUsage(currMem); this.setMemoryStats(this.data[timelineid]); //debug('Mem: %s - CLOSE', this.data[timelineid].sys.heapUsed.toFixed(0)); // start from last this.memorySum = currMem; this.memoryMeasurements = 1; //debug('Mem: %s - CURR %s - START %d', this.memorySum.heapUsed.toFixed(0),currMem.heapUsed,this.memoryMeasurements); // Lag this.data[timelineid].sys.lag = this.lag; this.lag=0; this.startTime = process.hrtime(); setImmediate(this.setMaxEvenLoopLag,this.startTime,this.data[timelineid]); this.startUsage = process.cpuUsage(); }; swsTimeline.prototype.expireTimelineBucket = function (timelineid) { delete this.data[timelineid]; }; swsTimeline.prototype.updateMemoryUsage = function(currMem){ this.memoryMeasurements++; this.memorySum.rss += currMem.rss; this.memorySum.heapTotal += currMem.heapTotal; this.memorySum.heapUsed += currMem.heapUsed; this.memorySum.external += currMem.external; //debug('Mem: %s - CURR %s - UPDATE %d', Math.round(this.memorySum.heapUsed/this.memoryMeasurements),currMem.heapUsed,this.memoryMeasurements); }; swsTimeline.prototype.setMemoryStats = function(bucket){ if(!('sys' in bucket )) return; bucket.sys.rss = Math.round(this.memorySum.rss/this.memoryMeasurements); bucket.sys.heapTotal = Math.round(this.memorySum.heapTotal/this.memoryMeasurements); bucket.sys.heapUsed = Math.round(this.memorySum.heapUsed/this.memoryMeasurements); bucket.sys.external = Math.round(this.memorySum.external/this.memoryMeasurements); }; // Count request swsTimeline.prototype.countRequest = function (req, res) { // Count in timeline this.getTimelineBucket(req.sws.timelineid).stats.countRequest(req.sws.req_clength); }; // Count finished response swsTimeline.prototype.countResponse = function (res) { var req = res._swsReq; // Update timeline stats this.getTimelineBucket(req.sws.timelineid).stats.countResponse( res.statusCode, swsUtil.getStatusCodeClass(res.statusCode), req.sws.duration, req.sws.res_clength); }; module.exports = swsTimeline;