Repository: open-telemetry/opentelemetry-python

Issue:
Update opentelemetry-instrumentation-system-metrics
The system metrics instrumentation needs to be updated to reflect semantic conventions in otep 119: https://github.com/open-telemetry/oteps/pull/119

It also needs to be revisited to ensure it follows the correct path to collecting metrics.


Discussion:

Resolving patch:
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
--- 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,12 +16,27 @@
 process (CPU, memory, garbage collection) metrics. By default, the
 following metrics are configured:
 
-"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"],
+.. code:: python
 
+    {
+        "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.disk.io": ["read", "write"],
+        "system.disk.operations": ["read", "write"],
+        "system.disk.time": ["read", "write"],
+        "system.disk.merged": ["read", "write"],
+        "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.memory": ["rss", "vms"],
+        "runtime.cpu.time": ["user", "system"],
+    }
 
 Usage
 -----
@@ -42,11 +57,11 @@
 
     # to configure custom metrics
     configuration = {
-        "system_memory": ["total", "available", "used", "free", "active", "inactive", "wired"],
-        "system_cpu": ["user", "nice", "system", "idle"],
-        "network_bytes": ["bytes_recv", "bytes_sent"],
-        "runtime_memory": ["rss", "vms"],
-        "runtime_cpu": ["user", "system"],
+        "system.memory.usage": ["used", "free", "cached"],
+        "system.cpu.time": ["idle", "user", "system", "irq"],
+        "system.network.io": ["trasmit", "receive"],
+        "runtime.memory": ["rss", "vms"],
+        "runtime.cpu.time": ["user", "system"],
     }
     SystemMetrics(exporter, config=configuration)
 
@@ -57,16 +72,23 @@
 import gc
 import os
 import typing
+from platform import python_implementation
 
 import psutil
 
 from opentelemetry import metrics
-from opentelemetry.sdk.metrics import ValueObserver
+from opentelemetry.sdk.metrics import (
+    SumObserver,
+    UpDownSumObserver,
+    ValueObserver,
+)
 from opentelemetry.sdk.metrics.export import MetricsExporter
 from opentelemetry.sdk.metrics.export.controller import PushController
+from opentelemetry.sdk.util import get_dict_as_key
 
 
 class SystemMetrics:
+    # pylint: disable=too-many-statements
     def __init__(
         self,
         exporter: MetricsExporter,
@@ -79,157 +101,591 @@ def __init__(
         self.controller = PushController(
             meter=self.meter, exporter=exporter, interval=interval
         )
+        self._python_implementation = python_implementation().lower()
         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.memory": ["rss", "vms"],
+                "runtime.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 = {}
-        # 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.meter.register_observer(
-            callback=self._get_system_memory,
-            name="system.mem",
-            description="System memory",
+
+        self._system_cpu_time_labels = self._labels.copy()
+        self._system_cpu_utilization_labels = self._labels.copy()
+
+        self._system_memory_usage_labels = self._labels.copy()
+        self._system_memory_utilization_labels = self._labels.copy()
+
+        self._system_swap_usage_labels = self._labels.copy()
+        self._system_swap_utilization_labels = self._labels.copy()
+        # self._system_swap_page_faults = self._labels.copy()
+        # self._system_swap_page_operations = self._labels.copy()
+
+        self._system_disk_io_labels = self._labels.copy()
+        self._system_disk_operations_labels = self._labels.copy()
+        self._system_disk_time_labels = self._labels.copy()
+        self._system_disk_merged_labels = self._labels.copy()
+
+        # self._system_filesystem_usage_labels = self._labels.copy()
+        # self._system_filesystem_utilization_labels = self._labels.copy()
+
+        self._system_network_dropped_packets_labels = self._labels.copy()
+        self._system_network_packets_labels = self._labels.copy()
+        self._system_network_errors_labels = self._labels.copy()
+        self._system_network_io_labels = self._labels.copy()
+        self._system_network_connections_labels = self._labels.copy()
+
+        self._runtime_memory_labels = self._labels.copy()
+        self._runtime_cpu_time_labels = self._labels.copy()
+        self._runtime_gc_count_labels = self._labels.copy()
+
+        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=ValueObserver,
+        )
+
+        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=ValueObserver,
         )
 
         self.meter.register_observer(
-            callback=self._get_system_cpu,
-            name="system.cpu",
-            description="System CPU",
-            unit="seconds",
+            callback=self._get_system_memory_utilization,
+            name="system.memory.utilization",
+            description="System memory utilization",
+            unit="1",
             value_type=float,
             observer_type=ValueObserver,
         )
 
         self.meter.register_observer(
-            callback=self._get_network_bytes,
-            name="system.net.bytes",
-            description="System network bytes",
-            unit="bytes",
+            callback=self._get_system_swap_usage,
+            name="system.swap.usage",
+            description="System swap usage",
+            unit="pages",
             value_type=int,
             observer_type=ValueObserver,
         )
 
+        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=ValueObserver,
+        )
+
+        # 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=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_disk_time,
+            name="system.disk.time",
+            description="System disk time",
+            unit="seconds",
+            value_type=float,
+            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=ValueObserver,
+        # )
+
+        # 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=ValueObserver,
+        # )
+
+        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_system_network_connections,
+            name="system.network.connections",
+            description="System network connections",
+            unit="connections",
+            value_type=int,
+            observer_type=UpDownSumObserver,
+        )
+
         self.meter.register_observer(
             callback=self._get_runtime_memory,
-            name="runtime.python.mem",
-            description="Runtime memory",
+            name="runtime.{}.memory".format(self._python_implementation),
+            description="Runtime {} memory".format(
+                self._python_implementation
+            ),
             unit="bytes",
             value_type=int,
-            observer_type=ValueObserver,
+            observer_type=SumObserver,
         )
 
         self.meter.register_observer(
-            callback=self._get_runtime_cpu,
-            name="runtime.python.cpu",
-            description="Runtime CPU",
+            callback=self._get_runtime_cpu_time,
+            name="runtime.{}.cpu_time".format(self._python_implementation),
+            description="Runtime {} CPU time".format(
+                self._python_implementation
+            ),
             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",
+            name="runtime.{}.gc_count".format(self._python_implementation),
+            description="Runtime {} GC count".format(
+                self._python_implementation
+            ),
+            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.ValueObserver
+    ) -> 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) / system_memory.total,
+                self._system_memory_utilization_labels,
+            )
+
+    def _get_system_swap_usage(self, observer: metrics.ValueObserver) -> None:
+        """Observer callback for swap usage
+
+        Args:
+            observer: the observer to update
+        """
+        system_swap = psutil.swap_memory()
+
+        for metric in self._config["system.swap.usage"]:
+            self._system_swap_usage_labels["state"] = metric
             observer.observe(
-                getattr(system_memory, metric), self._system_memory_labels
+                getattr(system_swap, metric), self._system_swap_usage_labels
             )
 
-    def _get_system_cpu(self, observer: metrics.ValueObserver) -> None:
-        """Observer callback for system cpu
+    def _get_system_swap_utilization(
+        self, observer: metrics.ValueObserver
+    ) -> None:
+        """Observer callback for swap utilization
 
         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.utilization"]:
+            self._system_swap_utilization_labels["state"] = metric
             observer.observe(
-                getattr(cpu_times, _type), self._system_cpu_labels
+                getattr(system_swap, metric) / system_swap.total,
+                self._system_swap_utilization_labels,
             )
 
-    def _get_network_bytes(self, observer: metrics.ValueObserver) -> None:
-        """Observer callback for network bytes
+    # 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
         """
-        net_io = psutil.net_io_counters()
-        for _type in self._config["network_bytes"]:
-            self._network_bytes_labels["type"] = _type
+        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_operations_labels["device"] = device
+                self._system_disk_operations_labels["direction"] = metric
+                observer.observe(
+                    getattr(counters, "{}_count".format(metric)),
+                    self._system_disk_operations_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_time_labels["device"] = device
+                self._system_disk_time_labels["direction"] = metric
+                observer.observe(
+                    getattr(counters, "{}_time".format(metric)) / 1000,
+                    self._system_disk_time_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_merged_labels["device"] = device
+                self._system_disk_merged_labels["direction"] = metric
+                observer.observe(
+                    getattr(counters, "{}_merged_count".format(metric)),
+                    self._system_disk_merged_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_network_dropped_packets_labels["device"] = device
+                self._system_network_dropped_packets_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_network_packets_labels["device"] = device
+                self._system_network_packets_labels["direction"] = metric
+                observer.observe(
+                    getattr(counters, "packets_{}".format(recv_sent)),
+                    self._system_network_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_network_errors_labels["device"] = device
+                self._system_network_errors_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_network_io_labels["device"] = device
+                self._system_network_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?
+
+        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(
-                getattr(net_io, _type), self._network_bytes_labels
+                connection_counter["counter"], connection_counter["labels"],
             )
 
-    def _get_runtime_memory(self, observer: metrics.ValueObserver) -> None:
+    def _get_runtime_memory(self, observer: metrics.SumObserver) -> None:
         """Observer callback for runtime 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.memory"]:
+            self._runtime_memory_labels["type"] = metric
             observer.observe(
-                getattr(proc_memory, _type), self._runtime_memory_labels
+                getattr(proc_memory, metric), self._runtime_memory_labels,
             )
 
-    def _get_runtime_cpu(self, observer: metrics.ValueObserver) -> None:
-        """Observer callback for runtime CPU
+    def _get_runtime_cpu_time(self, observer: metrics.SumObserver) -> None:
+        """Observer callback for runtime 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.cpu.time"]:
+            self._runtime_cpu_time_labels["type"] = metric
             observer.observe(
-                getattr(proc_cpu, _type), self._runtime_cpu_labels
+                getattr(proc_cpu, metric), self._runtime_cpu_time_labels,
             )
 
-    def _get_runtime_gc_count(self, observer: metrics.ValueObserver) -> None:
+    def _get_runtime_gc_count(self, observer: metrics.SumObserver) -> None:
         """Observer callback for 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_gc_count_labels["count"] = str(index)
+            observer.observe(count, self._runtime_gc_count_labels)


Test patch:
diff --git a/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py b/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py
--- a/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py
+++ b/instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py
@@ -12,11 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# pylint: disable=protected-access
+
 from collections import namedtuple
+from platform import python_implementation
 from unittest import mock
 
 from opentelemetry import metrics
 from opentelemetry.instrumentation.system_metrics import SystemMetrics
+from opentelemetry.sdk.metrics.export.aggregate import ValueObserverAggregator
 from opentelemetry.test.test_base import TestBase
 
 
@@ -24,6 +28,7 @@ class TestSystemMetrics(TestBase):
     def setUp(self):
         super().setUp()
         self.memory_metrics_exporter.clear()
+        self.implementation = python_implementation().lower()
 
     def test_system_metrics_constructor(self):
         # ensure the observers have been registered
@@ -31,15 +36,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.{}.memory".format(self.implementation),
+            "runtime.{}.cpu_time".format(self.implementation),
+            "runtime.{}.gc_count".format(self.implementation),
         ]
+
         for observer in meter.observers:
             self.assertIn(observer.name, observer_names)
             observer_names.remove(observer.name)
@@ -57,7 +77,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)
@@ -70,134 +90,614 @@ def _test_metrics(self, observer_name, expected):
             system_metrics = SystemMetrics(self.memory_metrics_exporter)
             self._assert_metrics(observer_name, system_metrics, expected)
 
+    # When this test case is executed, _get_system_cpu_utilization gets run
+    # too because of the controller thread which runs all observers. This patch
+    # is added here to stop a warning that would otherwise be raised.
+    # pylint: disable=unused-argument
+    @mock.patch("psutil.cpu_times_percent")
     @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
+    def test_system_cpu_time(self, mock_cpu_times, mock_cpu_times_percent):
+        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 = {
+            (("cpu", 1), ("state", "idle"),): 1.2,
+            (("cpu", 1), ("state", "user"),): 3.4,
+            (("cpu", 1), ("state", "system"),): 5.6,
+            (("cpu", 1), ("state", "irq"),): 7.8,
+            (("cpu", 2), ("state", "idle"),): 1.2,
+            (("cpu", 2), ("state", "user"),): 3.4,
+            (("cpu", 2), ("state", "system"),): 5.6,
+            (("cpu", 2), ("state", "irq"),): 7.8,
+        }
+        self._test_metrics("system.cpu.time", expected)
+
+    @mock.patch("psutil.cpu_times_percent")
+    def test_system_cpu_utilization(self, mock_cpu_times_percent):
+        CPUTimesPercent = namedtuple(
+            "CPUTimesPercent", ["idle", "user", "system", "irq"]
         )
+        mock_cpu_times_percent.return_value = [
+            CPUTimesPercent(idle=1.2, user=3.4, system=5.6, irq=7.8),
+            CPUTimesPercent(idle=1.2, user=3.4, system=5.6, irq=7.8),
+        ]
 
         expected = {
-            (("type", "user"),): 332277.48,
-            (("type", "system"),): 309836.43,
-            (("type", "idle"),): 6724698.94,
+            (("cpu", 1), ("state", "idle"),): ValueObserverAggregator._TYPE(
+                min=1.2 / 100,
+                max=1.2 / 100,
+                sum=1.2 / 100,
+                count=1,
+                last=1.2 / 100,
+            ),
+            (("cpu", 1), ("state", "user"),): ValueObserverAggregator._TYPE(
+                min=3.4 / 100,
+                max=3.4 / 100,
+                sum=3.4 / 100,
+                count=1,
+                last=3.4 / 100,
+            ),
+            (("cpu", 1), ("state", "system"),): ValueObserverAggregator._TYPE(
+                min=5.6 / 100,
+                max=5.6 / 100,
+                sum=5.6 / 100,
+                count=1,
+                last=5.6 / 100,
+            ),
+            (("cpu", 1), ("state", "irq"),): ValueObserverAggregator._TYPE(
+                min=7.8 / 100,
+                max=7.8 / 100,
+                sum=7.8 / 100,
+                count=1,
+                last=7.8 / 100,
+            ),
+            (("cpu", 2), ("state", "idle"),): ValueObserverAggregator._TYPE(
+                min=1.2 / 100,
+                max=1.2 / 100,
+                sum=1.2 / 100,
+                count=1,
+                last=1.2 / 100,
+            ),
+            (("cpu", 2), ("state", "user"),): ValueObserverAggregator._TYPE(
+                min=3.4 / 100,
+                max=3.4 / 100,
+                sum=3.4 / 100,
+                count=1,
+                last=3.4 / 100,
+            ),
+            (("cpu", 2), ("state", "system"),): ValueObserverAggregator._TYPE(
+                min=5.6 / 100,
+                max=5.6 / 100,
+                sum=5.6 / 100,
+                count=1,
+                last=5.6 / 100,
+            ),
+            (("cpu", 2), ("state", "irq"),): ValueObserverAggregator._TYPE(
+                min=7.8 / 100,
+                max=7.8 / 100,
+                sum=7.8 / 100,
+                count=1,
+                last=7.8 / 100,
+            ),
         }
-        self._test_metrics("system.cpu", expected)
+        self._test_metrics("system.cpu.utilization", expected)
 
     @mock.patch("psutil.virtual_memory")
-    def test_system_memory(self, mock_virtual_memory):
+    def test_system_memory_usage(self, mock_virtual_memory):
         VirtualMemory = namedtuple(
-            "VirtualMemory",
+            "VirtualMemory", ["used", "free", "cached", "total"]
+        )
+        mock_virtual_memory.return_value = VirtualMemory(
+            used=1, free=2, cached=3, total=4
+        )
+
+        expected = {
+            (("state", "used"),): ValueObserverAggregator._TYPE(
+                min=1, max=1, sum=1, count=1, last=1
+            ),
+            (("state", "free"),): ValueObserverAggregator._TYPE(
+                min=2, max=2, sum=2, count=1, last=2
+            ),
+            (("state", "cached"),): ValueObserverAggregator._TYPE(
+                min=3, max=3, sum=3, count=1, last=3
+            ),
+        }
+        self._test_metrics("system.memory.usage", expected)
+
+    @mock.patch("psutil.virtual_memory")
+    def test_system_memory_utilization(self, mock_virtual_memory):
+        VirtualMemory = namedtuple(
+            "VirtualMemory", ["used", "free", "cached", "total"]
+        )
+        mock_virtual_memory.return_value = VirtualMemory(
+            used=1, free=2, cached=3, total=4
+        )
+
+        expected = {
+            (("state", "used"),): ValueObserverAggregator._TYPE(
+                min=1 / 4, max=1 / 4, sum=1 / 4, count=1, last=1 / 4
+            ),
+            (("state", "free"),): ValueObserverAggregator._TYPE(
+                min=2 / 4, max=2 / 4, sum=2 / 4, count=1, last=2 / 4
+            ),
+            (("state", "cached"),): ValueObserverAggregator._TYPE(
+                min=3 / 4, max=3 / 4, sum=3 / 4, count=1, last=3 / 4
+            ),
+        }
+        self._test_metrics("system.memory.utilization", expected)
+
+    @mock.patch("psutil.swap_memory")
+    def test_system_swap_usage(self, mock_swap_memory):
+        SwapMemory = namedtuple("SwapMemory", ["used", "free", "total"])
+        mock_swap_memory.return_value = SwapMemory(used=1, free=2, total=3)
+
+        expected = {
+            (("state", "used"),): ValueObserverAggregator._TYPE(
+                min=1, max=1, sum=1, count=1, last=1
+            ),
+            (("state", "free"),): ValueObserverAggregator._TYPE(
+                min=2, max=2, sum=2, count=1, last=2
+            ),
+        }
+        self._test_metrics("system.swap.usage", expected)
+
+    @mock.patch("psutil.swap_memory")
+    def test_system_swap_utilization(self, mock_swap_memory):
+        SwapMemory = namedtuple("SwapMemory", ["used", "free", "total"])
+        mock_swap_memory.return_value = SwapMemory(used=1, free=2, total=3)
+
+        expected = {
+            (("state", "used"),): ValueObserverAggregator._TYPE(
+                min=1 / 3, max=1 / 3, sum=1 / 3, count=1, last=1 / 3
+            ),
+            (("state", "free"),): ValueObserverAggregator._TYPE(
+                min=2 / 3, max=2 / 3, sum=2 / 3, count=1, last=2 / 3
+            ),
+        }
+        self._test_metrics("system.swap.utilization", expected)
+
+    @mock.patch("psutil.disk_io_counters")
+    def test_system_disk_io(self, mock_disk_io_counters):
+        DiskIO = namedtuple(
+            "DiskIO",
             [
-                "total",
-                "available",
-                "percent",
-                "used",
-                "free",
-                "active",
-                "inactive",
-                "wired",
+                "read_count",
+                "write_count",
+                "read_bytes",
+                "write_bytes",
+                "read_time",
+                "write_time",
+                "read_merged_count",
+                "write_merged_count",
             ],
         )
-        mock_virtual_memory.return_value = VirtualMemory(
-            total=17179869184,
-            available=5520928768,
-            percent=67.9,
-            used=10263990272,
-            free=266964992,
-            active=5282459648,
-            inactive=5148700672,
-            wired=4981530624,
+        mock_disk_io_counters.return_value = {
+            "sda": DiskIO(
+                read_count=1,
+                write_count=2,
+                read_bytes=3,
+                write_bytes=4,
+                read_time=5,
+                write_time=6,
+                read_merged_count=7,
+                write_merged_count=8,
+            ),
+            "sdb": DiskIO(
+                read_count=9,
+                write_count=10,
+                read_bytes=11,
+                write_bytes=12,
+                read_time=13,
+                write_time=14,
+                read_merged_count=15,
+                write_merged_count=16,
+            ),
+        }
+
+        expected = {
+            (("device", "sda"), ("direction", "read"),): 3,
+            (("device", "sda"), ("direction", "write"),): 4,
+            (("device", "sdb"), ("direction", "read"),): 11,
+            (("device", "sdb"), ("direction", "write"),): 12,
+        }
+        self._test_metrics("system.disk.io", expected)
+
+    @mock.patch("psutil.disk_io_counters")
+    def test_system_disk_operations(self, mock_disk_io_counters):
+        DiskIO = namedtuple(
+            "DiskIO",
+            [
+                "read_count",
+                "write_count",
+                "read_bytes",
+                "write_bytes",
+                "read_time",
+                "write_time",
+                "read_merged_count",
+                "write_merged_count",
+            ],
+        )
+        mock_disk_io_counters.return_value = {
+            "sda": DiskIO(
+                read_count=1,
+                write_count=2,
+                read_bytes=3,
+                write_bytes=4,
+                read_time=5,
+                write_time=6,
+                read_merged_count=7,
+                write_merged_count=8,
+            ),
+            "sdb": DiskIO(
+                read_count=9,
+                write_count=10,
+                read_bytes=11,
+                write_bytes=12,
+                read_time=13,
+                write_time=14,
+                read_merged_count=15,
+                write_merged_count=16,
+            ),
+        }
+
+        expected = {
+            (("device", "sda"), ("direction", "read"),): 1,
+            (("device", "sda"), ("direction", "write"),): 2,
+            (("device", "sdb"), ("direction", "read"),): 9,
+            (("device", "sdb"), ("direction", "write"),): 10,
+        }
+        self._test_metrics("system.disk.operations", expected)
+
+    @mock.patch("psutil.disk_io_counters")
+    def test_system_disk_time(self, mock_disk_io_counters):
+        DiskIO = namedtuple(
+            "DiskIO",
+            [
+                "read_count",
+                "write_count",
+                "read_bytes",
+                "write_bytes",
+                "read_time",
+                "write_time",
+                "read_merged_count",
+                "write_merged_count",
+            ],
         )
+        mock_disk_io_counters.return_value = {
+            "sda": DiskIO(
+                read_count=1,
+                write_count=2,
+                read_bytes=3,
+                write_bytes=4,
+                read_time=5,
+                write_time=6,
+                read_merged_count=7,
+                write_merged_count=8,
+            ),
+            "sdb": DiskIO(
+                read_count=9,
+                write_count=10,
+                read_bytes=11,
+                write_bytes=12,
+                read_time=13,
+                write_time=14,
+                read_merged_count=15,
+                write_merged_count=16,
+            ),
+        }
 
         expected = {
-            (("type", "total"),): 17179869184,
-            (("type", "used"),): 10263990272,
-            (("type", "available"),): 5520928768,
-            (("type", "free"),): 266964992,
+            (("device", "sda"), ("direction", "read"),): 5 / 1000,
+            (("device", "sda"), ("direction", "write"),): 6 / 1000,
+            (("device", "sdb"), ("direction", "read"),): 13 / 1000,
+            (("device", "sdb"), ("direction", "write"),): 14 / 1000,
         }
-        self._test_metrics("system.mem", expected)
+        self._test_metrics("system.disk.time", expected)
+
+    @mock.patch("psutil.disk_io_counters")
+    def test_system_disk_merged(self, mock_disk_io_counters):
+        DiskIO = namedtuple(
+            "DiskIO",
+            [
+                "read_count",
+                "write_count",
+                "read_bytes",
+                "write_bytes",
+                "read_time",
+                "write_time",
+                "read_merged_count",
+                "write_merged_count",
+            ],
+        )
+        mock_disk_io_counters.return_value = {
+            "sda": DiskIO(
+                read_count=1,
+                write_count=2,
+                read_bytes=3,
+                write_bytes=4,
+                read_time=5,
+                write_time=6,
+                read_merged_count=7,
+                write_merged_count=8,
+            ),
+            "sdb": DiskIO(
+                read_count=9,
+                write_count=10,
+                read_bytes=11,
+                write_bytes=12,
+                read_time=13,
+                write_time=14,
+                read_merged_count=15,
+                write_merged_count=16,
+            ),
+        }
+
+        expected = {
+            (("device", "sda"), ("direction", "read"),): 7,
+            (("device", "sda"), ("direction", "write"),): 8,
+            (("device", "sdb"), ("direction", "read"),): 15,
+            (("device", "sdb"), ("direction", "write"),): 16,
+        }
+        self._test_metrics("system.disk.merged", 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"],
+    def test_system_network_dropped_packets(self, mock_net_io_counters):
+        NetIO = namedtuple(
+            "NetIO",
+            [
+                "dropin",
+                "dropout",
+                "packets_sent",
+                "packets_recv",
+                "errin",
+                "errout",
+                "bytes_sent",
+                "bytes_recv",
+            ],
         )
-        mock_net_io_counters.return_value = NetworkIO(
-            bytes_sent=23920188416,
-            bytes_recv=46798894080,
-            packets_sent=53127118,
-            packets_recv=53205738,
+        mock_net_io_counters.return_value = {
+            "eth0": NetIO(
+                dropin=1,
+                dropout=2,
+                packets_sent=3,
+                packets_recv=4,
+                errin=5,
+                errout=6,
+                bytes_sent=7,
+                bytes_recv=8,
+            ),
+            "eth1": NetIO(
+                dropin=9,
+                dropout=10,
+                packets_sent=11,
+                packets_recv=12,
+                errin=13,
+                errout=14,
+                bytes_sent=15,
+                bytes_recv=16,
+            ),
+        }
+
+        expected = {
+            (("device", "eth0"), ("direction", "receive"),): 1,
+            (("device", "eth0"), ("direction", "transmit"),): 2,
+            (("device", "eth1"), ("direction", "receive"),): 9,
+            (("device", "eth1"), ("direction", "transmit"),): 10,
+        }
+        self._test_metrics("system.network.dropped_packets", expected)
+
+    @mock.patch("psutil.net_io_counters")
+    def test_system_network_packets(self, mock_net_io_counters):
+        NetIO = namedtuple(
+            "NetIO",
+            [
+                "dropin",
+                "dropout",
+                "packets_sent",
+                "packets_recv",
+                "errin",
+                "errout",
+                "bytes_sent",
+                "bytes_recv",
+            ],
         )
+        mock_net_io_counters.return_value = {
+            "eth0": NetIO(
+                dropin=1,
+                dropout=2,
+                packets_sent=3,
+                packets_recv=4,
+                errin=5,
+                errout=6,
+                bytes_sent=7,
+                bytes_recv=8,
+            ),
+            "eth1": NetIO(
+                dropin=9,
+                dropout=10,
+                packets_sent=11,
+                packets_recv=12,
+                errin=13,
+                errout=14,
+                bytes_sent=15,
+                bytes_recv=16,
+            ),
+        }
 
         expected = {
-            (("type", "bytes_recv"),): 46798894080,
-            (("type", "bytes_sent"),): 23920188416,
+            (("device", "eth0"), ("direction", "receive"),): 4,
+            (("device", "eth0"), ("direction", "transmit"),): 3,
+            (("device", "eth1"), ("direction", "receive"),): 12,
+            (("device", "eth1"), ("direction", "transmit"),): 11,
         }
-        self._test_metrics("system.net.bytes", expected)
+        self._test_metrics("system.network.packets", 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)
+    @mock.patch("psutil.net_io_counters")
+    def test_system_network_errors(self, mock_net_io_counters):
+        NetIO = namedtuple(
+            "NetIO",
+            [
+                "dropin",
+                "dropout",
+                "packets_sent",
+                "packets_recv",
+                "errin",
+                "errout",
+                "bytes_sent",
+                "bytes_recv",
+            ],
+        )
+        mock_net_io_counters.return_value = {
+            "eth0": NetIO(
+                dropin=1,
+                dropout=2,
+                packets_sent=3,
+                packets_recv=4,
+                errin=5,
+                errout=6,
+                bytes_sent=7,
+                bytes_recv=8,
+            ),
+            "eth1": NetIO(
+                dropin=9,
+                dropout=10,
+                packets_sent=11,
+                packets_recv=12,
+                errin=13,
+                errout=14,
+                bytes_sent=15,
+                bytes_recv=16,
+            ),
+        }
 
-            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
-                )
+        expected = {
+            (("device", "eth0"), ("direction", "receive"),): 5,
+            (("device", "eth0"), ("direction", "transmit"),): 6,
+            (("device", "eth1"), ("direction", "receive"),): 13,
+            (("device", "eth1"), ("direction", "transmit"),): 14,
+        }
+        self._test_metrics("system.network.errors", 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)
+    @mock.patch("psutil.net_io_counters")
+    def test_system_network_io(self, mock_net_io_counters):
+        NetIO = namedtuple(
+            "NetIO",
+            [
+                "dropin",
+                "dropout",
+                "packets_sent",
+                "packets_recv",
+                "errin",
+                "errout",
+                "bytes_sent",
+                "bytes_recv",
+            ],
+        )
+        mock_net_io_counters.return_value = {
+            "eth0": NetIO(
+                dropin=1,
+                dropout=2,
+                packets_sent=3,
+                packets_recv=4,
+                errin=5,
+                errout=6,
+                bytes_sent=7,
+                bytes_recv=8,
+            ),
+            "eth1": NetIO(
+                dropin=9,
+                dropout=10,
+                packets_sent=11,
+                packets_recv=12,
+                errin=13,
+                errout=14,
+                bytes_sent=15,
+                bytes_recv=16,
+            ),
+        }
 
-            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 = {
+            (("device", "eth0"), ("direction", "receive"),): 8,
+            (("device", "eth0"), ("direction", "transmit"),): 7,
+            (("device", "eth1"), ("direction", "receive"),): 16,
+            (("device", "eth1"), ("direction", "transmit"),): 15,
+        }
+        self._test_metrics("system.network.io", expected)
 
-                expected = {
-                    (("type", "user"),): 100.48,
-                    (("type", "system"),): 200.43,
-                }
+    @mock.patch("psutil.net_connections")
+    def test_system_network_connections(self, mock_net_connections):
+        NetConnection = namedtuple(
+            "NetworkConnection", ["family", "type", "status"]
+        )
+        Type = namedtuple("Type", ["value"])
+        mock_net_connections.return_value = [
+            NetConnection(family=1, status="ESTABLISHED", type=Type(value=2),),
+            NetConnection(family=1, status="ESTABLISHED", type=Type(value=1),),
+        ]
 
-                self._assert_metrics(
-                    "runtime.python.cpu", system_metrics, expected
-                )
+        expected = {
+            (
+                ("family", 1),
+                ("protocol", "udp"),
+                ("state", "ESTABLISHED"),
+                ("type", Type(value=2)),
+            ): 1,
+            (
+                ("family", 1),
+                ("protocol", "tcp"),
+                ("state", "ESTABLISHED"),
+                ("type", Type(value=1)),
+            ): 1,
+        }
+        self._test_metrics("system.network.connections", expected)
+
+    @mock.patch("psutil.Process.memory_info")
+    def test_runtime_memory(self, mock_process_memory_info):
+
+        PMem = namedtuple("PMem", ["rss", "vms"])
+
+        mock_process_memory_info.configure_mock(
+            **{"return_value": PMem(rss=1, vms=2)}
+        )
+
+        expected = {
+            (("type", "rss"),): 1,
+            (("type", "vms"),): 2,
+        }
+        self._test_metrics(
+            "runtime.{}.memory".format(self.implementation), expected
+        )
+
+    @mock.patch("psutil.Process.cpu_times")
+    def test_runtime_cpu_time(self, mock_process_cpu_times):
+
+        PCPUTimes = namedtuple("PCPUTimes", ["user", "system"])
+
+        mock_process_cpu_times.configure_mock(
+            **{"return_value": PCPUTimes(user=1.1, system=2.2)}
+        )
+
+        expected = {
+            (("type", "user"),): 1.1,
+            (("type", "system"),): 2.2,
+        }
+        self._test_metrics(
+            "runtime.{}.cpu_time".format(self.implementation), 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_runtime_get_count(self, mock_gc_get_count):
+
+        mock_gc_get_count.configure_mock(**{"return_value": (1, 2, 3)})
 
         expected = {
-            (("count", "0"),): 100,
-            (("count", "1"),): 50,
-            (("count", "2"),): 10,
+            (("count", "0"),): 1,
+            (("count", "1"),): 2,
+            (("count", "2"),): 3,
         }
-        self._test_metrics("runtime.python.gc.count", expected)
+        self._test_metrics(
+            "runtime.{}.gc_count".format(self.implementation), expected
+        )