/
home
/
infinitibizsol
/
testingcrm.infinitibizsol.com
/
node_modules
/
swagger-stats
/
lib
/
File Upload :
llllll
Current File: /home/infinitibizsol/testingcrm.infinitibizsol.com/node_modules/swagger-stats/lib/swsAPIStats.js
/** * Created by sv2 on 2/18/17. * API Statistics */ 'use strict'; 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'); // API Statistics // Stores Definition of API based on Swagger Spec // Stores API Statistics, for both Swagger spec-based API, as well as for detected Express APIs (route.path) // Stores Detailed Stats for each API request function swsAPIStats() { // Options this.options = null; // API Base path per swagger spec this.basePath = '/'; // Array of possible API path matches, populated based on Swagger spec // Contains regex to match URI to Swagger path this.apiMatchIndex = {}; // API definition - entry per API request from swagger spec // Stores attributes of known Swagger APIs - description, summary, tags, parameters this.apidefs = {}; // API statistics - entry per API request from swagger // Paths not covered by swagger will be added on demand as used this.apistats = {}; // Detailed API stats // TODO Consider: individual timelines (?), parameters (query/path?) this.apidetails = {}; // Buckets for histograms, with default bucket values this.durationBuckets = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]; this.requestSizeBuckets = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]; this.responseSizeBuckets = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]; // Prometheus metrics in prom-client this.promClientMetrics = {}; } swsAPIStats.prototype.getAPIDefs = function() { return this.apidefs; }; swsAPIStats.prototype.getAPIStats = function() { return this.apistats; }; swsAPIStats.prototype.getAPIOperationStats = function(path,method) { if( (typeof path === 'undefined') || !path || (path==='')) return {}; if( (typeof method === 'undefined') || !method || (method==='')) return {}; var res = {}; res[path] = {}; res[path][method] = {}; // api op defs if( (path in this.apidefs) && (method in this.apidefs[path])) { res[path][method].defs = this.apidefs[path][method]; } // api op stats if( (path in this.apistats) && (method in this.apistats[path])) { res[path][method].stats = this.apistats[path][method]; } // api op details if( (path in this.apidetails) && (method in this.apidetails[path])) { res[path][method].details = this.apidetails[path][method]; } return res; }; swsAPIStats.prototype.initBasePath = function(swaggerSpec,swsOptions) { if(('basePath' in swsOptions) && (swsOptions.basePath!=='')){ this.basePath = swsOptions.basePath; }else{ if( swaggerSpec.openapi && swaggerSpec.openapi.startsWith('3') ){ this.basePath = '/'; }else{ this.basePath = swaggerSpec.basePath ? swaggerSpec.basePath : '/'; if (this.basePath.charAt(0) !== '/') { this.basePath = '/' + this.basePath; } } } if (this.basePath.charAt(this.basePath.length - 1) !== '/') { this.basePath = this.basePath + '/'; } }; // Get full swagger Path swsAPIStats.prototype.getFullPath = function (path) { var fullPath = this.basePath; if (path.charAt(0) === '/') { fullPath += path.substring(1); }else{ fullPath += path; } return fullPath; }; swsAPIStats.prototype.initialize = function(swsOptions) { // TODO remove if(!swsOptions) return; this.options = swsOptions; 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; if(swsOptions.swaggerSpec === null) return; var swaggerSpec = swsOptions.swaggerSpec; this.initBasePath(swaggerSpec,swsOptions); if(!swaggerSpec.paths) return; // Enumerate all paths entries for(var path in swaggerSpec.paths ){ var pathDef = swaggerSpec.paths[path]; // Create full path var fullPath = this.getFullPath(path); // by default, regex is null var keys = []; var re = null; // Convert to express path var fullExpressPath = fullPath; // Create regex if we have path parameters if( fullExpressPath.indexOf('{') !== -1 ) { fullExpressPath = fullExpressPath.replace(/\{/g, ':'); fullExpressPath = fullExpressPath.replace(/\}/g, ''); fullExpressPath = fullExpressPath.replace(/\?(\w+=)/g, '\\?$1'); re = pathToRegexp(fullExpressPath, keys); } // Add to API Match Index, leveraging express style matching this.apiMatchIndex[fullPath] = { re: re, keys: keys, expressPath: fullExpressPath, methods: {}}; var operations = ['get','put','post','delete','options','head','patch']; for(var i=0;i<operations.length;i++){ var op = operations[i]; if(op in pathDef){ var opDef = pathDef[op]; var opMethod = op.toUpperCase(); var apiOpDef = {}; // API Operation definition apiOpDef.swagger = true; // by definition apiOpDef.deprecated = ('deprecated' in opDef) ? opDef.deprecated : false; if( 'description' in opDef ) apiOpDef.description = opDef.description; if( 'operationId' in opDef ) apiOpDef.operationId = opDef.operationId; if( 'summary' in opDef ) apiOpDef.summary = opDef.summary; if( 'tags' in opDef ) apiOpDef.tags = opDef.tags; // Store in match index this.apiMatchIndex[fullPath].methods[opMethod] = apiOpDef; // Store in API Operation definitions. Stored separately so only definition can be retrieved if(!(fullPath in this.apidefs) ) this.apidefs[fullPath] = {}; this.apidefs[fullPath][opMethod] = apiOpDef; // Create Stats for this API Operation; stats stored separately so only stats can be retrieved this.getAPIOpStats(fullPath,opMethod); // Create entry in apidetails this.getApiOpDetails(fullPath,opMethod); // Process parameters for this op this.processParameters(swaggerSpec, pathDef, opDef, fullPath, opMethod); debug('SWS:Initialize API:added %s %s (%s)', op, fullPath, fullExpressPath); } } } }; // Process parameterss for given operation // Take into account parameters defined as common for path (from pathDef) swsAPIStats.prototype.processParameters = function(swaggerSpec, pathDef, opDef, fullPath, opMethod ) { var apidetailsEntry = this.getApiOpDetails(fullPath,opMethod); // Params from path if(('parameters' in pathDef) && (pathDef.parameters instanceof Array)) { var pathParams = pathDef.parameters; for(var j=0;j<pathParams.length;j++){ var param = pathParams[j]; this.processSingleParameter(apidetailsEntry,param); } } // Params from Op, overriding parameters from path if(('parameters' in opDef) && (opDef.parameters instanceof Array)){ var opParams = opDef.parameters; for(var k=0;k<opParams.length;k++){ var param = opParams[k]; this.processSingleParameter(apidetailsEntry,param); } } }; swsAPIStats.prototype.processSingleParameter = function(apidetailsEntry,param) { if( !('parameters' in apidetailsEntry) ) apidetailsEntry.parameters = {}; var params = apidetailsEntry.parameters; var pname = "name" in param ? param.name : null; if( pname === null ) return; if(!(pname in params)) params[pname] = { name: pname }; var paramEntry = params[pname]; // Process all supported parameter properties for( var supportedProp in swsUtil.swsParameterProperties ){ if(supportedProp in param){ paramEntry[supportedProp] = param[supportedProp]; } } // Process all vendor extensions for( var paramProp in param ){ if( paramProp.startsWith('x-') ){ paramEntry[paramProp] = param[paramProp]; } } // Add standard stats paramEntry.hits = 0; paramEntry.misses = 0; }; // Get or create API Operation Details swsAPIStats.prototype.getApiOpDetails = function(path,method) { if(!(path in this.apidetails) ) this.apidetails[path] = {}; if(!(method in this.apidetails[path]) ) this.apidetails[path][method] = { duration: new swsBucketStats(this.durationBuckets), // Request duration histogram req_size: new swsBucketStats(this.requestSizeBuckets), // Request size histogram res_size: new swsBucketStats(this.responseSizeBuckets), // Response size histogram code: {'200':{count:0}} // Counts by response code }; return this.apidetails[path][method]; }; // Get or create API Operation Stats swsAPIStats.prototype.getAPIOpStats = function( path, method ) { if( !(path in this.apistats)) this.apistats[path] = {}; if( !(method in this.apistats[path])) this.apistats[path][method] = new swsReqResStats(this.options.apdexThreshold); return this.apistats[path][method]; }; // Update and stats per tick swsAPIStats.prototype.tick = function (ts,totalElapsedSec) { // Update Rates in apistats for( var path in this.apistats ) { for( var method in this.apistats[path] ) { this.apistats[path][method].updateRates(totalElapsedSec); } } }; // Extract path parameter values based on successful path match results swsAPIStats.prototype.extractPathParams = function (matchResult,keys) { var pathParams = {}; for(var i=0;i<keys.length;i++){ if('name' in keys[i] ) { var vidx = i + 1; // first element in match result is URI if( vidx < matchResult.length ) { pathParams[keys[i].name] = swsUtil.swsStringValue(matchResult[vidx]); } } } return pathParams; }; // Try to match request to API to known API definition swsAPIStats.prototype.matchRequest = function (req) { var url = req.sws.originalUrl; // Handle "/pets" and "/pets/" the same way - #105 if( url.endsWith('/') ) { url = url.slice(0,-1); } req.sws.match = false; // No match by default // Strip query string parameters var qidx = url.indexOf('?'); if(qidx!=-1) { url = url.substring(0,qidx); } var matchEntry = null; var apiPath = null; var apiPathParams = null; var apiInfo = null; // First check if we can find exact match in apiMatchIndex if( url in this.apiMatchIndex ){ matchEntry = this.apiMatchIndex[url]; apiPath = url; debug('SWS:MATCH: %s exact match', url); } else { // if not, search by regex matching for(var swPath in this.apiMatchIndex) { if( this.apiMatchIndex[swPath].re !== null) { var matchResult = this.apiMatchIndex[swPath].re.exec(url); if (matchResult && (matchResult instanceof Array)) { matchEntry = this.apiMatchIndex[swPath]; apiPath = swPath; apiPathParams = this.extractPathParams(matchResult, this.apiMatchIndex[swPath].keys); debug('SWS:MATCH: %s matched to %s', url, swPath); break; // Done } } } } if( matchEntry ) { if (req.method in matchEntry.methods) { apiInfo = matchEntry.methods[req.method]; req.sws.match = true; // Match is found req.sws.api_path = apiPath; req.sws.swagger = true; // When matched, attach only subset of information to request, // so we don't overload reqresinfo with repeated description, etc if ('deprecated' in apiInfo) req.sws.deprecated = apiInfo.deprecated; if ('operationId' in apiInfo) req.sws.operationId = apiInfo.operationId; if ('tags' in apiInfo) req.sws.tags = apiInfo.tags; // Store path parameters from match result if (apiPathParams) req.sws.path_params = apiPathParams; } } }; // Count Api Operation Parameters Statistics // Only count hits and misses // Hit: parameter present // Miss: mandatory parameter is missing // Only supported path and query parameters swsAPIStats.prototype.countParametersStats = function (path, method, req, res) { if(!('swagger' in req.sws) || !req.sws.swagger ) return; // Only counting for swagger-defined API Ops var apiOpDetails = this.getApiOpDetails(path, method); if( !('parameters' in apiOpDetails) ) return; // Only counting if parameters spec is there for( var pname in apiOpDetails.parameters ){ var param = apiOpDetails.parameters[pname]; var isRrequired = 'required' in param ? param.required : false; if( 'in' in param ){ switch(param.in){ case "path": // Path param is always there, or request will not be matched param.hits++; break; case "query": if( ('query' in req.sws) && (pname in req.sws.query) ){ param.hits++; }else if(isRrequired){ param.misses++; } break; } } } }; // Get Api Operation Parameter Values per specification // Only supported path and query parameters swsAPIStats.prototype.getApiOpParameterValues = function (path, method, req, res) { if(!('swagger' in req.sws) || !req.sws.swagger ) return null; // Only for swagger-defined API Ops var apiOpDetails = this.getApiOpDetails(path, method); if( !('parameters' in apiOpDetails) ) return null; // Only if parameters spec is there var paramValues = {}; for( var pname in apiOpDetails.parameters ){ var param = apiOpDetails.parameters[pname]; if( 'in' in param ){ switch(param.in){ case "path": if(('path_params' in req.sws) && (pname in req.sws.path_params)) { paramValues[pname] = swsUtil.swsStringValue(req.sws.path_params[pname]); } break; case "query": if(('query' in req.sws) && (pname in req.sws.query)) { paramValues[pname] = swsUtil.swsStringValue(req.sws.query[pname]); } break; } } } return paramValues; }; // Count request swsAPIStats.prototype.countRequest = function (req, res) { // Count request if it was matched to API Operation if(('match' in req.sws) && req.sws.match ){ var apiOpStats = this.getAPIOpStats(req.sws.api_path,req.method); apiOpStats.countRequest(req.sws.req_clength); this.countParametersStats(req.sws.api_path,req.method, req, res ); } }; // Count finished response swsAPIStats.prototype.countResponse = function (res) { var req = res._swsReq; var codeclass = swsUtil.getStatusCodeClass(res.statusCode); // Only intersted in updating stats here var apiOpStats = this.getAPIOpStats(req.sws.api_path,req.method); // If request was not matched to API operation, // do both count request and count response here, // as only at this time we know path so can map request / response to API entry // This allows supporting API statistics on non-swagger express route APIs, like /path/:param // as express router would attach route.path to request if(!('match' in req.sws) || !req.sws.match) { apiOpStats.countRequest(req.sws.req_clength); } // In all cases, count response here apiOpStats.countResponse(res.statusCode,codeclass,req.sws.duration,req.sws.res_clength); // Count metrics var apiOpDetails = this.getApiOpDetails(req.sws.api_path,req.method); // Metrics by response code if( !('code' in apiOpDetails) ) { apiOpDetails.code = {}; } if(!(res.statusCode in apiOpDetails.code)){ apiOpDetails.code[res.statusCode] = { count:0 }; } apiOpDetails.code[res.statusCode].count++; apiOpDetails.duration.countValue(req.sws.duration); apiOpDetails.req_size.countValue(req.sws.req_clength); apiOpDetails.res_size.countValue(req.sws.res_clength); // update Prometheus metrics this.promClientMetrics.api_request_total.labels(req.method,req.sws.api_path,res.statusCode).inc(); this.promClientMetrics.api_request_duration_milliseconds.labels(req.method,req.sws.api_path,res.statusCode).observe(req.sws.duration); this.promClientMetrics.api_request_size_bytes.labels(req.method,req.sws.api_path,res.statusCode).observe(req.sws.req_clength); this.promClientMetrics.api_response_size_bytes.labels(req.method,req.sws.api_path,res.statusCode).observe(req.sws.res_clength); }; module.exports = swsAPIStats;
Copyright ©2k19 -
Hexid
|
Tex7ure