Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Parse and send container id with payloads to the agent #1007

Merged
merged 10 commits into from
Aug 7, 2019
8 changes: 8 additions & 0 deletions ddtrace/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .encoding import get_encoder, JSONEncoder
from .compat import httplib, PYTHON_VERSION, PYTHON_INTERPRETER, get_connection_response
from .internal.logger import get_logger
from .internal.runtime import container
from .payload import Payload, PayloadFull
from .utils.deprecation import deprecated

Expand Down Expand Up @@ -151,6 +152,13 @@ def __init__(self, hostname, port, uds_path=None, headers=None, encoder=None, pr
'Datadog-Meta-Tracer-Version': ddtrace.__version__,
})

# Add container information if we have it
self._container_info = container.get_container_info()
if self._container_info and self._container_info.container_id:
self._headers.update({
'Datadog-Container-Id': self._container_info.container_id,
})

def __str__(self):
if self.uds_path:
return self.uds_path
Expand Down
110 changes: 110 additions & 0 deletions ddtrace/internal/runtime/container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import re

from ..logger import get_logger

log = get_logger(__name__)


class CGroupInfo(object):
"""
CGroup class for container information parsed from a group cgroup file
"""
__slots__ = ('id', 'groups', 'path', 'container_id', 'controllers', 'pod_id')

UUID_SOURCE_PATTERN = r'[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}'
CONTAINER_SOURCE_PATTERN = r'[0-9a-f]{64}'

LINE_RE = re.compile(r'^(\d+):([^:]*):(.+)$')
POD_RE = re.compile(r'pod({0})(?:\.slice)?$'.format(UUID_SOURCE_PATTERN))
CONTAINER_RE = re.compile(r'({0}|{1})(?:\.scope)?$'.format(UUID_SOURCE_PATTERN, CONTAINER_SOURCE_PATTERN))

def __init__(self, **kwargs):
# Initialize all attributes in __slots__ to `None`
# DEV: Otherwise we'll get `AttributeError` when trying to access if they are unset
for attr in self.__slots__:
setattr(self, attr, kwargs.get(attr))

@classmethod
def from_line(cls, line):
"""
Parse a new :class:`CGroupInfo` from the provided line

:param line: A line from a cgroup file (e.g. /proc/self/cgroup) to parse information from
:type line: str
:returns: A :class:`CGroupInfo` object with all parsed data, if the line is valid, otherwise `None`
:rtype: :class:`CGroupInfo` | None

"""
# Clean up the line
line = line.strip()

# Ensure the line is valid
match = cls.LINE_RE.match(line)
if not match:
return None

# Create our new `CGroupInfo` and set attributes from the line
info = cls()
info.id, info.groups, info.path = match.groups()

# Parse the controllers from the groups
info.controllers = [c.strip() for c in info.groups.split(',') if c.strip()]

# Break up the path to grab container_id and pod_id if available
# e.g. /docker/<container_id>
# e.g. /kubepods/test/pod<pod_id>/<container_id>
parts = [p for p in info.path.split('/')]

# Grab the container id from the path if a valid id is present
if len(parts):
match = cls.CONTAINER_RE.match(parts.pop())
if match:
info.container_id = match.group(1)

# Grab the pod id from the path if a valid id is present
if len(parts):
match = cls.POD_RE.match(parts.pop())
if match:
info.pod_id = match.group(1)

return info

def __str__(self):
return self.__repr__()

def __repr__(self):
return '{}(id={!r}, groups={!r}, path={!r}, container_id={!r}, controllers={!r}, pod_id={!r})'.format(
self.__class__.__name__,
self.id,
self.groups,
self.path,
self.container_id,
self.controllers,
self.pod_id,
)


def get_container_info(pid='self'):
"""
Helper to fetch the current container id, if we are running in a container

We will parse `/proc/{pid}/cgroup` to determine our container id.

The results of calling this function are cached

:param pid: The pid of the cgroup file to parse (default: 'self')
:type pid: str | int
:returns: The cgroup file info if found, or else None
:rtype: :class:`CGroupInfo` | None
"""
try:
cgroup_file = '/proc/{0}/cgroup'.format(pid)
with open(cgroup_file, mode='r') as fp:
for line in fp:
info = CGroupInfo.from_line(line)
if info and info.container_id:
return info
except Exception:
log.exception('Failed to parse cgroup file for pid %r', pid)

return None
Loading