| # pylint: disable-msg=C0111 |
| # TODO: get rid of above, fix docstrings. crbug.com/273903 |
| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| |
| |
| try: |
| import statsd |
| except ImportError: |
| import statsd_mock as statsd |
| |
| |
| # This is _type for all metadata logged to elasticsearch from here. |
| STATS_ES_TYPE = 'stats_metadata' |
| |
| |
| # statsd logs details about what its sending at the DEBUG level, which I really |
| # don't want to see tons of stats in logs, so all of these are silenced by |
| # setting the logging level for all of statsdto WARNING. |
| logging.getLogger('statsd').setLevel(logging.WARNING) |
| |
| |
| def _prepend_init(_es, _conn, _prefix): |
| def wrapper(original): |
| """Decorator to override __init__.""" |
| |
| class _Derived(original): |
| def __init__(self, name, connection=None, bare=False, |
| metadata=None): |
| name = self._add_prefix(name, _prefix, bare) |
| conn = connection if connection else _conn |
| super(_Derived, self).__init__(name, conn) |
| self.metadata = metadata |
| self.es = _es |
| |
| def _add_prefix(self, name, prefix, bare=False): |
| """ |
| Since many people run their own local AFE, stats from a local |
| setup shouldn't get mixed into stats from prod. Therefore, |
| this function exists to add a prefix, nominally the name of |
| the local server, if |name| doesn't already start with the |
| server name, so that each person has their own "folder" of |
| stats that they can look at. |
| |
| However, this functionality might not always be wanted, so we |
| allow one to pass in |bare=True| to force us to not prepend |
| the local server name. (I'm not sure when one would use this, |
| but I don't see why I should disallow it...) |
| |
| >>> prefix = 'potato_nyc' |
| >>> _add_prefix('rpc.create_job', bare=False) |
| 'potato_nyc.rpc.create_job' |
| >>> _add_prefix('rpc.create_job', bare=True) |
| 'rpc.create_job' |
| |
| @param name The name to append to the server name if it |
| doesn't start with the server name. |
| @param bare If True, |name| will be returned un-altered. |
| @return A string to use as the stat name. |
| |
| """ |
| if not bare and not name.startswith(prefix): |
| name = '%s.%s' % (prefix, name) |
| return name |
| |
| return _Derived |
| return wrapper |
| |
| |
| class Statsd(object): |
| def __init__(self, es, host, port, prefix): |
| # This is the connection that we're going to reuse for every client |
| # that gets created. This should maximally reduce overhead of stats |
| # logging. |
| self.conn = statsd.Connection(host=host, port=port) |
| |
| @_prepend_init(es, self.conn, prefix) |
| class Average(statsd.Average): |
| """Wrapper around statsd.Average.""" |
| |
| def send(self, subname, value): |
| """Sends time-series data to graphite and metadata (if any) |
| to es. |
| |
| @param subname: The subname to report the data to (i.e. |
| 'daisy.reboot') |
| @param value: Value to be sent. |
| """ |
| statsd.Average.send(self, subname, value) |
| self.es.post(type_str=STATS_ES_TYPE, metadata=self.metadata, |
| subname=subname, value=value) |
| |
| self.Average = Average |
| |
| @_prepend_init(es, self.conn, prefix) |
| class Counter(statsd.Counter): |
| """Wrapper around statsd.Counter.""" |
| |
| def _send(self, subname, value): |
| """Sends time-series data to graphite and metadata (if any) |
| to es. |
| |
| @param subname: The subname to report the data to (i.e. |
| 'daisy.reboot') |
| @param value: Value to be sent. |
| """ |
| statsd.Counter._send(self, subname, value) |
| self.es.post(type_str=STATS_ES_TYPE, metadata=self.metadata, |
| subname=subname, value=value) |
| |
| self.Counter = Counter |
| |
| @_prepend_init(es, self.conn, prefix) |
| class Gauge(statsd.Gauge): |
| """Wrapper around statsd.Gauge.""" |
| |
| def send(self, subname, value): |
| """Sends time-series data to graphite and metadata (if any) |
| to es. |
| |
| @param subname: The subname to report the data to (i.e. |
| 'daisy.reboot') |
| @param value: Value to be sent. |
| """ |
| statsd.Gauge.send(self, subname, value) |
| self.es.post(type_str=STATS_ES_TYPE, metadata=self.metadata, |
| subname=subname, value=value) |
| |
| self.Gauge = Gauge |
| |
| @_prepend_init(es, self.conn, prefix) |
| class Timer(statsd.Timer): |
| """Wrapper around statsd.Timer.""" |
| |
| # To override subname to not implicitly append 'total'. |
| def stop(self, subname=''): |
| statsd.Timer.stop(self, subname) |
| |
| |
| def send(self, subname, value): |
| """Sends time-series data to graphite and metadata (if any) |
| to es. |
| |
| @param subname: The subname to report the data to (i.e. |
| 'daisy.reboot') |
| @param value: Value to be sent. |
| """ |
| statsd.Timer.send(self, subname, value) |
| self.es.post(type_str=STATS_ES_TYPE, metadata=self.metadata, |
| subname=self.name, value=value) |
| |
| |
| def __enter__(self): |
| self.start() |
| return self |
| |
| |
| def __exit__(self, exn_type, exn_value, traceback): |
| if exn_type is None: |
| self.stop() |
| |
| self.Timer = Timer |
| |
| @_prepend_init(es, self.conn, prefix) |
| class Raw(statsd.Raw): |
| """Wrapper around statsd.Raw.""" |
| |
| def send(self, subname, value, timestamp=None): |
| """Sends time-series data to graphite and metadata (if any) |
| to es. |
| |
| The datapoint we send is pretty much unchanged (will not be |
| aggregated) |
| |
| @param subname: The subname to report the data to (i.e. |
| 'daisy.reboot') |
| @param value: Value to be sent. |
| @param timestamp: Time associated with when this stat was sent. |
| """ |
| statsd.Raw.send(self, subname, value, timestamp) |
| self.es.post(type_str=STATS_ES_TYPE, metadata=self.metadata, |
| subname=subname, value=value, timestamp=timestamp) |
| |
| self.Raw = Raw |