mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
5638f06b3a
[metrics] collect HTTP socket stats for all the services GitOrigin-RevId: 52872f4ca46d8cf351f7f4b4df90f9d6b05010b3
333 lines
9.4 KiB
JavaScript
333 lines
9.4 KiB
JavaScript
const { promisify } = require('util')
|
|
const os = require('os')
|
|
const http = require('http')
|
|
const { expect } = require('chai')
|
|
const Metrics = require('../..')
|
|
|
|
const HOSTNAME = os.hostname()
|
|
const APP_NAME = 'test-app'
|
|
const sleep = promisify(setTimeout)
|
|
|
|
describe('Metrics module', function () {
|
|
before(function () {
|
|
Metrics.initialize(APP_NAME)
|
|
})
|
|
|
|
describe('at startup', function () {
|
|
it('increments the process_startup counter', async function () {
|
|
await expectMetricValue('process_startup', 1)
|
|
})
|
|
|
|
it('collects default metrics', async function () {
|
|
const metric = await getMetric('process_cpu_user_seconds_total')
|
|
expect(metric).to.exist
|
|
})
|
|
})
|
|
|
|
describe('inc()', function () {
|
|
it('increments counts by 1', async function () {
|
|
Metrics.inc('duck_count')
|
|
await expectMetricValue('duck_count', 1)
|
|
Metrics.inc('duck_count')
|
|
Metrics.inc('duck_count')
|
|
await expectMetricValue('duck_count', 3)
|
|
})
|
|
|
|
it('escapes special characters in the key', async function () {
|
|
Metrics.inc('show.me the $!!')
|
|
await expectMetricValue('show_me_the____', 1)
|
|
})
|
|
})
|
|
|
|
describe('count()', function () {
|
|
it('increments counts by the given count', async function () {
|
|
Metrics.count('rabbit_count', 5)
|
|
await expectMetricValue('rabbit_count', 5)
|
|
Metrics.count('rabbit_count', 6)
|
|
Metrics.count('rabbit_count', 7)
|
|
await expectMetricValue('rabbit_count', 18)
|
|
})
|
|
})
|
|
|
|
describe('summary()', function () {
|
|
it('collects observations', async function () {
|
|
Metrics.summary('oven_temp', 200)
|
|
Metrics.summary('oven_temp', 300)
|
|
Metrics.summary('oven_temp', 450)
|
|
const sum = await getSummarySum('oven_temp')
|
|
expect(sum).to.equal(950)
|
|
})
|
|
})
|
|
|
|
describe('timing()', function () {
|
|
it('collects timings', async function () {
|
|
Metrics.timing('sprint_100m', 10)
|
|
Metrics.timing('sprint_100m', 20)
|
|
Metrics.timing('sprint_100m', 30)
|
|
const sum = await getSummarySum('timer_sprint_100m')
|
|
expect(sum).to.equal(60)
|
|
})
|
|
})
|
|
|
|
describe('histogram()', function () {
|
|
it('collects in buckets', async function () {
|
|
const buckets = [10, 100, 1000]
|
|
Metrics.histogram('distance', 10, buckets)
|
|
Metrics.histogram('distance', 20, buckets)
|
|
Metrics.histogram('distance', 100, buckets)
|
|
Metrics.histogram('distance', 200, buckets)
|
|
Metrics.histogram('distance', 1000, buckets)
|
|
Metrics.histogram('distance', 2000, buckets)
|
|
const sum = await getSummarySum('histogram_distance')
|
|
expect(sum).to.equal(3330)
|
|
await checkHistogramValues('histogram_distance', {
|
|
10: 1,
|
|
100: 3,
|
|
1000: 5,
|
|
'+Inf': 6,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Timer', function () {
|
|
beforeEach('collect timings', async function () {
|
|
const buckets = [10, 100, 1000]
|
|
for (const duration of [1, 1, 1, 15, 15, 15, 105, 105, 105]) {
|
|
const withBuckets = new Metrics.Timer('height', 1, {}, buckets)
|
|
const withOutBuckets = new Metrics.Timer('depth', 1, {})
|
|
await sleep(duration)
|
|
withBuckets.done()
|
|
withOutBuckets.done()
|
|
}
|
|
})
|
|
|
|
it('with buckets', async function () {
|
|
await checkHistogramValues('histogram_height', {
|
|
10: 3,
|
|
100: 6,
|
|
1000: 9,
|
|
'+Inf': 9,
|
|
})
|
|
})
|
|
|
|
it('without buckets', async function () {
|
|
await checkSummaryValues('timer_depth', {
|
|
0.01: 1,
|
|
0.05: 1,
|
|
0.5: 15,
|
|
0.9: 105,
|
|
0.95: 105,
|
|
0.99: 105,
|
|
0.999: 105,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('gauge()', function () {
|
|
it('records values', async function () {
|
|
Metrics.gauge('water_level', 1.5)
|
|
await expectMetricValue('water_level', 1.5)
|
|
Metrics.gauge('water_level', 4.2)
|
|
await expectMetricValue('water_level', 4.2)
|
|
})
|
|
})
|
|
|
|
describe('globalGauge()', function () {
|
|
it('records values without a host label', async function () {
|
|
Metrics.globalGauge('tire_pressure', 99.99)
|
|
const { value, labels } = await getMetricValue('tire_pressure')
|
|
expect(value).to.equal(99.99)
|
|
expect(labels.host).to.equal('global')
|
|
expect(labels.app).to.equal(APP_NAME)
|
|
})
|
|
})
|
|
|
|
describe('open_sockets', function () {
|
|
const keyServer1 = 'open_connections_http_127_42_42_1'
|
|
const keyServer2 = 'open_connections_http_127_42_42_2'
|
|
|
|
let finish1, finish2, emitResponse1, emitResponse2
|
|
function resetEmitResponse1() {
|
|
emitResponse1 = new Promise(resolve => (finish1 = resolve))
|
|
}
|
|
resetEmitResponse1()
|
|
function resetEmitResponse2() {
|
|
emitResponse2 = new Promise(resolve => (finish2 = resolve))
|
|
}
|
|
resetEmitResponse2()
|
|
|
|
let server1, server2
|
|
before(function setupServer1(done) {
|
|
server1 = http.createServer((req, res) => {
|
|
res.write('...')
|
|
emitResponse1.then(() => res.end())
|
|
})
|
|
server1.listen(0, '127.42.42.1', done)
|
|
})
|
|
before(function setupServer2(done) {
|
|
server2 = http.createServer((req, res) => {
|
|
res.write('...')
|
|
emitResponse2.then(() => res.end())
|
|
})
|
|
server2.listen(0, '127.42.42.2', done)
|
|
})
|
|
after(function cleanupPendingRequests() {
|
|
finish1()
|
|
finish2()
|
|
})
|
|
after(function shutdownServer1(done) {
|
|
if (server1) server1.close(done)
|
|
})
|
|
after(function shutdownServer2(done) {
|
|
if (server2) server2.close(done)
|
|
})
|
|
|
|
let urlServer1, urlServer2
|
|
before(function setUrls() {
|
|
urlServer1 = `http://127.42.42.1:${server1.address().port}/`
|
|
urlServer2 = `http://127.42.42.2:${server2.address().port}/`
|
|
})
|
|
describe('gaugeOpenSockets()', function () {
|
|
beforeEach(function runGaugeOpenSockets() {
|
|
Metrics.open_sockets.gaugeOpenSockets(true)
|
|
})
|
|
|
|
describe('without pending connections', function () {
|
|
it('emits no open_connections', async function () {
|
|
await expectNoMetricValue(keyServer1)
|
|
await expectNoMetricValue(keyServer2)
|
|
})
|
|
})
|
|
|
|
describe('with pending connections for server1', function () {
|
|
before(function (done) {
|
|
http.get(urlServer1)
|
|
http.get(urlServer1)
|
|
setTimeout(done, 10)
|
|
})
|
|
|
|
it('emits 2 open_connections for server1', async function () {
|
|
await expectMetricValue(keyServer1, 2)
|
|
})
|
|
|
|
it('emits no open_connections for server2', async function () {
|
|
await expectNoMetricValue(keyServer2)
|
|
})
|
|
})
|
|
|
|
describe('with pending connections for server1 and server2', function () {
|
|
before(function (done) {
|
|
http.get(urlServer2)
|
|
http.get(urlServer2)
|
|
setTimeout(done, 10)
|
|
})
|
|
|
|
it('emits 2 open_connections for server1', async function () {
|
|
await expectMetricValue(keyServer1, 2)
|
|
})
|
|
|
|
it('emits 2 open_connections for server2', async function () {
|
|
await expectMetricValue(keyServer2, 2)
|
|
})
|
|
})
|
|
|
|
describe('when requests finish for server1', function () {
|
|
before(function (done) {
|
|
finish1()
|
|
resetEmitResponse1()
|
|
http.get(urlServer1)
|
|
|
|
setTimeout(done, 10)
|
|
})
|
|
|
|
it('emits 1 open_connections for server1', async function () {
|
|
await expectMetricValue(keyServer1, 1)
|
|
})
|
|
|
|
it('emits 2 open_connections for server2', async function () {
|
|
await expectMetricValue(keyServer2, 2)
|
|
})
|
|
})
|
|
|
|
describe('when all requests complete', function () {
|
|
before(function (done) {
|
|
finish1()
|
|
finish2()
|
|
|
|
setTimeout(done, 10)
|
|
})
|
|
|
|
it('emits no open_connections', async function () {
|
|
await expectNoMetricValue(keyServer1)
|
|
await expectNoMetricValue(keyServer2)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
function getMetric(key) {
|
|
return Metrics.register.getSingleMetric(key)
|
|
}
|
|
|
|
async function getSummarySum(key) {
|
|
const metric = getMetric(key)
|
|
const item = await metric.get()
|
|
for (const value of item.values) {
|
|
if (value.metricName === `${key}_sum`) {
|
|
return value.value
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function checkHistogramValues(key, values) {
|
|
const metric = getMetric(key)
|
|
const item = await metric.get()
|
|
const found = {}
|
|
for (const value of item.values) {
|
|
const bucket = value.labels.le
|
|
if (!bucket) continue
|
|
found[bucket] = value.value
|
|
}
|
|
expect(found).to.deep.equal(values)
|
|
return null
|
|
}
|
|
|
|
async function checkSummaryValues(key, values) {
|
|
const metric = getMetric(key)
|
|
const item = await metric.get()
|
|
const found = {}
|
|
for (const value of item.values) {
|
|
const quantile = value.labels.quantile
|
|
if (!quantile) continue
|
|
found[quantile] = value.value
|
|
}
|
|
for (const quantile of Object.keys(values)) {
|
|
expect(found[quantile]).to.be.within(
|
|
values[quantile] - 5,
|
|
values[quantile] + 5,
|
|
`quantile: ${quantile}`
|
|
)
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function getMetricValue(key) {
|
|
const metrics = await Metrics.register.getMetricsAsJSON()
|
|
const metric = metrics.find(m => m.name === key)
|
|
return metric.values[0]
|
|
}
|
|
|
|
async function expectMetricValue(key, expectedValue) {
|
|
const value = await getMetricValue(key)
|
|
expect(value.value).to.equal(expectedValue)
|
|
expect(value.labels.host).to.equal(HOSTNAME)
|
|
expect(value.labels.app).to.equal(APP_NAME)
|
|
}
|
|
|
|
async function expectNoMetricValue(key) {
|
|
const metric = getMetric(key)
|
|
if (!metric) return
|
|
await expectMetricValue(key, 0)
|
|
}
|