Show More
@@ -0,0 +1,49 b'' | |||
|
1 | from vcsserver.lib._vendor.statsd import client_from_config | |
|
2 | ||
|
3 | ||
|
4 | class StatsdClientNotInitialised(Exception): | |
|
5 | pass | |
|
6 | ||
|
7 | ||
|
8 | class _Singleton(type): | |
|
9 | """A metaclass that creates a Singleton base class when called.""" | |
|
10 | ||
|
11 | _instances = {} | |
|
12 | ||
|
13 | def __call__(cls, *args, **kwargs): | |
|
14 | if cls not in cls._instances: | |
|
15 | cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs) | |
|
16 | return cls._instances[cls] | |
|
17 | ||
|
18 | ||
|
19 | class Singleton(_Singleton("SingletonMeta", (object,), {})): | |
|
20 | pass | |
|
21 | ||
|
22 | ||
|
23 | class StatsdClientClass(Singleton): | |
|
24 | setup_run = False | |
|
25 | statsd_client = None | |
|
26 | statsd = None | |
|
27 | ||
|
28 | def __getattribute__(self, name): | |
|
29 | ||
|
30 | if name.startswith("statsd"): | |
|
31 | if self.setup_run: | |
|
32 | return super(StatsdClientClass, self).__getattribute__(name) | |
|
33 | else: | |
|
34 | return None | |
|
35 | #raise StatsdClientNotInitialised("requested key was %s" % name) | |
|
36 | ||
|
37 | return super(StatsdClientClass, self).__getattribute__(name) | |
|
38 | ||
|
39 | def setup(self, settings): | |
|
40 | """ | |
|
41 | Initialize the client | |
|
42 | """ | |
|
43 | statsd = client_from_config(settings) | |
|
44 | self.statsd = statsd | |
|
45 | self.statsd_client = statsd | |
|
46 | self.setup_run = True | |
|
47 | ||
|
48 | ||
|
49 | StatsdClient = StatsdClientClass() |
@@ -37,6 +37,7 b' from pyramid.compat import configparser' | |||
|
37 | 37 | from pyramid.response import Response |
|
38 | 38 | |
|
39 | 39 | from vcsserver.utils import safe_int |
|
40 | from vcsserver.lib.statsd_client import StatsdClient | |
|
40 | 41 | |
|
41 | 42 | log = logging.getLogger(__name__) |
|
42 | 43 | |
@@ -243,6 +244,9 b' class HTTPApplication(object):' | |||
|
243 | 244 | self._sanitize_settings_and_apply_defaults(settings) |
|
244 | 245 | |
|
245 | 246 | self.config = Configurator(settings=settings) |
|
247 | # Init our statsd at very start | |
|
248 | self.config.registry.statsd = StatsdClient.statsd | |
|
249 | ||
|
246 | 250 | self.global_config = global_config |
|
247 | 251 | self.config.include('vcsserver.lib.rc_cache') |
|
248 | 252 | |
@@ -359,10 +363,6 b' class HTTPApplication(object):' | |||
|
359 | 363 | 'vcsserver.lib.request_counter.get_request_counter', |
|
360 | 364 | 'request_count') |
|
361 | 365 | |
|
362 | self.config.add_request_method( | |
|
363 | 'vcsserver.lib._vendor.statsd.get_statsd_client', | |
|
364 | 'statsd', reify=True) | |
|
365 | ||
|
366 | 366 | def wsgi_app(self): |
|
367 | 367 | return self.config.make_wsgi_app() |
|
368 | 368 | |
@@ -397,6 +397,12 b' class HTTPApplication(object):' | |||
|
397 | 397 | log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s', |
|
398 | 398 | method, call_args, kwargs, context_uid, repo_state_uid) |
|
399 | 399 | |
|
400 | statsd = request.registry.statsd | |
|
401 | if statsd: | |
|
402 | statsd.incr( | |
|
403 | 'vcsserver_method_count', tags=[ | |
|
404 | "method:{}".format(method), | |
|
405 | ]) | |
|
400 | 406 | return payload, remote, method, args, kwargs |
|
401 | 407 | |
|
402 | 408 | def vcs_view(self, request): |
@@ -681,6 +687,10 b' class HTTPApplication(object):' | |||
|
681 | 687 | log.error( |
|
682 | 688 | 'error occurred handling this request for path: %s, \n tb: %s', |
|
683 | 689 | request.path, traceback_info) |
|
690 | ||
|
691 | statsd = request.registry.statsd | |
|
692 | if statsd: | |
|
693 | statsd.incr('vcsserver_exception') | |
|
684 | 694 | raise exception |
|
685 | 695 | |
|
686 | 696 | |
@@ -701,5 +711,8 b' def main(global_config, **settings):' | |||
|
701 | 711 | hgpatches.patch_largefiles_capabilities() |
|
702 | 712 | hgpatches.patch_subrepo_type_mapping() |
|
703 | 713 | |
|
714 | # init and bootstrap StatsdClient | |
|
715 | StatsdClient.setup(settings) | |
|
716 | ||
|
704 | 717 | app = HTTPApplication(settings=settings, global_config=global_config) |
|
705 | 718 | return app.wsgi_app() |
@@ -1,11 +1,27 b'' | |||
|
1 | 1 | from __future__ import absolute_import, division, unicode_literals |
|
2 | 2 | |
|
3 | import re | |
|
3 | 4 | import random |
|
4 | 5 | from collections import deque |
|
5 | 6 | from datetime import timedelta |
|
7 | from repoze.lru import lru_cache | |
|
6 | 8 | |
|
7 | 9 | from .timer import Timer |
|
8 | 10 | |
|
11 | TAG_INVALID_CHARS_RE = re.compile(r"[^\w\d_\-:/\.]", re.UNICODE) | |
|
12 | TAG_INVALID_CHARS_SUBS = "_" | |
|
13 | ||
|
14 | ||
|
15 | @lru_cache(maxsize=500) | |
|
16 | def _normalize_tags_with_cache(tag_list): | |
|
17 | return [TAG_INVALID_CHARS_RE.sub(TAG_INVALID_CHARS_SUBS, tag) for tag in tag_list] | |
|
18 | ||
|
19 | ||
|
20 | def normalize_tags(tag_list): | |
|
21 | # We have to turn our input tag list into a non-mutable tuple for it to | |
|
22 | # be hashable (and thus usable) by the @lru_cache decorator. | |
|
23 | return _normalize_tags_with_cache(tuple(tag_list)) | |
|
24 | ||
|
9 | 25 | |
|
10 | 26 | class StatsClientBase(object): |
|
11 | 27 | """A Base class for various statsd clients.""" |
@@ -20,10 +36,10 b' class StatsClientBase(object):' | |||
|
20 | 36 | def pipeline(self): |
|
21 | 37 | raise NotImplementedError() |
|
22 | 38 | |
|
23 | def timer(self, stat, rate=1): | |
|
24 | return Timer(self, stat, rate) | |
|
39 | def timer(self, stat, rate=1, tags=None): | |
|
40 | return Timer(self, stat, rate, tags) | |
|
25 | 41 | |
|
26 | def timing(self, stat, delta, rate=1): | |
|
42 | def timing(self, stat, delta, rate=1, tags=None): | |
|
27 | 43 | """ |
|
28 | 44 | Send new timing information. |
|
29 | 45 | |
@@ -32,17 +48,17 b' class StatsClientBase(object):' | |||
|
32 | 48 | if isinstance(delta, timedelta): |
|
33 | 49 | # Convert timedelta to number of milliseconds. |
|
34 | 50 | delta = delta.total_seconds() * 1000. |
|
35 | self._send_stat(stat, '%0.6f|ms' % delta, rate) | |
|
51 | self._send_stat(stat, '%0.6f|ms' % delta, rate, tags) | |
|
36 | 52 | |
|
37 | def incr(self, stat, count=1, rate=1): | |
|
53 | def incr(self, stat, count=1, rate=1, tags=None): | |
|
38 | 54 | """Increment a stat by `count`.""" |
|
39 | self._send_stat(stat, '%s|c' % count, rate) | |
|
55 | self._send_stat(stat, '%s|c' % count, rate, tags) | |
|
40 | 56 | |
|
41 | def decr(self, stat, count=1, rate=1): | |
|
57 | def decr(self, stat, count=1, rate=1, tags=None): | |
|
42 | 58 | """Decrement a stat by `count`.""" |
|
43 | self.incr(stat, -count, rate) | |
|
59 | self.incr(stat, -count, rate, tags) | |
|
44 | 60 | |
|
45 | def gauge(self, stat, value, rate=1, delta=False): | |
|
61 | def gauge(self, stat, value, rate=1, delta=False, tags=None): | |
|
46 | 62 | """Set a gauge value.""" |
|
47 | 63 | if value < 0 and not delta: |
|
48 | 64 | if rate < 1: |
@@ -53,16 +69,16 b' class StatsClientBase(object):' | |||
|
53 | 69 | pipe._send_stat(stat, '%s|g' % value, 1) |
|
54 | 70 | else: |
|
55 | 71 | prefix = '+' if delta and value >= 0 else '' |
|
56 | self._send_stat(stat, '%s%s|g' % (prefix, value), rate) | |
|
72 | self._send_stat(stat, '%s%s|g' % (prefix, value), rate, tags) | |
|
57 | 73 | |
|
58 | 74 | def set(self, stat, value, rate=1): |
|
59 | 75 | """Set a set value.""" |
|
60 | 76 | self._send_stat(stat, '%s|s' % value, rate) |
|
61 | 77 | |
|
62 | def _send_stat(self, stat, value, rate): | |
|
63 | self._after(self._prepare(stat, value, rate)) | |
|
78 | def _send_stat(self, stat, value, rate, tags=None): | |
|
79 | self._after(self._prepare(stat, value, rate, tags)) | |
|
64 | 80 | |
|
65 | def _prepare(self, stat, value, rate): | |
|
81 | def _prepare(self, stat, value, rate, tags=None): | |
|
66 | 82 | if rate < 1: |
|
67 | 83 | if random.random() > rate: |
|
68 | 84 | return |
@@ -71,7 +87,12 b' class StatsClientBase(object):' | |||
|
71 | 87 | if self._prefix: |
|
72 | 88 | stat = '%s.%s' % (self._prefix, stat) |
|
73 | 89 | |
|
74 |
re |
|
|
90 | res = '%s:%s%s' % ( | |
|
91 | stat, | |
|
92 | value, | |
|
93 | ("|#" + ",".join(normalize_tags(tags))) if tags else "", | |
|
94 | ) | |
|
95 | return res | |
|
75 | 96 | |
|
76 | 97 | def _after(self, data): |
|
77 | 98 | if data: |
@@ -21,10 +21,11 b' def safe_wraps(wrapper, *args, **kwargs)' | |||
|
21 | 21 | class Timer(object): |
|
22 | 22 | """A context manager/decorator for statsd.timing().""" |
|
23 | 23 | |
|
24 | def __init__(self, client, stat, rate=1): | |
|
24 | def __init__(self, client, stat, rate=1, tags=None): | |
|
25 | 25 | self.client = client |
|
26 | 26 | self.stat = stat |
|
27 | 27 | self.rate = rate |
|
28 | self.tags = tags | |
|
28 | 29 | self.ms = None |
|
29 | 30 | self._sent = False |
|
30 | 31 | self._start_time = None |
@@ -38,7 +39,7 b' class Timer(object):' | |||
|
38 | 39 | return f(*args, **kwargs) |
|
39 | 40 | finally: |
|
40 | 41 | elapsed_time_ms = 1000.0 * (time_now() - start_time) |
|
41 | self.client.timing(self.stat, elapsed_time_ms, self.rate) | |
|
42 | self.client.timing(self.stat, elapsed_time_ms, self.rate, self.tags) | |
|
42 | 43 | return _wrapped |
|
43 | 44 | |
|
44 | 45 | def __enter__(self): |
@@ -25,9 +25,9 b' from vcsserver.utils import safe_str' | |||
|
25 | 25 | log = logging.getLogger(__name__) |
|
26 | 26 | |
|
27 | 27 | |
|
28 |
def get_access_path( |
|
|
29 | environ = request.environ | |
|
30 | return environ.get('PATH_INFO') | |
|
28 | def get_access_path(environ): | |
|
29 | path = environ.get('PATH_INFO') | |
|
30 | return path | |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | def get_user_agent(environ): |
@@ -49,18 +49,28 b' class RequestWrapperTween(object):' | |||
|
49 | 49 | finally: |
|
50 | 50 | count = request.request_count() |
|
51 | 51 | _ver_ = vcsserver.__version__ |
|
52 | statsd = request.statsd | |
|
52 | _path = safe_str(get_access_path(request.environ)) | |
|
53 | ||
|
53 | 54 | total = time.time() - start |
|
54 | if statsd: | |
|
55 | statsd.timing('vcsserver.req.timing', total) | |
|
56 | statsd.incr('vcsserver.req.count') | |
|
57 | 55 | log.info( |
|
58 | 56 | 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s', |
|
59 | 57 | count, '127.0.0.1', request.environ.get('REQUEST_METHOD'), |
|
60 | safe_str(get_access_path(request)), total, | |
|
61 | get_user_agent(request.environ), _ver_ | |
|
58 | _path, total, get_user_agent(request.environ), _ver_ | |
|
62 | 59 | ) |
|
63 | 60 | |
|
61 | statsd = request.registry.statsd | |
|
62 | if statsd: | |
|
63 | elapsed_time_ms = 1000.0 * total | |
|
64 | statsd.timing( | |
|
65 | 'vcsserver_req_timing', elapsed_time_ms, | |
|
66 | tags=[ | |
|
67 | "path:{}".format(_path), | |
|
68 | ] | |
|
69 | ) | |
|
70 | statsd.incr( | |
|
71 | 'vcsserver_req_count', tags=[ | |
|
72 | "path:{}".format(_path), | |
|
73 | ]) | |
|
64 | 74 | return response |
|
65 | 75 | |
|
66 | 76 |
General Comments 0
You need to be logged in to leave comments.
Login now