Skip to content

Commit

Permalink
adding container detector (#933)
Browse files Browse the repository at this point in the history
detect container id from cgroup or mountinfo
  • Loading branch information
brettmc authored Mar 2, 2023
1 parent 746d25a commit 88ea7c3
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/SDK/Common/Configuration/KnownValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ interface KnownValues
public const VALUE_DETECTORS_PROCESS_RUNTIME = 'process_runtime';
public const VALUE_DETECTORS_SDK = 'sdk';
public const VALUE_DETECTORS_SDK_PROVIDED = 'sdk_provided';
public const VALUE_DETECTORS_CONTAINER = 'container';
public const OTEL_PHP_DETECTORS = [
self::VALUE_ALL,
self::VALUE_DETECTORS_ENVIRONMENT,
Expand All @@ -174,6 +175,7 @@ interface KnownValues
self::VALUE_DETECTORS_PROCESS_RUNTIME,
self::VALUE_DETECTORS_SDK,
self::VALUE_DETECTORS_SDK_PROVIDED,
self::VALUE_DETECTORS_CONTAINER,
self::VALUE_NONE,
];
}
80 changes: 80 additions & 0 deletions src/SDK/Resource/Detectors/Container.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Resource\Detectors;

use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SemConv\ResourceAttributes;

/**
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.18.0/specification/resource/semantic_conventions/container.md
*/
final class Container implements ResourceDetectorInterface
{
private string $dir;
private const CONTAINER_ID_LENGTH = 64;
private const CGROUP_V1 = 'cgroup';
private const CGROUP_V2 = 'mountinfo';
private const HOSTNAME = 'hostname';

public function __construct(string $dir = '/proc/self')
{
$this->dir = $dir;
}

public function getResource(): ResourceInfo
{
$attributes = [];
$id = $this->getContainerId();
if ($id) {
$attributes[ResourceAttributes::CONTAINER_ID] = $id;
}

return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL);
}

private function getContainerId(): ?string
{
return $this->getContainerIdV2() ?? $this->getContainerIdV1();
}

private function getContainerIdV1(): ?string
{
$data = file_get_contents(sprintf('%s/%s', $this->dir, self::CGROUP_V1));
if (!$data) {
return null;
}
$lines = explode('\n', $data);
foreach ($lines as $line) {
if (strlen($line) >= self::CONTAINER_ID_LENGTH) {
//if string is longer than CONTAINER_ID_LENGTH, return the last CONTAINER_ID_LENGTH chars
return substr($line, strlen($line) - self::CONTAINER_ID_LENGTH);
}
}

return null;
}
private function getContainerIdV2(): ?string
{
$data = file_get_contents(sprintf('%s/%s', $this->dir, self::CGROUP_V2));
if (!$data) {
return null;
}
$lines = explode(PHP_EOL, $data);
foreach ($lines as $line) {
if (strpos($line, self::HOSTNAME) !== false) {
$parts = explode('/', $line);
foreach ($parts as $part) {
if (strlen($part) === self::CONTAINER_ID_LENGTH) {
return $part;
}
}
}
}

return null;
}
}
5 changes: 5 additions & 0 deletions src/SDK/Resource/ResourceInfoFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static function defaultResource(): ResourceInfo
new Detectors\ProcessRuntime(),
new Detectors\Sdk(),
new Detectors\SdkProvided(),
new Detectors\Container(),
]))->getResource();
}

Expand Down Expand Up @@ -78,6 +79,10 @@ public static function defaultResource(): ResourceInfo
case Values::VALUE_DETECTORS_SDK_PROVIDED:
$resourceDetectors[] = new Detectors\SdkProvided();

break;
case Values::VALUE_DETECTORS_CONTAINER:
$resourceDetectors[] = new Detectors\Container();

break;
default:
}
Expand Down
76 changes: 76 additions & 0 deletions tests/Unit/SDK/Resource/Detectors/ContainerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Example\Unit\SDK\Resource\Detectors;

use OpenTelemetry\SDK\Resource\Detectors\Container;
use OpenTelemetry\SemConv\ResourceAttributes;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamFile;
use PHPUnit\Framework\TestCase;

/**
* @covers OpenTelemetry\SDK\Resource\Detectors\Container
*/
class ContainerTest extends TestCase
{
private vfsStreamFile $cgroup;
private vfsStreamFile $mountinfo;
private Container $detector;

public function setUp(): void
{
$root = vfsStream::setup();
$this->cgroup = vfsStream::newFile('cgroup')->at($root);
$this->mountinfo = vfsStream::newFile('mountinfo')->at($root);
$this->detector = new Container($root->url());
}

public function test_valid_v1(): void
{
$valid = 'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d';
$this->cgroup->setContent($valid);
$resource = $this->detector->getResource();

$this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
$this->assertIsString($resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
$this->assertSame($valid, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
}

public function test_invalid_v1(): void
{
$this->cgroup->setContent('0::/');
$resource = $this->detector->getResource();

$this->assertEmpty($resource->getAttributes());
}

public function test_valid_v2(): void
{
$expected = 'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d';
$data = <<< EOS
1366 1365 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
1408 1362 0:107 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
1579 1362 0:112 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64
1581 1359 259:2 /var/lib/docker/containers/a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d/hostname /etc/hostname rw,relatime - ext4 /dev/nvme0n1p2 rw,errors=remount-ro
1583 1359 259:3 /brett/docker/otel/opentelemetry-php /usr/src/myapp rw,relatime - ext4 /dev/nvme0n1p3 rw
EOS;
$this->mountinfo->withContent($data);
$resource = $this->detector->getResource();

$this->assertCount(1, $resource->getAttributes());
$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
}

public function test_invalid_v2(): void
{
$data = <<< EOS
1581 1359 259:2 /var/lib/docker/containers/a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d/wrongkeyword
EOS;
$this->mountinfo->withContent($data);
$resource = $this->detector->getResource();

$this->assertEmpty($resource->getAttributes());
}
}

0 comments on commit 88ea7c3

Please sign in to comment.