Socket
Socket
Sign inDemoInstall

swagger-stats

Package Overview
Dependencies
88
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.95.9 to 0.95.10

CHANGELOG.md

2

examples/authtest/authtest.js

@@ -67,3 +67,3 @@ 'use strict';

name: 'swagger-stats-authtest',
version: '0.95.9',
version: '0.95.10',
hostname: "hostname",

@@ -70,0 +70,0 @@ ip: "127.0.0.1",

'use strict';
const http = require('http');
const Hapi = require('@hapi/hapi');

@@ -8,2 +9,3 @@ const swStats = require('../../lib'); // require('swagger-stats');

const swaggerSpec = require('./petstore.json');
const githubAPISpec = require('./github.json');

@@ -59,5 +61,14 @@ let server = null;

server.route({
method: 'GET',
path: '/request',
handler: (request, h) => {
testEgressRequest();
return 'OK';
}
});
let swsOptions = {
name: 'swagger-stats-hapitest',
version: '0.95.9',
version: '0.95.10',
hostname: "hostname",

@@ -68,2 +79,4 @@ ip: "127.0.0.1",

swaggerSpec:swaggerSpec,
durationBuckets: [10,100,1000],
metricsPrefix: 'hapitest_',
elasticsearch: 'http://127.0.0.1:9200',

@@ -202,3 +215,30 @@ elasticsearchIndexPrefix: 'swaggerstats-'

function testEgressRequest(){
const options = {
hostname: 'www.google.com',
port: 80,
path: '/',
method: 'GET',
};
const req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
req.end();
}
// TODO https://api.github.com/orgs/slanatech/repos
init();

@@ -79,3 +79,3 @@ 'use strict';

name: 'swagger-stats-spectest',
version: '0.95.9',
version: '0.95.10',
hostname: "hostname",

@@ -82,0 +82,0 @@ ip: "127.0.0.1",

@@ -112,3 +112,3 @@ 'use strict';

name: 'swagger-stats-testapp',
version: '0.95.9',
version: '0.95.10',
timelineBucketDuration: tlBucket,

@@ -115,0 +115,0 @@ uriPath: '/swagger-stats',

@@ -8,13 +8,13 @@ /**

var util = require('util');
var pathToRegexp = require('path-to-regexp');
var debug = require('debug')('sws:apistats');
var promClient = require("prom-client");
const util = require('util');
const pathToRegexp = require('path-to-regexp');
const debug = require('debug')('sws:apistats');
const promClient = require("prom-client");
const swsSettings = require('./swssettings');
const swsMetrics = require('./swsmetrics');
const swsUtil = require('./swsUtil');
const swsReqResStats = require('./swsReqResStats');
const swsBucketStats = require('./swsBucketStats');
var swsUtil = require('./swsUtil');
var swsReqResStats = require('./swsReqResStats');
var swsBucketStats = require('./swsBucketStats');
// API Statistics

@@ -55,25 +55,2 @@ // Stores Definition of API based on Swagger Spec

this.promClientMetrics = {};
this.promClientMetrics.api_request_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_request_total.name,
help: swsUtil.swsMetrics.api_request_total.help,
labelNames: ['method','path','code'] });
this.promClientMetrics.api_request_duration_milliseconds = new promClient.Histogram({
name: swsUtil.swsMetrics.api_request_duration_milliseconds.name,
help: swsUtil.swsMetrics.api_request_duration_milliseconds.help,
labelNames: ['method','path','code'],
buckets: this.durationBuckets });
this.promClientMetrics.api_request_size_bytes = new promClient.Histogram({
name: swsUtil.swsMetrics.api_request_size_bytes.name,
help: swsUtil.swsMetrics.api_request_size_bytes.help,
labelNames: ['method','path','code'],
buckets: this.requestSizeBuckets });
this.promClientMetrics.api_response_size_bytes = new promClient.Histogram({
name: swsUtil.swsMetrics.api_response_size_bytes.name,
help: swsUtil.swsMetrics.api_response_size_bytes.help,
labelNames: ['method','path','code'],
buckets: this.responseSizeBuckets });
}

@@ -142,11 +119,18 @@

if(typeof swsOptions === 'undefined') return; // TODO ??? remove
// TODO remove
if(!swsOptions) return;
this.options = swsOptions;
if('durationBuckets' in swsOptions) this.durationBuckets = swsOptions.durationBuckets;
if('requestSizeBuckets' in swsOptions) this.requestSizeBuckets = swsOptions.requestSizeBuckets;
if('responseSizeBuckets' in swsOptions) this.responseSizeBuckets = swsOptions.responseSizeBuckets;
this.durationBuckets = swsSettings.durationBuckets;
this.requestSizeBuckets = swsSettings.requestSizeBuckets;
this.responseSizeBuckets = swsSettings.responseSizeBuckets;
// Update buckets to reflect passed options
swsMetrics.apiMetricsDefs.api_request_duration_milliseconds.buckets = this.durationBuckets;
swsMetrics.apiMetricsDefs.api_request_size_bytes.buckets = this.requestSizeBuckets;
swsMetrics.apiMetricsDefs.api_response_size_bytes.buckets = this.responseSizeBuckets;
swsMetrics.clearPrometheusMetrics(this.promClientMetrics);
this.promClientMetrics = swsMetrics.getPrometheusMetrics(swsSettings.metricsPrefix,swsMetrics.apiMetricsDefs);
if(!('swaggerSpec' in swsOptions)) return;

@@ -153,0 +137,0 @@ if(swsOptions.swaggerSpec === null) return;

@@ -8,264 +8,162 @@ /**

var util = require('util');
var debug = require('debug')('sws:corestats');
var promClient = require("prom-client");
const util = require('util');
const debug = require('debug')('sws:corestats');
const promClient = require("prom-client");
const swsSettings = require('./swssettings');
const swsMetrics = require('./swsmetrics');
const swsUtil = require('./swsUtil');
const SwsReqResStats = require('./swsReqResStats');
var swsUtil = require('./swsUtil');
var swsReqResStats = require('./swsReqResStats');
// Constructor
function swsCoreStats() {
/* swagger=stats Prometheus metrics */
class SwsCoreStats {
// Options
this.options = null;
constructor(){
// Timestamp when collecting statistics started
this.startts = Date.now();
// Statistics for all requests
this.all = null;
// Statistics for all requests
this.all = null;
// Statistics for requests by method
// Initialized with most frequent ones, other methods will be added on demand if actually used
this.method = null;
// Statistics for requests by method
// Initialized with most frequent ones, other methods will be added on demand if actually used
this.method = null;
// Additional prefix for prometheus metrics. Used if this coreStats instance
// plays special role, i.e. count stats for egress
this.metricsRolePrefix = '';
// System statistics
this.sys = null;
// Prometheus metrics
this.promClientMetrics = {};
}
// CPU
this.startTime = null;
this.startUsage = null;
// Initialize
initialize(metricsRolePrefix) {
// Array with last 5 hrtime / cpuusage, to calculate CPU usage during the last second sliding window ( 5 ticks )
this.startTimeAndUsage = null;
this.metricsRolePrefix = metricsRolePrefix || '';
// Prometheus metrics
this.promClientMetrics = {};
// Statistics for all requests
this.all = new SwsReqResStats(swsSettings.apdexThreshold);
this.promClientMetrics.api_all_request_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_all_request_total.name,
help: swsUtil.swsMetrics.api_all_request_total.help });
// Statistics for requests by method
// Initialized with most frequent ones, other methods will be added on demand if actually used
this.method = {
'GET': new SwsReqResStats(swsSettings.apdexThreshold),
'POST': new SwsReqResStats(swsSettings.apdexThreshold),
'PUT': new SwsReqResStats(swsSettings.apdexThreshold),
'DELETE': new SwsReqResStats(swsSettings.apdexThreshold)
};
this.promClientMetrics.api_all_success_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_all_success_total.name,
help: swsUtil.swsMetrics.api_all_success_total.help });
this.promClientMetrics.api_all_errors_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_all_errors_total.name,
help: swsUtil.swsMetrics.api_all_errors_total.help });
this.promClientMetrics.api_all_client_error_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_all_client_error_total.name,
help: swsUtil.swsMetrics.api_all_client_error_total.help });
this.promClientMetrics.api_all_server_error_total = new promClient.Counter({
name: swsUtil.swsMetrics.api_all_server_error_total.name,
help: swsUtil.swsMetrics.api_all_server_error_total.help });
this.promClientMetrics.api_all_request_in_processing_total = new promClient.Gauge({
name: swsUtil.swsMetrics.api_all_request_in_processing_total.name,
help: swsUtil.swsMetrics.api_all_request_in_processing_total.help });
// metrics
swsMetrics.clearPrometheusMetrics(this.promClientMetrics);
this.promClientMetrics.nodejs_process_memory_rss_bytes = new promClient.Gauge({
name: swsUtil.swsMetrics.nodejs_process_memory_rss_bytes.name,
help: swsUtil.swsMetrics.nodejs_process_memory_rss_bytes.help });
this.promClientMetrics.nodejs_process_memory_heap_total_bytes = new promClient.Gauge({
name: swsUtil.swsMetrics.nodejs_process_memory_heap_total_bytes.name,
help: swsUtil.swsMetrics.nodejs_process_memory_heap_total_bytes.help });
this.promClientMetrics.nodejs_process_memory_heap_used_bytes = new promClient.Gauge({
name: swsUtil.swsMetrics.nodejs_process_memory_heap_used_bytes.name,
help: swsUtil.swsMetrics.nodejs_process_memory_heap_used_bytes.help });
this.promClientMetrics.nodejs_process_memory_external_bytes = new promClient.Gauge({
name: swsUtil.swsMetrics.nodejs_process_memory_external_bytes.name,
help: swsUtil.swsMetrics.nodejs_process_memory_external_bytes.help });
this.promClientMetrics.nodejs_process_cpu_usage_percentage = new promClient.Gauge({
name: swsUtil.swsMetrics.nodejs_process_cpu_usage_percentage.name,
help: swsUtil.swsMetrics.nodejs_process_cpu_usage_percentage.help });
let prefix = swsSettings.metricsPrefix + this.metricsRolePrefix;
this.promClientMetrics = swsMetrics.getPrometheusMetrics(prefix,swsMetrics.coreMetricsDefs);
};
}
getStats() {
return this.all;
};
// Initialize
swsCoreStats.prototype.initialize = function (swsOptions) {
this.options = swsOptions;
// Statistics for all requests
this.all = new swsReqResStats(this.options.apdexThreshold);
// Statistics for requests by method
// Initialized with most frequent ones, other methods will be added on demand if actually used
this.method = {
'GET': new swsReqResStats(this.options.apdexThreshold),
'POST': new swsReqResStats(this.options.apdexThreshold),
'PUT': new swsReqResStats(this.options.apdexThreshold),
'DELETE': new swsReqResStats(this.options.apdexThreshold)
getMethodStats() {
return this.method;
};
// System statistics
this.sys = {
rss: 0,
heapTotal: 0,
heapUsed: 0,
external: 0,
cpu: 0
// TODO event loop delays
// Update timeline and stats per tick
tick(ts,totalElapsedSec) {
// Rates
this.all.updateRates(totalElapsedSec);
for( let mname of Object.keys(this.method)) {
this.method[mname].updateRates(totalElapsedSec);
}
};
// CPU
this.startTime = process.hrtime();
this.startUsage = process.cpuUsage();
// Count request
countRequest(req) {
// Array with last 5 hrtime / cpuusage, to calculate CPU usage during the last second sliding window ( 5 ticks )
this.startTimeAndUsage = [
{ hrtime: process.hrtime(), cpuUsage: process.cpuUsage() },
{ hrtime: process.hrtime(), cpuUsage: process.cpuUsage() },
{ hrtime: process.hrtime(), cpuUsage: process.cpuUsage() },
{ hrtime: process.hrtime(), cpuUsage: process.cpuUsage() },
{ hrtime: process.hrtime(), cpuUsage: process.cpuUsage() }
];
// Count in all
this.all.countRequest(req.sws.req_clength);
};
// Count by method
var method = req.method;
if (!(method in this.method)) {
this.method[method] = new SwsReqResStats();
}
this.method[method].countRequest(req.sws.req_clength);
swsCoreStats.prototype.getStats = function () {
return { startts: this.startts, all: this.all, sys: this.sys };
};
// Update prom-client metrics
this.promClientMetrics.api_all_request_total.inc();
this.promClientMetrics.api_all_request_in_processing_total.inc();
req.sws.inflightTimer = setTimeout(function() {
this.promClientMetrics.api_all_request_in_processing_total.dec();
}.bind(this), 250000);
};
swsCoreStats.prototype.getMethodStats = function () {
return this.method;
};
countResponse (res) {
var req = res._swsReq;
// Update timeline and stats per tick
swsCoreStats.prototype.tick = function (ts,totalElapsedSec) {
// Defaults
var startts = 0;
var duration = 0;
var resContentLength = 0;
var timelineid = 0;
var path = req.sws.originalUrl;
// Rates
this.all.updateRates(totalElapsedSec);
for( var method in this.method) {
this.method[method].updateRates(totalElapsedSec);
}
// TODO move all this to Processor, so it'll be in single place
// System stats
this.calculateSystemStats(ts,totalElapsedSec);
};
if ("_contentLength" in res && res['_contentLength'] !== null ){
resContentLength = res['_contentLength'];
}else{
// Try header
let hcl = res.getHeader('content-length');
if( (hcl !== undefined) && hcl && !isNaN(hcl)) {
resContentLength = parseInt(res.getHeader('content-length'));
}
}
// Calculate and store system statistics
swsCoreStats.prototype.calculateSystemStats = function(ts,totalElapsedSec) {
if("sws" in req) {
startts = req.sws.startts;
timelineid = req.sws.timelineid;
var endts = Date.now();
req['sws'].endts = endts;
duration = endts - startts;
req['sws'].duration = duration;
req['sws'].res_clength = resContentLength;
path = req['sws'].api_path;
clearTimeout(req.sws.inflightTimer);
}
// Memory
var memUsage = process.memoryUsage();
// Determine status code type
var codeclass = swsUtil.getStatusCodeClass(res.statusCode);
// See https://stackoverflow.com/questions/12023359/what-do-the-return-values-of-node-js-process-memoryusage-stand-for
// #22 Handle properly if any property is missing
this.sys.rss = 'rss' in memUsage ? memUsage.rss : 0;
this.sys.heapTotal = 'heapTotal' in memUsage ? memUsage.heapTotal : 0;
this.sys.heapUsed = 'heapUsed' in memUsage ? memUsage.heapUsed : 0;
this.sys.external = 'external' in memUsage ? memUsage.external : 0;
// update counts for all requests
this.all.countResponse(res.statusCode,codeclass,duration,resContentLength);
var startTU = this.startTimeAndUsage.shift();
// Update method-specific stats
var method = req.method;
if (method in this.method) {
var mstat = this.method[method];
mstat.countResponse(res.statusCode,codeclass,duration,resContentLength);
}
var cpuPercent = swsUtil.swsCPUUsagePct(startTU.hrtime, startTU.cpuUsage);
this.startTimeAndUsage.push( { hrtime: process.hrtime(), cpuUsage: process.cpuUsage() } );
//this.startTime = process.hrtime();
//this.startUsage = process.cpuUsage();
this.sys.cpu = cpuPercent;
// Update prom-client metrics
this.promClientMetrics.nodejs_process_memory_rss_bytes.set(this.sys.rss);
this.promClientMetrics.nodejs_process_memory_heap_total_bytes.set(this.sys.heapTotal);
this.promClientMetrics.nodejs_process_memory_heap_used_bytes.set(this.sys.heapUsed);
this.promClientMetrics.nodejs_process_memory_external_bytes.set(this.sys.external);
this.promClientMetrics.nodejs_process_cpu_usage_percentage.set(this.sys.cpu);
};
// Count request
swsCoreStats.prototype.countRequest = function (req, res) {
// Count in all
this.all.countRequest(req.sws.req_clength);
// Count by method
var method = req.method;
if (!(method in this.method)) {
this.method[method] = new swsReqResStats();
}
this.method[method].countRequest(req.sws.req_clength);
// Update prom-client metrics
this.promClientMetrics.api_all_request_total.inc();
this.promClientMetrics.api_all_request_in_processing_total.inc();
req.sws.inflightTimer = setTimeout(function() {
// Update Prometheus metrics
switch(codeclass){
case "success":
this.promClientMetrics.api_all_success_total.inc();
break;
case "redirect":
// NOOP //
break;
case "client_error":
this.promClientMetrics.api_all_errors_total.inc();
this.promClientMetrics.api_all_client_error_total.inc();
break;
case "server_error":
this.promClientMetrics.api_all_errors_total.inc();
this.promClientMetrics.api_all_server_error_total.inc();
break;
}
this.promClientMetrics.api_all_request_in_processing_total.dec();
}.bind(this), 250000);
};
};
}
// Count finished response
swsCoreStats.prototype.countResponse = function (res) {
var req = res._swsReq;
// Defaults
var startts = 0;
var duration = 0;
var resContentLength = 0;
var timelineid = 0;
var path = req.sws.originalUrl;
// TODO move all this to Processor, so it'll be in single place
if ("_contentLength" in res && res['_contentLength'] !== null ){
resContentLength = res['_contentLength'];
}else{
// Try header
if(res.getHeader('content-length') !==null ) {
resContentLength = parseInt(res.getHeader('content-length'));
}
}
if("sws" in req) {
startts = req.sws.startts;
timelineid = req.sws.timelineid;
var endts = Date.now();
req['sws'].endts = endts;
duration = endts - startts;
req['sws'].duration = duration;
req['sws'].res_clength = resContentLength;
path = req['sws'].api_path;
clearTimeout(req.sws.inflightTimer);
}
// Determine status code type
var codeclass = swsUtil.getStatusCodeClass(res.statusCode);
// update counts for all requests
this.all.countResponse(res.statusCode,codeclass,duration,resContentLength);
// Update method-specific stats
var method = req.method;
if (method in this.method) {
var mstat = this.method[method];
mstat.countResponse(res.statusCode,codeclass,duration,resContentLength);
}
// Update Prometheus metrics
switch(codeclass){
case "success":
this.promClientMetrics.api_all_success_total.inc();
break;
case "redirect":
// NOOP //
break;
case "client_error":
this.promClientMetrics.api_all_errors_total.inc();
this.promClientMetrics.api_all_client_error_total.inc();
break;
case "server_error":
this.promClientMetrics.api_all_errors_total.inc();
this.promClientMetrics.api_all_server_error_total.inc();
break;
}
this.promClientMetrics.api_all_request_in_processing_total.dec();
};
module.exports = swsCoreStats;
module.exports = SwsCoreStats;
/* swagger-stats Hapi plugin */
const path = require('path');
const promClient = require("prom-client");
const SwsProcessor = require('./swsProcessor');
const swsSettings = require('./swssettings');
const swsProcessor = require('./swsProcessor');
const swsUtil = require('./swsUtil');
const debug = require('debug')('sws:hapi');
//const Inert = require('@hapi/inert');
const url = require('url');

@@ -16,12 +16,4 @@ const qs = require('qs');

constructor() {
this.name = 'swagger-stats';
this.version = '0.97.5';
this.effectiveOptions = {};
this.processor = null;
this.pathBase = '/';
this.pathUI = '/ui';
this.pathDist = '/dist';
this.pathStats = '/stats';
this.pathMetrics = '/metrics';
this.pathLogout = '/logout';
this.processor = swsProcessor;
}

@@ -31,8 +23,3 @@

async register(server, options) {
this.processOptions(options);
this.processor = new SwsProcessor();
this.processor.init(this.effectiveOptions);
let processor = this.processor;
let swsURIPath = this.effectiveOptions.uriPath;
let swsPathUI = this.pathUI;
server.events.on('response', function(request){

@@ -48,3 +35,3 @@ let nodeReq = request.raw.req;

}catch(e){
debug("processRequest:ERROR: " + e);
debug("processResponse:ERROR: " + e);
}

@@ -59,3 +46,3 @@ });

let reqUrl = nodeReq.url;
if(reqUrl.startsWith(swsURIPath)){
if(reqUrl.startsWith(swsSettings.uriPath)){
// Don't track sws requests

@@ -75,3 +62,3 @@ nodeReq.sws.track = false;

method: 'GET',
path: this.pathStats,
path: swsSettings.pathStats,
handler: function (request, h) {

@@ -84,3 +71,3 @@ return processor.getStats(request.raw.req.sws.query);

method: 'GET',
path: this.pathMetrics,
path: swsSettings.pathMetrics,
handler: function (request, h) {

@@ -96,5 +83,5 @@ const response = h.response(promClient.register.metrics());

method: 'GET',
path: this.pathBase,
path: swsSettings.uriPath,
handler: function (request, h) {
return h.redirect(swsPathUI);
return h.redirect(swsSettings.pathUI);
}

@@ -105,3 +92,3 @@ });

method: 'GET',
path: this.pathUI,
path: swsSettings.pathUI,
handler: function (request, h) {

@@ -114,3 +101,3 @@ return swsUtil.swsEmbeddedUIMarkup;

method: 'GET',
path: this.pathDist+'/{file*}',
path: swsSettings.pathDist+'/{file*}',
handler: function (request, h) {

@@ -129,51 +116,5 @@ let fileName = request.params.file;

}
setPaths(){
this.pathBase = this.effectiveOptions.uriPath;
this.pathUI = this.effectiveOptions.uriPath+'/ui';
this.pathDist = this.effectiveOptions.uriPath+'/dist';
this.pathStats = this.effectiveOptions.uriPath+'/stats';
this.pathMetrics = this.effectiveOptions.uriPath+'/metrics';
this.pathLogout = this.effectiveOptions.uriPath+'/logout';
}
setDefaultOptions(options){
this.effectiveOptions = options;
this.setPaths();
}
// Override defaults if options are provided
processOptions(options){
if(!options) return;
for(let op in swsUtil.supportedOptions){
if(op in options){
this.effectiveOptions[op] = options[op];
}
}
// update standard path
this.setPaths();
/* no auth for now
if( swsOptions.authentication ){
setInterval(expireSessionIDs,500);
}
*/
}
}
let swsHapi = new SwsHapi();
let swsHapiPlugin = {
name: 'swagger-stats',
version: '0.97.5',
register: async function (server, options) {
return swsHapi.register(server, options);
}
};
module.exports = {
swsHapi,
swsHapiPlugin
};
module.exports = swsHapi;

@@ -16,38 +16,16 @@ /**

const swsSettings = require('./swssettings');
const swsUtil = require('./swsUtil');
const swsProcessor = require('./swsProcessor');
const swsEgress = require('./swsegress');
const send = require('send');
const qs = require('qs');
const {swsHapi,swsHapiPlugin} = require('./swsHapi');
const swsHapi = require('./swsHapi');
// API data processor
var processor = null;
//var processor = null;
// swagger-stats default options
let swsOptions = {
version:'',
swaggerSpec: null,
uriPath: '/swagger-stats',
durationBuckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
requestSizeBuckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
responseSizeBuckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
apdexThreshold: 25,
onResponseFinish: null,
authentication: false,
sessionMaxAge: 900,
onAuthenticate: null
};
swsHapi.setDefaultOptions(swsOptions);
var uiMarkup = swsUtil.swsEmbeddedUIMarkup;
var pathUI = swsOptions.uriPath+'/ui';
var pathDist = swsOptions.uriPath+'/dist';
var pathStats = swsOptions.uriPath+'/stats';
var pathMetrics = swsOptions.uriPath+'/metrics';
var pathLogout = swsOptions.uriPath+'/logout';
// Session IDs storage

@@ -58,3 +36,3 @@ var sessionIDs = {};

function storeSessionID(sid){
var tssec = Date.now() + swsOptions.sessionMaxAge*1000;
var tssec = Date.now() + swsSettings.sessionMaxAge*1000;
sessionIDs[sid] = tssec;

@@ -87,3 +65,3 @@ //debug('Session ID updated: %s=%d', sid,tssec);

try {
processor.processRequest(req,res);
swsProcessor.processRequest(req,res);
}catch(e){

@@ -108,3 +86,3 @@ debug("SWS:processRequest:ERROR: " + e);

try {
processor.processResponse(res);
swsProcessor.processResponse(res);
}catch(e){

@@ -115,29 +93,6 @@ debug("SWS:processResponse:ERROR: " + e);

// Override defaults if options are provided
function processOptions(options){
if(!options) return;
for(let op in swsUtil.supportedOptions){
if(op in options){
swsOptions[op] = options[op];
}
}
// update standard path
pathUI = swsOptions.uriPath+'/ui';
pathDist = swsOptions.uriPath+'/dist';
pathStats = swsOptions.uriPath+'/stats';
pathMetrics = swsOptions.uriPath+'/metrics';
pathLogout = swsOptions.uriPath+'/logout';
if( swsOptions.authentication ){
setInterval(expireSessionIDs,500);
}
}
function processAuth(req,res,useWWWAuth) {
return new Promise( function (resolve, reject) {
if( !swsOptions.authentication ){
if( !swsSettings.authentication ){
return resolve(true);

@@ -156,3 +111,3 @@ }

storeSessionID(sessionIdCookie);
cookies.set('sws-session-id',sessionIdCookie,{path:swsOptions.uriPath,maxAge:swsOptions.sessionMaxAge*1000});
cookies.set('sws-session-id',sessionIdCookie,{path:swsSettings.uriPath,maxAge:swsSettings.sessionMaxAge*1000});
// Ok

@@ -170,5 +125,5 @@ req['sws-auth'] = true;

if( (authInfo !== undefined) && (authInfo!==null) && ('name' in authInfo) && ('pass' in authInfo)){
if(typeof swsOptions.onAuthenticate === 'function'){
if(typeof swsSettings.onAuthenticate === 'function'){
Promise.resolve(swsOptions.onAuthenticate(req, authInfo.name, authInfo.pass)).then(function(onAuthResult) {
Promise.resolve(swsSettings.onAuthenticate(req, authInfo.name, authInfo.pass)).then(function(onAuthResult) {
if( onAuthResult ){

@@ -179,3 +134,3 @@

// Session is only for stats requests
if(req.url.startsWith(pathStats)){
if(req.url.startsWith(swsSettings.pathStats)){
// Generate session id

@@ -185,3 +140,3 @@ var sessid = uuidv1();

// Set session cookie with expiration in 15 min
cookies.set('sws-session-id',sessid,{path:swsOptions.uriPath,maxAge:swsOptions.sessionMaxAge*1000});
cookies.set('sws-session-id',sessid,{path:swsSettings.uriPath,maxAge:swsSettings.sessionMaxAge*1000});
}

@@ -247,3 +202,3 @@

res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(processor.getStats(req.sws.query)));
res.end(JSON.stringify(swsProcessor.getStats(req.sws.query)));
});

@@ -270,4 +225,19 @@ }

// Returns Hapi plugin
getHapiPlugin: swsHapiPlugin,
getHapiPlugin: {
name: 'swagger-stats',
version: '0.97.9',
register: async function (server, options) {
// Init settings
swsSettings.init(options);
// Init probes TODO Reconsider
swsEgress.init();
swsProcessor.init();
return swsHapi.register(server, options);
}
},
// Initialize swagger-stats and return

@@ -277,7 +247,14 @@ // middleware to perform API Data collection

processOptions(options);
// Init settings
swsSettings.init(options);
processor = new swsProcessor();
processor.init(swsOptions);
// Init probes
swsEgress.init();
if( swsSettings.authentication ){
setInterval(expireSessionIDs,500);
}
swsProcessor.init();
return function trackingMiddleware(req, res, next) {

@@ -291,10 +268,10 @@

// swagger-stats requests will not be counted in statistics
if(req.url.startsWith(pathStats)) {
if(req.url.startsWith(swsSettings.pathStats)) {
return processGetStats(req, res);
}else if(req.url.startsWith(pathMetrics)){
}else if(req.url.startsWith(swsSettings.pathMetrics)){
return processGetMetrics(req,res);
}else if(req.url.startsWith(pathLogout)){
}else if(req.url.startsWith(swsSettings.pathLogout)){
processLogout(req,res);
return;
}else if(req.url.startsWith(pathUI) ){
}else if(req.url.startsWith(swsSettings.pathUI) ){
res.statusCode = 200;

@@ -304,4 +281,4 @@ res.setHeader('Content-Type', 'text/html');

return;
}else if(req.url.startsWith(pathDist)) {
var fileName = req.url.replace(pathDist+'/','');
}else if(req.url.startsWith(swsSettings.pathDist)) {
var fileName = req.url.replace(swsSettings.pathDist+'/','');
var qidx = fileName.indexOf('?');

@@ -329,7 +306,3 @@ if(qidx!=-1) fileName = fileName.substring(0,qidx);

getCoreStats: function() {
if(processor) {
return processor.getStats();
}else if(swsHapi.processor){
return swsHapi.processor.getStats();
}
return swsProcessor.getStats();
},

@@ -349,8 +322,4 @@

stop: function () {
if(processor) {
return processor.stop();
}else if(swsHapi.processor){
return swsHapi.processor.stop();
}
return swsProcessor.stop();
}
};
/**
* Created by sv2 on 2/18/17.
* API usage statistics data
* swagger-stats Processor. Processes requests / responses and maintains metrics
*/

@@ -8,448 +8,415 @@

var os = require('os');
var util = require('util');
const os = require('os');
const util = require('util');
var debug = require('debug')('sws:processor');
var debugrrr = require('debug')('sws:rrr');
const debug = require('debug')('sws:processor');
const debugrrr = require('debug')('sws:rrr');
var swsUtil = require('./swsUtil');
var pathToRegexp = require('path-to-regexp');
var moment = require('moment');
const swsSettings = require('./swssettings');
const swsUtil = require('./swsUtil');
const pathToRegexp = require('path-to-regexp');
const moment = require('moment');
var swsReqResStats = require('./swsReqResStats');
var swsCoreStats = require('./swsCoreStats');
var swsErrors = require('./swsErrors');
var swsTimeline = require('./swsTimeline');
var swsAPIStats = require('./swsAPIStats');
var swsLastErrors = require('./swsLastErrors');
var swsLongestRequests = require('./swsLongestReq');
const swsReqResStats = require('./swsReqResStats');
const SwsSysStats = require('./swssysstats');
const SwsCoreStats = require('./swsCoreStats');
const swsErrors = require('./swsErrors');
const swsTimeline = require('./swsTimeline');
const swsAPIStats = require('./swsAPIStats');
const swsLastErrors = require('./swsLastErrors');
const swsLongestRequests = require('./swsLongestReq');
const swsElasticsearchEmitter = require('./swsElasticEmitter');
var swsElasticsearchEmitter = require('./swsElasticEmitter');
// swagger-stats Processor. Processes requests / responses and maintains metrics
class SwsProcessor {
// Constructor
function swsProcessor() {
constructor() {
// Name: Should be name of the service provided by this component
this.name = 'sws';
// Timestamp when collecting statistics started
this.startts = Date.now();
// Options
this.options = null;
// Name: Should be name of the service provided by this component
this.name = 'sws';
// Version of this component
this.version = '';
// Options
//this.options = null;
// This node hostname
this.nodehostname = '';
// Version of this component
this.version = '';
// Node name: there could be multiple nodes in this service
this.nodename = '';
// This node hostname
this.nodehostname = '';
// Node address: there could be multiple nodes in this service
this.nodeaddress = '';
// Node name: there could be multiple nodes in this service
this.nodename = '';
// onResponseFinish callback, if specified in options
this.onResponseFinish = null;
// Node address: there could be multiple nodes in this service
this.nodeaddress = '';
// If set to true via options, track only API defined in swagger spec
this.swaggerOnly = false;
// onResponseFinish callback, if specified in options
this.onResponseFinish = null;
// Core statistics
this.coreStats = new swsCoreStats();
// If set to true via options, track only API defined in swagger spec
this.swaggerOnly = false;
// Timeline
this.timeline = new swsTimeline();
// System statistics
this.sysStats = new SwsSysStats();
// API Stats
this.apiStats = new swsAPIStats();
// Core statistics
this.coreStats = new SwsCoreStats();
// Errors
this.errorsStats = new swsErrors();
// Core Egress statistics
this.coreEgressStats = new SwsCoreStats();
// Last Errors
this.lastErrors = new swsLastErrors();
// Timeline
this.timeline = new swsTimeline();
// Longest Requests
this.longestRequests = new swsLongestRequests();
// API Stats
this.apiStats = new swsAPIStats();
// ElasticSearch Emitter
this.elasticsearchEmitter = new swsElasticsearchEmitter();
}
// Errors
this.errorsStats = new swsErrors();
// Initialize
swsProcessor.prototype.init = function (swsOptions) {
// Last Errors
this.lastErrors = new swsLastErrors();
this.processOptions(swsOptions);
// Longest Requests
this.longestRequests = new swsLongestRequests();
this.coreStats.initialize(swsOptions);
// ElasticSearch Emitter
this.elasticsearchEmitter = new swsElasticsearchEmitter();
}
this.timeline.initialize(swsOptions);
init() {
this.processOptions();
this.apiStats.initialize(swsOptions);
this.sysStats.initialize();
this.elasticsearchEmitter.initialize(swsOptions);
this.coreStats.initialize();
// Start tick
this.timer = setInterval(this.tick, 200, this);
};
this.coreEgressStats.initialize('egress_');
// Stop
swsProcessor.prototype.stop = function () {
this.timeline.initialize(swsSettings);
clearInterval(this.timer);
this.apiStats.initialize(swsSettings);
};
this.elasticsearchEmitter.initialize(swsSettings);
swsProcessor.prototype.processOptions = function (swsOptions) {
if(typeof swsOptions === 'undefined') return;
if(!swsOptions) return;
this.options = swsOptions;
// Set or detect hostname
if(swsUtil.supportedOptions.hostname in swsOptions) {
this.hostname = swsOptions[swsUtil.supportedOptions.hostname];
}else{
this.hostname = os.hostname();
// Start tick
this.timer = setInterval(this.tick, 200, this);
}
// Set name and version
if(swsUtil.supportedOptions.name in swsOptions) {
this.name = swsOptions[swsUtil.supportedOptions.name];
}else{
this.name = this.hostname;
// Stop
stop() {
clearInterval(this.timer);
}
if(swsUtil.supportedOptions.version in swsOptions) {
this.version = swsOptions[swsUtil.supportedOptions.version];
}
processOptions() {
this.name = swsSettings.name;
this.hostname = swsSettings.hostname;
this.version = swsSettings.version;
this.ip = swsSettings.ip;
this.onResponseFinish = swsSettings.onResponseFinish;
this.swaggerOnly = swsSettings.swaggerOnly;
};
// Set or detect node address
if(swsUtil.supportedOptions.ip in swsOptions) {
this.ip = swsOptions[swsUtil.supportedOptions.ip];
}else{
// Attempt to detect network address
// Use first found interface name which starts from "e" ( en0, em0 ... )
var address = null;
var ifaces = os.networkInterfaces();
for( var ifacename in ifaces ){
var iface = ifaces[ifacename];
if( !address && !iface.internal && (ifacename.charAt(0)=='e') ){
if((iface instanceof Array) && (iface.length>0) ) {
address = iface[0].address;
}
}
}
this.ip = address ? address : '127.0.0.1';
}
// Tick - called with specified interval to refresh timelines
tick(that) {
let ts = Date.now();
let totalElapsedSec = (ts - that.startts)/1000;
that.sysStats.tick(ts,totalElapsedSec);
that.coreStats.tick(ts,totalElapsedSec);
that.timeline.tick(ts,totalElapsedSec);
that.apiStats.tick(ts,totalElapsedSec);
that.elasticsearchEmitter.tick(ts,totalElapsedSec);
};
if( swsOptions.onResponseFinish && (typeof swsOptions.onResponseFinish === 'function') ){
this.onResponseFinish = swsOptions.onResponseFinish;
}
// Collect all data for request/response pair
// TODO Support option to add arbitrary extra properties to sws request/response record
collectRequestResponseData(res) {
if(swsUtil.supportedOptions.swaggerOnly in swsOptions) {
this.swaggerOnly = swsUtil.supportedOptions.swaggerOnly;
}
var req = res._swsReq;
};
var codeclass = swsUtil.getStatusCodeClass(res.statusCode);
// Tick - called with specified interval to refresh timelines
swsProcessor.prototype.tick = function (that) {
var rrr = {
'path': req.sws.originalUrl,
'method': req.method,
'query' : req.method + ' ' + req.sws.originalUrl,
'startts': 0,
'endts': 0,
'responsetime': 0,
"node": {
"name": this.name,
"version": this.version,
"hostname": this.hostname,
"ip": this.ip
},
"http": {
"request": {
"url" : req.url
},
"response": {
'code': res.statusCode,
'class': codeclass,
'phrase': res.statusMessage
}
}
};
var ts = Date.now();
var totalElapsedSec = (ts - that.coreStats.startts)/1000;
// Request Headers
if ("headers" in req) {
rrr.http.request.headers = {};
for(var hdr in req.headers){
rrr.http.request.headers[hdr] = req.headers[hdr];
}
// TODO Split Cookies
}
that.coreStats.tick(ts,totalElapsedSec);
// Response Headers
if ("_headers" in res){
rrr.http.response.headers = {};
for(var hdr in res['_headers']){
rrr.http.response.headers[hdr] = res['_headers'][hdr];
}
}
that.timeline.tick(ts,totalElapsedSec);
// Additional details from collected info per request / response pair
that.apiStats.tick(ts,totalElapsedSec);
if ("sws" in req) {
that.elasticsearchEmitter.tick(ts,totalElapsedSec);
rrr.ip = req.sws.ip;
rrr.real_ip = req.sws.real_ip;
rrr.port = req.sws.port;
};
rrr["@timestamp"] = moment(req.sws.startts).toISOString();
//rrr.end = moment(req.sws.endts).toISOString();
rrr.startts = req.sws.startts;
rrr.endts = req.sws.endts;
rrr.responsetime = req.sws.duration;
rrr.http.request.clength = req.sws.req_clength;
rrr.http.response.clength = req.sws.res_clength;
rrr.http.request.route_path = req.sws.route_path;
// Collect all data for request/response pair
// TODO Support option to add arbitrary extra properties to sws request/response record
swsProcessor.prototype.collectRequestResponseData = function (res) {
// Add detailed swagger API info
rrr.api = {};
rrr.api.path = req.sws.api_path;
rrr.api.query = req.method + ' ' + req.sws.api_path;
if( 'swagger' in req.sws ) rrr.api.swagger = req.sws.swagger;
if( 'deprecated' in req.sws ) rrr.api.deprecated = req.sws.deprecated;
if( 'operationId' in req.sws ) rrr.api.operationId = req.sws.operationId;
if( 'tags' in req.sws ) rrr.api.tags = req.sws.tags;
var req = res._swsReq;
// Get API parameter values per definition in swagger spec
var apiParams = this.apiStats.getApiOpParameterValues(req.sws.api_path,req.method,req,res);
if(apiParams!==null){
rrr.api.params = apiParams;
}
var codeclass = swsUtil.getStatusCodeClass(res.statusCode);
// TODO Support Arbitrary extra properties added to request under sws
// So app can add any custom data to request, and it will be emitted in record
var rrr = {
'path': req.sws.originalUrl,
'method': req.method,
'query' : req.method + ' ' + req.sws.originalUrl,
'startts': 0,
'endts': 0,
'responsetime': 0,
"node": {
"name": this.name,
"version": this.version,
"hostname": this.hostname,
"ip": this.ip
},
"http": {
"request": {
"url" : req.url
},
"response": {
'code': res.statusCode,
'class': codeclass,
'phrase': res.statusMessage
}
}
};
// Request Headers
if ("headers" in req) {
rrr.http.request.headers = {};
for(var hdr in req.headers){
rrr.http.request.headers[hdr] = req.headers[hdr];
// Express/Koa parameters: req.params (router) and req.body (body parser)
if (req.hasOwnProperty("params")) {
rrr.http.request.params = {};
swsUtil.swsStringRecursive(rrr.http.request.params, req.params);
}
// TODO Split Cookies
}
// Response Headers
if ("_headers" in res){
rrr.http.response.headers = {};
for(var hdr in res['_headers']){
rrr.http.response.headers[hdr] = res['_headers'][hdr];
if (req.sws && req.sws.hasOwnProperty("query")) {
rrr.http.request.query = {};
swsUtil.swsStringRecursive(rrr.http.request.query, req.sws.query);
}
}
// Additional details from collected info per request / response pair
if (req.hasOwnProperty("body")) {
rrr.http.request.body = Object.assign({}, req.body);
swsUtil.swsStringRecursive(rrr.http.request.body, req.body);
}
if ("sws" in req) {
return rrr;
};
rrr.ip = req.sws.ip;
rrr.real_ip = req.sws.real_ip;
rrr.port = req.sws.port;
getRemoteIP(req ) {
let ip = '';
try {
ip = req.connection.remoteAddress;
}catch(e){}
return ip;
};
rrr["@timestamp"] = moment(req.sws.startts).toISOString();
//rrr.end = moment(req.sws.endts).toISOString();
rrr.startts = req.sws.startts;
rrr.endts = req.sws.endts;
rrr.responsetime = req.sws.duration;
rrr.http.request.clength = req.sws.req_clength;
rrr.http.response.clength = req.sws.res_clength;
rrr.http.request.route_path = req.sws.route_path;
getPort(req ) {
let p = 0;
try{
p = req.connection.localPort;
}catch(e){}
return p;
};
// Add detailed swagger API info
rrr.api = {};
rrr.api.path = req.sws.api_path;
rrr.api.query = req.method + ' ' + req.sws.api_path;
if( 'swagger' in req.sws ) rrr.api.swagger = req.sws.swagger;
if( 'deprecated' in req.sws ) rrr.api.deprecated = req.sws.deprecated;
if( 'operationId' in req.sws ) rrr.api.operationId = req.sws.operationId;
if( 'tags' in req.sws ) rrr.api.tags = req.sws.tags;
// Get API parameter values per definition in swagger spec
var apiParams = this.apiStats.getApiOpParameterValues(req.sws.api_path,req.method,req,res);
if(apiParams!==null){
rrr.api.params = apiParams;
getRemoteRealIP(req ) {
var remoteaddress = null;
var xfwd = req.headers['x-forwarded-for'];
if (xfwd) {
var fwdaddrs = xfwd.split(','); // Could be "client IP, proxy 1 IP, proxy 2 IP"
remoteaddress = fwdaddrs[0];
}
if (!remoteaddress) {
remoteaddress = this.getRemoteIP(req);
}
return remoteaddress;
};
// TODO Support Arbitrary extra properties added to request under sws
// So app can add any custom data to request, and it will be emitted in record
processRequest(req, res) {
}
// Placeholder for sws-specific attributes
req.sws = req.sws || {};
// Express/Koa parameters: req.params (router) and req.body (body parser)
if (req.hasOwnProperty("params")) {
rrr.http.request.params = {};
swsUtil.swsStringRecursive(rrr.http.request.params, req.params);
}
// Setup sws props and pass to stats processors
var ts = Date.now();
if (req.sws && req.sws.hasOwnProperty("query")) {
rrr.http.request.query = {};
swsUtil.swsStringRecursive(rrr.http.request.query, req.sws.query);
}
var reqContentLength = 0;
if('content-length' in req.headers) {
reqContentLength = parseInt(req.headers['content-length']);
}
if (req.hasOwnProperty("body")) {
rrr.http.request.body = Object.assign({}, req.body);
swsUtil.swsStringRecursive(rrr.http.request.body, req.body);
}
req.sws.originalUrl = req.originalUrl || req.url;
req.sws.track = true;
req.sws.startts = ts;
req.sws.timelineid = Math.floor( ts/ this.timeline.settings.bucket_duration );
req.sws.req_clength = reqContentLength;
req.sws.ip = this.getRemoteIP(req);
req.sws.real_ip = this.getRemoteRealIP(req);
req.sws.port = this.getPort(req);
return rrr;
};
// Try to match to API right away
this.apiStats.matchRequest(req);
// if no match, and tracking of non-swagger requests is disabled, return
if( !req.sws.match && this.swaggerOnly){
req.sws.track = false;
return;
}
swsProcessor.prototype.getRemoteIP = function (req ) {
var ip = '';
try {
ip = req.connection.remoteAddress;
}catch(e){}
return ip;
};
// Core stats
this.coreStats.countRequest(req, res);
swsProcessor.prototype.getPort = function (req ) {
var p = 0;
try{
p = req.connection.localPort;
}catch(e){}
return p;
};
// Timeline
this.timeline.countRequest(req, res);
// TODO Check if needed
this.apiStats.countRequest(req, res);
};
swsProcessor.prototype.getRemoteRealIP = function (req ) {
var remoteaddress = null;
var xfwd = req.headers['x-forwarded-for'];
if (xfwd) {
var fwdaddrs = xfwd.split(','); // Could be "client IP, proxy 1 IP, proxy 2 IP"
remoteaddress = fwdaddrs[0];
}
if (!remoteaddress) {
remoteaddress = this.getRemoteIP(req);
}
return remoteaddress;
};
processResponse(res) {
swsProcessor.prototype.processRequest = function (req, res) {
var req = res._swsReq;
// Placeholder for sws-specific attributes
req.sws = req.sws || {};
// Capture route path for the request, if set by router
var route_path = '';
if (("route" in req) && ("path" in req.route)) {
if (("baseUrl" in req) && (req.baseUrl != undefined)) route_path = req.baseUrl;
route_path += req.route.path;
req.sws.route_path = route_path;
}
// Setup sws props and pass to stats processors
var ts = Date.now();
// If request was not matched to Swagger API, set API path:
// Use route_path, if exist; if not, use sws.originalUrl
if(!('api_path' in req.sws)){
req.sws.api_path = (route_path!=''?route_path:req.sws.originalUrl);
}
var reqContentLength = 0;
if('content-length' in req.headers) {
reqContentLength = parseInt(req.headers['content-length']);
}
// Pass through Core Statistics
this.coreStats.countResponse(res);
req.sws.originalUrl = req.originalUrl || req.url;
req.sws.track = true;
req.sws.startts = ts;
req.sws.timelineid = Math.floor( ts/ this.timeline.settings.bucket_duration );
req.sws.req_clength = reqContentLength;
req.sws.ip = this.getRemoteIP(req);
req.sws.real_ip = this.getRemoteRealIP(req);
req.sws.port = this.getPort(req);
// Pass through Timeline
this.timeline.countResponse(res);
// Try to match to API right away
this.apiStats.matchRequest(req);
// Pass through API Statistics
this.apiStats.countResponse(res);
// if no match, and tracking of non-swagger requests is disabled, return
if( !req.sws.match && this.swaggerOnly){
req.sws.track = false;
return;
}
// Pass through Errors
this.errorsStats.countResponse(res);
// Core stats
this.coreStats.countRequest(req, res);
// Collect request / response record
var rrr = this.collectRequestResponseData(res);
// Timeline
this.timeline.countRequest(req, res);
// Pass through last errors
this.lastErrors.processReqResData(rrr);
// TODO Check if needed
this.apiStats.countRequest(req, res);
};
// Pass through longest request
this.longestRequests.processReqResData(rrr);
swsProcessor.prototype.processResponse = function (res) {
// Pass to app if callback is specified
if(this.onResponseFinish !== null ){
this.onResponseFinish(req,res,rrr);
}
var req = res._swsReq;
// Push Request/Response Data to Emitter(s)
this.elasticsearchEmitter.processRecord(rrr);
// Capture route path for the request, if set by router
var route_path = '';
if (("route" in req) && ("path" in req.route)) {
if (("baseUrl" in req) && (req.baseUrl != undefined)) route_path = req.baseUrl;
route_path += req.route.path;
req.sws.route_path = route_path;
}
//debugrrr('%s', JSON.stringify(rrr));
};
// If request was not matched to Swagger API, set API path:
// Use route_path, if exist; if not, use sws.originalUrl
if(!('api_path' in req.sws)){
req.sws.api_path = (route_path!=''?route_path:req.sws.originalUrl);
}
// Get stats according to fields and params specified in query
getStats( query ) {
// Pass through Core Statistics
this.coreStats.countResponse(res);
query = typeof query !== 'undefined' ? query: {};
query = query !== null ? query: {};
// Pass through Timeline
this.timeline.countResponse(res);
var statfields = []; // Default
// Pass through API Statistics
this.apiStats.countResponse(res);
// Check if we have query parameter "fields"
if ('fields' in query) {
if (query.fields instanceof Array) {
statfields = query.fields;
} else {
var fieldsstr = query.fields;
statfields = fieldsstr.split(',');
}
}
// Pass through Errors
this.errorsStats.countResponse(res);
// sys, ingress and egress core statistics are returned always
let result = {
startts: this.startts
};
result.all = this.coreStats.getStats();
result.egress = this.coreEgressStats.getStats();
result.sys = this.sysStats.getStats();
// Collect request / response record
var rrr = this.collectRequestResponseData(res);
// add standard properties, returned always
result.name = this.name;
result.version = this.version;
result.hostname = this.hostname;
result.ip = this.ip;
result.apdexThreshold = swsSettings.apdexThreshold;
// Pass through last errors
this.lastErrors.processReqResData(rrr);
var fieldMask = 0;
for(var i=0;i<statfields.length;i++){
var fld = statfields[i];
if( fld in swsUtil.swsStatFields ) fieldMask |= swsUtil.swsStatFields[fld];
}
// Pass through longest request
this.longestRequests.processReqResData(rrr);
//console.log('Field mask:' + fieldMask.toString(2) );
// Pass to app if callback is specified
if(this.onResponseFinish !== null ){
this.onResponseFinish(req,res,rrr);
}
// Populate per mask
if( fieldMask & swsUtil.swsStatFields.method ) result.method = this.coreStats.getMethodStats();
if( fieldMask & swsUtil.swsStatFields.timeline ) result.timeline = this.timeline.getStats();
if( fieldMask & swsUtil.swsStatFields.lasterrors ) result.lasterrors = this.lastErrors.getStats();
if( fieldMask & swsUtil.swsStatFields.longestreq ) result.longestreq = this.longestRequests.getStats();
if( fieldMask & swsUtil.swsStatFields.apidefs ) result.apidefs = this.apiStats.getAPIDefs();
if( fieldMask & swsUtil.swsStatFields.apistats ) result.apistats = this.apiStats.getAPIStats();
if( fieldMask & swsUtil.swsStatFields.errors ) result.errors = this.errorsStats.getStats();
// Push Request/Response Data to Emitter(s)
this.elasticsearchEmitter.processRecord(rrr);
//debugrrr('%s', JSON.stringify(rrr));
};
// Get stats according to fields and params specified in query
swsProcessor.prototype.getStats = function ( query ) {
query = typeof query !== 'undefined' ? query: {};
query = query !== null ? query: {};
var statfields = []; // Default
// Check if we have query parameter "fields"
if ('fields' in query) {
if (query.fields instanceof Array) {
statfields = query.fields;
} else {
var fieldsstr = query.fields;
statfields = fieldsstr.split(',');
if( fieldMask & swsUtil.swsStatFields.apiop ) {
if(("path" in query) && ("method" in query)) {
result.apiop = this.apiStats.getAPIOperationStats(query.path, query.method);
}
}
}
// core statistics are returned always
var result = this.coreStats.getStats();
return result;
};
// add standard properties, returned always
result.name = this.name;
result.version = this.version;
result.hostname = this.hostname;
result.ip = this.ip;
result.apdexThreshold = this.options.apdexThreshold;
}
var fieldMask = 0;
for(var i=0;i<statfields.length;i++){
var fld = statfields[i];
if( fld in swsUtil.swsStatFields ) fieldMask |= swsUtil.swsStatFields[fld];
}
//console.log('Field mask:' + fieldMask.toString(2) );
// Populate per mask
if( fieldMask & swsUtil.swsStatFields.method ) result.method = this.coreStats.getMethodStats();
if( fieldMask & swsUtil.swsStatFields.timeline ) result.timeline = this.timeline.getStats();
if( fieldMask & swsUtil.swsStatFields.lasterrors ) result.lasterrors = this.lastErrors.getStats();
if( fieldMask & swsUtil.swsStatFields.longestreq ) result.longestreq = this.longestRequests.getStats();
if( fieldMask & swsUtil.swsStatFields.apidefs ) result.apidefs = this.apiStats.getAPIDefs();
if( fieldMask & swsUtil.swsStatFields.apistats ) result.apistats = this.apiStats.getAPIStats();
if( fieldMask & swsUtil.swsStatFields.errors ) result.errors = this.errorsStats.getStats();
if( fieldMask & swsUtil.swsStatFields.apiop ) {
if(("path" in query) && ("method" in query)) {
result.apiop = this.apiStats.getAPIOperationStats(query.path, query.method);
}
}
return result;
};
let swsProcessor = new SwsProcessor();
module.exports = swsProcessor;

@@ -11,3 +11,3 @@ /**

// apdex_threshold: Thresold for apdex calculation, in milliseconds 50 (ms) by default
function swsReqResStats(apdex_threshold) {
function SwsReqResStats(apdex_threshold) {
this.requests=0; // Total number of requests received

@@ -39,3 +39,3 @@ this.responses=0; // Total number of responses sent

swsReqResStats.prototype.countRequest = function(clength) {
SwsReqResStats.prototype.countRequest = function(clength) {
this.requests++;

@@ -47,3 +47,3 @@ this.total_req_clength += clength;

swsReqResStats.prototype.countResponse = function(code,codeclass,duration,clength) {
SwsReqResStats.prototype.countResponse = function(code,codeclass,duration,clength) {
this.responses++;

@@ -70,3 +70,3 @@ this[codeclass]++;

swsReqResStats.prototype.updateRates = function(elapsed) {
SwsReqResStats.prototype.updateRates = function(elapsed) {
//this.req_rate = Math.round( (this.requests / elapsed) * 1e2 ) / 1e2; //;

@@ -77,2 +77,2 @@ this.req_rate = this.requests / elapsed;

module.exports = swsReqResStats;
module.exports = SwsReqResStats;

@@ -163,40 +163,3 @@ /*

// METRICS ////////////////////////////////////////////////////////////// //
module.exports.swsMetrics = {
// TOP level counters for all requests / responses, no labels
api_all_request_total: { name: 'api_all_request_total', type: 'counter', help: 'The total number of all API requests received'},
api_all_success_total: { name: 'api_all_success_total', type: 'counter', help: 'The total number of all API requests with success response'},
api_all_errors_total: { name: 'api_all_errors_total', type: 'counter', help: 'The total number of all API requests with error response'},
api_all_client_error_total: { name: 'api_all_client_error_total', type: 'counter', help: 'The total number of all API requests with client error response'},
api_all_server_error_total: { name: 'api_all_server_error_total', type: 'counter', help: 'The total number of all API requests with server error response'},
api_all_request_in_processing_total: { name: 'api_all_request_in_processing_total', type: 'gauge', help: 'The total number of all API requests currently in processing (no response yet)'},
// System metrics for node process
nodejs_process_memory_rss_bytes: { name: 'nodejs_process_memory_rss_bytes', type: 'gauge', help: 'Node.js process resident memory (RSS) bytes '},
nodejs_process_memory_heap_total_bytes: { name: 'nodejs_process_memory_heap_total_bytes', type: 'gauge', help: 'Node.js process memory heapTotal bytes'},
nodejs_process_memory_heap_used_bytes: { name: 'nodejs_process_memory_heap_used_bytes', type: 'gauge', help: 'Node.js process memory heapUsed bytes'},
nodejs_process_memory_external_bytes: { name: 'nodejs_process_memory_external_bytes', type: 'gauge', help: 'Node.js process memory external bytes'},
nodejs_process_cpu_usage_percentage: { name: 'nodejs_process_cpu_usage_percentage', type: 'gauge', help: 'Node.js process CPU usage percentage'},
// API Operation counters, labeled with method, path and code
api_request_total: { name: 'api_request_total', type: 'counter', help: 'The total number of all API requests'},
// DISABLED API Operation counters, labeled with method, path and codeclass
//api_request_codeclass_total: { name: 'api_request_codeclass_total', type: 'counter', help: 'The total number of all API requests by response code class'},
// API request duration histogram, labeled with method, path and code
api_request_duration_milliseconds: { name: 'api_request_duration_milliseconds', type: 'histogram', help: 'API requests duration'},
// API request size histogram, labeled with method, path and code
api_request_size_bytes: { name: 'api_request_size_bytes', type: 'histogram', help: 'API requests size'},
// API response size histogram, labeled with method, path and code
api_response_size_bytes: { name: 'api_response_size_bytes', type: 'histogram', help: 'API requests size'}
};
// ////////////////////////////////////////////////////////////////////// //
// returns string value of argument, depending on typeof

@@ -203,0 +166,0 @@ module.exports.swsStringValue = function (val) {

{
"name": "swagger-stats",
"version": "0.95.9",
"version": "0.95.10",
"description": "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",

@@ -10,3 +10,4 @@ "main": "lib/index.js",

"hapitestapp": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/hapitestapp node examples/hapijstest/hapijstest.js",
"hapitestappstop": "mocha --delay --exit test/stoptestapp.js",
"testappstop": "mocha --delay --exit test/stoptestapp.js",
"delay1s": "mocha --delay --exit test/delay.js",
"test-old": "npm run coverage && npm run karma-ci",

@@ -24,3 +25,3 @@ "coverage": "nyc --reporter=lcov --reporter=html --reporter=text --report-dir=coverage/mocha mocha -S --delay",

"testHapi": "concurrently -k --success first \"npm run hapitestapp\" \"npm run covHapi\"",
"covHapi": "npm run cov000h && npm run cov100h && npm run cov200h && npm run cov300h && npm run cov500h && npm run hapitestappstop",
"covHapi": "npm run delay1s && npm run cov000h && npm run cov100h && npm run cov200h && npm run cov300h && npm run cov500h && npm run testappstop",
"cov000h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/000h mocha --delay --exit test/000_baseline.js",

@@ -42,2 +43,4 @@ "cov100h": "nyc --reporter=lcov --reporter=html --reporter=json --reporter=text --report-dir=coverage/100h mocha --delay --exit test/100_method.js",

"express",
"koa",
"hapi",
"api",

@@ -44,0 +47,0 @@ "restful",

<p align="center">
<img src="https://github.com/slanatech/swagger-stats/blob/master/screenshots/logo-c-ssm.png?raw=true" alt="swagger-stats"/>
<img src="https://github.com/slanatech/swagger-stats/blob/master/screenshots/logo.png?raw=true" alt="swagger-stats"/>
</p>

@@ -8,3 +8,3 @@

#### [http://swaggerstats.io](http://swaggerstats.io) | [Documentation](http://swaggerstats.io/docs.html) | [API DOC](http://swaggerstats.io/apidoc.html) | [API SPEC](http://swaggerstats.io/sws-api-swagger.yaml)
#### [https://swaggerstats.io](https://swaggerstats.io) | [Guide](https://swaggerstats.io/guide/)

@@ -16,2 +16,3 @@ [![Build Status](https://travis-ci.org/slanatech/swagger-stats.svg?branch=master)](https://travis-ci.org/slanatech/swagger-stats)

[![npm version](https://badge.fury.io/js/swagger-stats.svg)](https://badge.fury.io/js/swagger-stats)
[![Gitter](https://badges.gitter.im/swagger-stats/community.svg)](https://gitter.im/swagger-stats/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)

@@ -213,137 +214,6 @@

### Embedded Monitoring User Interface
Swagger-stats comes with built-in User Interface. Navigate to `/swagger-stats/ui` in your app to start monitoring right away
```
http://<your app host:port>/swagger-stats/ui
```
##### Key metrics
![swagger-stats bundled User Interface](screenshots/metrics.png?raw=true)
##### Timeline
![swagger-stats bundled User Interface](screenshots/timeline.png?raw=true)
##### Request and error rates
![swagger-stats bundled User Interface](screenshots/rates.png?raw=true)
##### API Operations
![swagger-stats bundled User Interface](screenshots/apitable.png?raw=true)
##### Stats By Method
![swagger-stats bundled User Interface](screenshots/methods.png?raw=true)
## Updates
#### v0.95.9
See [Changelog](https://github.com/slanatech/swagger-stats/blob/master/CHANGELOG.md)
* [bug] Removed dependency on Inert when using with Hapi [#79](https://github.com/slanatech/swagger-stats/issues/79)
#### v0.95.8
* [feature] Hapijs support [#75](https://github.com/slanatech/swagger-stats/issues/75) - [Example how to use](https://github.com/slanatech/swagger-stats/blob/master/examples/hapijstest/hapijstest.js)
* [feature] Koa support [#70](https://github.com/slanatech/swagger-stats/pull/70), [#67](https://github.com/slanatech/swagger-stats/issues/67) - thank you @gombosg!
#### v0.95.7
* [bug] Fixes error in body stringification [#59](https://github.com/slanatech/swagger-stats/issues/59), [#60](https://github.com/slanatech/swagger-stats/pull/60)
* [bug] Cannot upload to elk and Built-In API Telemetry [#46](https://github.com/slanatech/swagger-stats/issues/46)
* [feature] Option `elasticsearchIndexPrefix` [#45](https://github.com/slanatech/swagger-stats/issues/45),[#47](https://github.com/slanatech/swagger-stats/issues/47)
#### v0.95.6
* [bug] Last Errors and Errors tab no populated using FeatherJS [#42](https://github.com/slanatech/swagger-stats/issues/42)
* [bug] Request Content Length null or undefined [#40](https://github.com/slanatech/swagger-stats/issues/40)
#### v0.95.5
* [feature] Allow onAuthenticate to be asynchronous [#31](https://github.com/slanatech/swagger-stats/issues/31)
* [feature] Prevent tracking of specific routes [#36](https://github.com/slanatech/swagger-stats/issues/36)
* [feature] Support for extracting request body [#38](https://github.com/slanatech/swagger-stats/issues/38)
Thanks to [DavisJaunzems](https://github.com/DavisJaunzems)!
#### v0.95.0
* [feature] Elasticsearch support [#12](https://github.com/slanatech/swagger-stats/issues/12)
*swagger-stats* now supports storing details about each API Request/Response in [Elasticsearch](https://www.elastic.co/), so you may use [Kibana](https://www.elastic.co/products/kibana) to perform analysis of API usage over time, build visualizations and dashboards.
Example Kibana dashboards provided in `dashboards/elastic6`
#### v0.94.0
* [feature] Apdex score [#10](https://github.com/slanatech/swagger-stats/issues/10)
* [feature] Support Authentication for /stats and /metrics [#14](https://github.com/slanatech/swagger-stats/issues/14)
* [feature] Add label "code" to Prometheus histogram metrics [#21](https://github.com/slanatech/swagger-stats/issues/21)
See updated dashboard at [Grafana Dashboards](https://grafana.com/dashboards/3091)
#### v0.93.1
* [bug] Can't start on node v7.10.1, Mac Os 10.12.6 [#22](https://github.com/slanatech/swagger-stats/issues/22)
#### v0.93.0
* [feature] Support providing Prometheus metrics via [prom-client](https://www.npmjs.com/package/prom-client) library [#20](https://github.com/slanatech/swagger-stats/issues/20)
#### v0.92.0
* [feature] OnResponseFinish hook: pass request/response record to callback so app can post proceses it add it to the log [#5](https://github.com/slanatech/swagger-stats/issues/5)
#### v0.91.0
* [feature] Option to specify alternative URI path for ui,stats and metrics [#17](https://github.com/slanatech/swagger-stats/issues/17)
```javascript
app.use(swStats.getMiddleware({
uriPath: '/myservice',
swaggerSpec:swaggerSpec
}));
```
```
$ curl http://<your app host:port>/myservice/stats
```
#### v0.90.3
* [feature] Added new chart to API Operation Page [#16](https://github.com/slanatech/swagger-stats/issues/16)
- handle time histogram
- request size histogram
- response size histogram
- response codes counts
#### v0.90.2
* [feature] Added [Prometheus](https://prometheus.io/) metrics and [Grafana](https://grafana.com/) dashboards [#9](https://github.com/slanatech/swagger-stats/issues/9)
#### v0.90.1
* [feature] Added CPU and Memory Usage Stats and monitoring in UI [#8](https://github.com/slanatech/swagger-stats/issues/8)
## Enhancements and Bug Reports

@@ -350,0 +220,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc