const consoleLogLevel = require('console-log-level'); const { CachedGauge, SettableGauge, Gauge, Timer, Counter, Meter, Histogram } = require('measured-core'); const DimensionAwareMetricsRegistry = require('./DimensionAwareMetricsRegistry'); const { validateSelfReportingMetricsRegistryParameters, validateRegisterOptions, validateGaugeOptions, validateCounterOptions, validateHistogramOptions, validateTimerOptions, validateSettableGaugeOptions, validateCachedGaugeOptions } = require('../validators/inputValidators'); function prefix() { return `${new Date().toISOString()}: `; } /** * A dimensional aware self-reporting metrics registry */ class SelfReportingMetricsRegistry { /** * @param {Reporter|Reporter[]} reporters A single {@link Reporter} or an array of reporters that will be used to report metrics on an interval. * @param {SelfReportingMetricsRegistryOptions} [options] Configurable options for the Self Reporting Metrics Registry */ constructor(reporters, options) { options = options || {}; if (!Array.isArray(reporters)) { reporters = [reporters]; } validateSelfReportingMetricsRegistryParameters(reporters, options); /** * @type {Reporter} * @protected */ this._reporters = reporters; /** * @type {DimensionAwareMetricsRegistry} * @protected */ this._registry = options.registry || new DimensionAwareMetricsRegistry(); this._reporters.forEach(reporter => reporter.setRegistry(this._registry)); /** * Loggers to use, defaults to a new console logger if nothing is supplied in options * @type {Logger} * @protected */ this._log = options.logger || consoleLogLevel({ name: 'SelfReportingMetricsRegistry', level: options.logLevel || 'info', prefix: prefix }); } /** * Registers a manually created Metric. * * @param {string} name The Metric name * @param {Metric} metric The {@link Metric} to register * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @example * const settableGauge = new SettableGauge(5); * // register the gauge and have it report to every 10 seconds * registry.register('my-gauge', settableGauge, {}, 10); * interval(() => { * // such as cpu % used * determineAValueThatCannotBeSync((value) => { * settableGauge.update(value); * }) * }, 10000) */ register(name, metric, dimensions, publishingIntervalInSeconds) { validateRegisterOptions(name, metric, dimensions, publishingIntervalInSeconds); if (this._registry.hasMetric(name, dimensions)) { throw new Error( `Metric with name: ${name} and dimensions: ${JSON.stringify(dimensions)} has already been registered` ); } else { const key = this._registry.putMetric(name, metric, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return metric; } /** * Creates a {@link Gauge} or gets the existing Gauge for a given name and dimension combo * * @param {string} name The Metric name * @param {function} callback The callback that will return a value to report to signal fx * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {Gauge} * @example * // https://nodejs.org/api/process.html#process_process_memoryusage * // Report heap total and heap used at the default interval * registry.getOrCreateGauge( * 'process-memory-heap-total', * () => { * return process.memoryUsage().heapTotal * } * ); * registry.getOrCreateGauge( * 'process-memory-heap-used', * () => { * return process.memoryUsage().heapUsed * } * ) */ getOrCreateGauge(name, callback, dimensions, publishingIntervalInSeconds) { validateGaugeOptions(name, callback, dimensions, publishingIntervalInSeconds); let gauge; if (this._registry.hasMetric(name, dimensions)) { gauge = this._registry.getMetric(name, dimensions); } else { gauge = new Gauge(callback); const key = this._registry.putMetric(name, gauge, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return gauge; } /** * Creates a {@link Histogram} or gets the existing Histogram for a given name and dimension combo * * @param {string} name The Metric name * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {Histogram} */ getOrCreateHistogram(name, dimensions, publishingIntervalInSeconds) { validateHistogramOptions(name, dimensions, publishingIntervalInSeconds); let histogram; if (this._registry.hasMetric(name, dimensions)) { histogram = this._registry.getMetric(name, dimensions); } else { histogram = new Histogram(); const key = this._registry.putMetric(name, histogram, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return histogram; } /** * Creates a {@link Meter} or gets the existing Meter for a given name and dimension combo * * @param {string} name The Metric name * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {Meter} */ getOrCreateMeter(name, dimensions, publishingIntervalInSeconds) { // todo validate options let meter; if (this._registry.hasMetric(name, dimensions)) { meter = this._registry.getMetric(name, dimensions); } else { meter = new Meter(); const key = this._registry.putMetric(name, meter, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return meter; } /** * Creates a {@link Counter} or gets the existing Counter for a given name and dimension combo * * @param {string} name The Metric name * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {Counter} */ getOrCreateCounter(name, dimensions, publishingIntervalInSeconds) { validateCounterOptions(name, dimensions, publishingIntervalInSeconds); let counter; if (this._registry.hasMetric(name, dimensions)) { counter = this._registry.getMetric(name, dimensions); } else { counter = new Counter(); const key = this._registry.putMetric(name, counter, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return counter; } /** * Creates a {@link Timer} or gets the existing Timer for a given name and dimension combo. * * @param {string} name The Metric name * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {Timer} */ getOrCreateTimer(name, dimensions, publishingIntervalInSeconds) { validateTimerOptions(name, dimensions, publishingIntervalInSeconds); let timer; if (this._registry.hasMetric(name, dimensions)) { timer = this._registry.getMetric(name, dimensions); } else { timer = new Timer(); const key = this._registry.putMetric(name, timer, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return timer; } /** * Creates a {@link SettableGauge} or gets the existing SettableGauge for a given name and dimension combo. * * @param {string} name The Metric name * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval * @return {SettableGauge} */ getOrCreateSettableGauge(name, dimensions, publishingIntervalInSeconds) { validateSettableGaugeOptions(name, dimensions, publishingIntervalInSeconds); let settableGauge; if (this._registry.hasMetric(name, dimensions)) { settableGauge = this._registry.getMetric(name, dimensions); } else { settableGauge = new SettableGauge(); const key = this._registry.putMetric(name, settableGauge, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return settableGauge; } /** * Creates a {@link CachedGauge} or gets the existing CachedGauge for a given name and dimension combo. * * @param {string} name The Metric name. * @param {function} valueProducingPromiseCallback. * @param {number} cachedGaugeUpdateIntervalInSeconds. * @param {Dimensions} [dimensions] any custom {@link Dimensions} for the Metric. * @param {number} [publishingIntervalInSeconds] a optional custom publishing interval. * @return {CachedGauge} */ getOrCreateCachedGauge( name, valueProducingPromiseCallback, cachedGaugeUpdateIntervalInSeconds, dimensions, publishingIntervalInSeconds ) { validateCachedGaugeOptions(name, valueProducingPromiseCallback, dimensions, publishingIntervalInSeconds); let cachedGauge; if (this._registry.hasMetric(name, dimensions)) { cachedGauge = this._registry.getMetric(name, dimensions); } else { cachedGauge = new CachedGauge(valueProducingPromiseCallback, cachedGaugeUpdateIntervalInSeconds); const key = this._registry.putMetric(name, cachedGauge, dimensions); this._reporters.forEach(reporter => reporter.reportMetricOnInterval(key, publishingIntervalInSeconds)); } return cachedGauge; } /** * Calls end on all metrics in the registry that support end() and calls end on the reporter */ shutdown() { // shutdown the reporter this._reporters.forEach(reporter => reporter.shutdown()); // shutdown any metrics that have an end method this._registry.allKeys().forEach(key => { const metricWrapper = this._registry.getMetricWrapperByKey(key); if (metricWrapper.metricImpl.end) { metricWrapper.metricImpl.end(); } }); } } module.exports = SelfReportingMetricsRegistry; /** * Configurable options for the Self Reporting Metrics Registry * * @interface SelfReportingMetricsRegistryOptions * @typedef SelfReportingMetricsRegistryOptions * @property {Logger} logger the Logger to use * @property {string} logLevel The Log level to use if defaulting to included logger * @property {DimensionAwareMetricsRegistry} registry The registry to use, defaults to new DimensionAwareMetricsRegistry */