Skip to content

Commit

Permalink
feat: add metricLimit and lru to DimensionAwareMetricsRegistry (#69)
Browse files Browse the repository at this point in the history
The `metricLevel` option allows limiting the number of metrics held by the registry
in order to prevent denial of service attacks. The `lru` flag changes the metric
dropping strategy from "least recently added" to "least recently used"
  • Loading branch information
Qard authored Aug 2, 2019
1 parent ba55d8b commit 84a6e54
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 149 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
const mapcap = require('mapcap');

/**
* Simple registry that stores Metrics by name and dimensions.
*/
class DimensionAwareMetricsRegistry {
constructor() {
this._metrics = {};
/**
* @param {DimensionAwareMetricsRegistryOptions} [options] Configurable options for the Dimension Aware Metrics Registry
*/
constructor(options) {
options = options || {};

let metrics = new Map();
if (options.metricLimit) {
metrics = mapcap(metrics, options.metricLimit, options.lru);
}

this._metrics = metrics;
}

/**
Expand All @@ -15,7 +27,7 @@ class DimensionAwareMetricsRegistry {
*/
hasMetric(name, dimensions) {
const key = this._generateStorageKey(name, dimensions);
return Object.prototype.hasOwnProperty.call(this._metrics, key);
return this._metrics.has(key);
}

/**
Expand All @@ -27,7 +39,7 @@ class DimensionAwareMetricsRegistry {
*/
getMetric(name, dimensions) {
const key = this._generateStorageKey(name, dimensions);
return this._metrics[key].metricImpl;
return this._metrics.get(key).metricImpl;
}

/**
Expand All @@ -37,7 +49,7 @@ class DimensionAwareMetricsRegistry {
* @returns {MetricWrapper} a wrapper object around name, dimension and {@link Metric}
*/
getMetricWrapperByKey(key) {
return this._metrics[key];
return this._metrics.get(key);
}

/**
Expand All @@ -50,11 +62,11 @@ class DimensionAwareMetricsRegistry {
*/
putMetric(name, metric, dimensions) {
const key = this._generateStorageKey(name, dimensions);
this._metrics[key] = {
this._metrics.set(key, {
name: name,
metricImpl: metric,
dimensions: dimensions || {}
};
});
return key;
}

Expand All @@ -63,7 +75,7 @@ class DimensionAwareMetricsRegistry {
* @return {string[]} all keys of metrics stored in this registry.
*/
allKeys() {
return Object.keys(this._metrics);
return Array.from(this._metrics.keys());
}

/**
Expand All @@ -88,3 +100,12 @@ class DimensionAwareMetricsRegistry {
}

module.exports = DimensionAwareMetricsRegistry;

/**
* Configurable options for the Dimension Aware Metrics Registry
*
* @interface DimensionAwareMetricsRegistryOptions
* @typedef DimensionAwareMetricsRegistryOptions
* @property {Number} metricLimit the maximum number of metrics the registry may hold before dropping metrics
* @property {Boolean} lru switch dropping strategy from "least recently added" to "least recently used"
*/
1 change: 1 addition & 0 deletions packages/measured-reporting/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
"dependencies": {
"console-log-level": "^1.4.1",
"mapcap": "^1.0.0",
"measured-core": "^1.49.0",
"optional-js": "^2.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,54 @@ describe('DimensionAwareMetricsRegistry', () => {

assert.equal(key1, key2);
});

it('metricLimit limits metric count', () => {
const limitedRegistry = new DimensionAwareMetricsRegistry({
metricLimit: 10
});

const counter = new Counter({
count: 10
});

const dimensions = {
foo: 'bar'
};

for (let i = 0; i < 20; i++) {
limitedRegistry.putMetric(`metric #${i}`, counter, dimensions);
}

assert.equal(10, limitedRegistry._metrics.size);
assert(!limitedRegistry.hasMetric('metric #0', dimensions));
});

it('lru changes metric dropping strategy', () => {
const limitedRegistry = new DimensionAwareMetricsRegistry({
metricLimit: 10,
lru: true
});

const counter = new Counter({
count: 10
});

const dimensions = {
foo: 'bar'
};

for (let i = 0; i < 10; i++) {
limitedRegistry.putMetric(`metric #${i}`, counter, dimensions);
}

// Touch the first added metric
limitedRegistry.getMetric('metric #0', dimensions);

// Put a new metric in to trigger a drop
limitedRegistry.putMetric('metric #11', counter, dimensions);

// Verify that it dropped metric #1, not metric #0
assert(limitedRegistry.hasMetric('metric #0', dimensions));
assert(!limitedRegistry.hasMetric('metric #1', dimensions));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('SelfReportingMetricsRegistry', () => {

selfReportingRegistry.register(metricKey, new Counter(), {}, reportInterval);

assert.equal(1, Object.keys(registry._metrics).length);
assert.equal(1, registry._metrics.size);

mockReporter.restore();
mockReporter.verify();
Expand Down
Loading

0 comments on commit 84a6e54

Please sign in to comment.