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

Collect version metadata for Fluentd #5057

Merged
merged 7 commits into from
Nov 25, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
11 changes: 11 additions & 0 deletions fluentd/datadog_checks/fluentd/data/conf.yaml.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
init_config:
## @param fluentd - string - optional - default: fluentd
## Command or path to fluentd (e.g. `/usr/local/bin/fluentd` or `docker exec container fluentd`).
## Can be overwritten on an instance.
#
# fluentd: fluentd

## @param proxy - object - optional
## Set HTTP or HTTPS proxies for all instances. Use the `no_proxy` list
## to specify hosts that must bypass proxies.
Expand Down Expand Up @@ -33,6 +39,11 @@ instances:
#
- monitor_agent_url: http://example.com:24220/api/plugins.json

## @param fluentd - string - optional - default: fluentd
## Command or path to fluentd (e.g. `/usr/local/bin/fluentd` or `docker exec container fluentd`).
#
# fluentd: fluentd

## @param plugin_ids - list of strings - optional
## Enter your Plugin IDs to monitor a specific scope of plugins.
#
Expand Down
32 changes: 32 additions & 0 deletions fluentd/datadog_checks/fluentd/fluentd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)

import re
import shlex

# 3rd party
from six.moves.urllib.parse import urlparse

# project
from datadog_checks.base.utils.subprocess_output import get_subprocess_output
from datadog_checks.checks import AgentCheck


Expand All @@ -15,6 +19,7 @@ class Fluentd(AgentCheck):
SERVICE_CHECK_NAME = 'fluentd.is_ok'
GAUGES = ['retry_count', 'buffer_total_queued_size', 'buffer_queue_length']
_AVAILABLE_TAGS = frozenset(['plugin_id', 'type'])
VERSION_PATTERN = r'.* (?P<version>[0-9\.]+)$'

def __init__(self, name, init_config, instances):
super(Fluentd, self).__init__(name, init_config, instances)
Expand All @@ -29,6 +34,8 @@ def __init__(self, name, init_config, instances):
)
self.http.options['timeout'] = (timeout, timeout)

self._fluentd_command = shlex.split(self.instance.get('fluentd', init_config.get('fluentd', 'fluentd')))

"""Tracks basic fluentd metrics via the monitor_agent plugin
* number of retry_count
* number of buffer_queue_length
Expand Down Expand Up @@ -81,9 +88,34 @@ def check(self, instance):
# Filter unspecified plugins to keep backward compatibility.
if len(plugin_ids) == 0 or p.get('plugin_id') in plugin_ids:
self.gauge('fluentd.%s' % (m), metric, [tag] + custom_tags)

self._collect_metadata()
except Exception as e:
msg = "No stats could be retrieved from %s : %s" % (url, str(e))
self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.CRITICAL, tags=service_check_tags, message=msg)
raise
else:
self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.OK, tags=service_check_tags)

def _collect_metadata(self):
raw_version = self._get_raw_version()

if raw_version:
self.set_metadata('version', raw_version)

def _get_raw_version(self):
command = self._fluentd_command + ['--version']

try:
out, _, _ = get_subprocess_output(command, self.log, False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the the api doesn't provide currently the version, but can be added easily upstream here, example:

      def config_json(req)
        obj = {
          'pid' => Process.pid,
          'ppid' => Process.ppid
          'version' => Fluent::VERSION
        }.merge(@agent.fluentd_opts)
        opts = build_option(req)

        render_json(obj, pretty_json: opts[:pretty_json])
      end

https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin/in_monitor_agent.rb#L65-L68

Even if we push this change upstream, it will be available only for new versions, so we will still need to retrieve versions via command line for old versions.

Copy link
Contributor Author

@florimondmanca florimondmanca Nov 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. I'll open an issue (or most likely a PR directly) on the fluentd repo to discuss adding the version to their monitoring REST API.

Agreed that this is not a blocker w.r.t. this PR, though? (i.e. we can add the API-based version lookup later.)

Copy link
Contributor Author

@florimondmanca florimondmanca Nov 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexandreYang I opened fluent/fluentd#2705 and fluent/fluentd#2706 on the Fluentd repo.

Will discuss there whether supporting accessing a remote Fluentd instance makes any sense at all from a monitoring perspective.

I'm now less sure that it can actually be a possible setup: most examples from the Fluentd monitoring docs show calls to localhost or similar.

So, if people always only install the integration on the host where Fluentd is installed, then the benefits we'd get from using the API are less clear IMO.

except OSError as exc:
self.log.warning("Error collecting fluentd version: %s", exc)
return None

match = re.match(self.VERSION_PATTERN, out)

if match is None:
self.log.warning("fluentd version not found in stdout: `%s`", out)
return None

return match.group('version')
4 changes: 4 additions & 0 deletions fluentd/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(HERE))

FLUENTD_VERSION = os.environ.get('FLUENTD_VERSION')
FLUENTD_IMAGE_TAG = os.environ.get('FLUENTD_IMAGE_TAG')
FLUENTD_CONTAINER_NAME = 'dd-test-fluentd'

HOST = get_docker_hostname()
PORT = 24220
BAD_PORT = 24222
Expand Down
3 changes: 2 additions & 1 deletion fluentd/tests/compose/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ version: '3'
services:

fluentd:
image: fluent/fluentd:${FLUENTD_VERSION}
image: fluent/fluentd:${FLUENTD_IMAGE_TAG}
container_name: ${FLUENTD_CONTAINER_NAME}
ports:
- 24220:24220
volumes:
Expand Down
7 changes: 5 additions & 2 deletions fluentd/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from datadog_checks.dev import docker_run

from .common import DEFAULT_INSTANCE, HERE, URL
from .common import DEFAULT_INSTANCE, FLUENTD_CONTAINER_NAME, FLUENTD_IMAGE_TAG, HERE, URL


@pytest.fixture(scope="session")
Expand All @@ -19,10 +19,13 @@ def dd_environment():
If there's any problem executing docker-compose, let the exception bubble
up.
"""
if not FLUENTD_IMAGE_TAG:
pytest.skip('FLUENTD_IMAGE_TAG is required')

env = {
'TD_AGENT_CONF_PATH': os.path.join(HERE, 'compose', 'td-agent.conf'),
'FLUENTD_VERSION': os.environ.get('FLUENTD_VERSION') or 'v0.12.23',
'FLUENTD_IMAGE_TAG': FLUENTD_IMAGE_TAG,
'FLUENTD_CONTAINER_NAME': FLUENTD_CONTAINER_NAME,
}

with docker_run(
Expand Down
5 changes: 5 additions & 0 deletions fluentd/tests/mock/fluentd_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys

if __name__ == "__main__":
mock_output = sys.argv[1]
print('fluentd {}'.format(mock_output))
59 changes: 59 additions & 0 deletions fluentd/tests/test_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# (C) Datadog, Inc. 2019
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
import os

import pytest

from datadog_checks.fluentd import Fluentd

from .common import CHECK_NAME, FLUENTD_CONTAINER_NAME, FLUENTD_VERSION, HERE

CHECK_ID = 'test:123'
VERSION_MOCK_SCRIPT = os.path.join(HERE, 'mock', 'fluentd_version.py')


@pytest.mark.skipif(not FLUENTD_VERSION, reason="FLUENTD_VERSION is required")
@pytest.mark.usefixtures("dd_environment")
def test_collect_metadata_instance(aggregator, datadog_agent, instance):
instance['fluentd'] = 'docker exec {} fluentd'.format(FLUENTD_CONTAINER_NAME)

check = Fluentd(CHECK_NAME, {}, [instance])
check.check_id = CHECK_ID
check.check(instance)

major, minor, patch = FLUENTD_VERSION.split('.')
version_metadata = {
'version.raw': FLUENTD_VERSION,
'version.scheme': 'semver',
'version.major': major,
'version.minor': minor,
'version.patch': patch,
}

datadog_agent.assert_metadata(CHECK_ID, version_metadata)
datadog_agent.assert_metadata_count(5)


@pytest.mark.usefixtures("dd_environment")
def test_collect_metadata_missing_version(aggregator, datadog_agent, instance):
instance["fluentd"] = "python {} 'fluentd not.a.version'".format(VERSION_MOCK_SCRIPT)

check = Fluentd(CHECK_NAME, {}, [instance])
check.check_id = CHECK_ID
check.check(instance)

datadog_agent.assert_metadata(CHECK_ID, {})
datadog_agent.assert_metadata_count(0)


@pytest.mark.usefixtures("dd_environment")
def test_collect_metadata_invalid_binary(datadog_agent, instance):
instance['fluentd'] = '/bin/does_not_exist'

check = Fluentd(CHECK_NAME, {}, [instance])
check.check_id = CHECK_ID
check.check(instance)

datadog_agent.assert_metadata(CHECK_ID, {})
datadog_agent.assert_metadata_count(0)
6 changes: 4 additions & 2 deletions fluentd/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ passenv =
COMPOSE*
DOCKER*
setenv =
0.12.23: FLUENTD_VERSION=v0.12.23
1.4: FLUENTD_VERSION=v1.4
0.12.23: FLUENTD_VERSION=0.12.23
0.12.23: FLUENTD_IMAGE_TAG=v0.12.23
1.4: FLUENTD_VERSION=1.4.2
1.4: FLUENTD_IMAGE_TAG=v1.4-2
commands =
pip install -r requirements.in
pytest -v {posargs}