Repository: open-telemetry/opentelemetry-python

Issue:
OTLPMetricExporter ignores preferred_aggregation
**Describe your environment** Describe any aspect of your environment relevant to the problem, including your Python version, [platform](https://docs.python.org/3/library/platform.html), version numbers of installed dependencies, information about your cloud hosting provider, etc. If you're reporting a problem with a specific version of a library in this repo, please check whether the problem has been fixed on main.

Python == 3.11.6
opentelemetry-exporter-otlp == 1.20.0
opentelemetry-sdk == 1.20.0

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.

Instantiating metric exporter:
```python
metric_reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(
        endpoint=<>,
        preferred_aggregation={
            Histogram: ExponentialBucketHistogramAggregation()
        },
    ),
)
meter_provider = MeterProvider(
    resource=resource, metric_readers=[metric_reader]
)
metrics.set_meter_provider(meter_provider)
```

**What is the expected behavior?**
What did you expect to see?

Preferred aggregation set on the exporter and reader for Histograms to be ExponentialBucketHistogramAggregation.

**What is the actual behavior?**
What did you see instead?


Preferred aggregation set on the exporter and reader for Histograms is default (ExplicitBucketHistogramAggregation)

**Additional context**
Add any other context about the problem here.

Looking at the code in OTLPMetricExporter.__init__, it looks like the preferred_aggregation arg is not used at all.
OTLPMetricExporter ignores preferred_aggregation
**Describe your environment** Describe any aspect of your environment relevant to the problem, including your Python version, [platform](https://docs.python.org/3/library/platform.html), version numbers of installed dependencies, information about your cloud hosting provider, etc. If you're reporting a problem with a specific version of a library in this repo, please check whether the problem has been fixed on main.

Python == 3.11.6
opentelemetry-exporter-otlp == 1.20.0
opentelemetry-sdk == 1.20.0

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.

Instantiating metric exporter:
```python
metric_reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(
        endpoint=<>,
        preferred_aggregation={
            Histogram: ExponentialBucketHistogramAggregation()
        },
    ),
)
meter_provider = MeterProvider(
    resource=resource, metric_readers=[metric_reader]
)
metrics.set_meter_provider(meter_provider)
```

**What is the expected behavior?**
What did you expect to see?

Preferred aggregation set on the exporter and reader for Histograms to be ExponentialBucketHistogramAggregation.

**What is the actual behavior?**
What did you see instead?


Preferred aggregation set on the exporter and reader for Histograms is default (ExplicitBucketHistogramAggregation)

**Additional context**
Add any other context about the problem here.

Looking at the code in OTLPMetricExporter.__init__, it looks like the preferred_aggregation arg is not used at all.


Discussion:
You are correct. As a workaround, you can set the env `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` to `base2_exponential_bucket_histogram`. Would you be interested in sending PR with a fix? We need to update the https://github.com/open-telemetry/opentelemetry-python/blob/91ac1bea6ed2b50e7e17b91cec2770674f869517/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py#L65
Hi @srikanthccv , I can pick this up if its still open.
Hi @sreejitkar @srikanthccv, is there any update on this? If there isn't, I can have a look and raise a PR for this.
You are correct. As a workaround, you can set the env `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` to `base2_exponential_bucket_histogram`. Would you be interested in sending PR with a fix? We need to update the https://github.com/open-telemetry/opentelemetry-python/blob/91ac1bea6ed2b50e7e17b91cec2770674f869517/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py#L65
Hi @srikanthccv , I can pick this up if its still open.
Hi @sreejitkar @srikanthccv, is there any update on this? If there isn't, I can have a look and raise a PR for this.

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py
--- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py
@@ -16,6 +16,7 @@
 from opentelemetry.sdk.metrics.export import (
     MetricExporter,
 )
+from opentelemetry.sdk.metrics.view import Aggregation
 from os import environ
 from opentelemetry.sdk.metrics import (
     Counter,
@@ -65,9 +66,18 @@ class OTLPMetricExporterMixin:
     def _common_configuration(
         self,
         preferred_temporality: Dict[type, AggregationTemporality] = None,
+        preferred_aggregation: Dict[type, Aggregation] = None,
     ) -> None:
 
-        instrument_class_temporality = {}
+        MetricExporter.__init__(
+            self,
+            preferred_temporality=self._get_temporality(preferred_temporality),
+            preferred_aggregation=self._get_aggregation(preferred_aggregation),
+        )
+
+    def _get_temporality(
+        self, preferred_temporality: Dict[type, AggregationTemporality]
+    ) -> Dict[type, AggregationTemporality]:
 
         otel_exporter_otlp_metrics_temporality_preference = (
             environ.get(
@@ -119,6 +129,13 @@ def _common_configuration(
 
         instrument_class_temporality.update(preferred_temporality or {})
 
+        return instrument_class_temporality
+
+    def _get_aggregation(
+        self,
+        preferred_aggregation: Dict[type, Aggregation],
+    ) -> Dict[type, Aggregation]:
+
         otel_exporter_otlp_metrics_default_histogram_aggregation = environ.get(
             OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION,
             "explicit_bucket_histogram",
@@ -128,7 +145,9 @@ def _common_configuration(
             "base2_exponential_bucket_histogram"
         ):
 
-            histogram_aggregation_type = ExponentialBucketHistogramAggregation
+            instrument_class_aggregation = {
+                Histogram: ExponentialBucketHistogramAggregation(),
+            }
 
         else:
 
@@ -145,13 +164,13 @@ def _common_configuration(
                     otel_exporter_otlp_metrics_default_histogram_aggregation,
                 )
 
-            histogram_aggregation_type = ExplicitBucketHistogramAggregation
+            instrument_class_aggregation = {
+                Histogram: ExplicitBucketHistogramAggregation(),
+            }
 
-        MetricExporter.__init__(
-            self,
-            preferred_temporality=instrument_class_temporality,
-            preferred_aggregation={Histogram: histogram_aggregation_type()},
-        )
+        instrument_class_aggregation.update(preferred_aggregation or {})
+
+        return instrument_class_aggregation
 
 
 def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py
--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py
@@ -127,7 +127,9 @@ def __init__(
             else compression
         )
 
-        self._common_configuration(preferred_temporality)
+        self._common_configuration(
+            preferred_temporality, preferred_aggregation
+        )
 
         OTLPExporterMixin.__init__(
             self,
diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py
--- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py
@@ -135,7 +135,9 @@ def __init__(
                 {"Content-Encoding": self._compression.value}
             )
 
-        self._common_configuration(preferred_temporality)
+        self._common_configuration(
+            preferred_temporality, preferred_aggregation
+        )
 
     def _export(self, serialized_data: str):
         data = serialized_data


Test patch:
diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py
--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py
@@ -968,6 +968,24 @@ def test_exponential_explicit_bucket_histogram(self):
                 ExplicitBucketHistogramAggregation,
             )
 
+    def test_preferred_aggregation_override(self):
+
+        histogram_aggregation = ExplicitBucketHistogramAggregation(
+            boundaries=[0.05, 0.1, 0.5, 1, 5, 10],
+        )
+
+        exporter = OTLPMetricExporter(
+            preferred_aggregation={
+                Histogram: histogram_aggregation,
+            },
+        )
+
+        self.assertEqual(
+            # pylint: disable=protected-access
+            exporter._preferred_aggregation[Histogram],
+            histogram_aggregation,
+        )
+
 
 def _resource_metrics(
     index: int, scope_metrics: List[ScopeMetrics]
diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py
--- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py
@@ -479,3 +479,19 @@ def test_2xx_status_code(self, mock_otlp_metric_exporter):
             OTLPMetricExporter().export(MagicMock()),
             MetricExportResult.SUCCESS,
         )
+
+    def test_preferred_aggregation_override(self):
+
+        histogram_aggregation = ExplicitBucketHistogramAggregation(
+            boundaries=[0.05, 0.1, 0.5, 1, 5, 10],
+        )
+
+        exporter = OTLPMetricExporter(
+            preferred_aggregation={
+                Histogram: histogram_aggregation,
+            },
+        )
+
+        self.assertEqual(
+            exporter._preferred_aggregation[Histogram], histogram_aggregation
+        )