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

reuse python code from datadog_checks_base #1704

Merged
merged 41 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b4388e3
reuse python code from datadog_checks_base
ofek May 16, 2018
2d82118
try on ci
ofek May 17, 2018
7a0429a
fix circle ci
ofek May 17, 2018
d577c82
derp
ofek May 18, 2018
3c834bf
try
ofek May 18, 2018
55950a3
see
ofek May 18, 2018
2e603f6
hmm
ofek May 18, 2018
fcff3af
try
ofek May 18, 2018
a1d2471
locate
ofek May 18, 2018
8befe76
try
ofek May 18, 2018
1d0be57
see
ofek May 18, 2018
fd286f3
try
ofek May 18, 2018
ad55436
try
ofek May 18, 2018
151c1dc
remove debugging
ofek May 18, 2018
b4da6b6
add to deps
ofek May 19, 2018
4f2ccfa
change default location, make quiet
ofek May 19, 2018
5a1bd55
refactor
ofek May 19, 2018
c534730
remove from cache
ofek May 19, 2018
b7c7597
need links actually
ofek May 19, 2018
4742765
see gl
ofek May 19, 2018
38adc6e
debug gitlab
ofek May 21, 2018
f058e67
see
ofek May 21, 2018
3cf9c81
hmm
ofek May 21, 2018
11f2484
upgrade pip
ofek May 21, 2018
7b206c3
try
ofek May 21, 2018
416a082
try
ofek May 21, 2018
5d0e819
try
ofek May 21, 2018
e231fa8
see
ofek May 21, 2018
87590c4
maybe
ofek May 21, 2018
dab5a57
see tls version
ofek May 21, 2018
e7664af
fixed in core, try removing things
ofek May 21, 2018
ad84a3c
add more options to deps command
ofek May 22, 2018
84c3bde
missed a spot
ofek May 22, 2018
48c8f8b
rename option
ofek May 22, 2018
b042fdb
add reno
ofek May 22, 2018
475117e
update changes
ofek May 22, 2018
8b73b70
document deps command
ofek May 22, 2018
1ab326d
document env var
ofek May 23, 2018
6b56695
address review
ofek May 23, 2018
14c16ec
update reno
ofek May 23, 2018
2cad164
rearrange
ofek May 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
- /go/src/github.com/DataDog/datadog-agent/vendor
- /go/pkg
- /go/bin
- /usr/local/lib/python2.7/dist-packages

unit_tests:
<<: *job_template
Expand Down
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ before_script:
# So we copy the agent sources to / and bootstrap from there the vendor dependencies before running any job.
- rsync -azr --delete ./ $SRC_PATH
- cd $SRC_PATH
- pip install -U pip
- inv -e deps


Expand Down
341 changes: 2 additions & 339 deletions cmd/agent/dist/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,342 +2,5 @@
# under the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2018 Datadog, Inc.

import copy
import json
import traceback
import re
import logging
import unicodedata
from collections import defaultdict

import aggregator
import datadog_agent
from config import _is_affirmative, _get_py_loglevel
from utils.common import ensure_bytes
from utils.proxy import (
get_requests_proxy,
get_no_proxy_from_env,
config_proxy_skip
)


class AgentLogHandler(logging.Handler):
"""
This handler forwards every log to the Go backend allowing python checks to
log message within the main agent logging system.
"""

def emit(self, record):
msg = self.format(record)
datadog_agent.log("(%s:%s) | %s" % (record.filename, record.lineno, msg), record.levelno)


def init_logging():
"""
Initialize logging (set up forwarding to Go backend and sane defaults)
"""
# Forward to Go backend
rootLogger = logging.getLogger()
rootLogger.addHandler(AgentLogHandler())
rootLogger.setLevel(_get_py_loglevel(datadog_agent.get_config('log_level')))

# `requests` (used in a lot of checks) imports `urllib3`, which logs a bunch of stuff at the info level
# Therefore, pre-emptively increase the default level of that logger to `WARN`
urllib_logger = logging.getLogger("requests.packages.urllib3")
urllib_logger.setLevel(logging.WARN)
urllib_logger.propagate = True


init_logging()


class CheckException(Exception):
pass


class AgentCheck(object):
OK, WARNING, CRITICAL, UNKNOWN = (0, 1, 2, 3)

def __init__(self, *args, **kwargs):
# `args` order is `name`, `init_config`, `agentConfig` (deprecated), `instances`

self.metrics = defaultdict(list)

self.instances = kwargs.get('instances', [])
self.name = kwargs.get('name', '')
self.init_config = kwargs.get('init_config', {})
self.agentConfig = kwargs.get('agentConfig', {})
self.warnings = []

if len(args) > 0:
self.name = args[0]
if len(args) > 1:
self.init_config = args[1]
if len(args) > 2:
if len(args) > 3 or 'instances' in kwargs:
# old-style init: the 3rd argument is `agentConfig`
self.agentConfig = args[2]
if len(args) > 3:
self.instances = args[3]
else:
# new-style init: the 3rd argument is `instances`
self.instances = args[2]

self.hostname = datadog_agent.get_hostname() # `self.hostname` is deprecated, use `datadog_agent.get_hostname()` instead

# the agent5 'AgentCheck' setup a log attribute.
self.log = logging.getLogger('%s.%s' % (__name__, self.name))

# Set proxy settings
self.proxies = get_requests_proxy(self.agentConfig)
if not self.init_config:
self._use_agent_proxy = True
else:
self._use_agent_proxy = _is_affirmative(
self.init_config.get("use_agent_proxy", True))

self.default_integration_http_timeout = float(self.agentConfig.get('default_integration_http_timeout', 9))

self._deprecations = {
'increment': [
False,
"DEPRECATION NOTICE: `AgentCheck.increment`/`AgentCheck.decrement` are deprecated, please use " +
"`AgentCheck.gauge` or `AgentCheck.count` instead, with a different metric name",
],
'device_name': [
False,
"DEPRECATION NOTICE: `device_name` is deprecated, please use a `device:` tag in the `tags` list instead",
],
'in_developer_mode': [
False,
"DEPRECATION NOTICE: `in_developer_mode` is deprecated, please stop using it.",
],
'no_proxy': [
False,
"DEPRECATION NOTICE: The `no_proxy` config option has been renamed "
"to `skip_proxy` and will be removed in a future release.",
],
}


@property
def in_developer_mode(self):
self._log_deprecation('in_developer_mode')
return False


def get_instance_proxy(self, instance, uri, proxies=None):
proxies = proxies if proxies is not None else self.proxies.copy()
proxies['no'] = get_no_proxy_from_env()

deprecated_skip = instance.get('no_proxy', None)
skip = (
_is_affirmative(instance.get('skip_proxy', not self._use_agent_proxy)) or
_is_affirmative(deprecated_skip)
)

if deprecated_skip is not None:
self._log_deprecation('no_proxy')

return config_proxy_skip(proxies, uri, skip)

def _submit_metric(self, mtype, name, value, tags=None, hostname=None, device_name=None):
if value is None:
# ignore metric sample
return

tags = self._normalize_tags(tags, device_name)
if hostname is None:
hostname = ""

aggregator.submit_metric(self, self.check_id, mtype, name, float(value), tags, hostname)

def gauge(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.GAUGE, name, value, tags=tags, hostname=hostname, device_name=device_name)

def count(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.COUNT, name, value, tags=tags, hostname=hostname, device_name=device_name)

def monotonic_count(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.MONOTONIC_COUNT, name, value, tags=tags, hostname=hostname, device_name=device_name)

def rate(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.RATE, name, value, tags=tags, hostname=hostname, device_name=device_name)

def histogram(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.HISTOGRAM, name, value, tags=tags, hostname=hostname, device_name=device_name)

def historate(self, name, value, tags=None, hostname=None, device_name=None):
self._submit_metric(aggregator.HISTORATE, name, value, tags=tags, hostname=hostname, device_name=device_name)

def increment(self, name, value=1, tags=None, hostname=None, device_name=None):
self._log_deprecation("increment")
self._submit_metric(aggregator.COUNTER, name, value, tags=tags, hostname=hostname, device_name=device_name)

def decrement(self, name, value=-1, tags=None, hostname=None, device_name=None):
self._log_deprecation("increment")
self._submit_metric(aggregator.COUNTER, name, value, tags=tags, hostname=hostname, device_name=device_name)

def _log_deprecation(self, deprecation_key):
"""
Logs a deprecation notice at most once per AgentCheck instance, for the pre-defined `deprecation_key`
"""
if not self._deprecations[deprecation_key][0]:
self.log.warning(self._deprecations[deprecation_key][1])
self._deprecations[deprecation_key][0] = True

def service_check(self, name, status, tags=None, hostname=None, message=None):
tags = self._normalize_tags_type(tags)
if hostname is None:
hostname = b''
if message is None:
message = b''
else:
message = ensure_bytes(message)

aggregator.submit_service_check(self, self.check_id, ensure_bytes(name), status, tags, hostname, message)

def event(self, event):
# Enforce types of some fields, considerably facilitates handling in go bindings downstream
for key, value in event.items():
# transform the unicode objects to plain strings with utf-8 encoding
if isinstance(value, unicode):
try:
event[key] = event[key].encode('utf-8')
except UnicodeError:
self.log.warning("Error encoding unicode field '%s' of event to utf-8 encoded string, can't submit event", key)
return
if event.get('tags'):
event['tags'] = self._normalize_tags_type(event['tags'])
if event.get('timestamp'):
event['timestamp'] = int(event['timestamp'])
if event.get('aggregation_key'):
event['aggregation_key'] = str(event['aggregation_key'])
aggregator.submit_event(self, self.check_id, event)

# TODO(olivier): implement service_metadata if it's worth it
def service_metadata(self, meta_name, value):
pass

def check(self, instance):
raise NotImplementedError

def normalize(self, metric, prefix=None, fix_case=False):
"""
Turn a metric into a well-formed metric name
prefix.b.c
:param metric The metric name to normalize
:param prefix A prefix to to add to the normalized name, default None
:param fix_case A boolean, indicating whether to make sure that
the metric name returned is in underscore_case
"""
if isinstance(metric, unicode):
metric_name = unicodedata.normalize('NFKD', metric).encode('ascii', 'ignore')
else:
metric_name = metric

if fix_case:
name = self.convert_to_underscore_separated(metric_name)
if prefix is not None:
prefix = self.convert_to_underscore_separated(prefix)
else:
name = re.sub(r"[,\+\*\-/()\[\]{}\s]", "_", metric_name)
# Eliminate multiple _
name = re.sub(r"__+", "_", name)
# Don't start/end with _
name = re.sub(r"^_", "", name)
name = re.sub(r"_$", "", name)
# Drop ._ and _.
name = re.sub(r"\._", ".", name)
name = re.sub(r"_\.", ".", name)

if prefix is not None:
return prefix + "." + name
else:
return name

FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)')
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')
METRIC_REPLACEMENT = re.compile(r'([^a-zA-Z0-9_.]+)|(^[^a-zA-Z]+)')
DOT_UNDERSCORE_CLEANUP = re.compile(r'_*\._*')

def convert_to_underscore_separated(self, name):
"""
Convert from CamelCase to camel_case
And substitute illegal metric characters
"""
metric_name = self.FIRST_CAP_RE.sub(r'\1_\2', name)
metric_name = self.ALL_CAP_RE.sub(r'\1_\2', metric_name).lower()
metric_name = self.METRIC_REPLACEMENT.sub('_', metric_name)
return self.DOT_UNDERSCORE_CLEANUP.sub('.', metric_name).strip('_')

def _normalize_tags(self, tags, device_name):
"""
Normalize tags:
- append `device_name` as `device:` tag
- normalize tags to type `str`
- always return a list
"""
if tags is None:
normalized_tags = []
else:
normalized_tags = list(tags) # normalize to `list` type, and make a copy

if device_name:
self._log_deprecation("device_name")
normalized_tags.append("device:%s" % device_name)

return self._normalize_tags_type(normalized_tags)

def _normalize_tags_type(self, tags):
"""
Normalize all the tags to strings (type `str`) so that the go bindings can handle them easily
Doesn't mutate the passed list, returns a new list
"""
normalized_tags = []
if tags is not None:
for tag in tags:
if not isinstance(tag, basestring):
try:
tag = str(tag)
except Exception:
self.log.warning("Error converting tag to string, ignoring tag")
continue
else:
try:
tag = ensure_bytes(tag)
except UnicodeError:
self.log.warning("Error encoding unicode tag to utf-8 encoded string, ignoring tag")
continue
normalized_tags.append(tag)

return normalized_tags

def warning(self, warning_message):
warning_message = str(warning_message)
self.log.warning(warning_message)
self.warnings.append(warning_message)

def get_warnings(self):
"""
Return the list of warnings messages to be displayed in the info page
"""
warnings = self.warnings
self.warnings = []
return warnings

def run(self):
try:
self.check(copy.deepcopy(self.instances[0]))
result = ''

except Exception, e:
result = json.dumps([
{
"message": str(e),
"traceback": traceback.format_exc(),
}
])

return result
from datadog_checks.checks import AgentCheck
from datadog_checks.errors import CheckException
1 change: 0 additions & 1 deletion cmd/agent/dist/checks/libs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
# under the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2018 Datadog, Inc.

Loading