-
Notifications
You must be signed in to change notification settings - Fork 814
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
Docker support #844
Merged
Merged
Docker support #844
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
feda751
Docker support. Connects to dockerd, and pulls memory, CPU and blkio …
steeve b4da86f
Allow to specify which metrics to fetch. Documentation.
steeve 0033698
Docker.yaml.example file.
steeve 56789bb
Simplify metric names
steeve c158828
Python 2.4 compatibility
steeve 615030d
Remove cli-mode code.
steeve ed08516
Remove ID and Created from tags
steeve 17b2955
Make sure we collect the Docker Image and Hostname
steeve 0d37ea7
Display a warning if not containers are running
steeve 897e405
PEP8
steeve d6e38a4
Replace for/else syntax. Trying to be clever, end up feeling dumb
steeve 3bfe7ec
Implement include/exclude rules. Set MAX_CONTAINERS_WITHOUT_RULES to …
steeve 28b2e5d
Denoise container tags, simplify tag collection, improves performance
steeve 305e194
Fix typo
steeve File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import urllib2 | ||
import urllib | ||
import httplib | ||
import socket | ||
import os | ||
import re | ||
from urlparse import urlsplit, urljoin | ||
from util import json, headers | ||
from checks import AgentCheck | ||
|
||
MAX_CONTAINERS_WITHOUT_RULES = 10 | ||
|
||
LXC_METRICS = [ | ||
{ | ||
"cgroup": "memory", | ||
"file": "lxc/{0}/memory.stat", | ||
"metrics": { | ||
"active_anon": ("docker.mem.active_anon", "gauge"), | ||
"active_file": ("docker.mem.active_file", "gauge"), | ||
"cache": ("docker.mem.cache", "gauge"), | ||
"hierarchical_memory_limit": ("docker.mem.hierarchical_memory_limit", "gauge"), | ||
"hierarchical_memsw_limit": ("docker.mem.hierarchical_memsw_limit", "gauge"), | ||
"inactive_anon": ("docker.mem.inactive_anon", "gauge"), | ||
"inactive_file": ("docker.mem.inactive_file", "gauge"), | ||
"mapped_file": ("docker.mem.mapped_file", "gauge"), | ||
"pgfault": ("docker.mem.pgfault", "gauge"), | ||
"pgmajfault": ("docker.mem.pgmajfault", "gauge"), | ||
"pgpgin": ("docker.mem.pgpgin", "gauge"), | ||
"pgpgout": ("docker.mem.pgpgout", "gauge"), | ||
"rss": ("docker.mem.rss", "gauge"), | ||
"swap": ("docker.mem.swap", "gauge"), | ||
"unevictable": ("docker.mem.unevictable", "gauge"), | ||
"total_active_anon": ("docker.mem.total_active_anon", "gauge"), | ||
"total_active_file": ("docker.mem.total_active_file", "gauge"), | ||
"total_cache": ("docker.mem.total_cache", "gauge"), | ||
"total_inactive_anon": ("docker.mem.total_inactive_anon", "gauge"), | ||
"total_inactive_file": ("docker.mem.total_inactive_file", "gauge"), | ||
"total_mapped_file": ("docker.mem.total_mapped_file", "gauge"), | ||
"total_pgfault": ("docker.mem.total_pgfault", "gauge"), | ||
"total_pgmajfault": ("docker.mem.total_pgmajfault", "gauge"), | ||
"total_pgpgin": ("docker.mem.total_pgpgin", "gauge"), | ||
"total_pgpgout": ("docker.mem.total_pgpgout", "gauge"), | ||
"total_rss": ("docker.mem.total_rss", "gauge"), | ||
"total_swap": ("docker.mem.total_swap", "gauge"), | ||
"total_unevictable": ("docker.mem.total_unevictable", "gauge"), | ||
} | ||
}, | ||
{ | ||
"cgroup": "cpuacct", | ||
"file": "lxc/{0}/cpuacct.stat", | ||
"metrics": { | ||
"user": ("docker.cpu.user", "gauge"), | ||
"system": ("docker.cpu.system", "gauge"), | ||
}, | ||
}, | ||
] | ||
|
||
DOCKER_METRICS = { | ||
"SizeRw": ("docker.disk.size", "gauge"), | ||
} | ||
|
||
DOCKER_TAGS = [ | ||
"Command", | ||
"Image", | ||
] | ||
|
||
class UnixHTTPConnection(httplib.HTTPConnection, object): | ||
"""Class used in conjuction with UnixSocketHandler to make urllib2 | ||
compatible with Unix sockets.""" | ||
def __init__(self, unix_socket): | ||
self._unix_socket = unix_socket | ||
|
||
def connect(self): | ||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
sock.connect(self._unix_socket) | ||
self.sock = sock | ||
|
||
def __call__(self, *args, **kwargs): | ||
httplib.HTTPConnection.__init__(self, *args, **kwargs) | ||
return self | ||
|
||
|
||
class UnixSocketHandler(urllib2.AbstractHTTPHandler): | ||
"""Class that makes Unix sockets work with urllib2 without any additional | ||
dependencies.""" | ||
def unix_open(self, req): | ||
full_path = "%s%s" % urlsplit(req.get_full_url())[1:3] | ||
path = os.path.sep | ||
for part in full_path.split("/"): | ||
path = os.path.join(path, part) | ||
if not os.path.exists(path): | ||
break | ||
unix_socket = path | ||
# add a host or else urllib2 complains | ||
url = req.get_full_url().replace(unix_socket, "/localhost") | ||
new_req = urllib2.Request(url, req.get_data(), dict(req.header_items())) | ||
new_req.timeout = req.timeout | ||
return self.do_open(UnixHTTPConnection(unix_socket), new_req) | ||
|
||
unix_request = urllib2.AbstractHTTPHandler.do_request_ | ||
|
||
|
||
class Docker(AgentCheck): | ||
def __init__(self, *args, **kwargs): | ||
super(Docker, self).__init__(*args, **kwargs) | ||
urllib2.install_opener(urllib2.build_opener(UnixSocketHandler())) | ||
self._mounpoints = {} | ||
for metric in LXC_METRICS: | ||
self._mounpoints[metric["cgroup"]] = self._find_cgroup(metric["cgroup"]) | ||
|
||
def check(self, instance): | ||
tags = instance.get("tags") or [] | ||
containers = self._get_containers(instance) | ||
if not containers: | ||
self.warning("No containers are running.") | ||
|
||
# If we don't have include/exclude rules, we cap to | ||
# MAX_CONTAINERS_WITHOUT_RULES containers to collect. | ||
if not instance.get("exclude") or not instance.get("include"): | ||
containers = containers[:MAX_CONTAINERS_WITHOUT_RULES] | ||
for container in containers: | ||
container_tags = list(tags) | ||
for name in container["Names"]: | ||
container_tags.append(self._make_tag("name", name.lstrip("/"))) | ||
for key in DOCKER_TAGS: | ||
container_tags.append(self._make_tag(key, container[key])) | ||
|
||
# Check if the container is included/excluded via its tags | ||
if not self._is_container_included(instance, container_tags): | ||
continue | ||
|
||
for key, (dd_key, metric_type) in DOCKER_METRICS.items(): | ||
if key in container: | ||
getattr(self, metric_type)(dd_key, int(container[key]), tags=container_tags) | ||
for metric in LXC_METRICS: | ||
mountpoint = self._mounpoints[metric["cgroup"]] | ||
stat_file = os.path.join(mountpoint, metric["file"].format(container["Id"])) | ||
stats = self._parse_cgroup_file(stat_file) | ||
for key, (dd_key, metric_type) in metric["metrics"].items(): | ||
if key in stats: | ||
getattr(self, metric_type)(dd_key, int(stats[key]), tags=container_tags) | ||
|
||
def _make_tag(self, key, value): | ||
return "%s:%s" % (key.lower(), value.strip()) | ||
|
||
def _is_container_included(self, instance, tags): | ||
def _is_tag_included(tag): | ||
for exclude_rule in instance.get("exclude") or []: | ||
if re.match(exclude_rule, tag): | ||
for include_rule in instance.get("include") or []: | ||
if re.match(include_rule, tag): | ||
return True | ||
return False | ||
return True | ||
for tag in tags: | ||
if _is_tag_included(tag): | ||
return True | ||
return False | ||
|
||
def _get_containers(self, instance): | ||
"""Gets the list of running containers in Docker.""" | ||
return self._get_json("%(url)s/containers/json" % instance, params={"size": 1}) | ||
|
||
def _get_container(self, instance, cid): | ||
"""Get container information from Docker, gived a container Id.""" | ||
return self._get_json("%s/containers/%s/json" % (instance["url"], cid)) | ||
|
||
def _get_json(self, uri, params=None): | ||
"""Utility method to get and parse JSON streams.""" | ||
if params: | ||
uri = "%s?%s" % (uri, urllib.urlencode(params)) | ||
req = urllib2.Request(uri, None) | ||
request = urllib2.urlopen(req) | ||
response = request.read() | ||
return json.loads(response) | ||
|
||
def _find_cgroup(self, hierarchy): | ||
"""Finds the mount point for a specified cgroup hierarchy. Works with | ||
old style and new style mounts.""" | ||
try: | ||
fp = open("/proc/mounts") | ||
mounts = map(lambda x: x.split(), fp.read().splitlines()) | ||
finally: | ||
fp.close() | ||
cgroup_mounts = filter(lambda x: x[2] == "cgroup", mounts) | ||
# Old cgroup style | ||
if len(cgroup_mounts) == 1: | ||
return cgroup_mounts[0][1] | ||
for _, mountpoint, _, opts, _, _ in cgroup_mounts: | ||
if hierarchy in opts: | ||
return mountpoint | ||
|
||
def _parse_cgroup_file(self, file_): | ||
"""Parses a cgroup pseudo file for key/values.""" | ||
try: | ||
fp = open(file_) | ||
return dict(map(lambda x: x.split(), fp.read().splitlines())) | ||
finally: | ||
fp.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
init_config: | ||
|
||
instances: | ||
- url: "unix://var/run/docker.sock" | ||
|
||
# Include/Exclude rules | ||
# | ||
# To include or exclude containers based on their tags, use the include and | ||
# exclude keys in your instance. | ||
# The reasoning is: if a tag matches an exclude rule, it won't be included | ||
# unless it also matches an include rule. | ||
# | ||
# Examples: | ||
# exclude all, except ubuntu and debian: | ||
# instances: | ||
# - url: "unix://var/run/docker.sock" | ||
# include: | ||
# - "image:ubuntu" | ||
# - "image:debian" | ||
# exclude: | ||
# - ".*" | ||
# | ||
# include all, except ubuntu and debian: | ||
# instances: | ||
# - url: "unix://var/run/docker.sock" | ||
# include: [] | ||
# exclude: | ||
# - "image:ubuntu" | ||
# - "image:debian" |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a chance that those metrics might be floating numbers ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that I know of. Except if you count
cpuacct.stat
jiffies as seconds.