def test_requests_exception_with_response(self, *_, **__): with self.assertRaises(requests.RequestException): self.perform_request(self.URL) span = self.assert_span() self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL, "http.status_code": 500, "http.status_text": "Internal Server Error", }, ) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.INTERNAL) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.metrics), 1) recorder = RequestsInstrumentor().meter.metrics.pop() match_key = get_dict_as_key({ "http.method": "GET", "http.status_code": "500", "http.url": "http://httpbin.org/status/200", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def test_requests_exception_without_proper_response_type(self, *_, **__): with self.assertRaises(InvalidResponseObjectException): self.perform_request(self.URL) span = self.assert_span() self.assertEqual( span.attributes, {"component": "http", "http.method": "GET", "http.url": self.URL}, ) self.assertEqual(span.status.status_code, StatusCode.ERROR) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.instruments), 1) recorder = list(RequestsInstrumentor().meter.instruments.values())[0] match_key = get_dict_as_key( { "http.method": "GET", "http.url": "http://httpbin.org/status/200", } ) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def test_create_timeseries(self): def create_label(name, value): label = Label() label.name = name label.value = value return label sum_aggregator = SumAggregator() sum_aggregator.update(5) sum_aggregator.take_checkpoint() export_record = ExportRecord( Counter("testname", "testdesc", "testunit", int, None), get_dict_as_key({"record_name": "record_value"}), sum_aggregator, Resource({"resource_name": "resource_value"}), ) expected_timeseries = TimeSeries() expected_timeseries.labels.append(create_label("__name__", "testname")) expected_timeseries.labels.append( create_label("resource_name", "resource_value")) expected_timeseries.labels.append( create_label("record_name", "record_value")) sample = expected_timeseries.samples.add() sample.timestamp = int(sum_aggregator.last_update_timestamp / 1000000) sample.value = 5.0 timeseries = self.exporter._create_timeseries(export_record, "testname", 5.0) self.assertEqual(timeseries, expected_timeseries)
def test_requests_exception_with_response(self, *_, **__): with self.assertRaises(HTTPError): self.perform_request("http://httpbin.org/status/500") span = self.assert_span() self.assertEqual( dict(span.attributes), { "http.method": "GET", "http.url": "http://httpbin.org/status/500", "http.status_code": 500, "http.status_text": "Internal Server Error", }, ) self.assertEqual(span.status.status_code, StatusCode.ERROR) self.assertIsNotNone(URLLibInstrumentor().meter) self.assertEqual(len(URLLibInstrumentor().meter.instruments), 1) recorder = list(URLLibInstrumentor().meter.instruments.values())[0] match_key = get_dict_as_key({ "http.method": "GET", "http.status_code": "500", "http.url": "http://httpbin.org/status/500", "http.flavor": "1.1", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def process(self, record) -> None: """Stores record information to be ready for exporting.""" # Checkpoints the current aggregator value to be collected for export aggregator = record.aggregator # The uniqueness of a batch record is defined by a specific metric # using an aggregator type with a specific set of labels. # If two aggregators are the same but with different configs, they are still two valid unique records # (for example, two histogram views with different buckets) key = ( record.instrument, aggregator.__class__, get_dict_as_key(aggregator.config), record.labels, ) batch_value = self._batch_map.get(key) if batch_value: if batch_value != aggregator: aggregator.take_checkpoint() batch_value.merge(aggregator) else: aggregator.take_checkpoint() if self.stateful: # if stateful batcher, create a copy of the aggregator and update # it with the current checkpointed value for long-term storage aggregator = record.aggregator.__class__( config=record.aggregator.config) aggregator.merge(record.aggregator) self._batch_map[key] = aggregator
def test_counter_to_prometheus(self): meter = get_meter_provider().get_meter(__name__) metric = meter.create_counter( "test@name", "testdesc", "unit", int, ) labels = {"environment@": "staging", "os": "Windows"} key_labels = get_dict_as_key(labels) aggregator = SumAggregator() aggregator.update(123) aggregator.take_checkpoint() record = ExportRecord(metric, key_labels, aggregator, get_meter_provider().resource) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) for prometheus_metric in collector.collect(): self.assertEqual(type(prometheus_metric), CounterMetricFamily) self.assertEqual(prometheus_metric.name, "testprefix_test_name") self.assertEqual(prometheus_metric.documentation, "testdesc") self.assertTrue(len(prometheus_metric.samples) == 1) self.assertEqual(prometheus_metric.samples[0].value, 123) self.assertTrue(len(prometheus_metric.samples[0].labels) == 2) self.assertEqual( prometheus_metric.samples[0].labels["environment_"], "staging") self.assertEqual(prometheus_metric.samples[0].labels["os"], "Windows")
def bind(self, labels: Dict[str, str]) -> BaseBoundInstrument: """See `opentelemetry.metrics.Metric.bind`.""" key = get_dict_as_key(labels) with self.bound_instruments_lock: bound_instrument = self.bound_instruments.get(key) if bound_instrument is None: bound_instrument = self.BOUND_INSTR_TYPE(key, self) self.bound_instruments[key] = bound_instrument bound_instrument.increase_ref_count() return bound_instrument
def test_invalid_metric(self): meter = get_meter_provider().get_meter(__name__) metric = StubMetric("tesname", "testdesc", "unit", int, meter) labels = {"environment": "staging"} key_labels = get_dict_as_key(labels) record = ExportRecord(metric, key_labels, None, get_meter_provider().resource) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) collector.collect() self.assertLogs("opentelemetry.exporter.prometheus", level="WARNING")
def observe(self, value: metrics_api.ValueT, labels: Dict[str, str]) -> None: key = get_dict_as_key(labels) if not self._validate_observe(value, key): return if key not in self.aggregators: # TODO: how to cleanup aggregators? self.aggregators[key] = get_default_aggregator(self)() aggregator = self.aggregators[key] aggregator.update(value)
def test_valid_export(self, mock_post): mock_post.return_value.configure_mock(**{"status_code": 200}) test_metric = Counter("testname", "testdesc", "testunit", int, None) labels = get_dict_as_key({"environment": "testing"}) record = ExportRecord(test_metric, labels, SumAggregator(), Resource({})) result = self.exporter.export([record]) self.assertIs(result, MetricsExportResult.SUCCESS) self.assertEqual(mock_post.call_count, 1) result = self.exporter.export([]) self.assertIs(result, MetricsExportResult.SUCCESS)
def setUp(self): set_meter_provider(metrics.MeterProvider()) self._meter = get_meter_provider().get_meter(__name__) self._test_metric = self._meter.create_counter( "testname", "testdesc", "unit", int, ) labels = {"environment": "staging"} self._labels_key = get_dict_as_key(labels) self._mock_registry_register = mock.Mock() self._registry_register_patch = mock.patch( "prometheus_client.core.REGISTRY.register", side_effect=self._mock_registry_register, )
def test_error(self): with self.assertRaises(ValueError): Client().get("/error/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual( span.name, "^error/" if DJANGO_2_2 else "tests.views.error" ) self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual( span.status.canonical_code, StatusCanonicalCode.INTERNAL ) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual( span.attributes["http.url"], "http://testserver/error/" ) self.assertEqual(span.attributes["http.route"], "^error/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 500) self.assertIsNotNone(_django_instrumentor.meter) self.assertEqual(len(_django_instrumentor.meter.metrics), 1) self.assertEqual(len(span.events), 1) event = span.events[0] self.assertEqual(event.name, "exception") self.assertEqual(event.attributes["exception.type"], "ValueError") self.assertEqual(event.attributes["exception.message"], "error") recorder = _django_instrumentor.meter.metrics.pop() match_key = get_dict_as_key( { "http.flavor": "1.1", "http.method": "GET", "http.status_code": "500", "http.url": "http://testserver/error/", } ) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def test_basic(self): result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertIs(span.kind, trace.SpanKind.CLIENT) self.assertEqual(span.name, "HTTP GET") self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL, "http.status_code": 200, "http.status_text": "OK", }, ) self.assertIs(span.status.canonical_code, trace.status.StatusCanonicalCode.OK) self.check_span_instrumentation_info( span, opentelemetry.instrumentation.requests) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.metrics), 1) recorder = RequestsInstrumentor().meter.metrics.pop() match_key = get_dict_as_key({ "http.flavor": "1.1", "http.method": "GET", "http.status_code": "200", "http.status_text": "OK", "http.url": "http://httpbin.org/status/200", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) self.assertGreater(view_data.aggregator.current.sum, 0)
def test_record_server_duration(self): meter = metrics_api.get_meter(__name__) recorder = HTTPMetricRecorder(meter, HTTPMetricType.SERVER) labels = {"test": "asd"} with mock.patch("time.time") as time_patch: time_patch.return_value = 5.0 with recorder.record_server_duration(labels): labels["test2"] = "asd2" match_key = get_dict_as_key({"test": "asd", "test2": "asd2"}) for key in recorder._server_duration.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder._server_duration.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) self.assertGreaterEqual(view_data.aggregator.current.sum, 0)
def test_min_max_sum_aggregator_to_prometheus(self): meter = get_meter_provider().get_meter(__name__) metric = meter.create_metric("test@name", "testdesc", "unit", int, metrics.ValueRecorder, []) labels = {} key_labels = get_dict_as_key(labels) aggregator = MinMaxSumCountAggregator() aggregator.update(123) aggregator.update(456) aggregator.take_checkpoint() record = MetricRecord(metric, key_labels, aggregator) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) result_bytes = generate_latest(collector) result = result_bytes.decode("utf-8") self.assertIn("testprefix_test_name_count 2.0", result) self.assertIn("testprefix_test_name_sum 579.0", result)
def _get_system_network_connections( self, observer: metrics.UpDownSumObserver ) -> None: """Observer callback for network connections Args: observer: the observer to update """ # TODO How to find the device identifier for a particular # connection? connection_counters = {} for net_connection in psutil.net_connections(): for metric in self._config["system.network.connections"]: self._system_network_connections_labels["protocol"] = { 1: "tcp", 2: "udp", }[net_connection.type.value] self._system_network_connections_labels[ "state" ] = net_connection.status self._system_network_connections_labels[metric] = getattr( net_connection, metric ) connection_counters_key = get_dict_as_key( self._system_network_connections_labels ) if connection_counters_key in connection_counters.keys(): connection_counters[connection_counters_key]["counter"] += 1 else: connection_counters[connection_counters_key] = { "counter": 1, "labels": self._system_network_connections_labels.copy(), } for connection_counter in connection_counters.values(): observer.observe( connection_counter["counter"], connection_counter["labels"], )
def test_traced_get(self): Client().get("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual( span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced" ) self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual( span.attributes["http.url"], "http://testserver/traced/" ) self.assertEqual(span.attributes["http.route"], "^traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") self.assertIsNotNone(_django_instrumentor.meter) self.assertEqual(len(_django_instrumentor.meter.metrics), 1) recorder = _django_instrumentor.meter.metrics.pop() match_key = get_dict_as_key( { "http.flavor": "1.1", "http.method": "GET", "http.status_code": "200", "http.url": "http://testserver/traced/", } ) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) self.assertGreaterEqual(view_data.aggregator.current.sum, 0)
def _get_system_network_connections( self, options: CallbackOptions ) -> Iterable[Observation]: """Observer callback for network connections""" # TODO How to find the device identifier for a particular # connection? connection_counters = {} for net_connection in psutil.net_connections(): for metric in self._config["system.network.connections"]: self._system_network_connections_labels["protocol"] = { 1: "tcp", 2: "udp", }[net_connection.type.value] self._system_network_connections_labels[ "state" ] = net_connection.status self._system_network_connections_labels[metric] = getattr( net_connection, metric ) connection_counters_key = get_dict_as_key( self._system_network_connections_labels ) if connection_counters_key in connection_counters: connection_counters[connection_counters_key]["counter"] += 1 else: connection_counters[connection_counters_key] = { "counter": 1, "labels": self._system_network_connections_labels.copy(), } for connection_counter in connection_counters.values(): yield Observation( connection_counter["counter"], connection_counter["labels"], )