128 lines
3.5 KiB
JavaScript
128 lines
3.5 KiB
JavaScript
import test from 'brittle'
|
|
import {
|
|
pickContainerDisplayName,
|
|
resolveContainerLabel,
|
|
extractContainerIdFromCgroupName,
|
|
metricCardSubtitle,
|
|
chartOptionLabel,
|
|
isHexId,
|
|
} from '../shared/container-names.js'
|
|
import {
|
|
mapDockerContainerNames,
|
|
parseHttpBody,
|
|
parseDockerPsNames,
|
|
} from '../server/services/collectors/docker.js'
|
|
import { makeDockerCpuChart, makeDockerMemChart } from '../shared/metrics.js'
|
|
|
|
test('pickContainerDisplayName prefers compose service', (t) => {
|
|
t.is(
|
|
pickContainerDisplayName({
|
|
Id: 'a'.repeat(64),
|
|
Names: ['/myproj_web_1'],
|
|
Labels: {
|
|
'com.docker.compose.project': 'myproj',
|
|
'com.docker.compose.service': 'web',
|
|
'com.docker.compose.container-number': '1',
|
|
},
|
|
}),
|
|
'web'
|
|
)
|
|
t.is(
|
|
pickContainerDisplayName({
|
|
Id: 'b'.repeat(64),
|
|
Names: ['/myproj_web_2'],
|
|
Labels: {
|
|
'com.docker.compose.service': 'web',
|
|
'com.docker.compose.container-number': '2',
|
|
},
|
|
}),
|
|
'web · 2'
|
|
)
|
|
})
|
|
|
|
test('pickContainerDisplayName falls back to Names then image', (t) => {
|
|
t.is(
|
|
pickContainerDisplayName({
|
|
Id: 'c'.repeat(64),
|
|
Names: ['/friendly-name'],
|
|
Labels: {},
|
|
}),
|
|
'friendly-name'
|
|
)
|
|
t.is(
|
|
pickContainerDisplayName({
|
|
Id: 'd'.repeat(64),
|
|
Names: [],
|
|
Image: 'ghcr.io/org/redis:7',
|
|
}),
|
|
'redis'
|
|
)
|
|
})
|
|
|
|
test('mapDockerContainerNames indexes full and short ids', (t) => {
|
|
const id = 'abcdef0123456789' + '0'.repeat(48)
|
|
const map = mapDockerContainerNames([
|
|
{
|
|
Id: id,
|
|
Names: ['/nginx'],
|
|
Labels: {},
|
|
},
|
|
])
|
|
t.is(map.get(id), 'nginx')
|
|
t.is(map.get(id.slice(0, 12)), 'nginx')
|
|
})
|
|
|
|
test('parseHttpBody handles Content-Length and chunked Docker replies', (t) => {
|
|
const json = '[{"Id":"aa","Names":["/x"]}]'
|
|
const plain =
|
|
`HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ${json.length}\r\n\r\n` +
|
|
json
|
|
t.is(parseHttpBody(plain), json)
|
|
|
|
const chunked =
|
|
'HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n' +
|
|
Number(json.length).toString(16) +
|
|
'\r\n' +
|
|
json +
|
|
'\r\n0\r\n\r\n'
|
|
t.is(parseHttpBody(chunked), json)
|
|
})
|
|
|
|
test('parseDockerPsNames maps id to Names', (t) => {
|
|
const id = '45b3a7631c68aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
|
const map = parseDockerPsNames(`${id}\tdozzle\n`)
|
|
t.is(map.get(id.slice(0, 12)), 'dozzle')
|
|
t.is(map.get('dozzle'), 'dozzle')
|
|
})
|
|
|
|
test('resolveContainerLabel humanizes cgroup docker scopes', (t) => {
|
|
const id = 'a1b2c3d4e5f6789012345678abcdef01'
|
|
const map = new Map([
|
|
[id, 'api'],
|
|
[id.slice(0, 12), 'api'],
|
|
])
|
|
t.is(resolveContainerLabel(`docker-${id}.scope`, map), 'api')
|
|
t.is(resolveContainerLabel(id, map), 'api')
|
|
t.is(resolveContainerLabel(`docker-${id}`, new Map()), `container ${id.slice(0, 12)}`)
|
|
t.ok(extractContainerIdFromCgroupName(`docker-${id}.scope`)?.startsWith('a1b2'))
|
|
})
|
|
|
|
test('docker chart titles use display names', (t) => {
|
|
const cpu = makeDockerCpuChart('abc123def456', 'nginx')
|
|
t.is(cpu.title, 'nginx · CPU')
|
|
t.is(cpu.family, 'nginx')
|
|
const mem = makeDockerMemChart('abc123def456', 'nginx')
|
|
t.is(mem.title, 'nginx · memory')
|
|
})
|
|
|
|
test('metricCardSubtitle hides hashes for container charts', (t) => {
|
|
const id = 'docker.cpu.abc123def456'
|
|
t.is(
|
|
metricCardSubtitle(id, { title: 'nginx · CPU', family: 'nginx', units: 'percentage' }),
|
|
'percentage'
|
|
)
|
|
t.absent(metricCardSubtitle(id, { units: 'percentage' }).includes('abc123'))
|
|
t.ok(isHexId('abc123def456'))
|
|
t.is(chartOptionLabel(id, { title: 'nginx · CPU' }), 'nginx · CPU')
|
|
})
|