Files
factorio-clusterio/test/lib/prometheus.js
T
Hornwitser 430aa6d207 Disable heap and memory size tests
Disable the tests for the memory and heap size metrics as the travis
environment is apparently using 3x as much memory as locally.
2020-04-09 20:44:06 +02:00

612 lines
18 KiB
JavaScript

const assert = require('assert').strict;
const prometheus = require('lib/prometheus');
describe("lib/prometheus", function() {
describe("class CollectorRegistry", function() {
let registry;
beforeEach(function() {
registry = new prometheus.CollectorRegistry();
})
describe(".register()", function() {
it("should register a collector", function() {
let collector = new prometheus.Collector(false);
registry.register(collector);
assert(
registry.collectors.includes(collector),
"Collector was not registered"
);
});
it("should throw when already registered", function() {
let collector = new prometheus.Collector(false);
registry.register(collector);
assert.throws(
() => registry.register(collector),
new Error("Collector is already registered in this registry.")
);
});
it("should allow being regitstered in another register", function() {
let otherCollectorRegistry = new prometheus.CollectorRegistry();
let collector = new prometheus.Collector(false);
registry.register(collector);
otherCollectorRegistry.register(collector);
assert(
registry.collectors.includes(collector),
"Collector was not registered in first register"
);
assert(
otherCollectorRegistry.collectors.includes(collector),
"Collector was not registered in second register"
);
});
});
describe(".unregister()", function() {
it("should removed a registered collector", function() {
let collector = new prometheus.Collector(false);
registry.register(collector);
registry.unregister(collector);
assert(
!registry.collectors.includes(collector),
"Collector was not removed"
);
});
it("should throw when not registered", function() {
let collector = new prometheus.Collector(false);
assert.throws(
() => registry.unregister(collector),
new Error("Collector is not registered in this registry.")
);
});
});
describe("defaultRegistry", function() {
it("should have collectors registered to it by default", function() {
let collector = new prometheus.Collector();
assert(
prometheus.defaultRegistry.collectors.includes(collector),
"collector was not registered to defaultRegistry"
);
prometheus.defaultRegistry.unregister(collector);
});
it("should have the default collectors registered to it", function() {
let collectors = Object.entries(prometheus.defaultCollectors);
for (let [name, collector] of collectors) {
assert(
prometheus.defaultRegistry.collectors.includes(collector),
`${name} is not registered`
);
}
});
it("should allow default collectors to be unregistered", function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
let collectors = Object.values(prometheus.defaultCollectors);
for (let collector of collectors) {
prometheus.defaultRegistry.unregister(collector);
}
assert(
prometheus.defaultRegistry.collectors.length == 0,
"Collectors left over after removing the default ones"
);
});
});
});
describe("class Collector", function() {
it("should implement .collect()", async function() {
let collector = new prometheus.Collector(false);
for await (let result of collector.collect()) {
assert.fail("Should not return any results");
}
});
});
describe("class Counter", function() {
let counter;
beforeEach(function() {
counter = new prometheus.Counter('test', "Help", { register: false });
});
describe("constructor", function() {
it("should be initialized to 0", function() {
assert.equal(counter._values.get(''), 0);
});
});
describe(".inc()", function() {
it("should increment the counter by 1 by default", function() {
counter.inc();
assert.equal(counter._values.get(''), 1);
});
it("should increment the counter by specified value", function() {
counter.inc(3.2);
assert.equal(counter._values.get(''), 3.2);
});
it("should throw if value is negative", function() {
assert.throws(
() => counter.inc(-1),
new Error("Expected value to be a positive number")
);
});
it("should throw if value is negative infinity", function() {
assert.throws(
() => counter.inc(-Infinity),
new Error("Expected value to be a positive number")
);
});
it("should throw if value is NaN", function() {
assert.throws(
() => counter.inc(NaN),
new Error("Expected value to be a positive number")
);
});
});
});
describe("class Gauge", function() {
let gauge;
beforeEach(function() {
gauge = new prometheus.Gauge('test', "Help", { register: false });
});
describe("constructor", function() {
it("should be initialized to 0", function() {
assert.equal(gauge._values.get(''), 0);
});
});
describe(".inc()", function() {
it("should increment the gauge by 1 by default", function() {
gauge.inc();
assert.equal(gauge._values.get(''), 1);
});
it("should increment the gauge by specified value", function() {
gauge.inc(3.2);
assert.equal(gauge._values.get(''), 3.2);
});
it("should decrement on negative value", function() {
gauge.inc(-2);
assert.equal(gauge._values.get(''), -2);
});
});
describe(".dec()", function() {
it("should decrement the gauge by 1 by default", function() {
gauge.dec();
assert.equal(gauge._values.get(''), -1);
});
it("should decrement the gauge by specified value", function() {
gauge.dec(3.2);
assert.equal(gauge._values.get(''), -3.2);
});
it("should increment on negative value", function() {
gauge.dec(-2);
assert.equal(gauge._values.get(''), 2);
});
});
describe(".set()", function() {
it("should set the gauge to the specified value", function() {
gauge.set(7);
assert.equal(gauge._values.get(''), 7);
});
});
describe(".setToCurrentTime()", function() {
it("should set the gauge to current unix time in seconds", function() {
gauge.setToCurrentTime();
assert(
Math.abs(gauge._values.get('') - Date.now() / 1000) < 1,
"value set is not close to the current unix time"
);
});
});
});
describe("class ValueCollector", function() {
describe("constructor", function() {
it("should throw on invalid metric name", function() {
for (let name of ["", " ", "0a", "$a"]) {
assert.throws(
() => new prometheus.Gauge(
name, "Help", { register: false }
),
new Error(`Invalid name '${name}'`)
)
}
});
it("should throw on invalid label names", function() {
for (let name of ["", " ", "0a", "$a", "a:"]) {
assert.throws(
() => new prometheus.Gauge(
'test', "Help", { register: false, labels: [name] }
),
new Error(`Invalid label '${name}'`)
)
}
});
it("should throw on missing name", function() {
assert.throws(
() => new prometheus.Gauge(),
new Error("Expected name to be a string")
);
});
it("should throw on missing help", function() {
assert.throws(
() => new prometheus.Gauge("test"),
new Error("Expected help to be a string")
);
});
it("should throw on unrecoginzed option", function() {
assert.throws(
() => new prometheus.Gauge("test", "help", { invalid: 1 }),
new Error("Unrecognized option 'invalid'")
);
});
});
describe(".get()", function() {
it("should return the current value", function() {
let gauge = new prometheus.Gauge('test', "Help", { register: false });
gauge.set(11);
assert.equal(gauge.get(), 11);
});
});
describe(".labels()", function() {
let gauge;
beforeEach(function() {
gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b'] }
);
});
it("should require all labels set at creation", function() {
assert.throws(
() => gauge.labels({a: "1"}),
new Error("Missing label 'b'")
);
assert.throws(
() => gauge.labels({b: "1"}),
new Error("Missing label 'a'")
);
assert.throws(
() => gauge.labels({a: '1', b: '2', c: '3'}),
new Error("Extra label 'c'")
);
assert.throws(
() => gauge.labels('1'),
new Error("Missing label 'b'")
);
assert.throws(
() => gauge.labels('1', '2', '3'),
new Error("Extra positional label")
);
});
it("should throw if label value is not a string", function() {
assert.throws(
() => gauge.labels({a: "1", b: 2}),
new Error("Expected value for label 'b' to be a string")
);
assert.throws(
() => gauge.labels("1", 2),
new Error("Expected value for label 'b' to be a string")
);
});
it("should initialize a value for the label set", function() {
gauge.labels({ a: '1', b: '2'});
assert(gauge._values.has('a="1",b="2"'), "value was not set");
gauge.labels('4', '5');
assert(gauge._values.has('a="4",b="5"'), "value was not set");
});
it("should return a child supporting the usual methods", function() {
let child = gauge.labels({ a: '1', b: '2'});
child.get();
child.inc();
child.dec();
child.setToCurrentTime();
child = gauge.labels('7', '8');
child.get();
child.inc();
child.dec();
child.setToCurrentTime();
});
it("should return the same child for the same labels", function() {
let child = gauge.labels('1', '2');
assert.equal(gauge.labels('1', '2'), child);
});
});
describe(".remove()", function() {
it("should remove a label set created with .labels()", function() {
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b'] }
);
gauge.labels({ a: '1', b: '2'});
gauge.remove({ a: '1', b: '2'});
assert(!gauge._values.has('a="1",b="2"'), "value was not removed");
});
it("should throw when removing the empty label set", function() {
let gauge = new prometheus.Gauge('test', "Help", { register: false });
assert.throws(
() => gauge.remove({}), new Error("labels cannot be empty")
);
});
});
describe(".removeAll()", function() {
it("should remove a label set created with .labels()", function() {
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b'] }
);
gauge.labels('1', '2');
gauge.labels('1', '3');
gauge.labels('4', '6');
gauge.removeAll({a: '1'});
assert(gauge._values.size <= 1, "Matching labels remained")
assert(
gauge._values.has('a="4",b="6"'),
"Non-matching label was removed"
);
});
it("should throw when removing the empty label set", function() {
let gauge = new prometheus.Gauge('test', "Help", { register: false });
assert.throws(
() => gauge.removeAll({}), new Error("labels cannot be empty")
);
});
it("should handle special characters", function() {
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b'] }
)
gauge.labels('x\\\\\n\"\"\n\\', '2');
gauge.removeAll({a: 'x\\\\\n\"\"\n\\'});
assert(gauge._values.size === 0, "Matching labels remained")
});
});
describe(".clear()", function() {
it("should remove all label sets created with .labels()", function() {
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b'] }
);
gauge.labels({ a: '1', b: '2'});
gauge.labels({ a: '3', b: '4'});
gauge.labels({ a: '5', b: '6'});
gauge.clear();
assert(gauge._values.size == 0, "values was not cleared");
});
it("should throw when used on an unlabled metric", function() {
let gauge = new prometheus.Gauge('test', "Help", { register: false });
assert.throws(
() => gauge.clear(), new Error("Cannot clear unlabeled metric")
);
});
});
});
describe("defaultCollectors", function() {
describe("processStartTimeSeconds", function() {
it("should give a close point in time", async function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
let results = []
let collector = prometheus.defaultCollectors.processStartTimeSeconds;
for await (let result of collector.collect()) {
results.push(result);
}
assert(
results.length == 1,
"collector did not give exactly one result"
);
assert(
Math.abs(results[0].samples.get('') - Date.now() / 1000) < 1,
"value set is not close to the current unix time"
);
});
});
describe("processCpuSecondsTotal", function() {
it("should give a low value", async function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
let results = []
let collector = prometheus.defaultCollectors.processCpuSecondsTotal;
for await (let result of collector.collect()) {
results.push(result);
}
assert(
results.length == 1,
"collector did not give exactly one result"
);
let value = results[0].samples.get('')
assert(
value < 5 && value > 0,
`process time is not between 0 and 5 seconds (${value})`
);
});
});
describe("processResidentMemoryBytes", function() {
it("should give a low value", async function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
let results = []
let collector = prometheus.defaultCollectors.processResidentMemoryBytes;
for await (let result of collector.collect()) {
results.push(result);
}
assert(
results.length == 1,
"collector did not give exactly one result"
);
let value = results[0].samples.get('')
assert(
value < 200e6 && value > 0,
`resident memory is not between 0 and 200 MB (${value})`
);
});
});
describe("processHeapBytes", function() {
it("should give a low value", async function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
let results = []
let collector = prometheus.defaultCollectors.processHeapBytes;
for await (let result of collector.collect()) {
results.push(result);
}
assert(
results.length == 1,
"collector did not give exactly one result"
);
let value = results[0].samples.get('')
assert(
value < 200e6 && value > 0,
`heap is not between 0 and 200 MB (${value})`
);
});
});
});
describe("exposition()", function() {
it("should format simple metrics", async function() {
let registry = new prometheus.CollectorRegistry();
let counter = new prometheus.Counter('a', "Help", { register: false });
registry.register(counter);
let gauge = new prometheus.Gauge('b', "Help", { register: false });
registry.register(gauge);
let exposition = await prometheus.exposition(registry.collect());
assert.equal(
exposition,
'# HELP a Help\n' +
'# TYPE a counter\n' +
'a 0\n' +
'\n' +
'# HELP b Help\n' +
'# TYPE b gauge\n' +
'b 0\n'
);
});
it("should format labeled metrics", async function() {
let registry = new prometheus.CollectorRegistry();
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['a', 'b']}
);
gauge.labels({a: '1', b: '2'});
gauge.labels({b: 'y', a: 'x'});
registry.register(gauge);
let exposition = await prometheus.exposition(registry.collect());
assert.equal(
exposition,
'# HELP test Help\n' +
'# TYPE test gauge\n' +
'test{a="1",b="2"} 0\n' +
'test{a="x",b="y"} 0\n'
);
});
it("should format Infinity and NaN", async function() {
let registry = new prometheus.CollectorRegistry();
let gauge = new prometheus.Gauge(
'test', "Help", { register: false, labels: ['n']}
);
gauge.labels({n: '1'}).set(Infinity);
gauge.labels({n: '2'}).set(-Infinity);
gauge.labels({n: '3'}).set(NaN);
registry.register(gauge);
let exposition = await prometheus.exposition(registry.collect());
assert.equal(
exposition,
'# HELP test Help\n' +
'# TYPE test gauge\n' +
'test{n="1"} +Inf\n' +
'test{n="2"} -Inf\n' +
'test{n="3"} NaN\n'
);
});
});
let serializedResult = {
metric: { type: 'type', name: 'name', help: "Help", labels: ['a', 'b'] },
samples: [ ['a="1",b="2"', 1], ['a="3",b="4"', 5], ]
};
let deserializedResult = {
metric: new prometheus.Metric('type', 'name', "Help", ['a', 'b']),
samples: new Map([['a="1",b="2"', 1], ['a="3",b="4"', 5]]),
};
describe("serializeResult()", function() {
it("should convert a result into plain object", function() {
assert.deepEqual(
prometheus.serializeResult(deserializedResult),
serializedResult
)
});
it("should support adding labels", function() {
assert.deepEqual(
prometheus.serializeResult({
metric: new prometheus.Metric('type', 'name', "Help"),
samples: new Map([['', 2]]),
}, { addLabels: { c: '8' }}),
{
metric: {type: 'type', name: 'name', help: "Help", labels: ['c']},
samples: [['c="8"', 2]],
}
);
assert.deepEqual(
prometheus.serializeResult({
metric: new prometheus.Metric('type', 'name', "Help", ['a']),
samples: new Map([['a="4"', 3]]),
}, { addLabels: { c: '8' }}),
{
metric: {type: 'type', name: 'name', help: "Help", labels: ['a', 'c']},
samples: [['a="4",c="8"', 3]],
}
);
});
it("should support redefining name and help", function() {
assert.deepEqual(
prometheus.serializeResult({
metric: new prometheus.Metric('type', 'name', "Help"),
samples: new Map([['', 2]]),
}, { metricName: 'new', }),
{
metric: {type: 'type', name: 'new', help: "Help", labels: []},
samples: [['', 2]],
}
);
assert.deepEqual(
prometheus.serializeResult({
metric: new prometheus.Metric('type', 'name', "Help"),
samples: new Map([['', 2]]),
}, { metricHelp: "old", }),
{
metric: {type: 'type', name: 'name', help: "old", labels: []},
samples: [['', 2]],
}
);
});
it("should throw on unrecoginzed option", function() {
assert.throws(
() => new prometheus.serializeResult({
metric: new prometheus.Metric('type', 'name', "Help"),
samples: new Map([['', 3]]),
}, { invalid: true }),
new Error("Unrecognized option 'invalid'")
);
});
});
describe("deserializeResult()", function() {
it("should convert a plain object into a result", function() {
assert.deepEqual(
prometheus.deserializeResult(serializedResult),
deserializedResult
)
});
});
describe("post test checks", function() {
it("should not have any collectors in the default registry", function() {
this.skip(); // XXX Doesn't work in Clusterio's shared testing env
assert(
prometheus.defaultRegistry.collectors.length == 0,
"Collectors left over by test code"
);
});
});
});