@@ -78,6 +78,12 @@ def register(self, class_path):
7878 except ImportError as e :
7979 logger .warning ("Could not register %s metricset: %s" , class_path , compat .text_type (e ))
8080
81+ def get_metricset (self , class_path ):
82+ try :
83+ return self ._metricsets [class_path ]
84+ except KeyError :
85+ raise MetricSetNotFound (class_path )
86+
8187 def collect (self ):
8288 """
8389 Collect metrics from all registered metric sets and queues them for sending
@@ -104,48 +110,64 @@ def _stop_collect_timer(self):
104110class MetricsSet (object ):
105111 def __init__ (self , registry ):
106112 self ._lock = threading .Lock ()
107- self ._counters = {}
108- self ._gauges = {}
113+ self ._counters = defaultdict (dict )
114+ self ._gauges = defaultdict (dict )
115+ self ._timers = defaultdict (dict )
109116 self ._registry = registry
110117
111- def counter (self , name , ** labels ):
118+ def counter (self , name , reset_on_collect = False , ** labels ):
112119 """
113120 Returns an existing or creates and returns a new counter
114121 :param name: name of the counter
122+ :param reset_on_collect: indicate if the counter should be reset to 0 when collecting
115123 :param labels: a flat key/value map of labels
116124 :return: the counter object
117125 """
118- labels = self ._labels_to_key (labels )
119- key = (name , labels )
120- with self ._lock :
121- if key not in self ._counters :
122- if self ._registry ._ignore_patterns and any (
123- pattern .match (name ) for pattern in self ._registry ._ignore_patterns
124- ):
125- counter = noop_metric
126- else :
127- counter = Counter (name )
128- self ._counters [key ] = counter
129- return self ._counters [key ]
126+ return self ._metric (self ._counters , Counter , name , reset_on_collect , labels )
130127
131- def gauge (self , name , ** labels ):
128+ def gauge (self , name , reset_on_collect = False , ** labels ):
132129 """
133130 Returns an existing or creates and returns a new gauge
134131 :param name: name of the gauge
132+ :param reset_on_collect: indicate if the gouge should be reset to 0 when collecting
133+ :param labels: a flat key/value map of labels
135134 :return: the gauge object
136135 """
136+ return self ._metric (self ._gauges , Gauge , name , reset_on_collect , labels )
137+
138+ def timer (self , name , reset_on_collect = False , ** labels ):
139+ """
140+ Returns an existing or creates and returns a new timer
141+ :param name: name of the timer
142+ :param reset_on_collect: indicate if the timer should be reset to 0 when collecting
143+ :param labels: a flat key/value map of labels
144+ :return: the timer object
145+ """
146+ return self ._metric (self ._timers , Timer , name , reset_on_collect , labels )
147+
148+ def _metric (self , container , metric_class , name , reset_on_collect , labels ):
149+ """
150+ Returns an existing or creates and returns a metric
151+ :param container: the container for the metric
152+ :param metric_class: the class of the metric
153+ :param name: name of the metric
154+ :param reset_on_collect: indicate if the metric should be reset to 0 when collecting
155+ :param labels: a flat key/value map of labels
156+ :return: the metric object
157+ """
158+
137159 labels = self ._labels_to_key (labels )
138160 key = (name , labels )
139161 with self ._lock :
140- if key not in self . _gauges :
162+ if key not in container :
141163 if self ._registry ._ignore_patterns and any (
142164 pattern .match (name ) for pattern in self ._registry ._ignore_patterns
143165 ):
144- gauge = noop_metric
166+ metric = noop_metric
145167 else :
146- gauge = Gauge (name )
147- self . _gauges [key ] = gauge
148- return self . _gauges [key ]
168+ metric = metric_class (name , reset_on_collect = reset_on_collect )
169+ container [key ] = metric
170+ return container [key ]
149171
150172 def collect (self ):
151173 """
@@ -166,16 +188,28 @@ def collect(self):
166188 for (name , labels ), c in compat .iteritems (self ._counters ):
167189 if c is not noop_metric :
168190 samples [labels ].update ({name : {"value" : c .val }})
191+ if c .reset_on_collect :
192+ c .reset ()
169193 if self ._gauges :
170194 for (name , labels ), g in compat .iteritems (self ._gauges ):
171195 if g is not noop_metric :
172196 samples [labels ].update ({name : {"value" : g .val }})
197+ if g .reset_on_collect :
198+ g .reset ()
199+ if self ._timers :
200+ for (name , labels ), t in compat .iteritems (self ._timers ):
201+ if t is not noop_metric :
202+ val , count = t .val
203+ samples [labels ].update ({name + ".sum.us" : {"value" : int (val * 1000000 )}})
204+ samples [labels ].update ({name + ".count" : {"value" : count }})
205+ if t .reset_on_collect :
206+ t .reset ()
173207 if samples :
174208 for labels , sample in compat .iteritems (samples ):
175209 result = {"samples" : sample , "timestamp" : timestamp }
176210 if labels :
177211 result ["tags" ] = {k : v for k , v in labels }
178- yield result
212+ yield self . before_yield ( result )
179213
180214 def before_collect (self ):
181215 """
@@ -184,22 +218,39 @@ def before_collect(self):
184218 """
185219 pass
186220
221+ def before_yield (self , data ):
222+ return data
223+
187224 def _labels_to_key (self , labels ):
188225 return tuple ((k , compat .text_type (v )) for k , v in sorted (compat .iteritems (labels )))
189226
190227
228+ class SpanBoundMetricSet (MetricsSet ):
229+ def before_yield (self , data ):
230+ tags = data .get ("tags" , None )
231+ if tags :
232+ span_type , span_subtype = tags .pop ("span.type" , None ), tags .pop ("span.subtype" , "" )
233+ if span_type or span_subtype :
234+ data ["span" ] = {"type" : span_type , "subtype" : span_subtype }
235+ transaction_name , transaction_type = tags .pop ("transaction.name" , None ), tags .pop ("transaction.type" , None )
236+ if transaction_name or transaction_type :
237+ data ["transaction" ] = {"name" : transaction_name , "type" : transaction_type }
238+ return data
239+
240+
191241class Counter (object ):
192- __slots__ = ("label " , "_lock" , "_initial_value" , "_val" )
242+ __slots__ = ("name " , "_lock" , "_initial_value" , "_val" , "reset_on_collect " )
193243
194- def __init__ (self , label , initial_value = 0 ):
244+ def __init__ (self , name , initial_value = 0 , reset_on_collect = False ):
195245 """
196246 Creates a new counter
197- :param label: label of the counter
247+ :param name: name of the counter
198248 :param initial_value: initial value of the counter, defaults to 0
199249 """
200- self .label = label
250+ self .name = name
201251 self ._lock = threading .Lock ()
202252 self ._val = self ._initial_value = initial_value
253+ self .reset_on_collect = reset_on_collect
203254
204255 def inc (self , delta = 1 ):
205256 """
@@ -237,15 +288,16 @@ def val(self):
237288
238289
239290class Gauge (object ):
240- __slots__ = ("label " , "_val" )
291+ __slots__ = ("name " , "_val" , "reset_on_collect " )
241292
242- def __init__ (self , label ):
293+ def __init__ (self , name , reset_on_collect = False ):
243294 """
244295 Creates a new gauge
245- :param label : label of the gauge
296+ :param name : label of the gauge
246297 """
247- self .label = label
298+ self .name = name
248299 self ._val = None
300+ self .reset_on_collect = reset_on_collect
249301
250302 @property
251303 def val (self ):
@@ -255,6 +307,35 @@ def val(self):
255307 def val (self , value ):
256308 self ._val = value
257309
310+ def reset (self ):
311+ self ._val = 0
312+
313+
314+ class Timer (object ):
315+ __slots__ = ("name" , "_val" , "_count" , "_lock" , "reset_on_collect" )
316+
317+ def __init__ (self , name = None , reset_on_collect = False ):
318+ self .name = name
319+ self ._val = 0
320+ self ._count = 0
321+ self ._lock = threading .Lock ()
322+ self .reset_on_collect = reset_on_collect
323+
324+ def update (self , duration , count = 1 ):
325+ with self ._lock :
326+ self ._val += duration
327+ self ._count += count
328+
329+ def reset (self ):
330+ with self ._lock :
331+ self ._val = 0
332+ self ._count = 0
333+
334+ @property
335+ def val (self ):
336+ with self ._lock :
337+ return self ._val , self ._count
338+
258339
259340class NoopMetric (object ):
260341 """
@@ -285,3 +366,8 @@ def reset(self):
285366
286367
287368noop_metric = NoopMetric ("noop" )
369+
370+
371+ class MetricSetNotFound (LookupError ):
372+ def __init__ (self , class_path ):
373+ super (MetricSetNotFound , self ).__init__ ("%s metric set not found" % class_path )
0 commit comments