Skip to content

Commit

Permalink
feat: Added support for parsing container ids from docker versions us…
Browse files Browse the repository at this point in the history
…ing cgroups v2. (#1830)
  • Loading branch information
bizob2828 committed Oct 24, 2023
1 parent 81f9450 commit 9892901
Show file tree
Hide file tree
Showing 20 changed files with 294 additions and 90 deletions.
63 changes: 59 additions & 4 deletions lib/utilization/docker-info.js
Expand Up @@ -10,6 +10,8 @@ const common = require('./common')
const NAMES = require('../metrics/names')
const os = require('os')
let vendorInfo = null
const CGROUPS_V1_PATH = '/proc/self/cgroup'
const CGROUPS_V2_PATH = '/proc/self/mountinfo'

module.exports.getVendorInfo = fetchDockerVendorInfo
module.exports.clearVendorCache = function clearDockerVendorCache() {
Expand Down Expand Up @@ -51,6 +53,12 @@ module.exports.getBootId = function getBootId(agent, callback) {
}
}

/**
* Attempt to extract container id from either cgroups v1 or v2 file
*
* @param {object} agent NR instance
* @param {Function} callback function to call when done
*/
function fetchDockerVendorInfo(agent, callback) {
if (!agent.config.utilization || !agent.config.utilization.detect_docker) {
return callback(null, null)
Expand All @@ -62,16 +70,56 @@ function fetchDockerVendorInfo(agent, callback) {

if (!os.platform().match(/linux/i)) {
logger.debug('Platform is not a flavor of linux, omitting docker info')
return callback(null)
return callback(null, null)
}

common.readProc('/proc/self/cgroup', function getCGroup(err, data) {
// try v2 path first and if null try parsing v1 path
common.readProc(CGROUPS_V2_PATH, function getV2CGroup(err, data) {
if (data === null) {
logger.debug(
`${CGROUPS_V2_PATH} not found, trying to parse container id from ${CGROUPS_V1_PATH}`
)
findCGroupsV1(callback)
return
}

parseCGroupsV2(data, callback)
})
}

/**
* Try extracting container id from a /proc/self/mountinfo
* e.g. - `528 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/resolv.conf`
*
* @param {string} data file contents
* @param {Function} callback function to call when done
*/
function parseCGroupsV2(data, callback) {
const containerLine = new RegExp('/docker/containers/([0-9a-f]{64})/')
const line = containerLine.exec(data)
if (line) {
callback(null, { id: line[1] })
} else {
logger.debug(`Found ${CGROUPS_V2_PATH} but failed to parse Docker container id.`)
callback(null, null)
}
}

/**
* Read /proc/self/cgroup and try to extract the container id from a cpu line
* e.g. - `4:cpu:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee`
*
* @param {Function} callback function to call when done
*/
function findCGroupsV1(callback) {
common.readProc(CGROUPS_V1_PATH, function getCGroup(err, data) {
if (!data) {
logger.debug(`${CGROUPS_V1_PATH} not found, exiting parsing containerId.`)
return callback(null)
}

let id = null
findCGroups(data, 'cpu', function forEachCpuGroup(cpuGroup) {
parseCGroupsV1(data, 'cpu', function forEachCpuGroup(cpuGroup) {
const match = /(?:^|[^0-9a-f])([0-9a-f]{64})(?:[^0-9a-f]|$)/.exec(cpuGroup)
if (match) {
id = match[1]
Expand All @@ -91,7 +139,14 @@ function fetchDockerVendorInfo(agent, callback) {
})
}

function findCGroups(info, cgroup, eachCb) {
/**
* Iterate line by line to extract the container id from the cpu stanza
*
* @param {string} info contents of file
* @param {string} cgroup value is cpu
* @param {Function} eachCb funtion to test if the container id exists
*/
function parseCGroupsV1(info, cgroup, eachCb) {
const target = new RegExp('^\\d+:[^:]*?\\b' + cgroup + '\\b[^:]*:')
const lines = info.split('\n')
for (let i = 0; i < lines.length; ++i) {
Expand Down
86 changes: 0 additions & 86 deletions test/integration/pricing/docker-info.tap.js

This file was deleted.

File renamed without changes.
File renamed without changes.
89 changes: 89 additions & 0 deletions test/integration/utilization/docker-info.tap.js
@@ -0,0 +1,89 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const test = require('tap').test
const fs = require('fs/promises')
const common = require('../../../lib/utilization/common')
const dockerInfo = require('../../../lib/utilization/docker-info')
const helper = require('../../lib/agent_helper')
const path = require('path')
const sinon = require('sinon')

const TEST_DIRECTORY = path.resolve(__dirname, '../../lib/cross_agent_tests/docker_container_id/')
const TEST_DIRECTORY_V2 = path.resolve(
__dirname,
'../../lib/cross_agent_tests/docker_container_id_v2/'
)

const tests = [
{ name: 'v1', testsDir: TEST_DIRECTORY },
{ name: 'v2', testsDir: TEST_DIRECTORY_V2 }
]

tests.forEach(({ name, testsDir }) => {
test(`pricing docker info ${name}`, async function (t) {
const os = require('os')
t.teardown(function () {
os.platform.restore()
})

sinon.stub(os, 'platform')
os.platform.returns('linux')
const data = await fs.readFile(`${testsDir}/cases.json`)
const cases = JSON.parse(data)

cases.forEach((testCase) => {
const testFile = path.join(testsDir, testCase.filename)
t.test(testCase.filename, makeTest(testCase, testFile, name === 'v2'))
})
t.end()
})
})

function makeTest(testCase, testFile, v2) {
return async function (t) {
const agent = helper.loadMockedAgent()
sinon.stub(common, 'readProc')
const file = await fs.readFile(testFile, { encoding: 'utf8' })
mockProcRead(file, v2)

t.teardown(function () {
helper.unloadAgent(agent)
dockerInfo.clearVendorCache()
common.readProc.restore()
})

await new Promise((resolve) => {
dockerInfo.getVendorInfo(agent, function (err, info) {
if (testCase.containerId) {
t.error(err, 'should not have failed')
t.same(info, { id: testCase.containerId }, 'should have expected container id')
} else {
t.notOk(info, 'should not have found container id')
}

if (testCase.expectedMetrics) {
// TODO: No tests currently expect metrics, when one does we'll have to
// update this test depending on the format of that.
t.bailout('Docker expected metrics found but can not be handled.')
} else {
t.equal(agent.metrics._metrics.toJSON().length, 0, 'should have no metrics')
}
resolve()
})
})
}
}

function mockProcRead(data, v2) {
if (!v2) {
common.readProc.onCall(0).yields(null, null)
common.readProc.onCall(1).yields(null, data)
} else {
common.readProc.onCall(0).yields(null, data)
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions test/lib/cross_agent_tests/docker_container_id_v2/README.md
@@ -0,0 +1,6 @@
These tests cover parsing of Docker container IDs on Linux hosts out of
`/proc/self/mountinfo` (or `/proc/<pid>/mountinfo` more generally).

The `cases.json` file lists each filename in this directory containing
example `/proc/self/mountinfo` content, and the expected Docker container ID that
should be parsed from that file.
32 changes: 32 additions & 0 deletions test/lib/cross_agent_tests/docker_container_id_v2/cases.json
@@ -0,0 +1,32 @@
[
{
"filename": "docker-20.10.16.txt",
"containerId": "84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce",
"expectedMetrics": null
},
{
"filename": "docker-24.0.2.txt",
"containerId": "b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65",
"expectedMetrics": null
},
{
"filename": "empty.txt",
"containerId": null,
"expectedMetrics": null
},
{
"filename": "invalid-characters.txt",
"containerId": null,
"expectedMetrics": null
},
{
"filename": "docker-too-long.txt",
"containerId": null,
"expectedMetrics": null
},
{
"filename": "invalid-length.txt",
"containerId": null,
"expectedMetrics": null
}
]
@@ -0,0 +1,24 @@
519 413 0:152 / / rw,relatime master:180 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/YCID3333O5VYPYDNTQRZX4GI67:/var/lib/docker/overlay2/l/G7H4TULAFM2UBFRL7QFQPUNXY5:/var/lib/docker/overlay2/l/RLC4GCL75VGXXXYJJO57STHIYN:/var/lib/docker/overlay2/l/YOZKNWFAP6YX74XEKPHX4KG4UN:/var/lib/docker/overlay2/l/46EQ6YX5PQQZ4Z3WCSMQ6Z4YWI:/var/lib/docker/overlay2/l/KGKX3Z5ZMOCDWOFKBS2FSHMQMQ:/var/lib/docker/overlay2/l/CKFYAF4TXZD4RCE6RG6UNL5WVI,upperdir=/var/lib/docker/overlay2/358c429f7b04ee5a228b94efaebe3413a98fcc676b726f078fe875727e3bddd2/diff,workdir=/var/lib/docker/overlay2/358c429f7b04ee5a228b94efaebe3413a98fcc676b726f078fe875727e3bddd2/work
520 519 0:155 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
521 519 0:156 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
522 521 0:157 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
523 519 0:158 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro
524 523 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
525 521 0:154 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
526 521 0:159 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
527 519 254:1 /docker/volumes/3237dea4f8022f1addd7b6f072a9c847eb3e5b8df0d599f462ba7040884d4618/_data /data rw,relatime master:28 - ext4 /dev/vda1 rw
528 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw
529 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw
530 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw
414 521 0:157 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
415 520 0:155 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
416 520 0:155 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
417 520 0:155 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
418 520 0:155 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
419 520 0:155 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
420 520 0:160 / /proc/acpi ro,relatime - tmpfs tmpfs ro
421 520 0:156 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
422 520 0:156 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
423 520 0:156 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
424 520 0:156 /null /proc/sched_debug rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
425 523 0:161 / /sys/firmware ro,relatime - tmpfs tmpfs ro
@@ -0,0 +1,21 @@
1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro
1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
1025 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard
1026 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard
1027 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard
717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro
727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro
@@ -0,0 +1,21 @@
1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro
1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
1025 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard
1026 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard
1027 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard
717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro
727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755
730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro
Empty file.

0 comments on commit 9892901

Please sign in to comment.