mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
613 lines
18 KiB
JavaScript
613 lines
18 KiB
JavaScript
"use strict";
|
|
const assert = require("assert").strict;
|
|
|
|
const prometheus = require("@clusterio/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"
|
|
);
|
|
});
|
|
});
|
|
});
|