Switch statsd config to zuul.conf

The automatic statsd configuration based on env variables has
proven cumbersome and counter-intuitive.  Move its configuration
into zuul.conf in preparation for other components emitting stats.

Change-Id: I3f6b5010d31c05e295f3d70925cac8460d334283
This commit is contained in:
James E. Blair 2017-10-10 13:22:40 -07:00
parent 76fc525d14
commit ded241e598
11 changed files with 87 additions and 39 deletions

View File

@ -101,6 +101,27 @@ The following sections of ``zuul.conf`` are used by all Zuul components:
An openssl file containing the client private key in PEM format. An openssl file containing the client private key in PEM format.
.. attr:: statsd
Information about the optional statsd server. If the ``statsd``
python module is installed and this section is configured,
statistics will be reported to statsd. See :ref:`statsd` for more
information.
.. attr:: server
Hostname or IP address of the statsd server.
.. attr:: port
:default: 8125
The UDP port on which the statsd server is listening.
.. attr:: prefix
If present, this will be prefixed to all of the keys before
transmitting to the statsd server.
.. NOTE: this is a white lie at this point, since only the scheduler .. NOTE: this is a white lie at this point, since only the scheduler
uses this, however, we expect other components to use it later, so uses this, however, we expect other components to use it later, so
it's reasonable for admins to plan for this now. it's reasonable for admins to plan for this now.

View File

@ -3,6 +3,8 @@
Monitoring Monitoring
========== ==========
.. _statsd:
Statsd reporting Statsd reporting
---------------- ----------------
@ -13,20 +15,11 @@ which let you in turn generate nice graphics.
Configuration Configuration
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
Statsd support uses the statsd python module. Note that Zuul will start without Statsd support uses the ``statsd`` python module. Note that support
the statsd python module, so an existing Zuul installation may be missing it. is optional and Zuul will start without the statsd python module
present.
The configuration is done via environment variables STATSD_HOST and Configuration is in the :attr:`statsd` section of ``zuul.conf``.
STATSD_PORT. They are interpreted by the statsd module directly and there is no
such parameter in zuul.conf yet. Your init script will have to initialize both
of them before executing Zuul.
Your init script most probably loads a configuration file named
``/etc/default/zuul`` which would contain the environment variables::
$ cat /etc/default/zuul
STATSD_HOST=10.0.0.1
STATSD_PORT=8125
Metrics Metrics
~~~~~~~ ~~~~~~~

View File

@ -5,6 +5,9 @@ server=127.0.0.1
;ssl_cert=/path/to/client.pem ;ssl_cert=/path/to/client.pem
;ssl_key=/path/to/client.key ;ssl_key=/path/to/client.key
[statsd]
server=127.0.0.1
[zookeeper] [zookeeper]
hosts=127.0.0.1:2181 hosts=127.0.0.1:2181

View File

@ -20,7 +20,6 @@ from contextlib import contextmanager
import datetime import datetime
import gc import gc
import hashlib import hashlib
import importlib
from io import StringIO from io import StringIO
import json import json
import logging import logging
@ -48,7 +47,6 @@ import fixtures
import kazoo.client import kazoo.client
import kazoo.exceptions import kazoo.exceptions
import pymysql import pymysql
import statsd
import testtools import testtools
import testtools.content import testtools.content
import testtools.content_type import testtools.content_type
@ -2047,14 +2045,9 @@ class ZuulTestCase(BaseTestCase):
self.config.set('executor', 'state_dir', self.executor_state_root) self.config.set('executor', 'state_dir', self.executor_state_root)
self.statsd = FakeStatsd() self.statsd = FakeStatsd()
# note, use 127.0.0.1 rather than localhost to avoid getting ipv6 if self.config.has_section('statsd'):
# see: https://github.com/jsocol/pystatsd/issues/61 self.config.set('statsd', 'port', str(self.statsd.port))
os.environ['STATSD_HOST'] = '127.0.0.1'
os.environ['STATSD_PORT'] = str(self.statsd.port)
self.statsd.start() self.statsd.start()
# the statsd client object is configured in the statsd module import
importlib.reload(statsd)
importlib.reload(zuul.scheduler)
self.gearman_server = FakeGearmanServer(self.use_ssl) self.gearman_server = FakeGearmanServer(self.use_ssl)

View File

@ -1,6 +1,11 @@
[gearman] [gearman]
server=127.0.0.1 server=127.0.0.1
[statsd]
# note, use 127.0.0.1 rather than localhost to avoid getting ipv6
# see: https://github.com/jsocol/pystatsd/issues/61
server=127.0.0.1
[scheduler] [scheduler]
tenant_config=main.yaml tenant_config=main.yaml

View File

@ -2152,8 +2152,7 @@ class TestScheduler(ZuulTestCase):
def test_statsd(self): def test_statsd(self):
"Test each of the statsd methods used in the scheduler" "Test each of the statsd methods used in the scheduler"
import extras statsd = self.sched.statsd
statsd = extras.try_import('statsd.statsd')
statsd.incr('test-incr') statsd.incr('test-incr')
statsd.timing('test-timing', 3) statsd.timing('test-timing', 3)
statsd.gauge('test-gauge', 12) statsd.gauge('test-gauge', 12)

View File

@ -5,10 +5,7 @@ envlist = pep8,py35
[testenv] [testenv]
basepython = python3 basepython = python3
# Set STATSD env variables so that statsd code paths are tested. setenv = VIRTUAL_ENV={envdir}
setenv = STATSD_HOST=127.0.0.1
STATSD_PORT=8125
VIRTUAL_ENV={envdir}
OS_TEST_TIMEOUT=120 OS_TEST_TIMEOUT=120
passenv = ZUUL_TEST_ROOT OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_LOG_CAPTURE OS_LOG_DEFAULTS passenv = ZUUL_TEST_ROOT OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_LOG_CAPTURE OS_LOG_DEFAULTS
usedevelop = True usedevelop = True

View File

@ -29,6 +29,7 @@ import signal
import zuul.cmd import zuul.cmd
from zuul.lib.config import get_default from zuul.lib.config import get_default
from zuul.lib.statsd import get_statsd_config
# No zuul imports here because they pull in paramiko which must not be # No zuul imports here because they pull in paramiko which must not be
# imported until after the daemonization. # imported until after the daemonization.
@ -97,8 +98,14 @@ class Scheduler(zuul.cmd.ZuulApp):
os.close(pipe_write) os.close(pipe_write)
self.setup_logging('gearman_server', 'log_config') self.setup_logging('gearman_server', 'log_config')
import zuul.lib.gearserver import zuul.lib.gearserver
statsd_host = os.environ.get('STATSD_HOST')
statsd_port = int(os.environ.get('STATSD_PORT', 8125)) (statsd_host, statsd_port, statsd_prefix) = get_statsd_config(
self.config)
if statsd_prefix:
statsd_prefix += '.zuul.geard'
else:
statsd_prefix = 'zuul.geard'
host = get_default(self.config, 'gearman_server', 'listen_address') host = get_default(self.config, 'gearman_server', 'listen_address')
port = int(get_default(self.config, 'gearman_server', 'port', port = int(get_default(self.config, 'gearman_server', 'port',
4730)) 4730))
@ -112,7 +119,7 @@ class Scheduler(zuul.cmd.ZuulApp):
host=host, host=host,
statsd_host=statsd_host, statsd_host=statsd_host,
statsd_port=statsd_port, statsd_port=statsd_port,
statsd_prefix='zuul.geard') statsd_prefix=statsd_prefix)
# Keep running until the parent dies: # Keep running until the parent dies:
pipe_read = os.fdopen(pipe_read) pipe_read = os.fdopen(pipe_read)

View File

@ -14,8 +14,6 @@
import abc import abc
import extras
class BaseConnection(object, metaclass=abc.ABCMeta): class BaseConnection(object, metaclass=abc.ABCMeta):
"""Base class for connections. """Base class for connections.
@ -42,7 +40,6 @@ class BaseConnection(object, metaclass=abc.ABCMeta):
self.driver = driver self.driver = driver
self.connection_name = connection_name self.connection_name = connection_name
self.connection_config = connection_config self.connection_config = connection_config
self.statsd = extras.try_import('statsd.statsd')
def logEvent(self, event): def logEvent(self, event):
self.log.debug( self.log.debug(
@ -50,11 +47,11 @@ class BaseConnection(object, metaclass=abc.ABCMeta):
connection=self.connection_name, connection=self.connection_name,
event=event)) event=event))
try: try:
if self.statsd: if self.sched.statsd:
self.statsd.incr( self.sched.statsd.incr(
'zuul.event.{driver}.{event}'.format( 'zuul.event.{driver}.{event}'.format(
driver=self.driver.name, event=event.type)) driver=self.driver.name, event=event.type))
self.statsd.incr( self.sched.statsd.incr(
'zuul.event.{driver}.{connection}.{event}'.format( 'zuul.event.{driver}.{connection}.{event}'.format(
driver=self.driver.name, driver=self.driver.name,
connection=self.connection_name, connection=self.connection_name,

33
zuul/lib/statsd.py Normal file
View File

@ -0,0 +1,33 @@
# Copyright 2017 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import extras
from zuul.lib.config import get_default
def get_statsd_config(config):
statsd_host = get_default(config, 'statsd', 'server')
statsd_port = int(get_default(config, 'statsd', 'port', 8125))
statsd_prefix = get_default(config, 'statsd', 'prefix')
return (statsd_host, statsd_port, statsd_prefix)
def get_statsd(config):
statsd = extras.try_import('statsd')
if statsd is None:
return None
(statsd_host, statsd_port, statsd_prefix) = get_statsd_config(config)
if statsd_host is None:
return None
return statsd.StatsClient(statsd_host, statsd_port, statsd_prefix)

View File

@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import extras
import json import json
import logging import logging
import os import os
@ -31,6 +30,7 @@ from zuul import model
from zuul import exceptions from zuul import exceptions
from zuul import version as zuul_version from zuul import version as zuul_version
from zuul.lib.config import get_default from zuul.lib.config import get_default
from zuul.lib.statsd import get_statsd
class ManagementEvent(object): class ManagementEvent(object):
@ -211,7 +211,7 @@ class Scheduler(threading.Thread):
self.executor = None self.executor = None
self.merger = None self.merger = None
self.connections = None self.connections = None
self.statsd = extras.try_import('statsd.statsd') self.statsd = get_statsd(config)
# TODO(jeblair): fix this # TODO(jeblair): fix this
# Despite triggers being part of the pipeline, there is one trigger set # Despite triggers being part of the pipeline, there is one trigger set
# per scheduler. The pipeline handles the trigger filters but since # per scheduler. The pipeline handles the trigger filters but since