'use strict' ; import Meter from '../utils/metrics/meter' ; import Counter from '../utils/metrics/counter' ; import Histogram from '../utils/metrics/histogram' ; import { ServiceManager , Service } from '../serviceManager' ; import constants from '../constants' ; import { Transport } from './transport' ; import * as Debug from 'debug' ; import Gauge from '../utils/metrics/gauge' ; export enum MetricType { 'meter' = 'meter' , 'histogram' = 'histogram' , 'counter' = 'counter' , 'gauge' = 'gauge' , 'metric' = 'metric' } export enum MetricMeasurements { 'min' = 'min' , 'max' = 'max' , 'sum' = 'sum' , 'count' = 'count' , 'variance' = 'variance' , 'mean' = 'mean' , 'stddev' = 'stddev' , 'median' = 'median' , 'p75' = 'p75' , 'p95' = 'p95' , 'p99' = 'p99' , 'p999' = 'p999' } export interface InternalMetric { /**
     * Display name of the metric, it should be a clear name that everyone can understand
     */ name ? : string ; type ? : MetricType ; /**
     * An precise identifier for your metric, exemple:
     * The heap usage can be shown to user as 'Heap Usage' but internally
     * we want to put few namespace to be sure we talking about the main heap of v8
     * so we would choose something like: process/v8/heap/usage
     */ id ? : string ; /**
     * Choose if the metrics will be saved in our datastore or only be used in realtime
     */ historic ? : boolean ; /**
     * Unit of the metric
     */ unit ? : string ; /**
     * The handler is the function that will be called to get the current value
     * of the metrics
     */ handler : Function ; /**
     * The implementation is the instance of the class that handle the computation
     * of the metric value
     */ implementation : any ; /**
     * Last known value of the metric
     */ value ? : number ; } export class Metric { /**
     * Display name of the metric, it should be a clear name that everyone can understand
     */ name ? : string ; /**
     * An precise identifier for your metric, exemple:
     * The heap usage can be shown to user as 'Heap Usage' but internally
     * we want to put few namespace to be sure we talking about the main heap of v8
     * so we would choose something like: process/v8/heap/usage
     */ id ? : string ; /**
     * Choose if the metrics will be saved in our datastore or only be used in realtime
     */ historic ? : boolean ; /**
     * Unit of the metric
     */ unit ? : string ; /**
     * Allow to automatically update the metric value
     * Note: only available with io.metric
     * Note: if you use this property, you will not be able to set the value with .set
     */ value ? : ( ) => number ; } export class MetricBulk extends Metric { type : MetricType ; } export class HistogramOptions extends Metric { measurement : MetricMeasurements ; } export class MetricService implements Service { private metrics : Map < string , InternalMetric > = new Map ( ) ; private timer : NodeJS . Timer | null = null ; private transport : Transport | null = null ; private logger : any = Debug ( 'axm:services:metrics' ) ; init ( ) { this . transport = ServiceManager . get ( 'transport' ) ; if ( this . transport === null ) return this . logger ( 'Failed to init metrics service cause no transporter' ) ; this . logger ( 'init' ) ; this . timer = setInterval ( ( ) => { if ( this . transport === null ) return this . logger ( 'Abort metrics update since transport is not available' ) ; this . logger ( 'refreshing metrics value' ) ; for ( let metric of this . metrics . values ( ) ) { metric . value = metric . handler ( ) ; } this . logger ( 'sending update metrics value to transporter' ) ; const metricsToSend = Array . from ( this . metrics . values ( ) ) . filter ( metric => { if ( metric === null || metric === undefined ) return false ; if ( metric . value === undefined || metric . value === null ) return false ; const isNumber = typeof metric . value === 'number' ; const isString = typeof metric . value === 'string' ; const isBoolean = typeof metric . value === 'boolean' ; const isValidNumber = ! isNaN ( metric . value ) ; return isString || isBoolean || ( isNumber && isValidNumber ) ; } ) ; this . transport . setMetrics ( metricsToSend ) ; } , constants . METRIC_INTERVAL ) ; this . timer . unref ( ) ; } registerMetric ( metric ) { if ( typeof metric . name !== 'string' ) { console . error ( `Invalid metric name declared: ${ metric . name }` ) ; return console . trace ( ) ; } else if ( typeof metric . type !== 'string' ) { console . error ( `Invalid metric type declared: ${ metric . type }` ) ; return console . trace ( ) ; } else if ( typeof metric . handler !== 'function' ) { console . error ( `Invalid metric handler declared: ${ metric . handler }` ) ; return console . trace ( ) ; } if ( typeof metric . historic !== 'boolean' ) { metric . historic = true ; } this . logger ( `Registering new metric: ${ metric . name }` ) ; this . metrics . set ( metric . name , metric ) ; } meter ( opts ) { const metric = { name : opts . name , type : MetricType . meter , id : opts . id , historic : opts . historic , implementation : new Meter ( opts ) , unit : opts . unit , handler : function ( ) { return this . implementation . isUsed ( ) ? this . implementation . val ( ) : NaN ; } } ; this . registerMetric ( metric ) ; return metric . implementation ; } counter ( opts ) { const metric = { name : opts . name , type : MetricType . counter , id : opts . id , historic : opts . historic , implementation : new Counter ( opts ) , unit : opts . unit , handler : function ( ) { return this . implementation . isUsed ( ) ? this . implementation . val ( ) : NaN ; } } ; this . registerMetric ( metric ) ; return metric . implementation ; } histogram ( opts ) { if ( opts . measurement === undefined || opts . measurement === null ) { opts . measurement = MetricMeasurements . mean ; } const metric = { name : opts . name , type : MetricType . histogram , id : opts . id , historic : opts . historic , implementation : new Histogram ( opts ) , unit : opts . unit , handler : function ( ) { return this . implementation . isUsed ( ) ? ( Math . round ( this . implementation . val ( ) * 100 ) / 100 ) : NaN ; } } ; this . registerMetric ( metric ) ; return metric . implementation ; } metric ( opts ) { let metric ; if ( typeof opts . value === 'function' ) { metric = { name : opts . name , type : MetricType . gauge , id : opts . id , implementation : undefined , historic : opts . historic , unit : opts . unit , handler : opts . value } ; } else { metric = { name : opts . name , type : MetricType . gauge , id : opts . id , historic : opts . historic , implementation : new Gauge ( ) , unit : opts . unit , handler : function ( ) { return this . implementation . isUsed ( ) ? this . implementation . val ( ) : NaN ; } } ; } this . registerMetric ( metric ) ; return metric . implementation ; } deleteMetric ( name ) { return this . metrics . delete ( name ) ; } destroy ( ) { if ( this . timer !== null ) { clearInterval ( this . timer ) ; } this . metrics . clear ( ) ; } }