| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Helper classes that make it easier to instrument code for monitoring.""" | 5 """Helper classes that make it easier to instrument code for monitoring.""" |
| 6 | 6 |
| 7 | 7 |
| 8 from infra_libs.ts_mon.common import metrics |
| 9 |
| 10 import time |
| 11 |
| 12 |
| 8 class ScopedIncrementCounter(object): | 13 class ScopedIncrementCounter(object): |
| 9 """Increment a counter when the wrapped code exits. | 14 """Increment a counter when the wrapped code exits. |
| 10 | 15 |
| 11 The counter will be given a 'status' = 'success' or 'failure' label whose | 16 The counter will be given a 'status' = 'success' or 'failure' label whose |
| 12 value will be set to depending on whether the wrapped code threw an exception. | 17 value will be set to depending on whether the wrapped code threw an exception. |
| 13 | 18 |
| 14 Example: | 19 Example: |
| 15 | 20 |
| 16 mycounter = Counter('foo/stuff_done') | 21 mycounter = Counter('foo/stuff_done') |
| 17 with ScopedIncrementCounter(mycounter): | 22 with ScopedIncrementCounter(mycounter): |
| (...skipping 26 matching lines...) Expand all Loading... |
| 44 self.status = None | 49 self.status = None |
| 45 return self | 50 return self |
| 46 | 51 |
| 47 def __exit__(self, exc_type, exc_value, traceback): | 52 def __exit__(self, exc_type, exc_value, traceback): |
| 48 if self.status is None: | 53 if self.status is None: |
| 49 if exc_type is None: | 54 if exc_type is None: |
| 50 self.status = self.success_value | 55 self.status = self.success_value |
| 51 else: | 56 else: |
| 52 self.status = self.failure_value | 57 self.status = self.failure_value |
| 53 self.counter.increment({self.label: self.status}) | 58 self.counter.increment({self.label: self.status}) |
| 59 |
| 60 |
| 61 class ScopedMeasureTime(object): |
| 62 """Report durations metric with status when the wrapped code exits. |
| 63 |
| 64 The metric must be CumulativeDistributionMetric with a field to set status. |
| 65 The status field will be set to 'success' or 'failure' depending on whether |
| 66 the wrapped code threw an exception. The status field values can be customized |
| 67 with constructor kwargs or by calling `set_status`. |
| 68 |
| 69 A new instance of this class should be constructed each time it is used. |
| 70 |
| 71 Example: |
| 72 |
| 73 mymetric = CumulativeDistributionMetric( |
| 74 'xxx/durations', 'duration of xxx op' |
| 75 [StringField('status')], |
| 76 bucketer=ts_mon.GeometricBucketer(10**0.04), |
| 77 units=ts_mon.MetricsDataUnits.SECONDS) |
| 78 with ScopedMeasureTime(mymetric): |
| 79 DoStuff() |
| 80 |
| 81 To set a custom label and status value: |
| 82 |
| 83 mymetric = CumulativeDistributionMetric( |
| 84 'xxx/durations', 'duration of xxx op' |
| 85 [IntegerField('response_code')], |
| 86 bucketer=ts_mon.GeometricBucketer(10**0.04), |
| 87 units=ts_mon.MetricsDataUnits.MILLISECONDS) |
| 88 with ScopedMeasureTime(mymetric, field='response_code') as sd: |
| 89 sd.set_status(404) # This custom status now won't be overwritten |
| 90 # even if exception is raised later. |
| 91 """ |
| 92 |
| 93 _UNITS_PER_SECOND = { |
| 94 metrics.MetricsDataUnits.SECONDS: 1e0, |
| 95 metrics.MetricsDataUnits.MILLISECONDS: 1e3, |
| 96 metrics.MetricsDataUnits.MICROSECONDS: 1e6, |
| 97 metrics.MetricsDataUnits.NANOSECONDS: 1e9, |
| 98 } |
| 99 |
| 100 def __init__(self, metric, field='status', success_value='success', |
| 101 failure_value='failure', time_fn=time.time): |
| 102 assert isinstance(metric, metrics.CumulativeDistributionMetric) |
| 103 assert sum(1 for spec in metric.field_spec if spec.name == field) == 1, ( |
| 104 'typo in field name `%s`?' % field) |
| 105 assert metric.units in self._UNITS_PER_SECOND, ( |
| 106 'metric\'s units (%s) is not one of %s' % |
| 107 (metric.units, self._UNITS_PER_SECOND.keys())) |
| 108 |
| 109 self._metric = metric |
| 110 self._field = field |
| 111 self._units_per_second = self._UNITS_PER_SECOND[metric.units] |
| 112 self._success_value = success_value |
| 113 self._failure_value = failure_value |
| 114 self._status = None |
| 115 self._start_timestamp = None |
| 116 self._time_fn = time_fn |
| 117 |
| 118 def set_status(self, status): |
| 119 assert self._start_timestamp is not None, ( |
| 120 'set_status must be called only inside with statement') |
| 121 self._status = status |
| 122 |
| 123 def set_failure(self): |
| 124 return self.set_status(self._failure_value) |
| 125 |
| 126 def __enter__(self): |
| 127 assert self._start_timestamp is None, ('re-use of ScopedMeasureTime ' |
| 128 'instances detected') |
| 129 self._start_timestamp = self._time_fn() |
| 130 return self |
| 131 |
| 132 def __exit__(self, exc_type, exc_value, traceback): |
| 133 elapsed_seconds = self._time_fn() - self._start_timestamp |
| 134 if self._status is None: |
| 135 if exc_type is None: |
| 136 self._status = self._success_value |
| 137 else: |
| 138 self._status = self._failure_value |
| 139 self._metric.add(elapsed_seconds * self._units_per_second, |
| 140 {self._field: self._status}) |
| OLD | NEW |