import { IMetricListener } from "./IMetricListener";
import { IMetricContext } from "./contexts";
import { LogService } from "..";
/**
* Tracks metrics.
* @category Metrics
*/
export class Metrics {
private listeners: IMetricListener[] = [];
private requestStartTimes: { [contextId: string]: number } = {};
private uid = 0;
/**
* Creates a new Metrics handler with optional parent handler. When
* a parent handler is defined, metrics will be automatically published
* upwards to the parent.
* @param {Metrics} parent Optional parent for upstream metrics.
*/
constructor(parent: Metrics = null) {
if (parent !== null) {
this.registerListener({
onIncrement(metricName: string, context: IMetricContext, amount: number) {
parent.listeners.forEach(h => h.onIncrement(metricName, context, amount));
},
onDecrement(metricName: string, context: IMetricContext, amount: number) {
parent.listeners.forEach(h => h.onDecrement(metricName, context, amount));
},
onReset(metricName: string, context: IMetricContext) {
parent.listeners.forEach(h => h.onReset(metricName, context));
},
onStartMetric(metricName: string, context: IMetricContext): void {
parent.listeners.forEach(h => h.onStartMetric(metricName, context));
},
onEndMetric(metricName: string, context: IMetricContext, timeMs: number): void {
parent.listeners.forEach(h => h.onEndMetric(metricName, context, timeMs));
},
});
}
}
/**
* Registers a metric listener.
* @param {IMetricListener} listener The listener.
*/
public registerListener(listener: IMetricListener) {
this.listeners.push(listener);
}
/**
* De-registers a metric listener.
* @param {IMetricListener} listener The listener.
*/
public unregisterListener(listener: IMetricListener) {
const idx = this.listeners.indexOf(listener);
if (idx !== -1) this.listeners.splice(idx, 1);
}
/**
* Starts a timer on a metric.
* @param {string} metricName The metric name.
* @param {IMetricContext} context The metric context. Expected to have a unique ID.
*/
public start(metricName: string, context: IMetricContext) {
this.requestStartTimes[context.uniqueId] = new Date().getTime();
this.listeners.forEach(h => h.onStartMetric(metricName, context));
}
/**
* Ends a timer on a metric.
* @param {string} metricName The metric name.
* @param {IMetricContext} context The metric context. Expected to have a unique ID.
*/
public end(metricName: string, context: IMetricContext) {
const timeMs = (new Date().getTime()) - this.requestStartTimes[context.uniqueId];
delete this.requestStartTimes[context.uniqueId];
this.listeners.forEach(h => h.onEndMetric(metricName, context, timeMs));
// Trim the context for logging
const trimmedContext = {};
for (const key of Object.keys(context)) {
if (key === 'client') {
const client = context[key];
trimmedContext[key] = `<MatrixClient ${client['userId'] || 'NoCachedUserID'}>`;
} else if (key === 'intent') {
const intent = context[key];
trimmedContext[key] = `<Intent ${intent['userId'] || 'NoImpersonatedUserID'}>`;
} else {
trimmedContext[key] = context[key];
}
}
LogService.trace("Metrics", metricName, trimmedContext, timeMs);
}
/**
* Increments a metric.
* @param {string} metricName The metric name.
* @param {IMetricContext} context The metric context. Expected to have a unique ID.
* @param {number} amount The amount.
*/
public increment(metricName: string, context: IMetricContext, amount: number) {
this.listeners.forEach(h => h.onIncrement(metricName, context, amount));
}
/**
* Decrements a metric.
* @param {string} metricName The metric name.
* @param {IMetricContext} context The metric context. Expected to have a unique ID.
* @param {number} amount The amount.
*/
public decrement(metricName: string, context: IMetricContext, amount: number) {
this.listeners.forEach(h => h.onDecrement(metricName, context, amount));
}
/**
* Resets a metric.
* @param {string} metricName The metric name.
* @param {IMetricContext} context The metric context. Expected to have a unique ID.
*/
public reset(metricName: string, context: IMetricContext) {
this.listeners.forEach(h => h.onReset(metricName, context));
}
/**
* Assigns a unique ID to the context object, returning it back.
* @param {IMetricContext} context The context to modify.
* @returns {IMetricContext} The provided context.
*/
public assignUniqueContextId(context: IMetricContext): IMetricContext {
context.uniqueId = `${new Date().getTime()}-${this.uid++}`;
return context;
}
}
Source