From ffae05a474c596f27cdeacc0bbd995705af43f6c Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Wed, 19 Aug 2020 10:03:58 -0600 Subject: [PATCH] Update system metrics Fixes #1006 --- .../system_metrics/__init__.py | 609 +++++++++++++++--- .../tests/test_system_metrics.py | 167 +---- .../src/opentelemetry/sdk/util/__init__.py | 25 +- 3 files changed, 579 insertions(+), 222 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/__init__.py b/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/__init__.py index fcd96f8210a..28e0839ed33 100644 --- a/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/__init__.py @@ -16,8 +16,8 @@ process (CPU, memory, garbage collection) metrics. By default, the following metrics are configured: +"system_cpu_times": ["user", "system", "idle"], "system_memory": ["total", "available", "used", "free"], -"system_cpu": ["user", "system", "idle"], "network_bytes": ["bytes_recv", "bytes_sent"], "runtime_memory": ["rss", "vms"], "runtime_cpu": ["user", "system"], @@ -61,7 +61,7 @@ import psutil from opentelemetry import metrics -from opentelemetry.sdk.metrics import ValueObserver +from opentelemetry.sdk.metrics import UpDownSumObserver, SumObserver from opentelemetry.sdk.metrics.export import MetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController @@ -71,8 +71,8 @@ def __init__( self, exporter: MetricsExporter, interval: int = 30, - labels: typing.Optional[typing.Dict[str, str]] = None, - config: typing.Optional[typing.Dict[str, typing.List[str]]] = None, + labels: typing.Optional[typing.Dict[str, str]]=None, + config: typing.Optional[typing.Dict[str, typing.List[str]]]=None, ): self._labels = {} if labels is None else labels self.meter = metrics.get_meter(__name__) @@ -81,155 +81,608 @@ def __init__( ) if config is None: self._config = { - "system_memory": ["total", "available", "used", "free"], - "system_cpu": ["user", "system", "idle"], - "network_bytes": ["bytes_recv", "bytes_sent"], - "runtime_memory": ["rss", "vms"], - "runtime_cpu": ["user", "system"], + "system_cpu_time": ["idle", "user", "system", "irq"], + "system_cpu_utilization": ["idle", "user", "system", "irq"], + + "system_memory_usage": ["used", "free", "cached"], + "system_memory_utilization": ["used", "free", "cached"], + + "system_swap_usage": ["used", "free"], + "system_swap_utilization": ["used", "free"], + # system_swap_page_faults: [], + # system_swap_page_operations: [], + + "system_disk_io": ["read", "write"], + "system_disk_operations": ["read", "write"], + "system_disk_time": ["read", "write"], + "system_disk_merged": ["read", "write"], + + # "system_filesystem_usage": [], + # "system_filesystem_utilization": [], + + "system_network_dropped_packets": ["transmit", "receive"], + "system_network_packets": ["transmit", "receive"], + "system_network_errors": ["transmit", "receive"], + "system_network_io": ["trasmit", "receive"], + "system_network_connections": ["family", "type"], + + "runtime_cpython_memory": ["rss", "vms"], + "runtime_cpython_cpu_time": ["user", "system"], } else: self._config = config self._proc = psutil.Process(os.getpid()) - self._system_memory_labels = {} - self._system_cpu_labels = {} - self._network_bytes_labels = {} - self._runtime_memory_labels = {} - self._runtime_cpu_labels = {} - self._runtime_gc_labels = {} + + self._system_cpu_time_labels = {} + self._system_cpu_utilization_labels = {} + + self._system_memory_usage_labels = {} + self._system_memory_utilization_labels = {} + + self._system_swap_usage_labels = {} + self._system_swap_utilization_labels = {} + # self._system_swap_page_faults = {} + # self._system_swap_page_operations = {} + + self._system_disk_io_labels = {} + self._system_disk_operations_labels = {} + self._system_disk_time_labels = {} + self._system_disk_merged_labels = {} + + # self._system_filesystem_usage_labels = {} + # self._system_filesystem_utilization_labels = {} + + self._system_network_dropped_packets_labels = {} + self._system_network_packets_labels = {} + self._system_network_errors_labels = {} + self._system_network_io_labels = {} + self._system_network_connections_labels = {} + + self._runtime_cpython_memory_labels = {} + self._runtime_cpython_cpu_time_labels = {} + self._runtime_cpython_gc_count_labels = {} + # create the label set for each observer once for key, value in self._labels.items(): - self._system_memory_labels[key] = value - self._system_cpu_labels[key] = value - self._network_bytes_labels[key] = value - self._runtime_memory_labels[key] = value - self._runtime_gc_labels[key] = value + self._system_cpu_time_labels[key] = value + self._system_cpu_utilization_labels[key] = value + + self._system_memory_usage_labels[key] = value + self._system_memory_utilization_labels[key] = value + + self._system_swap_usage_labels[key] = value + self._system_swap_utilization_labels[key] = value + + self._system_disk_io_labels[key] = value + self._system_disk_operations_labels[key] = value + self._system_disk_time_labels[key] = value + self._system_disk_merged_labels[key] = value + + # self._system_filesystem_usage_labels[key] = value + # self._system_filesystem_utilization_labels[key] = value + + self._system_network_dropped_packets[key] = value + self._system_network_packets[key] = value + self._system_network_errors[key] = value + self._system_network_io[key] = value + self._system_network_connections[key] = value + + self.meter.register_observer( + callback=self._get_system_cpu_time, + name="system.cpu.time", + description="System CPU time", + unit="seconds", + value_type=float, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_cpu_utilization, + name="system.cpu.utilization", + description="System CPU utilization", + unit="1", + value_type=float, + observer_type=UpDownSumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_memory_usage, + name="system.memory.usage", + description="System memory usage", + unit="bytes", + value_type=int, + observer_type=UpDownSumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_memory_utilization, + name="system.memory.utilization", + description="System memory utilization", + unit="1", + value_type=float, + observer_type=UpDownSumObserver, + ) self.meter.register_observer( - callback=self._get_system_memory, - name="system.mem", - description="System memory", + callback=self._get_system_swap_usage, + name="system.swap.usage", + description="System swap usage", + unit="pages", + value_type=int, + observer_type=UpDownSumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_swap_utilization, + name="system.swap.utilization", + description="System swap utilization", + unit="1", + value_type=float, + observer_type=UpDownSumObserver, + ) + + # self.meter.register_observer( + # callback=self._get_system_swap_page_faults, + # name="system.swap.page_faults", + # description="System swap page faults", + # unit="faults", + # value_type=int, + # observer_type=SumObserver, + # ) + + # self.meter.register_observer( + # callback=self._get_system_swap_page_operations, + # name="system.swap.page_operations", + # description="System swap page operations", + # unit="operations", + # value_type=int, + # observer_type=SumObserver, + # ) + + self.meter.register_observer( + callback=self._get_system_disk_io, + name="system.disk.io", + description="System disk IO", unit="bytes", value_type=int, - observer_type=ValueObserver, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_disk_operations, + name="system.disk.operations", + description="System disk operations", + unit="operations", + value_type=int, + observer_type=SumObserver, ) self.meter.register_observer( - callback=self._get_system_cpu, - name="system.cpu", - description="System CPU", + callback=self._get_system_disk_time, + name="system.disk.time", + description="System disk time", unit="seconds", value_type=float, - observer_type=ValueObserver, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_disk_merged, + name="system.disk.merged", + description="System disk merged", + unit="1", + value_type=int, + observer_type=SumObserver, + ) + + # self.meter.register_observer( + # callback=self._get_system_filesystem_usage, + # name="system.filesystem.usage", + # description="System filesystem usage", + # unit="bytes", + # value_type=int, + # observer_type=UpDownSumObserver, + # ) + + # self.meter.register_observer( + # callback=self._get_system_filesystem_utilization, + # name="system.filesystem.utilization", + # description="System filesystem utilization", + # unit="1", + # value_type=float, + # observer_type=UpDownSumObserver, + # ) + + self.meter.register_observer( + callback=self._get_system_network_dropped_packets, + name="system.network.dropped_packets", + description="System network dropped_packets", + unit="packets", + value_type=int, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_network_packets, + name="system.network.packets", + description="System network packets", + unit="packets", + value_type=int, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_network_errors, + name="system.network.errors", + description="System network errors", + unit="errors", + value_type=int, + observer_type=SumObserver, + ) + + self.meter.register_observer( + callback=self._get_system_network_io, + name="system.network.io", + description="System network io", + unit="bytes", + value_type=int, + observer_type=SumObserver, ) self.meter.register_observer( - callback=self._get_network_bytes, - name="system.net.bytes", - description="System network bytes", + callback=self._get_system_network_connections, + name="system.network.connections", + description="System network connections", unit="bytes", value_type=int, - observer_type=ValueObserver, + observer_type=UpDownSumObserver, ) self.meter.register_observer( - callback=self._get_runtime_memory, - name="runtime.python.mem", - description="Runtime memory", + callback=self._get_runtime_cpython_memory, + name="runtime.cpython.memory", + description="Runtime CPython memory", unit="bytes", value_type=int, - observer_type=ValueObserver, + observer_type=UpDownSumObserver, ) self.meter.register_observer( - callback=self._get_runtime_cpu, - name="runtime.python.cpu", - description="Runtime CPU", + callback=self._get_runtime_cpython_cpu_time, + name="runtime.cpython.cpu_time", + description="Runtime CPython CPU time", unit="seconds", value_type=float, - observer_type=ValueObserver, + observer_type=SumObserver, ) self.meter.register_observer( - callback=self._get_runtime_gc_count, - name="runtime.python.gc.count", - description="Runtime: gc objects", - unit="objects", + callback=self._get_runtime_cpython_memory, + name="runtime.cpython.gc_count", + description="Runtime CPython GC count", + unit="bytes", value_type=int, - observer_type=ValueObserver, + observer_type=SumObserver, ) - def _get_system_memory(self, observer: metrics.ValueObserver) -> None: - """Observer callback for memory available + def _get_system_cpu_time(self, observer: metrics.ValueObserver) -> None: + """Observer callback for system CPU time + + Args: + observer: the observer to update + """ + for cpu, times in enumerate(psutil.cpu_times(percpu=True)): + for metric in self._config["system_cpu_time"]: + self._system_cpu_time_labels["state"] = metric + self._system_cpu_time_labels["cpu"] = cpu + 1 + observer.observe( + getattr(times, metric), self._system_cpu_time_labels + ) + + def _get_system_cpu_utilization( + self, observer: metrics.ValueObserver + ) -> None: + """Observer callback for system CPU utilization + + Args: + observer: the observer to update + """ + + for cpu, times_percent in enumerate( + psutil.cpu_times_percent(percpu=True) + ): + for metric in self._config["system_cpu_utilization"]: + self._system_cpu_utilization_labels["state"] = metric + self._system_cpu_utilization_labels["cpu"] = cpu + 1 + observer.observe( + getattr(times_percent, metric) / 100, + self._system_cpu_utilization_labels + ) + + def _get_system_memory_usage( + self, observer: metrics.ValueObserver + ) -> None: + """Observer callback for memory usage + + Args: + observer: the observer to update + """ + virtual_memory = psutil.virtual_memory() + for metric in self._config["system_memory_usage"]: + self._system_memory_usage_labels["state"] = metric + observer.observe( + getattr(virtual_memory, metric), + self._system_memory_usage_labels + ) + + def _get_system_memory_utilization( + self, observer: metrics.UpDownSumObserver + ) -> None: + """Observer callback for memory utilization Args: observer: the observer to update """ system_memory = psutil.virtual_memory() - for metric in self._config["system_memory"]: - self._system_memory_labels["type"] = metric + + for metric in self._config["system_memory_utilization"]: + self._system_memory_utilization_labels["state"] = metric observer.observe( - getattr(system_memory, metric), self._system_memory_labels + getattr( + system_memory, metric + ) / system_memory.total, + self._system_memory_utilization_labels ) - def _get_system_cpu(self, observer: metrics.ValueObserver) -> None: - """Observer callback for system cpu + def _get_system_swap_usage( + self, observer: metrics.ValueObserver + ) -> None: + """Observer callback for swap usage Args: observer: the observer to update """ - cpu_times = psutil.cpu_times() - for _type in self._config["system_cpu"]: - self._system_cpu_labels["type"] = _type + system_swap = psutil.swap_memory() + + for metric in self._config["system_swap_usage"]: + self._system_swap_usage_labels["state"] = metric observer.observe( - getattr(cpu_times, _type), self._system_cpu_labels + getattr(system_swap, metric), self._system_swap_usage_labels ) - def _get_network_bytes(self, observer: metrics.ValueObserver) -> None: - """Observer callback for network bytes + def _get_system_swap_utilization( + self, observer: metrics.ValueObserver + ) -> None: + """Observer callback for swap utilization Args: observer: the observer to update """ - net_io = psutil.net_io_counters() - for _type in self._config["network_bytes"]: - self._network_bytes_labels["type"] = _type + system_swap = psutil.swap_memory() + + for metric in self._config["system_swap_utilization"]: + self._system_swap_utilization_labels["state"] = metric observer.observe( - getattr(net_io, _type), self._network_bytes_labels + getattr(system_swap, metric) / system_swap.total, + self._system_swap_utilization_labels ) - def _get_runtime_memory(self, observer: metrics.ValueObserver) -> None: - """Observer callback for runtime memory + # TODO Add _get_system_swap_page_faults + # TODO Add _get_system_swap_page_operations + + def _get_system_disk_io(self, observer: metrics.SumObserver) -> None: + """Observer callback for disk IO + + Args: + observer: the observer to update + """ + for device, counters in psutil.disk_io_counters(perdisk=True).items(): + for metric in self._config["system_disk_io"]: + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "{}_bytes".format(metric)), + self._system_disk_io_labels + ) + + def _get_system_disk_operations( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for disk operations + + Args: + observer: the observer to update + """ + for device, counters in psutil.disk_io_counters(perdisk=True).items(): + for metric in self._config["system_disk_operations"]: + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "{}_count".format(metric)), + self._system_disk_io_labels + ) + + def _get_system_disk_time( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for disk time + + Args: + observer: the observer to update + """ + for device, counters in psutil.disk_io_counters(perdisk=True).items(): + for metric in self._config["system_disk_time"]: + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "{}_time".format(metric)), + self._system_disk_io_labels + ) + + def _get_system_disk_merged( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for disk merged operations + + Args: + observer: the observer to update + """ + + # FIXME The units in the spec is 1, it seems like it should be + # operations or the value type should be Double + + for device, counters in psutil.disk_io_counters(perdisk=True).items(): + for metric in self._config["system_disk_time"]: + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "{}_merged_count".format(metric)), + self._system_disk_io_labels + ) + + # TODO Add _get_system_filesystem_usage + # TODO Add _get_system_filesystem_utilization + # TODO Filesystem information can be obtained with os.statvfs in Unix-like + # OSs, how to do the same in Windows? + + def _get_system_network_dropped_packets( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for network dropped packets + + Args: + observer: the observer to update + """ + + for device, counters in psutil.net_io_counters(pernic=True).items(): + for metric in self._config["system_network_dropped_packets"]: + in_out = {"receive": "in", "transmit": "out"}[metric] + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "drop{}".format(in_out)), + self._system_network_dropped_packets_labels + ) + + def _get_system_network_packets( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for network packets + + Args: + observer: the observer to update + """ + + for device, counters in psutil.net_io_counters(pernic=True).items(): + for metric in self._config["system_network_dropped_packets"]: + recv_sent = {"receive": "recv", "transmit": "sent"}[metric] + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "packets_{}".format(recv_sent)), + self._system_network_dropped_packets_labels + ) + + def _get_system_network_errors( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for network errors + + Args: + observer: the observer to update + """ + for device, counters in psutil.net_io_counters(pernic=True).items(): + for metric in self._config["system_network_errors"]: + in_out = {"receive": "in", "transmit": "out"}[metric] + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "err{}".format(in_out)), + self._system_network_errors_labels + ) + + def _get_system_network_io( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for network IO + + Args: + observer: the observer to update + """ + + for device, counters in psutil.net_io_counters(pernic=True).items(): + for metric in self._config["system_network_dropped_packets"]: + recv_sent = {"receive": "recv", "transmit": "sent"}[metric] + self._system_disk_io_labels["device"] = device + self._system_disk_io_labels["direction"] = metric + observer.observe( + getattr(counters, "bytes_{}".format(recv_sent)), + self._system_network_io_labels + ) + + 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? + 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 + observer.observe( + getattr(net_connection, metric), + self._system_network_connections_labels + ) + + def _get_runtime_cpython_memory( + self, observer: metrics.UpDownSumObserver + ) -> None: + """Observer callback for runtime CPyhton memory Args: observer: the observer to update """ proc_memory = self._proc.memory_info() - for _type in self._config["runtime_memory"]: - self._runtime_memory_labels["type"] = _type + for metric in self._config["runtime_cpython_memory"]: + self._runtime_cpython_memory_labels["type"] = metric observer.observe( - getattr(proc_memory, _type), self._runtime_memory_labels + getattr(proc_memory, metric), + self._runtime_cpython_memory_labels ) - def _get_runtime_cpu(self, observer: metrics.ValueObserver) -> None: - """Observer callback for runtime CPU + def _get_runtime_cpython_cpu_time( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for runtime CPython CPU time Args: observer: the observer to update """ proc_cpu = self._proc.cpu_times() - for _type in self._config["runtime_cpu"]: - self._runtime_cpu_labels["type"] = _type + for metric in self._config["runtime_cpython_cpu_time"]: + self._runtime_cpython_cpu_time_labels["type"] = metric observer.observe( - getattr(proc_cpu, _type), self._runtime_cpu_labels + getattr(proc_cpu, metric), + self._runtime_cpython_cpu_time_labels ) - def _get_runtime_gc_count(self, observer: metrics.ValueObserver) -> None: - """Observer callback for garbage collection + def _get_runtime_cpython_gc_count( + self, observer: metrics.SumObserver + ) -> None: + """Observer callback for CPython garbage collection Args: observer: the observer to update """ - gc_count = gc.get_count() - for index, count in enumerate(gc_count): - self._runtime_gc_labels["count"] = str(index) - observer.observe(count, self._runtime_gc_labels) + for index, count in enumerate(gc.get_count()): + self._runtime_cpython_gc_count_labels["count"] = str(index) + observer.observe(count, self._runtime_cpython_gc_count_labels) diff --git a/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py b/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py index b9ae662af19..f617d3f547f 100644 --- a/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py +++ b/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py @@ -31,15 +31,30 @@ def test_system_metrics_constructor(self): with mock.patch("opentelemetry.metrics.get_meter") as mock_get_meter: mock_get_meter.return_value = meter SystemMetrics(self.memory_metrics_exporter) - self.assertEqual(len(meter.observers), 6) + + self.assertEqual(len(meter.observers), 18) + observer_names = [ - "system.mem", - "system.cpu", - "system.net.bytes", - "runtime.python.mem", - "runtime.python.cpu", - "runtime.python.gc.count", + "system.cpu.time", + "system.cpu.utilization", + "system.memory.usage", + "system.memory.utilization", + "system.swap.usage", + "system.swap.utilization", + "system.disk.io", + "system.disk.operations", + "system.disk.time", + "system.disk.merged", + "system.network.dropped_packets", + "system.network.packets", + "system.network.errors", + "system.network.io", + "system.network.connections", + "runtime.cpython.memory", + "runtime.cpython.cpu_time", + "runtime.cpython.gc_count", ] + for observer in meter.observers: self.assertIn(observer.name, observer_names) observer_names.remove(observer.name) @@ -57,7 +72,7 @@ def _assert_metrics(self, observer_name, system_metrics, expected): and metric.instrument.name == observer_name ): self.assertEqual( - metric.aggregator.checkpoint.last, expected[metric.labels], + metric.aggregator.checkpoint, expected[metric.labels], ) assertions += 1 self.assertEqual(len(expected), assertions) @@ -71,133 +86,17 @@ def _test_metrics(self, observer_name, expected): self._assert_metrics(observer_name, system_metrics, expected) @mock.patch("psutil.cpu_times") - def test_system_cpu(self, mock_cpu_times): - CPUTimes = namedtuple("CPUTimes", ["user", "nice", "system", "idle"]) - mock_cpu_times.return_value = CPUTimes( - user=332277.48, nice=0.0, system=309836.43, idle=6724698.94 - ) - - expected = { - (("type", "user"),): 332277.48, - (("type", "system"),): 309836.43, - (("type", "idle"),): 6724698.94, - } - self._test_metrics("system.cpu", expected) - - @mock.patch("psutil.virtual_memory") - def test_system_memory(self, mock_virtual_memory): - VirtualMemory = namedtuple( - "VirtualMemory", - [ - "total", - "available", - "percent", - "used", - "free", - "active", - "inactive", - "wired", - ], - ) - mock_virtual_memory.return_value = VirtualMemory( - total=17179869184, - available=5520928768, - percent=67.9, - used=10263990272, - free=266964992, - active=5282459648, - inactive=5148700672, - wired=4981530624, - ) - - expected = { - (("type", "total"),): 17179869184, - (("type", "used"),): 10263990272, - (("type", "available"),): 5520928768, - (("type", "free"),): 266964992, - } - self._test_metrics("system.mem", expected) - - @mock.patch("psutil.net_io_counters") - def test_network_bytes(self, mock_net_io_counters): - NetworkIO = namedtuple( - "NetworkIO", - ["bytes_sent", "bytes_recv", "packets_recv", "packets_sent"], - ) - mock_net_io_counters.return_value = NetworkIO( - bytes_sent=23920188416, - bytes_recv=46798894080, - packets_sent=53127118, - packets_recv=53205738, - ) - - expected = { - (("type", "bytes_recv"),): 46798894080, - (("type", "bytes_sent"),): 23920188416, - } - self._test_metrics("system.net.bytes", expected) - - def test_runtime_memory(self): - meter = self.meter_provider.get_meter(__name__) - with mock.patch("opentelemetry.metrics.get_meter") as mock_get_meter: - mock_get_meter.return_value = meter - system_metrics = SystemMetrics(self.memory_metrics_exporter) - - with mock.patch.object( - system_metrics._proc, # pylint: disable=protected-access - "memory_info", - ) as mock_runtime_memory: - RuntimeMemory = namedtuple( - "RuntimeMemory", ["rss", "vms", "pfaults", "pageins"], - ) - mock_runtime_memory.return_value = RuntimeMemory( - rss=9777152, vms=4385665024, pfaults=2631, pageins=49 - ) - expected = { - (("type", "rss"),): 9777152, - (("type", "vms"),): 4385665024, - } - self._assert_metrics( - "runtime.python.mem", system_metrics, expected - ) - - def test_runtime_cpu(self): - meter = self.meter_provider.get_meter(__name__) - with mock.patch("opentelemetry.metrics.get_meter") as mock_get_meter: - mock_get_meter.return_value = meter - system_metrics = SystemMetrics(self.memory_metrics_exporter) - - with mock.patch.object( - system_metrics._proc, # pylint: disable=protected-access - "cpu_times", - ) as mock_runtime_cpu_times: - RuntimeCPU = namedtuple( - "RuntimeCPU", ["user", "nice", "system"] - ) - mock_runtime_cpu_times.return_value = RuntimeCPU( - user=100.48, nice=0.0, system=200.43 - ) - - expected = { - (("type", "user"),): 100.48, - (("type", "system"),): 200.43, - } - - self._assert_metrics( - "runtime.python.cpu", system_metrics, expected - ) - - @mock.patch("gc.get_count") - def test_runtime_gc_count(self, mock_gc): - mock_gc.return_value = [ - 100, # gen0 - 50, # gen1 - 10, # gen2 + def test_system_cpu_time(self, mock_cpu_times): + CPUTimes = namedtuple("CPUTimes", ["idle", "user", "system", "irq"]) + mock_cpu_times.return_value = [ + CPUTimes(idle=1.2, user=3.4, system=5.6, irq=7.8), + CPUTimes(idle=1.2, user=3.4, system=5.6, irq=7.8), ] expected = { - (("count", "0"),): 100, - (("count", "1"),): 50, - (("count", "2"),): 10, + (("state", "idle"),): 1.2, + (("state", "user"),): 3.4, + (("state", "system"),): 5.6, + (("state", "irq"),): 7.8, } - self._test_metrics("runtime.python.gc.count", expected) + self._test_metrics("system.cpu.time", expected) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py index 09d7283cab7..3945eea8f96 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py @@ -33,16 +33,21 @@ def ns_to_iso_str(nanoseconds): def get_dict_as_key(labels): """Converts a dict to be used as a unique key""" - return tuple( - sorted( - map( - lambda kv: (kv[0], tuple(kv[1])) - if isinstance(kv[1], list) - else kv, - labels.items(), - ) - ) - ) + + result = [] + + for key, value in labels.items(): + + if isinstance(value, list): + result.append((key, tuple(value))) + + elif isinstance(value, dict): + result.append((key, get_dict_as_key(value))) + + else: + result.append((key, value)) + + return tuple(sorted(result)) class BoundedList(Sequence):