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 | from pyramid.response import Response |
|
37 | from pyramid.response import Response | |
38 |
|
38 | |||
39 | from vcsserver.utils import safe_int |
|
39 | from vcsserver.utils import safe_int | |
|
40 | from vcsserver.lib.statsd_client import StatsdClient | |||
40 |
|
41 | |||
41 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
42 |
|
43 | |||
@@ -243,6 +244,9 b' class HTTPApplication(object):' | |||||
243 | self._sanitize_settings_and_apply_defaults(settings) |
|
244 | self._sanitize_settings_and_apply_defaults(settings) | |
244 |
|
245 | |||
245 | self.config = Configurator(settings=settings) |
|
246 | self.config = Configurator(settings=settings) | |
|
247 | # Init our statsd at very start | |||
|
248 | self.config.registry.statsd = StatsdClient.statsd | |||
|
249 | ||||
246 | self.global_config = global_config |
|
250 | self.global_config = global_config | |
247 | self.config.include('vcsserver.lib.rc_cache') |
|
251 | self.config.include('vcsserver.lib.rc_cache') | |
248 |
|
252 | |||
@@ -359,10 +363,6 b' class HTTPApplication(object):' | |||||
359 | 'vcsserver.lib.request_counter.get_request_counter', |
|
363 | 'vcsserver.lib.request_counter.get_request_counter', | |
360 | 'request_count') |
|
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 | def wsgi_app(self): |
|
366 | def wsgi_app(self): | |
367 | return self.config.make_wsgi_app() |
|
367 | return self.config.make_wsgi_app() | |
368 |
|
368 | |||
@@ -397,6 +397,12 b' class HTTPApplication(object):' | |||||
397 | log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s', |
|
397 | log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s', | |
398 | method, call_args, kwargs, context_uid, repo_state_uid) |
|
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 | return payload, remote, method, args, kwargs |
|
406 | return payload, remote, method, args, kwargs | |
401 |
|
407 | |||
402 | def vcs_view(self, request): |
|
408 | def vcs_view(self, request): | |
@@ -681,6 +687,10 b' class HTTPApplication(object):' | |||||
681 | log.error( |
|
687 | log.error( | |
682 | 'error occurred handling this request for path: %s, \n tb: %s', |
|
688 | 'error occurred handling this request for path: %s, \n tb: %s', | |
683 | request.path, traceback_info) |
|
689 | request.path, traceback_info) | |
|
690 | ||||
|
691 | statsd = request.registry.statsd | |||
|
692 | if statsd: | |||
|
693 | statsd.incr('vcsserver_exception') | |||
684 | raise exception |
|
694 | raise exception | |
685 |
|
695 | |||
686 |
|
696 | |||
@@ -701,5 +711,8 b' def main(global_config, **settings):' | |||||
701 | hgpatches.patch_largefiles_capabilities() |
|
711 | hgpatches.patch_largefiles_capabilities() | |
702 | hgpatches.patch_subrepo_type_mapping() |
|
712 | hgpatches.patch_subrepo_type_mapping() | |
703 |
|
713 | |||
|
714 | # init and bootstrap StatsdClient | |||
|
715 | StatsdClient.setup(settings) | |||
|
716 | ||||
704 | app = HTTPApplication(settings=settings, global_config=global_config) |
|
717 | app = HTTPApplication(settings=settings, global_config=global_config) | |
705 | return app.wsgi_app() |
|
718 | return app.wsgi_app() |
@@ -1,11 +1,27 b'' | |||||
1 | from __future__ import absolute_import, division, unicode_literals |
|
1 | from __future__ import absolute_import, division, unicode_literals | |
2 |
|
2 | |||
|
3 | import re | |||
3 | import random |
|
4 | import random | |
4 | from collections import deque |
|
5 | from collections import deque | |
5 | from datetime import timedelta |
|
6 | from datetime import timedelta | |
|
7 | from repoze.lru import lru_cache | |||
6 |
|
8 | |||
7 | from .timer import Timer |
|
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 | class StatsClientBase(object): |
|
26 | class StatsClientBase(object): | |
11 | """A Base class for various statsd clients.""" |
|
27 | """A Base class for various statsd clients.""" | |
@@ -20,10 +36,10 b' class StatsClientBase(object):' | |||||
20 | def pipeline(self): |
|
36 | def pipeline(self): | |
21 | raise NotImplementedError() |
|
37 | raise NotImplementedError() | |
22 |
|
38 | |||
23 | def timer(self, stat, rate=1): |
|
39 | def timer(self, stat, rate=1, tags=None): | |
24 | return Timer(self, stat, rate) |
|
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 | Send new timing information. |
|
44 | Send new timing information. | |
29 |
|
45 | |||
@@ -32,17 +48,17 b' class StatsClientBase(object):' | |||||
32 | if isinstance(delta, timedelta): |
|
48 | if isinstance(delta, timedelta): | |
33 | # Convert timedelta to number of milliseconds. |
|
49 | # Convert timedelta to number of milliseconds. | |
34 | delta = delta.total_seconds() * 1000. |
|
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 | """Increment a stat by `count`.""" |
|
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 | """Decrement a stat by `count`.""" |
|
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 | """Set a gauge value.""" |
|
62 | """Set a gauge value.""" | |
47 | if value < 0 and not delta: |
|
63 | if value < 0 and not delta: | |
48 | if rate < 1: |
|
64 | if rate < 1: | |
@@ -53,16 +69,16 b' class StatsClientBase(object):' | |||||
53 | pipe._send_stat(stat, '%s|g' % value, 1) |
|
69 | pipe._send_stat(stat, '%s|g' % value, 1) | |
54 | else: |
|
70 | else: | |
55 | prefix = '+' if delta and value >= 0 else '' |
|
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 | def set(self, stat, value, rate=1): |
|
74 | def set(self, stat, value, rate=1): | |
59 | """Set a set value.""" |
|
75 | """Set a set value.""" | |
60 | self._send_stat(stat, '%s|s' % value, rate) |
|
76 | self._send_stat(stat, '%s|s' % value, rate) | |
61 |
|
77 | |||
62 | def _send_stat(self, stat, value, rate): |
|
78 | def _send_stat(self, stat, value, rate, tags=None): | |
63 | self._after(self._prepare(stat, value, rate)) |
|
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 | if rate < 1: |
|
82 | if rate < 1: | |
67 | if random.random() > rate: |
|
83 | if random.random() > rate: | |
68 | return |
|
84 | return | |
@@ -71,7 +87,12 b' class StatsClientBase(object):' | |||||
71 | if self._prefix: |
|
87 | if self._prefix: | |
72 | stat = '%s.%s' % (self._prefix, stat) |
|
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 | def _after(self, data): |
|
97 | def _after(self, data): | |
77 | if data: |
|
98 | if data: |
@@ -21,10 +21,11 b' def safe_wraps(wrapper, *args, **kwargs)' | |||||
21 | class Timer(object): |
|
21 | class Timer(object): | |
22 | """A context manager/decorator for statsd.timing().""" |
|
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 | self.client = client |
|
25 | self.client = client | |
26 | self.stat = stat |
|
26 | self.stat = stat | |
27 | self.rate = rate |
|
27 | self.rate = rate | |
|
28 | self.tags = tags | |||
28 | self.ms = None |
|
29 | self.ms = None | |
29 | self._sent = False |
|
30 | self._sent = False | |
30 | self._start_time = None |
|
31 | self._start_time = None | |
@@ -38,7 +39,7 b' class Timer(object):' | |||||
38 | return f(*args, **kwargs) |
|
39 | return f(*args, **kwargs) | |
39 | finally: |
|
40 | finally: | |
40 | elapsed_time_ms = 1000.0 * (time_now() - start_time) |
|
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 | return _wrapped |
|
43 | return _wrapped | |
43 |
|
44 | |||
44 | def __enter__(self): |
|
45 | def __enter__(self): |
@@ -25,9 +25,9 b' from vcsserver.utils import safe_str' | |||||
25 | log = logging.getLogger(__name__) |
|
25 | log = logging.getLogger(__name__) | |
26 |
|
26 | |||
27 |
|
27 | |||
28 |
def get_access_path( |
|
28 | def get_access_path(environ): | |
29 | environ = request.environ |
|
29 | path = environ.get('PATH_INFO') | |
30 | return environ.get('PATH_INFO') |
|
30 | return path | |
31 |
|
31 | |||
32 |
|
32 | |||
33 | def get_user_agent(environ): |
|
33 | def get_user_agent(environ): | |
@@ -49,18 +49,28 b' class RequestWrapperTween(object):' | |||||
49 | finally: |
|
49 | finally: | |
50 | count = request.request_count() |
|
50 | count = request.request_count() | |
51 | _ver_ = vcsserver.__version__ |
|
51 | _ver_ = vcsserver.__version__ | |
52 | statsd = request.statsd |
|
52 | _path = safe_str(get_access_path(request.environ)) | |
|
53 | ||||
53 | total = time.time() - start |
|
54 | total = time.time() - start | |
54 | if statsd: |
|
|||
55 | statsd.timing('vcsserver.req.timing', total) |
|
|||
56 | statsd.incr('vcsserver.req.count') |
|
|||
57 | log.info( |
|
55 | log.info( | |
58 | 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s', |
|
56 | 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s', | |
59 | count, '127.0.0.1', request.environ.get('REQUEST_METHOD'), |
|
57 | count, '127.0.0.1', request.environ.get('REQUEST_METHOD'), | |
60 | safe_str(get_access_path(request)), total, |
|
58 | _path, total, get_user_agent(request.environ), _ver_ | |
61 | 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 | return response |
|
74 | return response | |
65 |
|
75 | |||
66 |
|
76 |
General Comments 0
You need to be logged in to leave comments.
Login now