From a5d37e771e93c82a9e6fca412d5867695401610c Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Mon, 23 Jan 2017 11:53:14 -0500 Subject: [PATCH 01/10] make interface tag only --- librato/__init__.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/librato/__init__.py b/librato/__init__.py index 898b191..03d4400 100644 --- a/librato/__init__.py +++ b/librato/__init__.py @@ -267,18 +267,7 @@ def list_all_metrics(self, **query_props): if len(metric_list) < page_size: break - def submit(self, name, value, type="gauge", **query_props): - if 'tags' in query_props: - self.submit_tagged(name, value, **query_props) - else: - payload = {'gauges': [], 'counters': []} - metric = {'name': self.sanitize(name), 'value': value} - for k, v in query_props.items(): - metric[k] = v - payload[type + 's'].append(metric) - self._mexe("metrics", method="POST", query_props=payload) - - def submit_tagged(self, name, value, **query_props): + def submit(self, name, value, **query_props): payload = {'measurements': []} if self.tags: @@ -295,16 +284,7 @@ def submit_tagged(self, name, value, **query_props): self._mexe("measurements", method="POST", query_props=payload) def get(self, name, **query_props): - resp = self._mexe("metrics/%s" % self.sanitize(name), method="GET", query_props=query_props) - if resp['type'] == 'gauge': - return Gauge.from_dict(self, resp) - elif resp['type'] == 'counter': - return Counter.from_dict(self, resp) - else: - raise Exception('The server sent me something that is not a Gauge nor a Counter.') - - def get_tagged(self, name, **query_props): - """Fetches multi-dimensional metrics""" + """Fetches metric data""" if 'resolution' not in query_props: # Default to raw resolution query_props['resolution'] = 1 @@ -320,15 +300,17 @@ def get_tagged(self, name, **query_props): return self._mexe("measurements/%s" % self.sanitize(name), method="GET", query_props=query_props) def get_composite(self, compose, **query_props): + """Get a composite result""" if 'resolution' not in query_props: # Default to raw resolution query_props['resolution'] = 1 if 'start_time' not in query_props: raise Exception("You must provide a 'start_time'") query_props['compose'] = compose - return self._mexe("metrics", method="GET", query_props=query_props) + return self._mexe("measurements", method="GET", query_props=query_props) def create_composite(self, name, compose, **query_props): + """Create a composite""" query_props['composite'] = compose query_props['type'] = 'composite' return self.update(name, **query_props) From 4c495450729992e5c8163d15a9cb3d01cd8fa798 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Mon, 23 Jan 2017 13:03:23 -0500 Subject: [PATCH 02/10] transition aggregator to tag only mode --- librato/aggregator.py | 40 ++++++---------------------------------- tests/mock_connection.py | 6 ++++-- tests/test_aggregator.py | 16 +--------------- 3 files changed, 11 insertions(+), 51 deletions(-) diff --git a/librato/aggregator.py b/librato/aggregator.py index 78d6eb2..f819a13 100644 --- a/librato/aggregator.py +++ b/librato/aggregator.py @@ -40,7 +40,6 @@ def __init__(self, connection, **args): # Global tags, which apply to MD metrics only self.tags = dict(args.get('tags', {})) self.measurements = {} - self.tagged_measurements = {} self.period = args.get('period') self.measure_time = args.get('measure_time') @@ -75,25 +74,6 @@ def add(self, name, value): return self.measurements - def add_tagged(self, name, value): - if name not in self.tagged_measurements: - self.tagged_measurements[name] = { - 'count': 1, - 'sum': value, - 'min': value, - 'max': value - } - else: - m = self.tagged_measurements[name] - m['sum'] += value - m['count'] += 1 - if value < m['min']: - m['min'] = value - if value > m['max']: - m['max'] = value - - return self.tagged_measurements - def to_payload(self): # Map measurements into Librato POST (array) format # { @@ -135,9 +115,9 @@ def to_md_payload(self): # } body = [] - for metric_name in self.tagged_measurements: - # Create a clone so we don't change self.tagged_measurements - vals = dict(self.tagged_measurements[metric_name]) + for metric_name in self.measurements: + # Create a clone so we don't change self.measurements + vals = dict(self.measurements[metric_name]) vals["name"] = metric_name body.append(vals) @@ -179,19 +159,11 @@ def floor_measure_time(self): def clear(self): self.measurements = {} - self.tagged_measurements = {} self.measure_time = None def submit(self): - # Submit any legacy or tagged measurements to API - # This will actually return an empty 200 response (no body) - if self.measurements: - self.connection._mexe("metrics", - method="POST", - query_props=self.to_payload()) - if self.tagged_measurements: - self.connection._mexe("measurements", - method="POST", - query_props=self.to_md_payload()) + self.connection._mexe("measurements", + method="POST", + query_props=self.to_md_payload()) # Clear measurements self.clear() diff --git a/tests/mock_connection.py b/tests/mock_connection.py index 4b550a0..f63f8d1 100644 --- a/tests/mock_connection.py +++ b/tests/mock_connection.py @@ -74,8 +74,10 @@ def create_tagged_measurements(self, payload): if 'value' in metric: value = metric['value'] elif 'sum' in metric: - if 'count' not in metric or metric['count'] != 1: - raise Exception('mock_connection only supports a count value of one') + # REVIEW + #if 'count' not in metric or metric['count'] != 1: + if 'count' not in metric: + raise Exception('mock_connection only supports a count value of one', metric) value = metric['sum'] else: raise Exception('md submit payload must provide value or sum/count attributes') diff --git a/tests/test_aggregator.py b/tests/test_aggregator.py index e22662c..b6425e3 100644 --- a/tests/test_aggregator.py +++ b/tests/test_aggregator.py @@ -30,7 +30,7 @@ def test_constructor_tags(self): assert 'sky' in tags assert tags['sky'] == 'blue' - def test_add_tags(self): + def test_add(self): agg = Aggregator(self.conn, tags={'sky': 'blue'}) agg.add_tags({'sky': 'red', 'coal': 'black'}) tags = agg.get_tags() @@ -226,19 +226,5 @@ def test_floored_measure_time_in_payload(self): self.agg.period = 60 assert self.agg.to_payload()['measure_time'] == 1418838360 - def test_submit_side_by_side(self): - # Tagged and untagged measurements should be handled as separate - self.agg.add_tags({'hostname': 'web-1'}) - self.agg.add('test.metric', 42) - self.agg.add_tagged('test.metric', 10) - self.agg.submit() - - gauge = self.conn.get('test.metric', duration=60) - assert len(gauge.measurements['unassigned']) == 1 - - resp = self.conn.get_tagged('test.metric', duration=60, tags_search="hostname=web-1") - assert len(resp['series']) == 1 - - if __name__ == '__main__': unittest.main() From 6fd6980acfd7d0e374be56d8d2bd7fe1377fca4a Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Mon, 23 Jan 2017 17:26:16 -0500 Subject: [PATCH 03/10] fix metrics tests --- librato/__init__.py | 11 +++- librato/metrics.py | 19 ------ tests/mock_connection.py | 9 +-- tests/test_metrics.py | 130 ++++----------------------------------- 4 files changed, 28 insertions(+), 141 deletions(-) diff --git a/librato/__init__.py b/librato/__init__.py index 03d4400..deba482 100644 --- a/librato/__init__.py +++ b/librato/__init__.py @@ -35,7 +35,7 @@ import email.message from librato import exceptions from librato.queue import Queue -from librato.metrics import Gauge, Counter +from librato.metrics import Gauge from librato.alerts import Alert, Service from librato.annotations import Annotation from librato.spaces import Space, Chart @@ -283,6 +283,15 @@ def submit(self, name, value, **query_props): payload['measurements'].append(measurement) self._mexe("measurements", method="POST", query_props=payload) + def get_metric(self, name, **query_props): + resp = self._mexe("metrics/%s" % self.sanitize(name), method="GET", query_props=query_props) + if resp['type'] == 'gauge': + return Gauge.from_dict(self, resp) + elif resp['type'] == 'counter': + return Counter.from_dict(self, resp) + else: + raise Exception('The server sent me something that is not a Gauge nor a Counter.') + def get(self, name, **query_props): """Fetches metric data""" if 'resolution' not in query_props: diff --git a/librato/metrics.py b/librato/metrics.py index c1ad3bd..2c3c5a3 100644 --- a/librato/metrics.py +++ b/librato/metrics.py @@ -67,24 +67,5 @@ def __repr__(self): class Gauge(Metric): - """Librato Gauge metric""" - def add(self, value, source=None, **params): - """Add a new measurement to this gauge""" - if source: - params['source'] = source - return self.connection.submit(self.name, value, type="gauge", **params) - def what_am_i(self): return 'gauges' - - -class Counter(Metric): - """Librato Counter metric""" - def add(self, value, source=None, **params): - if source: - params['source'] = source - - return self.connection.submit(self.name, value, type="counter", **params) - - def what_am_i(self): - return 'counters' diff --git a/tests/mock_connection.py b/tests/mock_connection.py index f63f8d1..12b46b2 100644 --- a/tests/mock_connection.py +++ b/tests/mock_connection.py @@ -68,6 +68,10 @@ def create_tagged_measurements(self, payload): def_time = payload.get('time', int(time.time())) for metric in payload['measurements']: name = metric['name'] + desc = '' + if 'description' in metric: + desc = metric['description'] + self.add_metric_to_store({"name": name, "description": desc}, 'gauge') mt = metric.get('time', def_time) @@ -75,7 +79,7 @@ def create_tagged_measurements(self, payload): value = metric['value'] elif 'sum' in metric: # REVIEW - #if 'count' not in metric or metric['count'] != 1: + # if 'count' not in metric or metric['count'] != 1: if 'count' not in metric: raise Exception('mock_connection only supports a count value of one', metric) value = metric['sum'] @@ -135,9 +139,6 @@ def get_instrument(self, uri): else: return json.dumps(self.instruments[int(_id)]).encode('utf-8') - def __an_empty_list_metrics(self): - answer = {} - def create_alert(self, payload): self.last_a_id += 1 payload["id"] = self.last_a_id diff --git a/tests/test_metrics.py b/tests/test_metrics.py index bd5232e..494569d 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -61,34 +61,13 @@ def test_list_metrics_adding_gauge(self): assert metrics[1].name == 'gauge_2' assert metrics[1].description == 'desc 2' - def test_list_metrics_adding_counter_metrics(self): - self.conn.submit('c1', 10, 'counter', description='counter desc 1') - self.conn.submit('c2', 20, 'counter', description='counter desc 2') - # Get all metrics - metrics = self.conn.list_metrics() - - assert len(metrics) == 2 - - assert isinstance(metrics[0], librato.metrics.Counter) - assert metrics[0].name == 'c1' - assert metrics[0].description == 'counter desc 1' - - assert isinstance(metrics[1], librato.metrics.Counter) - assert metrics[1].name == 'c2' - assert metrics[1].description == 'counter desc 2' - - def test_list_metrics_adding_one_counter_one_gauge(self): + def test_list_metrics_adding_one_gauge(self): self.conn.submit('gauge1', 10) - self.conn.submit('counter2', 20, type='counter', description="desc c2") # Get all metrics metrics = self.conn.list_metrics() assert isinstance(metrics[0], librato.metrics.Gauge) assert metrics[0].name == 'gauge1' - assert isinstance(metrics[1], librato.metrics.Counter) - assert metrics[1].name == 'counter2' - assert metrics[1].description == 'desc c2' - def test_deleting_a_gauge(self): self.conn.submit('test', 100) assert len(self.conn.list_metrics()) == 1 @@ -102,96 +81,30 @@ def test_deleting_a_batch_of_gauges(self): self.conn.delete(['test', 'test2']) assert len(self.conn.list_metrics()) == 0 - def test_deleting_a_counter(self): - self.conn.submit('test', 200, type='counter') - assert len(self.conn.list_metrics()) == 1 - self.conn.delete('test') - assert len(self.conn.list_metrics()) == 0 - def test_get_gauge_basic(self): - name, desc = '1', 'desc 1' - self.conn.submit(name, 10, description=desc) - gauge = self.conn.get(name) + name, desc, tags = 'my_metric', 'desc 1', {'city': 'austin'} + self.conn.submit(name, 10, description=desc, tags=tags) + gauge = self.conn.get_metric(name, duration=60) assert isinstance(gauge, librato.metrics.Gauge) assert gauge.name == name assert gauge.description == desc - assert len(gauge.measurements['unassigned']) == 1 - assert gauge.measurements['unassigned'][0]['value'] == 10 - - def test_get_counter_basic(self): - name, desc = 'counter1', 'count desc 1' - self.conn.submit(name, 20, type='counter', description=desc) - counter = self.conn.get(name) - assert isinstance(counter, librato.metrics.Counter) - assert counter.name == name - assert counter.description == desc - assert len(counter.measurements['unassigned']) == 1 - assert counter.measurements['unassigned'][0]['value'] == 20 - - def test_send_single_measurements_for_gauge_with_source(self): - name, desc, src = 'Test', 'A Test Gauge.', 'from_source' - self.conn.submit(name, 10, description=desc, source=src) - gauge = self.conn.get(name) - assert gauge.name == name - assert gauge.description == desc - assert len(gauge.measurements[src]) == 1 - assert gauge.measurements[src][0]['value'] == 10 - - def test_send_single_measurements_for_counter_with_source(self): - name, desc, src = 'Test', 'A Test Counter.', 'from_source' - self.conn.submit(name, 111, type='counter', description=desc, source=src) - counter = self.conn.get(name) - assert counter.name == name - assert counter.description == desc - assert len(counter.measurements[src]) == 1 - assert counter.measurements[src][0]['value'] == 111 - - def test_add_in_counter(self): - name, desc, src = 'Test', 'A Test Counter.', 'from_source' - self.conn.submit(name, 111, type='counter', description=desc, source=src) - counter = self.conn.get(name) - assert counter.name == name - assert counter.description == desc - assert len(counter.measurements[src]) == 1 - assert counter.measurements[src][0]['value'] == 111 - - counter.add(1, source=src) - - counter = self.conn.get(name) - assert counter.name == name - assert counter.description == desc - assert len(counter.measurements[src]) == 2 - assert counter.measurements[src][-1]['value'] == 1 - - def test_add_in_gauge(self): - name, desc, src = 'Test', 'A Test Gauge.', 'from_source' - self.conn.submit(name, 10, description=desc, source=src) - gauge = self.conn.get(name) - assert gauge.name == name - assert gauge.description == desc - assert len(gauge.measurements[src]) == 1 - assert gauge.measurements[src][0]['value'] == 10 - gauge.add(1, source=src) - - gauge = self.conn.get(name) - assert gauge.name == name - assert gauge.description == desc - assert len(gauge.measurements[src]) == 2 - assert gauge.measurements[src][-1]['value'] == 1 + data = self.conn.get(name, duration=60, tags=tags) + assert len(data['series']) == 1 + assert data['series'][0]['measurements'][0]['value'] == 10 - def test_md_submit(self): + def test_submit(self): mt1 = int(time.time()) - 5 tags = {'hostname': 'web-1'} - self.conn.submit_tagged('user_cpu', 20.2, time=mt1, tags=tags) + self.conn.submit('user_cpu', 20.2, time=mt1, tags=tags) - resp = self.conn.get_tagged('user_cpu', duration=60, tags_search="hostname=web-1") + resp = self.conn.get('user_cpu', duration=60, tags_search="hostname=web-1") assert len(resp['series']) == 1 assert resp['series'][0].get('tags', {}) == tags # Same query using tags param instead - resp = self.conn.get_tagged('user_cpu', duration=60, tags={'hostname': 'web-1'}) + resp = self.conn.get('user_cpu', duration=60, tags={'hostname': 'web-1'}) assert len(resp['series']) == 1 assert resp['series'][0].get('tags', {}) == tags @@ -206,11 +119,11 @@ 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('user_cpu', 20.2, time=mt1, tags=tags) # Ensure 'company' and 'hostname' tags made it through for tags_search in ["hostname=web-1", "company=Librato"]: - resp = self.conn.get_tagged('user_cpu', duration=60, tags_search=tags_search) + resp = self.conn.get('user_cpu', duration=60, tags_search=tags_search) assert len(resp['series']) == 1 @@ -220,22 +133,5 @@ def test_merge_tags(self): assert measurements[0]['time'] == mt1 assert measurements[0]['value'] == 20.2 - def test_submit_transparent_tagging(self): - mt1 = int(time.time()) - 5 - - tags = {'hostname': 'web-1'} - self.conn.submit('user_cpu', 20.2, time=mt1, tags=tags) - - resp = self.conn.get_tagged('user_cpu', duration=60, tags_search="hostname=web-1") - - assert len(resp['series']) == 1 - assert resp['series'][0].get('tags', {}) == tags - - measurements = resp['series'][0]['measurements'] - assert len(measurements) == 1 - - assert measurements[0]['time'] == mt1 - assert measurements[0]['value'] == 20.2 - if __name__ == '__main__': unittest.main() From 1db837fe73516956547df5c4114e6bf68b8f587e Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Tue, 24 Jan 2017 11:14:53 -0500 Subject: [PATCH 04/10] fix rest of tests --- README.md | 12 +-- librato/__init__.py | 2 - librato/queue.py | 43 ++------- tests/mock_connection.py | 2 +- tests/test_aggregator.py | 3 +- tests/test_queue.py | 194 ++++++++++++++++----------------------- 6 files changed, 88 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index 80b5248..2a15c53 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ python-librato A Python wrapper for the Librato Metrics API. -NOTE: Starting in version 3, we have deprecated Dashboards and Instruments in favor of Spaces and Charts. - ## Installation In your shell: @@ -64,12 +62,6 @@ Let's now create a Metric: By default ```submit()``` will create a gauge metric. The metric will be created automatically by the server if it does not exist -To create a counter metric (note: counters are expected to be *absolute* counters and take a monotonically increasing value such as network throughput): - -```python - api.submit("connections", 20, type="counter", description="server connections") -``` - To iterate over your metric names: ```python @@ -154,8 +146,6 @@ api = librato.connect('email', 'token') q = api.new_queue() q.add('temperature', 22.1, source='upstairs') q.add('temperature', 23.1, source='dowstairs') -q.add('num_requests', 100, type='counter', source='server1') -q.add('num_requests', 102, type='counter', source='server2') q.submit() ``` @@ -170,7 +160,7 @@ api = librato.connect('email', 'token') with api.new_queue() as q: q.add('temperature', 22.1, source='upstairs') potentially_dangerous_operation() - q.add('num_requests', 100, type='counter', source='server1') + q.add('num_requests', 100, source='server1') ``` Queues by default will collect metrics until they are told to submit. You may create a queue diff --git a/librato/__init__.py b/librato/__init__.py index deba482..d49c455 100644 --- a/librato/__init__.py +++ b/librato/__init__.py @@ -287,8 +287,6 @@ def get_metric(self, name, **query_props): resp = self._mexe("metrics/%s" % self.sanitize(name), method="GET", query_props=query_props) if resp['type'] == 'gauge': return Gauge.from_dict(self, resp) - elif resp['type'] == 'counter': - return Counter.from_dict(self, resp) else: raise Exception('The server sent me something that is not a Gauge nor a Counter.') diff --git a/librato/queue.py b/librato/queue.py index 0ff3834..ddc9a5d 100644 --- a/librato/queue.py +++ b/librato/queue.py @@ -62,24 +62,7 @@ def set_tags(self, d): 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: - self.add_tagged(name, value, **query_props) - else: - nm = {} # new measurement - nm['name'] = self.connection.sanitize(name) - nm['value'] = value - - for pn, v in query_props.items(): - nm[pn] = v - - self._add_measurement(type, nm) - self._auto_submit_if_necessary() - - def add_tagged(self, name, value, **query_props): + def add(self, name, value, **query_props): nm = {} # new measurement nm['name'] = self.connection.sanitize(name) nm['sum'] = value @@ -92,26 +75,12 @@ def add_tagged(self, name, value, **query_props): self._auto_submit_if_necessary() def add_aggregator(self, aggregator): - cloned_measurements = dict(aggregator.measurements) - # Find measure_time, if any mt = aggregator.get_measure_time() - for name in cloned_measurements: - nm = cloned_measurements[name] - # Set metric name - nm['name'] = name - # Set measure_time - if mt: - nm['measure_time'] = mt - # Set source - if aggregator.source: - nm['source'] = aggregator.source - self._add_measurement('gauge', nm) - - tagged_measurements = dict(aggregator.tagged_measurements) - for name in tagged_measurements: - nm = tagged_measurements[name] + measurements = dict(aggregator.measurements) + for name in measurements: + nm = measurements[name] nm['name'] = name if mt: @@ -165,13 +134,13 @@ def _add_tagged_measurement(self, nm): self.tagged_chunks.append({'measurements': []}) self.tagged_chunks[-1]['measurements'].append(nm) - def _current_chunk(self, tagged=False): + def _current_chunk(self, tagged=True): if tagged: return self.tagged_chunks[-1] if self.tagged_chunks else None else: return self.chunks[-1] if self.chunks else None - def _num_measurements_in_current_chunk(self, tagged=False): + def _num_measurements_in_current_chunk(self, tagged=True): if tagged: if self.tagged_chunks: return len(self.tagged_chunks[-1]['measurements']) diff --git a/tests/mock_connection.py b/tests/mock_connection.py index 12b46b2..09b6e9f 100644 --- a/tests/mock_connection.py +++ b/tests/mock_connection.py @@ -68,7 +68,7 @@ def create_tagged_measurements(self, payload): def_time = payload.get('time', int(time.time())) for metric in payload['measurements']: name = metric['name'] - desc = '' + desc = None if 'description' in metric: desc = metric['description'] self.add_metric_to_store({"name": name, "description": desc}, 'gauge') diff --git a/tests/test_aggregator.py b/tests/test_aggregator.py index b6425e3..e147e42 100644 --- a/tests/test_aggregator.py +++ b/tests/test_aggregator.py @@ -103,7 +103,7 @@ def test_add_multiple_metrics(self): assert meas['min'] == 42 assert meas['max'] == 44 - # Only gauges are supported (not counters) + # Only gauges are supported def test_to_payload(self): self.agg.source = 'mysource' self.agg.add('test.metric', 42) @@ -115,7 +115,6 @@ def test_to_payload(self): 'source': 'mysource' } assert 'gauges' in self.agg.to_payload() - assert 'counters' not in self.agg.to_payload() def test_to_payload_no_source(self): self.agg.source = None diff --git a/tests/test_queue.py b/tests/test_queue.py index cc43728..a93a4a1 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -30,11 +30,11 @@ def test_inherited_tags(self): assert conn.get_tags() == {'sky': 'blue'} q = conn.new_queue() - q.add_tagged('user_cpu', 10) + q.add('user_cpu', 10) q.submit() # Measurement must inherit 'sky' tag from connection - resp = self.conn.get_tagged('user_cpu', duration=60, tags_search="sky=blue") + resp = self.conn.get('user_cpu', duration=60, tags_search="sky=blue") assert len(resp['series']) == 1 assert resp['series'][0].get('tags', {}) == conn.get_tags() @@ -79,22 +79,13 @@ def test_set_tags(self): def test_single_measurement_gauge(self): q = self.q q.add('temperature', 22.1) - assert len(q.chunks) == 1 + assert len(q.tagged_chunks) == 1 assert q._num_measurements_in_current_chunk() == 1 def test_default_type_measurement(self): q = self.q q.add('temperature', 22.1) - assert len(q._current_chunk()['gauges']) == 1 - assert len(q._current_chunk()['counters']) == 0 - - def test_single_measurement_counter(self): - q = self.q - q.add('num_requests', 2000, type='counter') - assert len(q.chunks) == 1 - assert q._num_measurements_in_current_chunk() == 1 - assert len(q._current_chunk()['gauges']) == 0 - assert len(q._current_chunk()['counters']) == 1 + assert len(q._current_chunk()['measurements']) == 1 def test_num_metrics_in_queue(self): q = self.q @@ -104,7 +95,7 @@ def test_num_metrics_in_queue(self): assert q._num_measurements_in_queue() == 290 # Now ensure multiple chunks for _ in range(100): - q.add('num_requests', randint(100, 300), type='counter') + q.add('num_requests', randint(100, 300)) assert q._num_measurements_in_queue() == 390 def test_auto_submit_on_metric_count(self): @@ -123,111 +114,125 @@ def test_reach_chunk_limit(self): q = self.q for i in range(1, q.MAX_MEASUREMENTS_PER_CHUNK + 1): q.add('temperature', randint(20, 30)) - assert len(q.chunks) == 1 + assert len(q.tagged_chunks) == 1 assert q._num_measurements_in_current_chunk() == q.MAX_MEASUREMENTS_PER_CHUNK q.add('temperature', 40) # damn is pretty hot :) assert q._num_measurements_in_current_chunk() == 1 - assert len(q.chunks) == 2 + assert len(q.tagged_chunks) == 2 def test_submit_context_manager(self): + tags = {"host": "machine1"} try: with self.conn.new_queue() as q: - q.add('temperature', 32) + q.add('temperature', 32, tags=tags) raise ValueError except ValueError: - gauge = self.conn.get('temperature', resolution=1, count=2) - assert gauge.name == 'temperature' - assert gauge.description is None - assert len(gauge.measurements['unassigned']) == 1 + metric = self.conn.get_metric('temperature') + data = self.conn.get('temperature', resolution=1, count=2, duration=60, tags=tags) + assert metric.name == 'temperature' + assert metric.description is None + assert len(data['series'][0]['measurements']) == 1 def test_submit_one_measurement_batch_mode(self): + tags = {'city': 'barcelona'} q = self.q - q.add('temperature', 22.1) + q.add('temperature', 22.1, tags=tags) q.submit() metrics = self.conn.list_metrics() assert len(metrics) == 1 - gauge = self.conn.get('temperature', resolution=1, count=2) - assert gauge.name == 'temperature' - assert gauge.description is None - assert len(gauge.measurements['unassigned']) == 1 + metric = self.conn.get_metric('temperature', resolution=1, count=2) + assert metric.name == 'temperature' + assert metric.description is None + data = self.conn.get('temperature', resolution=1, count=2, duration=60, tags=tags) + assert len(data['series'][0]['measurements']) == 1 # Add another measurements for temperature - q.add('temperature', 23) + q.add('temperature', 23, tags=tags) q.submit() metrics = self.conn.list_metrics() assert len(metrics) == 1 - gauge = self.conn.get('temperature', resolution=1, count=2) - assert gauge.name == 'temperature' - assert gauge.description is None - assert len(gauge.measurements['unassigned']) == 2 - assert gauge.measurements['unassigned'][0]['value'] == 22.1 - assert gauge.measurements['unassigned'][1]['value'] == 23 + metric = self.conn.get_metric('temperature', resolution=1, count=2) + assert metric.name == 'temperature' + assert metric.description is None + data = self.conn.get('temperature', resolution=1, count=2, duration=60, tags=tags) + assert len(data['series'][0]['measurements']) == 2 + assert data['series'][0]['measurements'][0]['value'] == 22.1 + assert data['series'][0]['measurements'][1]['value'] == 23 def test_submit_tons_of_measurement_batch_mode(self): q = self.q metrics = self.conn.list_metrics() assert len(metrics) == 0 + tags = {'city': 'barcelona'} for t in range(1, q.MAX_MEASUREMENTS_PER_CHUNK + 1): - q.add('temperature', t) + q.add('temperature', t, tags=tags) q.submit() + metrics = self.conn.list_metrics() assert len(metrics) == 1 - gauge = self.conn.get('temperature', resolution=1, count=q.MAX_MEASUREMENTS_PER_CHUNK + 1) - assert gauge.name == 'temperature' - assert gauge.description is None + m = self.conn.get_metric('temperature', resolution=1, count=q.MAX_MEASUREMENTS_PER_CHUNK + 1) + assert m.name == 'temperature' + assert m.description is None + + data = self.conn.get('temperature', resolution=1, duration=60, tags=tags) + measurements = data['series'][0]['measurements'] for t in range(1, q.MAX_MEASUREMENTS_PER_CHUNK + 1): - assert gauge.measurements['unassigned'][t - 1]['value'] == t + assert measurements[t - 1]['value'] == t + tags = {'host': 'pollux'} for cl in range(1, q.MAX_MEASUREMENTS_PER_CHUNK + 1): - q.add('cpu_load', cl) + q.add('cpu_load', cl, tags=tags) q.submit() + metrics = self.conn.list_metrics() assert len(metrics) == 2 - gauge = self.conn.get('cpu_load', resolution=1, count=q.MAX_MEASUREMENTS_PER_CHUNK + 1) - assert gauge.name == 'cpu_load' - assert gauge.description is None + + m = self.conn.get_metric('cpu_load', resolution=1, count=q.MAX_MEASUREMENTS_PER_CHUNK + 1) + assert m.name == 'cpu_load' + assert m.description is None + + data = self.conn.get('cpu_load', resolution=1, duration=60, tags=tags) + measurements = data['series'][0]['measurements'] for t in range(1, q.MAX_MEASUREMENTS_PER_CHUNK + 1): - assert gauge.measurements['unassigned'][t - 1]['value'] == t + assert measurements[t - 1]['value'] == t def test_add_aggregator(self): q = self.q - metrics = self.conn.list_metrics() - a = Aggregator(self.conn, source='mysource', period=10) + tags = {'sky': 'blue'} + a = Aggregator(self.conn, tags=tags, measure_time=123) a.add('foo', 42) a.add('bar', 37) q.add_aggregator(a) - gauges = q.chunks[0]['gauges'] - names = [g['name'] for g in gauges] - - assert len(q.chunks) == 1 - + measurements = q.tagged_chunks[0]['measurements'] + names = [g['name'] for g in measurements] + assert len(q.tagged_chunks) == 1 assert 'foo' in names assert 'bar' in names - # All gauges should have the same source - assert gauges[0]['source'] == 'mysource' - assert gauges[1]['source'] == 'mysource' + assert measurements[0]['tags'] == tags + assert measurements[1]['tags'] == tags # All gauges should have the same measure_time - assert 'measure_time' in gauges[0] - assert 'measure_time' in gauges[1] + assert 'time' in measurements[0] + assert 'time' in measurements[1] - # Test that time was snapped to 10s - assert gauges[0]['measure_time'] % 10 == 0 + assert measurements[0]['time'] == 123 + assert measurements[1]['time'] == 123 - def test_md_submit(self): + def test_submit(self): q = self.q - q.set_tags({'hostname': 'web-1'}) + tags = {'hostname': 'web-1'} + q.set_tags(tags) mt1 = int(time.time()) - 5 - q.add_tagged('system_cpu', 3.2, time=mt1) + q.add('system_cpu', 3.2, time=mt1) assert q._num_measurements_in_queue() == 1 q.submit() - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search="hostname=web-1") + resp = self.conn.get('system_cpu', duration=60, tags_search="hostname=web-1") assert len(resp['series']) == 1 assert resp['series'][0].get('tags', {}) == {'hostname': 'web-1'} @@ -238,17 +243,17 @@ def test_md_submit(self): assert measurements[0]['time'] == mt1 assert measurements[0]['value'] == 3.2 - def test_md_measurement_level_tag(self): + def test_measurement_level_tag(self): q = self.q 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('system_cpu', 33.22, time=mt1, tags={"user": "james"}) q.submit() # Ensure both tags get submitted for tag_search in ["hostname=web-1", "user=james"]: - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search=tag_search) + resp = self.conn.get('system_cpu', duration=60, tags_search=tag_search) assert len(resp['series']) == 1 @@ -263,14 +268,14 @@ def test_md_measurement_level_tag_supersedes(self): q.set_tags({'hostname': 'web-1'}) mt1 = int(time.time()) - 5 - q.add_tagged('system_cpu', 33.22, time=mt1, tags={"hostname": "web-2"}) + q.add('system_cpu', 33.22, time=mt1, tags={"hostname": "web-2"}) q.submit() # Ensure measurement-level tag takes precedence - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search="hostname=web-1") + resp = self.conn.get('system_cpu', duration=60, tags_search="hostname=web-1") assert len(resp['series']) == 0 - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search="hostname=web-2") + resp = self.conn.get('system_cpu', duration=60, tags_search="hostname=web-2") assert len(resp['series']) == 1 measurements = resp['series'][0]['measurements'] @@ -279,64 +284,23 @@ def test_md_measurement_level_tag_supersedes(self): assert measurements[0]['time'] == mt1 assert measurements[0]['value'] == 33.22 - def test_side_by_side(self): - # Ensure tagged and untagged measurements are handled independently - q = self.conn.new_queue(tags={'hostname': 'web-1'}) - - q.add('system_cpu', 10) - q.add_tagged('system_cpu', 20) - q.submit() - - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search="hostname=web-1") - assert len(resp['series']) == 1 - - measurements = resp['series'][0]['measurements'] - assert len(measurements) == 2 - assert measurements[0]['value'] == 10 - assert measurements[1]['value'] == 20 - - def test_md_auto_submit_on_metric_count(self): + def test_auto_submit_on_metric_count_2(self): q = self.conn.new_queue(auto_submit_count=2) - q.add('untagged_cpu', 10) - q.add_tagged('tagged_cpu', 20, tags={'hostname': 'web-2'}) + q.add('tagged_cpu', 10, tags={'hostname': 'web-2'}) + q.add('tagged_cpu', 20, tags={'hostname': 'web-2'}) - assert q._num_measurements_in_queue() == 0 - - gauge = self.conn.get('untagged_cpu', duration=60) - assert len(gauge.measurements['unassigned']) == 1 - - resp = self.conn.get_tagged('tagged_cpu', duration=60, tags_search="hostname=web-2") - assert len(resp['series']) == 1 + resp = self.conn.get('tagged_cpu', duration=60, tags_search="hostname=web-2") + assert len(resp['series'][0]['measurements']) == 2 - def queue_send_as_md_when_queue_has_tags(self): + def queue_send_as_when_queue_has_tags(self): q = self.conn.new_queue(tags={'foo': 1}) q.add('a_metric', 10) assert q._num_measurements_in_queue() == 1 - resp = self.conn.get_tagged('a_metric', duration=60, tags_search="foo=1") + resp = self.conn.get('a_metric', duration=60, tags_search="foo=1") assert len(resp['series']) == 1 - def test_transparent_submit_md(self): - q = self.q - tags = {'hostname': 'web-1'} - - mt1 = int(time.time()) - 5 - q.add('system_cpu', 3.2, time=mt1, tags=tags) - assert q._num_measurements_in_queue() == 1 - q.submit() - - resp = self.conn.get_tagged('system_cpu', duration=60, tags_search="hostname=web-1") - - assert len(resp['series']) == 1 - assert resp['series'][0].get('tags', {}) == {'hostname': 'web-1'} - - measurements = resp['series'][0]['measurements'] - assert len(measurements) == 1 - - assert measurements[0]['time'] == mt1 - assert measurements[0]['value'] == 3.2 - if __name__ == '__main__': unittest.main() From a618bafa7fdbf988505deb35cae2542f5a8365d5 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Tue, 24 Jan 2017 16:19:30 -0500 Subject: [PATCH 05/10] Update README --- README.md | 122 +++++++++++++++----------------------------- librato/__init__.py | 10 +++- librato/metrics.py | 2 - 3 files changed, 49 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 2a15c53..1fbdd4e 100644 --- a/README.md +++ b/README.md @@ -56,75 +56,56 @@ transparent pagination. Let's now create a Metric: ```python - api.submit("temperature", 10, description="temperature at home") + api.submit("temperature", 10, tags={'city': 'Barcelona'}) ``` -By default ```submit()``` will create a gauge metric. The metric will be -created automatically by the server if it does not exist +The metric will be created automatically by the server if it does not exist. To iterate over your metric names: ```python - for m in api.list_metrics(): - print "%s: %s" % (m.name, m.description) + for m in api.list_metrics(): + print "%s: %s" % (m.name, m.description) ``` To retrieve a specific metric: ```python - # Retrieve metric metadata ONLY - gauge = api.get("temperature") - gauge.name # "temperature" - gauge.description # "temperature at home" - gauge.measurements # {} - # Retrive metric with the last measurement seen - gauge = api.get("temperature", count=1, resolution=1) - gauge.measurements - # {u'unassigned': [{u'count': 1, u'sum_squares': 100.0, u'min': 10.0, u'measure_time': 1474988647, u'max': 10.0, u'sum': 10.0, u'value': 10.0}]} + tags = {"city": "Barcelona} + metric = api.get_metric("temperature") + metric.name # "temperature" + + # Retrieve the last measurements seen on that metric + data = api.get("temperature", duration=60, tags=tags) + data = self.api.get(name, duration=60, tags=tags) + data['series'][0]['measurements'] ``` Iterate over measurements: ```python - metric = api.get("temperature", count=100, resolution=1) - source = 'unassigned' - for m in metric.measurements[source]: - print "%s: %s" % (m['value'], m['measure_time']) + data = api.get("temperature", duration=60, tags=tags) + data = self.api.get(name, duration=60, tags=tags) + data['series'][0]['measurements'] + for m in data['series'][0]['measurements']: + print m ``` -Notice a couple of things here. First, we are using the key `unassigned` since -we have not associated our measurements to any source. If we had specified a -source such as `sf` we could use it in the same fashion. Read more the -[API documentation](https://www.librato.com/docs/api/). In addition, notice how -we are passing the count and resolution parameters to make sure the API -returns measurements in its answer and not only the metric properties. -Read more about them [here](https://www.librato.com/docs/api/#retrieve-metric-by-name). - To retrieve a composite metric: ```python - # Get average temperature across all cities for last 8 hours - compose = 'mean(s("temperature", "*", {function: "mean", period: "3600"}))' - import time - start_time = int(time.time()) - 8 * 3600 - resp = api.get_composite(compose, start_time=start_time) - resp['measurements'][0]['series'] - # [ - # {u'measure_time': 1421744400, u'value': 41.23944444444444}, - # {u'measure_time': 1421748000, u'value': 40.07611111111111}, - # {u'measure_time': 1421751600, u'value': 38.77444444444445}, - # {u'measure_time': 1421755200, u'value': 38.05833333333333}, - # {u'measure_time': 1421758800, u'value': 37.983333333333334}, - # {u'measure_time': 1421762400, u'value': 38.93333333333333}, - # {u'measure_time': 1421766000, u'value': 40.556666666666665} - # ] + compose = 'mean(s("' + name + '", "*", {function: "mean", period: "3600"}))' + import time + start_time = int(time.time()) - 3600 + data = self.api.get_composite(compose, start_time=start_time) + for m in data['series'][0]['measurements']: + print m ``` To create a saved composite metric: ```python - api.create_composite('humidity', 'sum(s("all.*", "*"))', - description='a test composite') + api.create_composite('humidity', 'sum(s("all.*", "*"))', description='a test composite') ``` Delete a metric: @@ -142,13 +123,12 @@ in batch mode. We push measurements that are stored and when we are ready, they will be submitted in an efficient manner. Here is an example: ```python -api = librato.connect('email', 'token') -q = api.new_queue() -q.add('temperature', 22.1, source='upstairs') -q.add('temperature', 23.1, source='dowstairs') -q.submit() + q = api.new_queue() + q.add('temperature', 12, tags={'city': 'sf' , 'station': '12'}) + q.add('temperature', 14, tags={'city': 'new york', 'station': '1'}) + q.add('temperature', 22, tags={'city': 'austin' , 'station': '112'}) + q.submit() ``` - Queues can also be used as context managers. Once the context block is complete the queue is submitted automatically. This is true even if an exception interrupts flow. In the example below if ```potentially_dangerous_operation``` causes an exception the queue will @@ -158,40 +138,18 @@ If the operation succeeds both measurements will be submitted. ```python api = librato.connect('email', 'token') with api.new_queue() as q: - q.add('temperature', 22.1, source='upstairs') + q.add('temperature', 22.1, tags={'city': 'sf', 'station': '12'}) potentially_dangerous_operation() - q.add('num_requests', 100, source='server1') + q.add('num_requests', tags={'city': 'austin' , 'station': '112'}) ``` Queues by default will collect metrics until they are told to submit. You may create a queue that autosubmits based on metric volume. ```python -api = librato.connect('email', 'token') -# Submit when the 400th metric is queued -q = api.new_queue(auto_submit_count=400) -``` - -## Submitting tagged measurements - -NOTE: **Tagged measurements are only available in the Tags Beta. Please [contact Librato support](mailto:support@librato.com) to join the beta.** - -We can use tags in the submit method in order to associate key value pairs with our -measurements: - -```python - api.submit("temperature", 22, tags={'city': 'austin', 'station': '27'}) -``` - -Queues also support tags. When adding measurements to a queue, we can associate tags to them -in the same way we do with the submit method: - -```python - q = api.new_queue() - q.add('temperature', 12, tags={'city': 'sf' , 'station': '12'}) - q.add('temperature', 14, tags={'city': 'new york', 'station': '1'}) - q.add('temperature', 22, tags={'city': 'austin' , 'station': '112'}) - q.submit() + api = librato.connect('email', 'token') + # Submit when the 400th metric is queued + q = api.new_queue(auto_submit_count=400) ``` ## Updating Metric Attributes @@ -200,12 +158,10 @@ You can update the information for a metric by using the `update` method, for example: ```python -api = librato.connect('email', 'token') -for metric in api.list_metrics(name=" "): - gauge = api.get(metric.name) - attrs = gauge.attributes - attrs['display_units_long'] = 'ms' - api.update(metric.name, attributes=attrs) + metric = self.api.get_metric("temperature") + attrs = metric.attributes + attrs['description'] = "A temperature metric" + self.api.update(name, attributes=attrs) ``` ## Annotations @@ -244,6 +200,7 @@ doesn't exist). Title is a required parameter, and all other parameters are opti ```python api.post_annotation("testing",title="foobarbiz") +####################### UPDATE ########################## api.post_annotation("TravisCI",title="build %s"%travisBuildID, source="SystemSource", description="Application %s, Travis build %s"%(appName,travisBuildID), @@ -290,6 +247,7 @@ space.delete() ### Create a Chart ```python # Create a Chart directly via API (defaults to line chart) +############################################################# UPDATE space = api.find_space('Production') chart = api.create_chart( 'cpu', diff --git a/librato/__init__.py b/librato/__init__.py index d49c455..5f0e702 100644 --- a/librato/__init__.py +++ b/librato/__init__.py @@ -248,12 +248,14 @@ def add_tags(self, d): # def list_metrics(self, **query_props): """List a page of metrics""" + from librato.metrics import Metric resp = self._mexe("metrics", query_props=query_props) return self._parse(resp, "metrics", Metric) def list_all_metrics(self, **query_props): """List all avaliable metrics""" + if 'length' not in query_props: query_props['length'] = 100 if 'offset' not in query_props: @@ -268,6 +270,8 @@ def list_all_metrics(self, **query_props): break def submit(self, name, value, **query_props): + """Send measurements for a metric""" + payload = {'measurements': []} if self.tags: @@ -284,6 +288,8 @@ def submit(self, name, value, **query_props): self._mexe("measurements", method="POST", query_props=payload) def get_metric(self, name, **query_props): + """Get a metric definition""" + resp = self._mexe("metrics/%s" % self.sanitize(name), method="GET", query_props=query_props) if resp['type'] == 'gauge': return Gauge.from_dict(self, resp) @@ -323,9 +329,11 @@ def create_composite(self, name, compose, **query_props): return self.update(name, **query_props) def update(self, name, **query_props): + """update a metric""" return self._mexe("metrics/%s" % self.sanitize(name), method="PUT", query_props=query_props) def delete(self, names): + """delete a metric or a group of metrics""" if isinstance(names, six.string_types): names = self.sanitize(names) else: @@ -443,9 +451,9 @@ def get_space(self, id, **query_props): return Space.from_dict(self, resp) def find_space(self, name): + """Find specific space by Name""" if type(name) is int: raise ValueError("This method expects name as a parameter, %s given" % name) - """Find specific space by Name""" spaces = self.list_spaces(name=name) # Find the Space by name (case-insensitive) # This returns the first space found matching the name diff --git a/librato/metrics.py b/librato/metrics.py index 2c3c5a3..640131c 100644 --- a/librato/metrics.py +++ b/librato/metrics.py @@ -49,8 +49,6 @@ def from_dict(cls, connection, data): which is usually from librato's API""" if data.get('type') == "gauge": cls = Gauge - elif data.get('type') == "counter": - cls = Counter obj = cls(connection, data['name']) obj.period = data['period'] From 64857b2557104db7b5fdeb0815a8503e4ae6d8a2 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Wed, 25 Jan 2017 11:26:39 -0500 Subject: [PATCH 06/10] update integrations --- tests/integration.py | 102 ++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/tests/integration.py b/tests/integration.py index 15476e9..ebe4906 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -28,7 +28,8 @@ import os from random import randint import time -logging.basicConfig(level=logging.INFO) +#logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) class TestLibratoBase(unittest.TestCase): @@ -42,6 +43,7 @@ def setUpClass(cls): """ Ensure user really wants to run these tests """ are_you_sure = os.environ.get('LIBRATO_ALLOW_INTEGRATION_TESTS') + print are_you_sure assert are_you_sure == 'Y', "INTEGRATION TESTS WILL DELETE METRICS " \ "IN YOUR ACCOUNT!!! " \ "If you are absolutely sure that you want to run tests "\ @@ -59,19 +61,26 @@ def wait_for_replication(self): time.sleep(1) +def tags(): + return {'key1': 'value1'} + + +def m_time(t): + return int(time.time() - t) + + class TestLibratoBasic(TestLibratoBase): def test_list_metrics(self): metrics = self.conn.list_metrics() - def _add_and_verify_metric(self, name, value, desc, connection=None, type='gauge'): + def _add_and_verify_metric(self, name, value, tags, connection=None): if not connection: connection = self.conn - connection.submit(name, value, type=type, description=desc) + connection.submit(name, value, tags=tags) self.wait_for_replication() - metric = connection.get(name) - assert metric and metric.name == connection.sanitize(name) - assert metric.description == desc + metric = connection.get(name, duration=60, tags_search="foo=bar") + assert metric and metric['name'] == connection.sanitize(name) return metric def _delete_and_verify_metric(self, names, connection=None): @@ -87,58 +96,53 @@ def _delete_and_verify_metric(self, names, connection=None): assert(metric is None) def test_long_sanitized_metric(self): - name, desc = 'a' * 256, 'Too long, will error' + name = 'a' * 256 with self.assertRaises(BadRequest): - self._add_and_verify_metric(name, 10, desc, self.conn) - self._add_and_verify_metric(name, 10, desc, self.conn_sanitize) + self._add_and_verify_metric(name, 10, tags(), self.conn) + self._add_and_verify_metric(name, 10, tags(), self.conn_sanitize) self._delete_and_verify_metric(name, self.conn_sanitize) def test_invalid_sanitized_metric(self): - name, desc = r'I AM #*@#@983221 CRazy((\\\\] invalid', 'Crazy invalid' + name = r'I AM #*@#@983221 CRazy((\\\\] invalid' with self.assertRaises(BadRequest): - self._add_and_verify_metric(name, 10, desc, self.conn) - self._add_and_verify_metric(name, 10, desc, self.conn_sanitize) + self._add_and_verify_metric(name, 10, tags(), self.conn) + self._add_and_verify_metric(name, 10, tags(), self.conn_sanitize) self._delete_and_verify_metric(name, self.conn_sanitize) def test_create_and_delete_gauge(self): - name, desc = 'Test', 'Test Gauge to be removed' - self._add_and_verify_metric(name, 10, desc) - self._delete_and_verify_metric(name) - - def test_create_and_delete_counter(self): - name, desc = 'Test_counter', 'Test Counter to be removed' - self._add_and_verify_metric(name, 10, desc, type='counter') + name = 'Test' + self._add_and_verify_metric(name, 10, tags()) self._delete_and_verify_metric(name) def test_batch_delete(self): - name_one, desc_one = 'Test_one', 'Test gauge to be removed' - name_two, desc_two = 'Test_two', 'Test counter to be removed' - self._add_and_verify_metric(name_one, 10, desc_one) - self._add_and_verify_metric(name_two, 10, desc_two, type='counter') + name_one = 'Test_one' + name_two = 'Test_two' + self._add_and_verify_metric(name_one, 10, tags()) + self._add_and_verify_metric(name_two, 10, tags()) self._delete_and_verify_metric([name_one, name_two]) def test_save_gauge_metrics(self): - name, desc = 'Test', 'Test Counter to be removed' - self.conn.submit(name, 10, description=desc) - self.conn.submit(name, 20, description=desc) + name = 'Test' + self.conn.submit(name, 10, tags=tags()) + self.conn.submit(name, 20, tags=tags()) self.conn.delete(name) def test_send_batch_gauge_measurements(self): - q = self.conn.new_queue() + q = self.conn.new_queue(tags=tags()) for t in range(1, 10): q.add('temperature', randint(20, 40)) q.submit() for t in range(1, 10): - q.add('temperature', randint(20, 40), measure_time=time.time() + t) + q.add('temperature', randint(20, 40), time=m_time(t)) q.submit() for t in range(1, 10): - q.add('temperature', randint(20, 40), source='upstairs', measure_time=time.time() + t) + q.add('temperature', randint(20, 40), tags={'k1': 'v2'}, time=m_time(t)) q.submit() for t in range(1, 50): - q.add('temperature', randint(20, 30), source='downstairs', measure_time=time.time() + t) + q.add('temperature', randint(20, 30), tags={'k2': 'v3'}, time=m_time(t)) q.submit() self.conn.delete('temperature') @@ -146,7 +150,7 @@ def test_batch_sanitation(self): name_one, name_two = 'a' * 500, r'DSJAK#32102391S,m][][[{{]\\' def run_batch(connection): - q = connection.new_queue() + q = connection.new_queue(tags=tags()) q.add(name_one, 10) q.add(name_two, 10) q.submit() @@ -160,39 +164,28 @@ def run_batch(connection): def test_submit_empty_queue(self): self.conn.new_queue().submit() - def test_send_batch_counter_measurements(self): - q = self.conn.new_queue() - for nr in range(1, 2): - q.add('num_req', nr, type='counter', source='server1', measure_time=time.time() - 1) - q.add('num_req', nr, type='counter', source='server2', measure_time=time.time() - 1) - q.submit() - def test_update_metrics_attributes(self): - name, desc = 'Test', 'A great gauge.' - self.conn.submit(name, 10, description=desc) + name = 'Test' + self.conn.submit(name, 10, tags=tags()) self.wait_for_replication() - gauge = self.conn.get(name) + gauge = self.conn.get_metric(name) assert gauge and gauge.name == name - assert gauge.description == desc - gauge = self.conn.get(name) attrs = gauge.attributes attrs['display_min'] = 0 self.conn.update(name, attributes=attrs) - gauge = self.conn.get(name) + gauge = self.conn.get_metric(name) assert gauge.attributes['display_min'] == 0 self.conn.delete(name) def test_sanitized_update(self): - name, desc = 'a' * 1000, 'too long, really' - new_desc = 'different' - self.conn_sanitize.submit(name, 10, description=desc) - gauge = self.conn_sanitize.get(name) - assert gauge.description == desc + name = 'a' * 1000 + self.conn_sanitize.submit(name, 10, tags=tags()) + gauge = self.conn_sanitize.get_metric(name) - attrs = gauge.attributes['description'] = new_desc + attrs = gauge.attributes['description'] = 'foo' with self.assertRaises(BadRequest): self.conn.update(name, attributes=attrs) self.conn_sanitize.delete(name) @@ -207,7 +200,7 @@ def setUp(self): # Ensure metric names exist so we can create conditions on them for m in self.gauges_used_during_test: # Create or just update a gauge metric - self.conn.submit(m, 42) + self.conn.submit(m, 42, tags={'number': '1'}) def tearDown(self): for name in self.alerts_created_during_test: @@ -362,8 +355,8 @@ def test_delete_space_via_model(self): def test_create_chart(self): # Ensure metrics exist - self.conn.submit('memory.free', 100) - self.conn.submit('memory.used', 200) + self.conn.submit('memory.free', 100, tags=tags()) + self.conn.submit('memory.used', 200, tags=tags()) # Create space space = librato.Space(self.conn, 'my space') space.save() @@ -387,7 +380,7 @@ def test_create_chart(self): def test_create_big_number(self): # Ensure metrics exist - self.conn.submit('memory.free', 100) + self.conn.submit('memory.free', 100, tags=tags()) # Create space space = librato.Space(self.conn, 'my space') space.save() @@ -414,7 +407,6 @@ def test_create_big_number(self): self.assertEqual(len(chart2.streams), 1) self.assertTrue(chart2.use_last_value) - if __name__ == '__main__': # TO run a specific test: # $ nosetests tests/integration.py:TestLibratoBasic.test_update_metrics_attributes From 13cecafe5efbf69631dd78b777115dd22de79485 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Wed, 25 Jan 2017 16:47:24 -0500 Subject: [PATCH 07/10] Remove charts and spaces for now --- README.md | 139 +----------------- librato/spaces.py | 229 ----------------------------- librato/streams.py | 56 ------- tests/integration.py | 104 ------------- tests/test_charts.py | 326 ----------------------------------------- tests/test_spaces.py | 333 ------------------------------------------ tests/test_streams.py | 128 ---------------- 7 files changed, 4 insertions(+), 1311 deletions(-) delete mode 100644 librato/spaces.py delete mode 100644 librato/streams.py delete mode 100644 tests/test_charts.py delete mode 100644 tests/test_spaces.py delete mode 100644 tests/test_streams.py diff --git a/README.md b/README.md index 1fbdd4e..554baf2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ python-librato A Python wrapper for the Librato Metrics API. +## Sections + ## Installation In your shell: @@ -129,6 +131,7 @@ ready, they will be submitted in an efficient manner. Here is an example: q.add('temperature', 22, tags={'city': 'austin' , 'station': '112'}) q.submit() ``` + Queues can also be used as context managers. Once the context block is complete the queue is submitted automatically. This is true even if an exception interrupts flow. In the example below if ```potentially_dangerous_operation``` causes an exception the queue will @@ -140,7 +143,7 @@ api = librato.connect('email', 'token') with api.new_queue() as q: q.add('temperature', 22.1, tags={'city': 'sf', 'station': '12'}) potentially_dangerous_operation() - q.add('num_requests', tags={'city': 'austin' , 'station': '112'}) + q.add('num_requests', tags={'city': 'austin', 'station': '112'}) ``` Queues by default will collect metrics until they are told to submit. You may create a queue @@ -200,7 +203,6 @@ doesn't exist). Title is a required parameter, and all other parameters are opti ```python api.post_annotation("testing",title="foobarbiz") -####################### UPDATE ########################## api.post_annotation("TravisCI",title="build %s"%travisBuildID, source="SystemSource", description="Application %s, Travis build %s"%(appName,travisBuildID), @@ -213,139 +215,6 @@ Delete a named annotation stream: api.delete_annotation_stream("testing") ``` -## Spaces API -### List Spaces -```python -# List spaces -spaces = api.list_spaces() -``` - -### Create a Space -```python -# Create a new Space directly via API -space = api.create_space("space_name") -print("Created '%s'" % space.name) - -# Create a new Space via the model, passing the connection -space = Space(api, 'Production') -space.save() -``` - -### Find a Space -```python -space = api.find_space('Production') -``` - -### Delete a Space -```python -space = api.create_space('Test') -api.delete_space(space.id) -# or -space.delete() -``` - -### Create a Chart -```python -# Create a Chart directly via API (defaults to line chart) -############################################################# UPDATE -space = api.find_space('Production') -chart = api.create_chart( - 'cpu', - space, - streams=[{'metric': 'cpu.idle', 'source': '*'}] -) -``` - -```python -# Create line chart using the Space model -space = api.find_space('Production') - -# You can actually create an empty chart (default to line) -chart = space.add_chart('cpu') - -# Create a chart with all attributes -chart = space.add_chart( - 'memory', - type='line', - streams=[ - {'metric': 'memory.free', 'source': '*'}, - {'metric': 'memory.used', 'source': '*', - 'group_function': 'breakout', 'summary_function': 'average'} - ], - min=0, - max=50, - label='the y axis label', - use_log_yaxis=True, - related_space=1234 -) -``` - -```python -# Shortcut to create a line chart with a single metric on it -chart = space.add_single_line_chart('my chart', 'my.metric', '*') -chart = space.add_single_line_chart('my chart', metric='my.metric', source='*') -``` - -```python -# Shortcut to create a stacked chart with a single metric on it -chart = space.add_single_stacked_chart('my chart', 'my.metric', '*') -``` - -```python -# Create a big number chart -bn = space.add_chart( - 'memory', - type='bignumber', - streams=[{'metric': 'my.metric', 'source': '*'}] -) -# Shortcut to add big number chart -bn = space.add_bignumber_chart('My Chart', 'my.metric', '*') -bn = space.add_bignumber_chart('My Chart', 'my.metric', - source='*', - group_function='sum', - summary_function='sum', - use_last_value=True -) -``` - -### Find a Chart -```python -# Takes either space_id or a space object -chart = api.get_chart(chart_id, space_id) -chart = api.get_chart(chart_id, space) -``` - -### Update a Chart -```python -chart = api.get_chart(chart_id, space_id) -chart.min = 0 -chart.max = 50 -chart.save() -``` - -### Rename a Chart -```python -chart = api.get_chart(chart_id, space_id) -# save() gets called automatically here -chart.rename('new chart name') -``` - -### Add new metrics to a Chart -```python -chart = space.charts()[-1] -chart.new_stream('foo', '*') -chart.new_stream(metric='foo', source='*') -chart.new_stream(composite='s("foo", "*")') -chart.save() -``` - -### Delete a Chart -```python -chart = api.get_chart(chart_id, space_id) -chart.delete() -``` - - ## Alerts List all alerts: diff --git a/librato/spaces.py b/librato/spaces.py deleted file mode 100644 index 719db3a..0000000 --- a/librato/spaces.py +++ /dev/null @@ -1,229 +0,0 @@ -from librato.streams import Stream - - -class Space(object): - """Librato Space Base class""" - - def __init__(self, - connection, - name, - id=None, - chart_dicts=None, - tags=False): - self.connection = connection - self.name = name - self.chart_ids = [] - self._charts = None - self.tags = tags - for c in (chart_dicts or []): - self.chart_ids.append(c['id']) - self.id = id - - @classmethod - def from_dict(cls, connection, data): - """ - Returns a Space object from a dictionary item, - which is usually from librato's API - """ - obj = cls(connection, - data['name'], - id=data['id'], - chart_dicts=data.get('charts'), - tags=data.get('tags')) - return obj - - def get_payload(self): - return {'name': self.name} - - def persisted(self): - return self.id is not None - - def charts(self): - if self._charts is None or self._charts == []: - self._charts = self.connection.list_charts_in_space(self) - return self._charts[:] - - # New up a chart - def new_chart(self, name, **kwargs): - return Chart(self.connection, name, space_id=self.id, **kwargs) - - # New up a chart and save it - def add_chart(self, name, **kwargs): - chart = self.new_chart(name, **kwargs) - return chart.save() - - def add_line_chart(self, name, streams=[]): - return self.add_chart(name, streams=streams) - - def add_single_line_chart(self, name, metric=None, source='*', - group_function=None, summary_function=None): - stream = {'metric': metric, 'source': source} - - if group_function: - stream['group_function'] = group_function - if summary_function: - stream['summary_function'] = summary_function - return self.add_line_chart(name, streams=[stream]) - - def add_stacked_chart(self, name, streams=[]): - return self.add_chart(name, type='stacked', streams=streams) - - def add_single_stacked_chart(self, name, metric, source='*'): - stream = {'metric': metric, 'source': source} - return self.add_stacked_chart(name, streams=[stream]) - - def add_bignumber_chart(self, name, metric, source='*', - group_function='average', - summary_function='average', use_last_value=True): - stream = { - 'metric': metric, - 'source': source, - 'group_function': group_function, - 'summary_function': summary_function - } - chart = self.add_chart(name, - type='bignumber', - use_last_value=use_last_value, - streams=[stream]) - return chart - - # This currently only updates the name of the Space - def save(self): - if self.persisted(): - return self.connection.update_space(self) - else: - s = self.connection.create_space(self.name, tags=self.tags) - self.id = s.id - return s - - def rename(self, new_name): - self.name = new_name - self.save() - - def delete(self): - return self.connection.delete_space(self.id) - - -class Chart(object): - # Payload example from /spaces/123/charts/456 API - # { - # "id": 1723352, - # "name": "Hottest City", - # "type": "line", - # "streams": [ - # { - # "id": 19261984, - # "metric": "apparent_temperature", - # "type": "gauge", - # "source": "*", - # "group_function": "max", - # "summary_function": "max" - # } - # ], - # "max": 105, - # "min": 0, - # "related_space": 96893, - # "label": "The y axis label", - # "use_log_yaxis": true - # } - def __init__(self, connection, name=None, id=None, type='line', - space_id=None, streams=[], - min=None, max=None, - label=None, - use_log_yaxis=None, - use_last_value=None, - related_space=None): - self.connection = connection - self.name = name - self.type = type - self.space_id = space_id - self._space = None - self.streams = [] - self.label = label - self.min = min - self.max = max - self.use_log_yaxis = use_log_yaxis - self.use_last_value = use_last_value - self.related_space = related_space - for i in streams: - if isinstance(i, Stream): - self.streams.append(i) - elif isinstance(i, dict): # Probably parsing JSON here - # dict - self.streams.append(Stream(**i)) - else: - # list? - self.streams.append(Stream(*i)) - self.id = id - - @classmethod - def from_dict(cls, connection, data): - """ - Returns a Chart object from a dictionary item, - which is usually from librato's API - """ - obj = cls(connection, - data['name'], - id=data['id'], - type=data.get('type', 'line'), - space_id=data.get('space_id'), - streams=data.get('streams'), - min=data.get('min'), - max=data.get('max'), - label=data.get('label'), - use_log_yaxis=data.get('use_log_yaxis'), - use_last_value=data.get('use_last_value'), - related_space=data.get('related_space')) - return obj - - def space(self): - if self._space is None and self.space_id is not None: - # Find the Space - self._space = self.connection.get_space(self.space_id) - return self._space - - def known_attributes(self): - return ['min', 'max', 'label', 'use_log_yaxis', 'use_last_value', - 'related_space'] - - def get_payload(self): - # Set up the things that we aren't considering just "attributes" - payload = { - 'name': self.name, - 'type': self.type, - 'streams': self.streams_payload() - } - for attr in self.known_attributes(): - if getattr(self, attr) is not None: - payload[attr] = getattr(self, attr) - return payload - - def streams_payload(self): - return [s.get_payload() for s in self.streams] - - def new_stream(self, metric=None, source='*', **kwargs): - stream = Stream(metric, source, **kwargs) - self.streams.append(stream) - return stream - - def persisted(self): - return self.id is not None - - def save(self): - if self.persisted(): - return self.connection.update_chart(self, self.space()) - else: - payload = self.get_payload() - # Don't include name twice - payload.pop('name') - resp = self.connection.create_chart(self.name, self.space(), - **payload) - self.id = resp.id - return resp - - def rename(self, new_name): - self.name = new_name - self.save() - - def delete(self): - return self.connection.delete_chart(self.id, self.space_id) diff --git a/librato/streams.py b/librato/streams.py deleted file mode 100644 index 4bb4a42..0000000 --- a/librato/streams.py +++ /dev/null @@ -1,56 +0,0 @@ -class Stream(object): - def __init__(self, metric=None, source='*', composite=None, - name=None, type=None, id=None, - group_function=None, summary_function=None, - transform_function=None, downsample_function=None, - period=None, split_axis=None, gap_detection=None, - min=None, max=None, - units_short=None, units_long=None, color=None, - position=None, - # deprecated - composite_function=None, **kwargs - ): - self.metric = metric - self.source = source - # Spaces API - self.composite = composite - # For instrument compatibility - self.name = name - self.type = type - self.id = id - # average, sum, min, max, breakout - self.group_function = group_function - # average, sum, min, max, count (or derivative if counter) - self.summary_function = summary_function - self.transform_function = transform_function - self.downsample_function = downsample_function - self.period = period - self.split_axis = split_axis - self.min = min - self.max = max - self.units_short = units_short - self.units_long = units_long - self.color = color - self.gap_detection = gap_detection - self.position = position - - # Pick up any attributes that are not explicitly defined - for attr in kwargs: - setattr(self, attr, kwargs[attr]) - - # Can't have a composite and source/metric - if self.composite: - self.source = None - self.metric = None - - def _attrs(self): - return ['metric', 'source', 'composite', 'name', - 'type', 'id', 'group_function', 'summary_function', 'transform_function', 'downsample_function', - 'period', 'split_axis', 'min', 'max', 'units_short', 'units_long'] - - def get_payload(self): - payload = {} - for attr in self._attrs(): - if getattr(self, attr) is not None: - payload[attr] = getattr(self, attr) - return payload diff --git a/tests/integration.py b/tests/integration.py index ebe4906..a2e97dd 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -303,110 +303,6 @@ def unique_name(self, prefix): return name -class TestSpacesApi(TestLibratoBase): - @classmethod - def setUpClass(cls): - super(TestSpacesApi, cls).setUpClass() - for space in cls.conn.list_spaces(): - cls.conn.delete_space(space.id) - - def test_create_space(self): - space = self.conn.create_space("my space") - self.assertIsNotNone(space.id) - - def test_get_space_by_id(self): - space = self.conn.create_space("find me by id") - self.wait_for_replication() - found = self.conn.get_space(space.id) - self.assertEqual(space.id, found.id) - - def test_find_space_by_name(self): - # This assumes you only have 1 space with this name (it's not unique) - space = self.conn.create_space("find me by name") - self.wait_for_replication() - found = self.conn.find_space(space.name) - self.assertEqual(space.id, found.id) - space.delete() - - def test_list_spaces(self): - name = 'list me' - space = self.conn.create_space(name) - spaces = self.conn.list_spaces() - self.assertTrue(name in [s.name for s in spaces]) - - def test_delete_space(self): - space = self.conn.create_space("delete me") - self.wait_for_replication() - self.conn.delete_space(space.id) - - def test_create_space_via_model(self): - space = librato.Space(self.conn, 'Production') - self.assertIsNone(space.id) - space.save() - self.assertIsNotNone(space.id) - - def test_delete_space_via_model(self): - space = librato.Space(self.conn, 'delete me') - space.save() - self.wait_for_replication() - space.delete() - self.wait_for_replication() - self.assertRaises(librato.exceptions.NotFound, self.conn.get_space, space.id) - - def test_create_chart(self): - # Ensure metrics exist - self.conn.submit('memory.free', 100, tags=tags()) - self.conn.submit('memory.used', 200, tags=tags()) - # Create space - space = librato.Space(self.conn, 'my space') - space.save() - # Add chart - chart = space.add_chart( - 'memory', - type='line', - streams=[ - {'metric': 'memory.free', 'source': '*'}, - {'metric': 'memory.used', 'source': '*', - 'group_function': 'breakout', 'summary_function': 'average'} - ], - min=0, - max=50, - label='the y axis label', - use_log_yaxis=True, - related_space=1234 - ) - self.wait_for_replication() - self.assertEqual(len(chart.streams), 2) - - def test_create_big_number(self): - # Ensure metrics exist - self.conn.submit('memory.free', 100, tags=tags()) - # Create space - space = librato.Space(self.conn, 'my space') - space.save() - self.wait_for_replication() - chart = space.add_chart( - 'memory', - type='bignumber', - streams=[ - {'metric': 'memory.free', 'source': '*'} - ], - use_last_value=False - ) - - # Shortcut - chart2 = space.add_bignumber_chart('memory 2', 'memory.free', 'foo*', - use_last_value=True) - - self.wait_for_replication() - - self.assertEqual(chart.type, 'bignumber') - self.assertEqual(len(chart.streams), 1) - self.assertFalse(chart.use_last_value) - self.assertEqual(chart2.type, 'bignumber') - self.assertEqual(len(chart2.streams), 1) - self.assertTrue(chart2.use_last_value) - if __name__ == '__main__': # TO run a specific test: # $ nosetests tests/integration.py:TestLibratoBasic.test_update_metrics_attributes diff --git a/tests/test_charts.py b/tests/test_charts.py deleted file mode 100644 index 5ac0c79..0000000 --- a/tests/test_charts.py +++ /dev/null @@ -1,326 +0,0 @@ -import logging -import unittest -import librato -from librato import Space, Chart -from librato.streams import Stream -from mock_connection import MockConnect, server - -# logging.basicConfig(level=logging.DEBUG) -# Mock the server -librato.HTTPSConnection = MockConnect - - -class ChartsTest(unittest.TestCase): - def setUp(self): - self.conn = librato.connect('user_test', 'key_test') - server.clean() - - -# Charts -class TestChartsConnection(ChartsTest): - def setUp(self): - super(TestChartsConnection, self).setUp() - self.space = self.conn.create_space("My Space") - - def test_create_chart(self): - # Create a couple of metrics - self.conn.submit('my.metric', 42) - self.conn.submit('my.metric2', 43) - # Create charts in the space - chart_name = "Typical chart" - chart = self.conn.create_chart( - chart_name, - self.space, - streams=[ - {'metric': 'my.metric', 'source': '*', 'summary_function': 'max'}, - {'metric': 'my.metric2', 'source': 'foo', 'color': '#FFFFFF'} - ] - ) - self.assertIsInstance(chart, Chart) - self.assertIsNotNone(chart.id) - self.assertEqual(chart.space_id, self.space.id) - self.assertEqual(chart.name, chart_name) - self.assertEqual(chart.streams[0].metric, 'my.metric') - self.assertEqual(chart.streams[0].source, '*') - self.assertEqual(chart.streams[0].summary_function, 'max') - self.assertEqual(chart.streams[1].metric, 'my.metric2') - self.assertEqual(chart.streams[1].source, 'foo') - self.assertEqual(chart.streams[1].color, '#FFFFFF') - - def test_create_chart_without_streams(self): - chart_name = "Empty Chart" - chart = self.conn.create_chart(chart_name, self.space) - self.assertIsInstance(chart, Chart) - self.assertEqual(chart.name, chart_name) - # Line by default - self.assertEqual(chart.type, 'line') - self.assertEqual(len(chart.streams), 0) - - def test_rename_chart(self): - chart = self.conn.create_chart('CPU', self.space) - chart.rename('CPU 2') - self.assertEqual(chart.name, 'CPU 2') - self.assertEqual(self.conn.get_chart(chart.id, self.space).name, 'CPU 2') - - def test_delete_chart(self): - chart = self.conn.create_chart('cpu', self.space) - self.conn.delete_chart(chart.id, self.space.id) - self.assertEqual(len(self.conn.list_charts_in_space(self.space)), 0) - - def test_add_stream_to_chart(self): - chart = self.conn.create_chart("Chart with no streams", self.space) - metric_name = 'my.metric' - self.conn.submit(metric_name, 42, description='metric description') - chart.new_stream(metric=metric_name) - chart.save() - self.assertEqual(len(chart.streams), 1) - stream = chart.streams[0] - self.assertEqual(stream.metric, metric_name) - self.assertIsNone(stream.composite) - - def test_get_chart_from_space(self): - chart = self.conn.create_chart('cpu', self.space) - found = self.conn.get_chart(chart.id, self.space) - self.assertEqual(found.id, chart.id) - self.assertEqual(found.name, chart.name) - - def test_get_chart_from_space_id(self): - chart = self.conn.create_chart('cpu', self.space) - found = self.conn.get_chart(chart.id, self.space.id) - self.assertEqual(found.id, chart.id) - self.assertEqual(found.name, chart.name) - - def test_find_chart_by_name(self): - chart = self.conn.create_chart('cpu', self.space) - found = self.conn.find_chart('cpu', self.space) - self.assertEqual(found.name, 'cpu') - - -class TestChartModel(ChartsTest): - def setUp(self): - super(TestChartModel, self).setUp() - self.space = self.conn.create_space('My Space') - - def test_init_connection(self): - self.assertEqual(Chart(self.conn).connection, self.conn) - - def test_init_name(self): - self.assertIsNone(Chart(self.conn).name) - self.assertEqual(Chart(self.conn, 'cpu').name, 'cpu') - - def test_init_chart_type(self): - # Debated `chart_type` vs `type`, going with `type` - self.assertEqual(Chart(self.conn, type='line').type, 'line') - self.assertEqual(Chart(self.conn, type='stacked').type, 'stacked') - self.assertEqual(Chart(self.conn, type='bignumber').type, 'bignumber') - - def test_init_space_id(self): - self.assertEqual(Chart(self.conn, space_id=42).space_id, 42) - - def test_space_attribute(self): - chart = Chart(self.conn) - chart._space = self.space - self.assertEqual(chart._space, self.space) - - def test_init_streams(self): - self.assertEqual(Chart(self.conn).streams, []) - - s = [Stream('my.metric'), Stream('other.metric')] - chart = Chart(self.conn, streams=s) - self.assertEqual(chart.streams, s) - - def test_init_streams_dict(self): - streams_dict = [ - {'metric': 'my.metric', 'source': 'blah', 'composite': None}, - {'metric': 'other.metric', 'source': '*', 'composite': None} - ] - chart = Chart(self.conn, streams=streams_dict) - self.assertEqual(chart.streams[0].metric, streams_dict[0]['metric']) - self.assertEqual(chart.streams[0].source, streams_dict[0]['source']) - self.assertEqual(chart.streams[0].composite, streams_dict[0]['composite']) - self.assertEqual(chart.streams[1].metric, streams_dict[1]['metric']) - self.assertEqual(chart.streams[1].source, streams_dict[1]['source']) - self.assertEqual(chart.streams[1].composite, streams_dict[1]['composite']) - - def test_init_streams_list(self): - streams_list = [['my.metric', '*', None]] - chart = Chart(self.conn, streams=streams_list) - self.assertEqual(chart.streams[0].metric, streams_list[0][0]) - - def test_init_streams_group_functions(self): - streams_dict = [ - {'metric': 'my.metric', 'source': '*', - 'group_function': 'sum', 'summary_function': 'max'} - ] - chart = Chart(self.conn, streams=streams_dict) - stream = chart.streams[0] - self.assertEqual(stream.group_function, 'sum') - self.assertEqual(stream.summary_function, 'max') - - def test_init_min_max(self): - chart = Chart(self.conn, min=-42, max=100) - self.assertEqual(chart.min, -42) - self.assertEqual(chart.max, 100) - - def test_init_label(self): - chart = Chart(self.conn, label='I heart charts') - self.assertEqual(chart.label, 'I heart charts') - - def test_init_use_log_yaxis(self): - chart = Chart(self.conn, use_log_yaxis=True) - self.assertTrue(chart.use_log_yaxis) - - def test_save_chart(self): - chart = Chart(self.conn, 'test', space_id=self.space.id) - self.assertFalse(chart.persisted()) - self.assertIsNone(chart.id) - resp = chart.save() - self.assertIsInstance(resp, Chart) - self.assertTrue(chart.persisted()) - self.assertIsNotNone(chart.id) - self.assertEqual(chart.type, 'line') - - def test_save_persists_type(self): - # Ensure that type gets passed in the payload - for t in ['stacked', 'bignumber']: - chart = Chart(self.conn, space_id=self.space.id, type=t) - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertEqual(found.type, t) - - def test_save_persists_min_max(self): - chart = Chart(self.conn, space_id=self.space.id) - self.assertIsNone(chart.min) - self.assertIsNone(chart.max) - chart.min = 5 - chart.max = 30 - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertEqual(found.min, 5) - self.assertEqual(found.max, 30) - - def test_save_persists_label(self): - chart = Chart(self.conn, space_id=self.space.id) - self.assertIsNone(chart.label) - chart.label = 'my label' - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertEqual(found.label, 'my label') - - def test_save_persists_log_y_axis(self): - chart = Chart(self.conn, space_id=self.space.id) - self.assertIsNone(chart.use_log_yaxis) - chart.use_log_yaxis = True - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertTrue(found.use_log_yaxis) - - def test_save_persists_use_last_value(self): - chart = Chart(self.conn, space_id=self.space.id) - self.assertIsNone(chart.use_last_value) - chart.use_last_value = True - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertTrue(found.use_last_value) - - def test_save_persists_related_space(self): - chart = Chart(self.conn, space_id=self.space.id) - self.assertIsNone(chart.related_space) - chart.related_space = 1234 - chart.save() - found = self.conn.get_chart(chart.id, self.space.id) - self.assertTrue(found.related_space) - - def test_chart_is_not_persisted(self): - chart = Chart('not saved', self.space) - self.assertFalse(chart.persisted()) - - def test_chart_is_persisted_if_id_present(self): - chart = Chart(self.conn, 'test', id=42) - self.assertTrue(chart.persisted()) - chart = Chart(self.conn, 'test', id=None) - self.assertFalse(chart.persisted()) - - def test_get_space_from_chart(self): - chart = Chart(self.conn, space_id=self.space.id) - space = chart.space() - self.assertIsInstance(space, Space) - self.assertEqual(space.id, self.space.id) - - def test_new_stream_defaults(self): - chart = Chart(self.conn, 'test') - self.assertEqual(len(chart.streams), 0) - stream = chart.new_stream('my.metric') - self.assertIsInstance(stream, Stream) - self.assertEqual(stream.metric, 'my.metric') - self.assertEqual(stream.source, '*') - self.assertEqual(stream.composite, None) - # Appends to chart streams - self.assertEqual(len(chart.streams), 1) - self.assertEqual(chart.streams[0].metric, 'my.metric') - # Another way to do the same thing - stream = chart.new_stream(metric='my.metric') - self.assertEqual(stream.metric, 'my.metric') - - def test_new_stream_with_source(self): - chart = Chart(self.conn, 'test') - stream = chart.new_stream('my.metric', 'prod*') - self.assertEqual(stream.metric, 'my.metric') - self.assertEqual(stream.source, 'prod*') - self.assertEqual(stream.composite, None) - stream = chart.new_stream(metric='my.metric', source='prod*') - self.assertEqual(stream.metric, 'my.metric') - self.assertEqual(stream.source, 'prod*') - self.assertEqual(stream.composite, None) - - def test_new_stream_with_composite(self): - chart = Chart(self.conn) - composite_formula = 's("my.metric", "*")' - stream = chart.new_stream(composite=composite_formula) - self.assertIsNone(stream.metric) - self.assertIsNone(stream.source) - self.assertEqual(stream.composite, composite_formula) - - def test_get_payload(self): - chart = Chart(self.conn) - payload = chart.get_payload() - self.assertEqual(payload['name'], chart.name) - self.assertEqual(payload['type'], chart.type) - self.assertEqual(payload['streams'], chart.streams) - - def test_get_payload_bignumber(self): - streams = [{'metric': 'my.metric', 'source': '*'}] - chart = Chart(self.conn, type='bignumber', streams=streams, - use_last_value=False) - payload = chart.get_payload() - self.assertEqual(payload['name'], chart.name) - self.assertEqual(payload['type'], chart.type) - self.assertEqual(payload['streams'], streams) - self.assertEqual(payload['use_last_value'], chart.use_last_value) - - def test_streams_payload(self): - streams_payload = [ - {'metric': 'some.metric', 'source': None, 'composite': None}, - {'metric': None, 'source': None, 'composite': 's("other.metric", "sf", {function: "sum"})'} - ] - chart = Chart(self.conn, streams=streams_payload) - self.assertEqual(chart.streams_payload()[0]['metric'], streams_payload[0]['metric']) - - def test_get_payload_with_streams_dict(self): - streams_payload = [ - {'metric': 'some.metric', 'source': None, 'composite': None}, - {'metric': 'another.metric', 'source': None, 'composite': None} - ] - chart = Chart(self.conn, type='bignumber', space_id=42, streams=streams_payload) - chart_payload = chart.get_payload() - self.assertEqual(chart_payload['streams'][0]['metric'], streams_payload[0]['metric']) - self.assertEqual(chart_payload['streams'][1]['metric'], streams_payload[1]['metric']) - - def test_delete_chart(self): - chart = self.conn.create_chart('cpu', self.space) - chart.delete() - self.assertEqual(self.space.charts(), []) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_spaces.py b/tests/test_spaces.py deleted file mode 100644 index 7bd9941..0000000 --- a/tests/test_spaces.py +++ /dev/null @@ -1,333 +0,0 @@ -import logging -import unittest -import librato -from librato import Space, Chart -from librato.streams import Stream -from mock_connection import MockConnect, server - -# logging.basicConfig(level=logging.DEBUG) -# Mock the server -librato.HTTPSConnection = MockConnect - - -class SpacesTest(unittest.TestCase): - def setUp(self): - self.conn = librato.connect('user_test', 'key_test') - server.clean() - - -class TestSpacesConnection(SpacesTest): - def test_list_spaces(self): - self.conn.create_space('My Space') - self.assertEqual(len(self.conn.list_spaces()), 1) - - def test_list_spaces_when_none(self): - spcs = self.conn.list_spaces() - self.assertEqual(len(spcs), 0) - - # Find a space by ID - def test_get_space(self): - space = self.conn.create_space('My Space') - found = self.conn.get_space(space.id) - self.assertEqual(found.id, space.id) - self.assertEqual(found.name, space.name) - - # Find a space by name - def test_find_space(self): - space = self.conn.create_space('My Space') - found = self.conn.find_space(space.name) - self.assertIsInstance(found, Space) - self.assertEqual(found.name, space.name) - - def test_create_space(self): - name = "My Space" - space = self.conn.create_space(name) - self.assertIsInstance(space, Space) - self.assertEqual(space.name, name) - - def test_rename_space(self): - name = 'My Space' - new_name = 'My Space 42' - space = self.conn.create_space(name) - space.rename(new_name) - found = self.conn.find_space(new_name) - self.assertEqual(found.name, new_name) - - def test_list_charts(self): - space_name = "My Space" - space = self.conn.create_space(space_name) - chart1 = self.conn.create_chart('CPU 1', space) - chart2 = self.conn.create_chart('CPU 2', space) - charts = space.charts() - for c in charts: - self.assertIsInstance(c, Chart) - self.assertIn(c.name, ['CPU 1', 'CPU 2']) - - def test_update_space(self): - space = self.conn.create_space('My Space') - self.conn.update_space(space, name='New Name') - - updated_spaces = self.conn.list_spaces() - self.assertEqual(len(updated_spaces), 1) - - updated = updated_spaces[0] - updated = self.conn.get_space(space.id) - self.assertEqual(updated.id, space.id) - self.assertEqual(updated.name, 'New Name') - - def test_delete_chart(self): - space = self.conn.create_space('My Space') - chart = self.conn.create_chart('My Chart', space) - self.conn.delete_chart(chart.id, space.id) - self.assertEqual(len(self.conn.list_charts_in_space(space)), 0) - - def test_delete_space(self): - space = self.conn.create_space('My Space') - self.conn.delete_space(space.id) - self.assertEqual(len(self.conn.list_spaces()), 0) - - -class TestSpaceModel(SpacesTest): - def setUp(self): - super(TestSpaceModel, self).setUp() - self.space = Space(self.conn, 'My Space', id=123) - - def test_connection(self): - self.assertEqual(Space(self.conn, 'My Space').connection, self.conn) - - def test_init_with_name(self): - self.assertEqual(Space(self.conn, 'My Space').name, 'My Space') - - def test_init_with_tags(self): - self.assertFalse(Space(self.conn, 'My Space').tags) - self.assertFalse(Space(self.conn, 'My Space', tags=False).tags) - self.assertTrue(Space(self.conn, 'My Space', tags=True).tags) - - def test_init_with_empty_name(self): - self.assertEqual(Space(self.conn, None).name, None) - - def test_init_with_id(self): - self.assertEqual(Space(self.conn, 'My Space', 123).id, 123) - - def test_charts_var(self): - self.assertEqual(self.space._charts, None) - - def test_init_chart_ids_empty(self): - self.assertEqual(self.space.chart_ids, []) - - def test_init_with_chart_payload(self): - space = Space(self.conn, 'My Space', chart_dicts=[{'id': 123}, {'id': 456}]) - self.assertEqual(space.chart_ids, [123, 456]) - - def test_space_is_not_persisted(self): - space = Space(self.conn, 'not saved') - self.assertFalse(space.persisted()) - - def test_space_is_persisted_if_id_present(self): - space = Space(self.conn, 'saved', id=42) - self.assertTrue(space.persisted()) - - # This only returns the name because that's all we can send to the Spaces API - def test_get_payload(self): - self.assertEqual(self.space.get_payload(), {'name': self.space.name}) - - def test_from_dict(self): - payload = {'id': 42, 'name': 'test', 'charts': [{'id': 123}, {'id': 456}]} - space = Space.from_dict(self.conn, payload) - self.assertIsInstance(space, Space) - self.assertEqual(space.id, 42) - self.assertEqual(space.name, 'test') - self.assertEqual(space.chart_ids, [123, 456]) - - def test_save_creates_space(self): - space = Space(self.conn, 'not saved') - self.assertFalse(space.persisted()) - resp = space.save() - self.assertIsInstance(resp, Space) - self.assertTrue(space.persisted()) - - def save_updates_space(self): - space = Space(self.conn, 'some name').save() - self.assertEqual(space.name, 'some name') - space.name = 'new name' - space.save() - self.assertEqual(self.conn.find_space('new_name').name, 'new name') - - def test_new_chart_name(self): - chart = self.space.new_chart('test') - self.assertIsInstance(chart, Chart) - self.assertEqual(chart.name, 'test') - - def test_new_chart_not_persisted(self): - # Doesn't save - self.assertFalse(self.space.new_chart('test').persisted()) - - def test_new_chart_type(self): - chart = self.space.new_chart('test') - self.assertEqual(chart.type, 'line') - chart = self.space.new_chart('test', type='stacked') - self.assertEqual(chart.type, 'stacked') - chart = self.space.new_chart('test', type='bignumber') - self.assertEqual(chart.type, 'bignumber') - - def test_new_chart_attrs(self): - chart = self.space.new_chart('test', - label='hello', - min=-5, - max=30, - use_log_yaxis=True, - use_last_value=True, - related_space=1234) - self.assertEqual(chart.label, 'hello') - self.assertEqual(chart.min, -5) - self.assertEqual(chart.max, 30) - self.assertTrue(chart.use_log_yaxis) - self.assertTrue(chart.use_last_value) - self.assertEqual(chart.related_space, 1234) - - def test_new_chart_bignumber(self): - chart = self.space.new_chart('test', type='bignumber', - use_last_value=False) - self.assertEqual(chart.type, 'bignumber') - self.assertFalse(chart.use_last_value) - - def test_add_chart_name(self): - space = self.conn.create_space('foo') - chart = space.add_chart('bar') - self.assertIsInstance(chart, Chart) - self.assertEqual(chart.name, 'bar') - - def test_add_chart_type(self): - space = self.conn.create_space('foo') - chart = space.add_chart('baz', type='stacked') - self.assertEqual(chart.type, 'stacked') - - def test_add_chart_persisted(self): - space = self.conn.create_space('foo') - chart = space.add_chart('bar') - # Does save - self.assertTrue(chart.persisted()) - - def test_add_chart_streams(self): - space = self.conn.create_space('foo') - streams = [ - {'metric': 'my.metric', 'source': 'foo'}, - {'metric': 'my.metric2', 'source': 'bar'} - ] - chart = space.add_chart('cpu', streams=streams) - self.assertEqual(chart.streams[0].metric, 'my.metric') - self.assertEqual(chart.streams[0].source, 'foo') - self.assertEqual(chart.streams[1].metric, 'my.metric2') - self.assertEqual(chart.streams[1].source, 'bar') - - def test_add_chart_bignumber_default(self): - space = self.conn.create_space('foo') - chart = space.add_chart('baz', type='bignumber') - self.assertEqual(chart.type, 'bignumber') - # Leave this up to the Librato API to default - self.assertIsNone(chart.use_last_value) - - def test_add_chart_bignumber_use_last_value(self): - space = self.conn.create_space('foo') - chart = space.add_chart('baz', type='bignumber', use_last_value=False) - self.assertFalse(chart.use_last_value) - chart = space.add_chart('baz', type='bignumber', use_last_value=True) - self.assertTrue(chart.use_last_value) - - def test_add_line_chart(self): - space = self.conn.create_space('foo') - streams = [{'metric': 'my.metric', 'source': 'my.source'}] - chart = space.add_line_chart('cpu', streams=streams) - self.assertEqual([chart.name, chart.type], ['cpu', 'line']) - self.assertEqual(len(chart.streams), 1) - - def test_add_single_line_chart_default(self): - space = self.conn.create_space('foo') - chart = space.add_single_line_chart('cpu', 'my.cpu.metric') - self.assertEqual(chart.type, 'line') - self.assertEqual(chart.name, 'cpu') - self.assertEqual(len(chart.streams), 1) - self.assertEqual(chart.streams[0].metric, 'my.cpu.metric') - self.assertEqual(chart.streams[0].source, '*') - - def test_add_single_line_chart_source(self): - space = self.conn.create_space('foo') - chart = space.add_single_line_chart('cpu', 'my.cpu.metric', 'prod*') - self.assertEqual(chart.streams[0].source, 'prod*') - - def test_add_single_line_chart_group_functions(self): - space = self.conn.create_space('foo') - chart = space.add_single_line_chart('cpu', 'my.cpu.metric', '*', 'min', 'max') - stream = chart.streams[0] - self.assertEqual(stream.group_function, 'min') - self.assertEqual(stream.summary_function, 'max') - - def test_add_stacked_chart(self): - space = self.conn.create_space('foo') - streams = [{'metric': 'my.metric', 'source': 'my.source'}] - chart = space.add_stacked_chart('cpu', streams=streams) - self.assertEqual(chart.type, 'stacked') - self.assertEqual([chart.name, chart.type], ['cpu', 'stacked']) - self.assertEqual(len(chart.streams), 1) - - def test_add_single_stacked_chart(self): - space = self.conn.create_space('foo') - chart = space.add_single_stacked_chart('cpu', 'my.cpu.metric', '*') - self.assertEqual(chart.type, 'stacked') - self.assertEqual(chart.name, 'cpu') - self.assertEqual(len(chart.streams), 1) - self.assertEqual(chart.streams[0].metric, 'my.cpu.metric') - self.assertEqual(chart.streams[0].source, '*') - - def test_add_bignumber_chart_default(self): - space = self.conn.create_space('foo') - chart = space.add_bignumber_chart('cpu', 'my.metric') - self.assertEqual(chart.type, 'bignumber') - self.assertEqual(chart.name, 'cpu') - self.assertTrue(chart.use_last_value) - stream = chart.streams[0] - self.assertEqual(stream.metric, 'my.metric') - self.assertEqual(stream.source, '*') - self.assertEqual(stream.summary_function, 'average') - - def test_add_bignumber_chart_source(self): - space = self.conn.create_space('foo') - chart = space.add_bignumber_chart('cpu', 'my.metric', 'foo') - self.assertEqual(chart.streams[0].source, 'foo') - - def test_add_bignumber_chart_summary_function(self): - space = self.conn.create_space('foo') - chart = space.add_bignumber_chart('cpu', 'my.metric', - summary_function='min') - self.assertEqual(chart.streams[0].summary_function, 'min') - - def test_add_bignumber_chart_group_function(self): - space = self.conn.create_space('foo') - chart = space.add_bignumber_chart('cpu', 'my.metric', - group_function='max') - self.assertEqual(chart.streams[0].group_function, 'max') - - def test_add_bignumber_chart_use_last_value(self): - space = self.conn.create_space('foo') - # True shows the most recent value, False reduces over time - # Default to True - chart = space.add_bignumber_chart('cpu', 'my.metric') - self.assertTrue(chart.use_last_value) - chart = space.add_bignumber_chart('cpu', 'my.metric', use_last_value=True) - self.assertTrue(chart.use_last_value) - chart = space.add_bignumber_chart('cpu', 'my.metric', use_last_value=False) - self.assertFalse(chart.use_last_value) - - def test_delete_space(self): - space = self.conn.create_space('Delete Me') - # Ensure we can find it - self.assertEqual(self.conn.find_space(space.name).name, space.name) - resp = space.delete() - # Returns None - self.assertIsNone(resp) - # Ensure it was deleted - self.assertIsNone(self.conn.find_space(space.name)) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_streams.py b/tests/test_streams.py deleted file mode 100644 index 64a177d..0000000 --- a/tests/test_streams.py +++ /dev/null @@ -1,128 +0,0 @@ -import logging -import unittest -import librato -from librato.streams import Stream -from mock_connection import MockConnect, server - -# logging.basicConfig(level=logging.DEBUG) -# Mock the server -librato.HTTPSConnection = MockConnect - - -class TestStreamModel(unittest.TestCase): - def setUp(self): - self.conn = librato.connect('user_test', 'key_test') - server.clean() - - def test_init_metric(self): - self.assertEqual(Stream('my.metric').metric, 'my.metric') - self.assertEqual(Stream(metric='my.metric').metric, 'my.metric') - - def test_init_source(self): - self.assertEqual(Stream('my.metric', 'my.source').source, 'my.source') - self.assertEqual(Stream(source='my.source').source, 'my.source') - - def test_init_composite(self): - composite_formula = 's("my.metric", "*")' - self.assertEqual(Stream(composite=composite_formula).composite, composite_formula) - - def test_source_defaults_to_all(self): - self.assertEqual(Stream('my.metric').source, '*') - - def test_composite_defaults_to_none(self): - self.assertIsNone(Stream('my.metric').composite) - - def test_init_group_function(self): - self.assertIsNone(Stream('my.metric').group_function) - self.assertEqual(Stream(group_function='max').group_function, 'max') - - def test_init_summary_function(self): - self.assertIsNone(Stream('my.metric').summary_function) - self.assertEqual(Stream(summary_function='min').summary_function, 'min') - - def test_init_transform_function(self): - self.assertIsNone(Stream('my.metric').transform_function) - self.assertEqual(Stream(transform_function='x/p*60').transform_function, 'x/p*60') - - # For composites ONLY - def test_init_downsample_function(self): - self.assertIsNone(Stream('my.metric').downsample_function) - self.assertEqual(Stream(downsample_function='sum').downsample_function, 'sum') - - def test_init_period(self): - self.assertIsNone(Stream('my.metric').period) - self.assertEqual(Stream(period=60).period, 60) - - # Not very useful but part of the API - def test_init_type(self): - self.assertIsNone(Stream().type) - self.assertEqual(Stream(type='gauge').type, 'gauge') - - def test_init_split_axis(self): - self.assertIsNone(Stream().split_axis) - self.assertTrue(Stream(split_axis=True).split_axis) - self.assertFalse(Stream(split_axis=False).split_axis) - - def test_init_min_max(self): - self.assertIsNone(Stream().min) - self.assertIsNone(Stream().max) - self.assertEqual(Stream(min=-2).min, -2) - self.assertEqual(Stream(max=42).max, 42) - - def test_init_units(self): - self.assertIsNone(Stream().units_short) - self.assertIsNone(Stream().units_long) - self.assertEqual(Stream(units_short='req/s').units_short, 'req/s') - self.assertEqual(Stream(units_long='requests per second').units_long, 'requests per second') - - def test_init_color(self): - self.assertIsNone(Stream().color) - self.assertEqual(Stream(color='#f00').color, '#f00') - - def test_init_gap_detection(self): - self.assertIsNone(Stream().gap_detection) - self.assertTrue(Stream(gap_detection=True).gap_detection) - self.assertFalse(Stream(gap_detection=False).gap_detection) - - # Adding this to avoid exceptions raised due to unknown Stream attributes - def test_init_with_extra_attributes(self): - attrs = {"color": "#f00", "something": "foo"} - s = Stream(**attrs) - # color is a known attribute - self.assertEqual(s.color, '#f00') - self.assertEqual(s.something, 'foo') - - def test_get_payload(self): - self.assertEqual(Stream(metric='my.metric').get_payload(), - {'metric': 'my.metric', 'source': '*'}) - - def test_payload_all_attributes(self): - s = Stream(metric='my.metric', source='*', name='my display name', - type='gauge', id=1234, - group_function='min', summary_function='max', - transform_function='x/p', downsample_function='min', - period=60, split_axis=False, - min=0, max=42, - units_short='req/s', units_long='requests per second') - payload = { - 'metric': 'my.metric', - 'source': '*', - 'name': 'my display name', - 'type': 'gauge', - 'id': 1234, - 'group_function': 'min', - 'summary_function': 'max', - 'transform_function': 'x/p', - 'downsample_function': 'min', - 'period': 60, - 'split_axis': False, - 'min': 0, - 'max': 42, - 'units_short': 'req/s', - 'units_long': 'requests per second' - } - self.assertEqual(s.get_payload(), payload) - - -if __name__ == '__main__': - unittest.main() From b381834436f388714b12966c64a642b36d576df4 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Wed, 25 Jan 2017 17:08:03 -0500 Subject: [PATCH 08/10] fixing integration issues --- tests/integration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration.py b/tests/integration.py index a2e97dd..76a584d 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -110,7 +110,7 @@ def test_invalid_sanitized_metric(self): self._delete_and_verify_metric(name, self.conn_sanitize) def test_create_and_delete_gauge(self): - name = 'Test' + name = 'a_py_metric_del_gau_test' self._add_and_verify_metric(name, 10, tags()) self._delete_and_verify_metric(name) @@ -122,7 +122,7 @@ def test_batch_delete(self): self._delete_and_verify_metric([name_one, name_two]) def test_save_gauge_metrics(self): - name = 'Test' + name = 'py_s_ga_me_test' self.conn.submit(name, 10, tags=tags()) self.conn.submit(name, 20, tags=tags()) self.conn.delete(name) @@ -165,7 +165,7 @@ def test_submit_empty_queue(self): self.conn.new_queue().submit() def test_update_metrics_attributes(self): - name = 'Test' + name = 'py_update_m__att' self.conn.submit(name, 10, tags=tags()) self.wait_for_replication() gauge = self.conn.get_metric(name) From 1b5296d0df244239dec80b45eb1671bf894ee7a1 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Wed, 25 Jan 2017 17:17:57 -0500 Subject: [PATCH 09/10] update aggregator --- README.md | 25 ++++++++++++++----------- librato/aggregator.py | 5 ----- tests/test_aggregator.py | 4 ---- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 554baf2..646346e 100644 --- a/README.md +++ b/README.md @@ -293,44 +293,47 @@ print(alert.services) ## Client-side Aggregation -You can aggregate measurements before submission using the `Aggregator` class. Optionally, specify a `measure_time` to submit that timestamp to the API. You may also optionally specify a `period` to floor the timestamp to a particular interval. If `period` is specified without a `measure_time`, the current timestamp will be used, and floored to `period`. Specifying an optional `source` allows the aggregated measurement to report a source name. +You can aggregate measurements before submission using the `Aggregator` class. +Optionally, specify a `measure_time` to submit that timestamp to the API. +You may also optionally specify a `period` to floor the timestamp to a particular interval. +If `period` is specified without a `measure_time`, the current timestamp will be used, and floored to `period`. -Aggregator instances can be sent immediately by calling `submit()` or added to a `Queue` by calling `queue.add_aggregator()`. +Aggregator instances can be sent immediately by calling `submit()` or added to +a `Queue` by calling `queue.add_aggregator()`. ```python from librato.aggregator import Aggregator api = librato.connect('email', 'token') -a = Aggregator(api) +a = Aggregator(api, tags={'host': 'machine1'}) a.add("foo", 42) a.add("foo", 5) -# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5), source=unassigned +# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5) # measure_time = a.submit() -a = Aggregator(api, source='my.source', period=60) +a = Aggregator(api, tags={'host': 'machine1'}, period=60) a.add("foo", 42) a.add("foo", 5) -# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5), source=my.source +# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5) # measure_time = - ( % 60) a.submit() -a = Aggregator(api, period=60, measure_time=1419302671) +a = Aggregator(api, period=60, measure_time=1419302671, tags={'host': 'machine1'}) a.add("foo", 42) a.add("foo", 5) -# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5), source=unassigned +# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5) # measure_time = 1419302671 - (1419302671 % 60) = 1419302671 - 31 = 1419302640 a.submit() -a = Aggregator(api, measure_time=1419302671) +a = Aggregator(api, measure_time=1419302671, tags={'host': 'machine1'}) a.add("foo", 42) a.add("foo", 5) -# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5), source=unassigned +# count=2, min=5, max=42, sum=47 (value calculated by API = mean = 23.5) # measure_time = 1419302671 a.submit() - # You can also add an Aggregator instance to a queue q = librato.queue.Queue(api) q.add_aggregator(a) diff --git a/librato/aggregator.py b/librato/aggregator.py index f819a13..15959a1 100644 --- a/librato/aggregator.py +++ b/librato/aggregator.py @@ -35,8 +35,6 @@ class Aggregator(object): def __init__(self, connection, **args): self.connection = connection - # Global source for all 'legacy' metrics sent into the aggregator - self.source = args.get('source') # Global tags, which apply to MD metrics only self.tags = dict(args.get('tags', {})) self.measurements = {} @@ -81,7 +79,6 @@ def to_payload(self): # {'count': 1, 'max': 42, 'sum': 42, 'name': 'foo', 'min': 42} # ] # 'measure_time': 1418838418 (optional) - # 'source': 'mysource' (optional) # } # Note: hash format would work too, but the mocks aren't currently set up # for the hash format :-( @@ -95,8 +92,6 @@ def to_payload(self): body.append(vals) result = {'gauges': body} - if self.source: - result['source'] = self.source mt = self.floor_measure_time() if mt: diff --git a/tests/test_aggregator.py b/tests/test_aggregator.py index e147e42..53c23a6 100644 --- a/tests/test_aggregator.py +++ b/tests/test_aggregator.py @@ -50,10 +50,6 @@ def test_set_tags(self): assert 'coal' in tags assert tags['coal'] == 'black' - def test_initialize_source(self): - assert Aggregator(self.conn).source is None - assert Aggregator(self.conn, source='my.source').source == 'my.source' - def test_initialize_period(self): assert Aggregator(self.conn).period is None assert Aggregator(self.conn, period=300).period == 300 From 48593545e009eace3c189a1231d0de6cde1deea3 Mon Sep 17 00:00:00 2001 From: David Rio Deiros Date: Wed, 25 Jan 2017 17:41:42 -0500 Subject: [PATCH 10/10] fix aggregator payload --- librato/aggregator.py | 29 +------------------------ tests/test_aggregator.py | 46 ++++++++++++++-------------------------- 2 files changed, 17 insertions(+), 58 deletions(-) diff --git a/librato/aggregator.py b/librato/aggregator.py index 15959a1..65ec6cf 100644 --- a/librato/aggregator.py +++ b/librato/aggregator.py @@ -73,33 +73,6 @@ def add(self, name, value): return self.measurements def to_payload(self): - # Map measurements into Librato POST (array) format - # { - # 'gauges': [ - # {'count': 1, 'max': 42, 'sum': 42, 'name': 'foo', 'min': 42} - # ] - # 'measure_time': 1418838418 (optional) - # } - # Note: hash format would work too, but the mocks aren't currently set up - # for the hash format :-( - # i.e. result = {'gauges': dict(self.measurements)} - - body = [] - for metric_name in self.measurements: - # Create a clone so we don't change self.measurements - vals = dict(self.measurements[metric_name]) - vals["name"] = metric_name - body.append(vals) - - result = {'gauges': body} - - mt = self.floor_measure_time() - if mt: - result['measure_time'] = mt - - return result - - def to_md_payload(self): # Map measurements into Librato MD POST format # { # 'measures': [ @@ -159,6 +132,6 @@ def clear(self): def submit(self): self.connection._mexe("measurements", method="POST", - query_props=self.to_md_payload()) + query_props=self.to_payload()) # Clear measurements self.clear() diff --git a/tests/test_aggregator.py b/tests/test_aggregator.py index 53c23a6..eb6592e 100644 --- a/tests/test_aggregator.py +++ b/tests/test_aggregator.py @@ -99,34 +99,20 @@ def test_add_multiple_metrics(self): assert meas['min'] == 42 assert meas['max'] == 44 - # Only gauges are supported def test_to_payload(self): - self.agg.source = 'mysource' - self.agg.add('test.metric', 42) - self.agg.add('test.metric', 43) - assert self.agg.to_payload() == { - 'gauges': [ - {'name': 'test.metric', 'count': 2, 'sum': 85, 'min': 42, 'max': 43} - ], - 'source': 'mysource' - } - assert 'gauges' in self.agg.to_payload() - - def test_to_payload_no_source(self): - self.agg.source = None - self.agg.add('test.metric', 42) - - assert self.agg.to_payload() == { - 'gauges': [ - { - 'name': 'test.metric', - 'count': 1, - 'sum': 42, - 'min': 42, - 'max': 42 - } - ] - } + a = Aggregator(self.conn, tags={'foo': 'bar'}) + a.add('test.metric', 42) + a.add('test.metric', 43) + print a.to_payload() + assert a.to_payload() == { + 'measurements': [{ + 'count': 2, + 'max': 43, + 'sum': 85, + 'name': 'test.metric', + 'min': 42}], + 'tags': {'foo': 'bar'} + } # If 'value' is specified in the payload, the API will throw an error # This is because it must be calculated at the API via sum/count=avg @@ -177,8 +163,8 @@ def test_measure_time_in_payload(self): self.agg.measure_time = mt self.agg.period = None self.agg.add("foo", 42) - assert 'measure_time' in self.agg.to_payload() - assert self.agg.to_payload()['measure_time'] == mt + assert 'time' in self.agg.to_payload() + assert self.agg.to_payload()['time'] == mt def test_measure_time_not_in_payload(self): self.agg.measure_time = None @@ -219,7 +205,7 @@ def test_floored_measure_time_in_payload(self): # This will occur only if period is set self.agg.measure_time = 1418838418 self.agg.period = 60 - assert self.agg.to_payload()['measure_time'] == 1418838360 + assert self.agg.to_payload()['time'] == 1418838360 if __name__ == '__main__': unittest.main()