Repository: open-telemetry/opentelemetry-python-contrib

Issue:
System metrics instrumentation not working with custom defined configuration
System metric instrumentation is not functional if configuration on which metrics to be exported is explicitly provided. As a minimal example, this code

```python
from opentelemetry.metrics import set_meter_provider
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    ConsoleMetricExporter,
    PeriodicExportingMetricReader,
)

exporter = ConsoleMetricExporter()
set_meter_provider(MeterProvider([PeriodicExportingMetricReader(exporter)]))

configuration = {
    "runtime.memory": ["rss", "vms"],
    "runtime.cpu.time": ["user", "system"],
}

SystemMetricsInstrumentor(config=configuration).instrument()
```

results in

```
Traceback (most recent call last):
  File ".../test.py", line 15, in <module>
    SystemMetricsInstrumentor(config=configuration).instrument()
  File ".../lib/python3.10/site-packages/opentelemetry/instrumentation/instrumentor.py", line 51, in __new__
    cls._instance = object.__new__(cls, *args, **kwargs)
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
```

I am happy to look into fixing this. Removing `*args` and `**kwargs` in `opentelemetry/instrumentation/instrumentor.py:51` actually solves the issue here but I'd like to understand the implications as this implies changing the interface class.


Discussion:

Resolving patch:
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py
--- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py
+++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py
@@ -47,7 +47,7 @@ class BaseInstrumentor(ABC):
 
     def __new__(cls, *args, **kwargs):
         if cls._instance is None:
-            cls._instance = object.__new__(cls, *args, **kwargs)
+            cls._instance = object.__new__(cls)
 
         return cls._instance
 


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
@@ -61,11 +61,31 @@ def setUp(self):
         )
         self._patch_net_connections.start()
 
+        # Reset the singleton class on each test run
+        SystemMetricsInstrumentor._instance = None
+
     def tearDown(self):
         super().tearDown()
         self._patch_net_connections.stop()
         SystemMetricsInstrumentor().uninstrument()
 
+    def test_system_metrics_instrumentor_initialization(self):
+        try:
+            SystemMetricsInstrumentor()
+            SystemMetricsInstrumentor(config={})
+        except Exception as error:  # pylint: disable=broad-except
+            self.fail(f"Unexpected exception {error} raised")
+
+        SystemMetricsInstrumentor._instance = None
+
+        try:
+            SystemMetricsInstrumentor(config={})
+            SystemMetricsInstrumentor()
+        except Exception as error:  # pylint: disable=broad-except
+            self.fail(f"Unexpected exception {error} raised")
+
+        SystemMetricsInstrumentor().instrument()
+
     def test_system_metrics_instrument(self):
         reader = InMemoryMetricReader()
         meter_provider = MeterProvider(metric_readers=[reader])
@@ -103,6 +123,35 @@ def test_system_metrics_instrument(self):
             self.assertIn(observer, observer_names)
             observer_names.remove(observer)
 
+    def test_runtime_metrics_instrument(self):
+        runtime_config = {
+            "runtime.memory": ["rss", "vms"],
+            "runtime.cpu.time": ["user", "system"],
+            "runtime.gc_count": None,
+        }
+
+        reader = InMemoryMetricReader()
+        meter_provider = MeterProvider(metric_readers=[reader])
+        runtime_metrics = SystemMetricsInstrumentor(config=runtime_config)
+        runtime_metrics.instrument(meter_provider=meter_provider)
+
+        metric_names = []
+        for resource_metrics in reader.get_metrics_data().resource_metrics:
+            for scope_metrics in resource_metrics.scope_metrics:
+                for metric in scope_metrics.metrics:
+                    metric_names.append(metric.name)
+        self.assertEqual(len(metric_names), 3)
+
+        observer_names = [
+            f"runtime.{self.implementation}.memory",
+            f"runtime.{self.implementation}.cpu_time",
+            f"runtime.{self.implementation}.gc_count",
+        ]
+
+        for observer in metric_names:
+            self.assertIn(observer, observer_names)
+            observer_names.remove(observer)
+
     def _assert_metrics(self, observer_name, reader, expected):
         assertions = 0
         # pylint: disable=too-many-nested-blocks