diff --git a/README.md b/README.md index db0fd3b..28290d8 100644 --- a/README.md +++ b/README.md @@ -168,11 +168,43 @@ with api.new_queue() as q: Queues by default will collect metrics until they are told to submit. You may create a queue that autosubmits based on metric volume. + ```python # Submit when the 400th metric is queued q = api.new_queue(auto_submit_count=400) ``` +## Tag Inheritance + +Tags can be inherited from the queue or connection object if `inherit_tags=True` is passed as +an attribute. If inherit_tags is not passed, but tags are added to the measurement, the measurement +tags will be the only tags added to that measurement. + +When there are tag collisions, the measurement, then the batch, then the connection is the order of +priority. + +```python +api = librato.connect('email', 'token', tags={'company': 'librato', 'service': 'app'}) + +# tags will be {'city': 'sf'} +api.submit('temperature', 80, tags={'city': 'sf'}) + +# tags will be {'city': 'sf', 'company': 'librato', 'service': 'app'} +api.submit('temperature', 80, tags={'city': 'sf'}, inherit_tags=True) + +q = api.new_queue(tags={'service':'api'}) + +# tags will be {'location': 'downstairs'} +q.add('temperature', 22.1, tags={'location': 'downstairs'}) + +# tags will be {'company': 'librato', 'service':'api'} +q.add('temperature', 23.1) + +# tags will be {'location': 'downstairs', 'company': 'librato', 'service': 'api'} +q.add('temperature', 22.1, tags={'location': 'downstairs'}, inherit_tags=True) +q.submit() +``` + ## Updating Metric Attributes You can update the information for a metric by using the `update` method, diff --git a/librato/__init__.py b/librato/__init__.py index 4f4e8bb..8592e34 100644 --- a/librato/__init__.py +++ b/librato/__init__.py @@ -284,19 +284,26 @@ def submit(self, name, value, type="gauge", **query_props): def submit_tagged(self, name, value, **query_props): payload = {'measurements': []} + payload['measurements'].append(self.create_tagged_payload(name, value, **query_props)) + self._mexe("measurements", method="POST", query_props=payload) - if self.tags: - payload['tags'] = self.tags - + def create_tagged_payload(self, name, value, **query_props): + """Create the measurement for forwarding to Librato""" measurement = { 'name': self.sanitize(name), 'value': value } + if 'tags' in query_props: + inherit_tags = query_props.pop('inherit_tags', False) + if inherit_tags: + tags = query_props.pop('tags', {}) + measurement['tags'] = dict(self.get_tags(), **tags) + elif self.tags: + measurement['tags'] = self.tags + for k, v in query_props.items(): measurement[k] = v - - payload['measurements'].append(measurement) - self._mexe("measurements", method="POST", query_props=payload) + return measurement def get(self, name, **query_props): resp = self._mexe("metrics/%s" % self.sanitize(name), method="GET", query_props=query_props) @@ -561,13 +568,7 @@ def delete_chart(self, chart_id, space_id, **query_props): # Queue # def new_queue(self, **kwargs): - tags = self.tags - if 'tags' in kwargs: - # Supplied tag set takes precedence - tags.update(kwargs.pop('tags')) - - q = Queue(self, tags=tags, **kwargs) - return q + return Queue(self, **kwargs) # # misc diff --git a/librato/queue.py b/librato/queue.py index 0ff3834..ffb1f9e 100644 --- a/librato/queue.py +++ b/librato/queue.py @@ -63,10 +63,8 @@ def add_tags(self, d): self.tags.update(d) def add(self, name, value, type='gauge', **query_props): - if len(self.tags) > 0: - query_props['tags'] = self.tags - - if 'tags' in query_props: + """add measurements to the Q""" + if 'tags' in query_props or len(self.tags) > 0: self.add_tagged(name, value, **query_props) else: nm = {} # new measurement @@ -85,6 +83,13 @@ def add_tagged(self, name, value, **query_props): nm['sum'] = value nm['count'] = 1 + # must remove the inherit_tags key for compliance with json + inherit_tags = query_props.pop('inherit_tags', False) + tags = query_props.get('tags', {}) + if inherit_tags or tags == {}: + inheritted_tags = dict(self.connection.get_tags(), **self.get_tags()) + query_props['tags'] = dict(inheritted_tags, **tags) + for pn, v in query_props.items(): nm[pn] = v @@ -134,12 +139,8 @@ def submit(self): self.connection._mexe("metrics", method="POST", query_props=c) self.chunks = [] - for c in self.tagged_chunks: - if 'tags' in c: - c['tags'] = dict(self.tags).update(c['tags']) - elif self.tags: - c['tags'] = dict(self.tags) - self.connection._mexe("measurements", method="POST", query_props=c) + for chunk in self.tagged_chunks: + self.connection._mexe("measurements", method="POST", query_props=chunk) self.tagged_chunks = [] def __enter__(self): diff --git a/tests/test_metrics.py b/tests/test_metrics.py index ce402a3..311820e 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -197,6 +197,20 @@ def test_add_in_gauge(self): assert len(gauge.measurements[src]) == 2 assert gauge.measurements[src][-1]['value'] == 1 + def test_md_inherit_tags(self): + self.conn.set_tags({'company': 'Librato', 'hi': 'four'}) + + measurement = self.conn.create_tagged_payload('user_cpu', 20.2, tags={'hi': 'five'}, inherit_tags=True) + + assert measurement['tags'] == {'hi': 'five', 'company': 'Librato'} + + def test_md_donot_inherit_tags(self): + self.conn.set_tags({'company': 'Librato', 'hi': 'four'}) + + measurement = self.conn.create_tagged_payload('user_cpu', 20.2, tags={'hi': 'five'}) + + assert measurement['tags'] == {'hi': 'five'} + def test_md_submit(self): mt1 = int(time.time()) - 5 @@ -223,7 +237,7 @@ def test_merge_tags(self): self.conn.set_tags({'company': 'Librato'}) tags = {'hostname': 'web-1'} - self.conn.submit_tagged('user_cpu', 20.2, time=mt1, tags=tags) + self.conn.submit_tagged('user_cpu', 20.2, time=mt1, tags=tags, inherit_tags=True) # Ensure 'company' and 'hostname' tags made it through for tags_search in ["hostname=web-1", "company=Librato"]: diff --git a/tests/test_queue.py b/tests/test_queue.py index cc43728..c63cf7b 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -43,6 +43,56 @@ def test_inherited_tags(self): assert len(measurements) == 1 assert measurements[0]['value'] == 10 + def test_inherit_connection_level_tags(self): + """test if top level tags are ignored when passing measurement level tags""" + conn = librato.connect('user_test', 'key_test', tags={'sky': 'blue'}) + + q = conn.new_queue() + q.add_tagged('user_cpu', 10, tags={"hi": "five"}, inherit_tags=True) + + measurements = q.tagged_chunks[0]['measurements'] + + assert len(measurements) == 1 + assert measurements[0].get('tags', {}) == {'sky': 'blue', 'hi': 'five'} + + def test_ignore_connection_queue_level_tags(self): + """test if queue level tags are ignored when passing measurement level tags""" + conn = librato.connect('user_test', 'key_test', tags={'sky': 'blue'}) + + q = conn.new_queue(tags={"service": "api"}) + q.add_tagged('user_cpu', 10, tags={"hi": "five"}) + measurements = q.tagged_chunks[0]['measurements'] + + assert len(measurements) == 1 + assert measurements[0].get('tags', {}) == {'hi': 'five'} + + q.submit() + + resp = self.conn.get_tagged('user_cpu', duration=60, tags_search="sky=blue") + assert len(resp['series']) == 0 + + def test_inherit_queue_connection_level_tags(self): + """test if queue level tags are ignored when passing measurement level tags""" + conn = librato.connect('user_test', 'key_test', tags={'sky': 'blue', 'company': 'Librato'}) + + q = conn.new_queue(tags={"service": "api", "hi": "four", "sky": "red"}) + q.add_tagged('user_cpu', 100, tags={"hi": "five"}, inherit_tags=True) + measurements = q.tagged_chunks[0]['measurements'] + + assert len(measurements) == 1 + assert measurements[0].get('tags', {}) == {'sky': 'red', 'service': 'api', 'hi': 'five', 'company': 'Librato'} + + def test_inherit_queue_level_tags(self): + """test if queue level tags are ignored when passing measurement level tags""" + conn = librato.connect('user_test', 'key_test') + + q = conn.new_queue(tags={"service": "api", "hi": "four"}) + q.add_tagged('user_cpu', 100, tags={"hi": "five"}, inherit_tags=True) + measurements = q.tagged_chunks[0]['measurements'] + + assert len(measurements) == 1 + assert measurements[0].get('tags', {}) == {'service': 'api', 'hi': 'five'} + def test_constructor_tags(self): conn = librato.connect('user_test', 'key_test', tags={'sky': 'blue'}) q = conn.new_queue(tags={'sky': 'red', 'coal': 'black'}) @@ -243,7 +293,7 @@ def test_md_measurement_level_tag(self): q.set_tags({'hostname': 'web-1'}) mt1 = int(time.time()) - 5 - q.add_tagged('system_cpu', 33.22, time=mt1, tags={"user": "james"}) + q.add_tagged('system_cpu', 33.22, time=mt1, tags={"user": "james"}, inherit_tags=True) q.submit() # Ensure both tags get submitted