Repository navigation
Description
Describe your environment
- Repository: open-telemetry/opentelemetry-python
- Branch: main
- Tested commit: 3462000
- Operating system: Windows 11
- Python version: 3.10.9
- uv version: 0.13.5
- Package: opentelemetry-exporter-prometheus
My local main branch matched upstream/main, and the working tree was clean when I reproduced the issue.
What happened?
The Prometheus exporter's map_unit function does not correctly normalize compound ratio units when the numerator is the percent symbol.
The following standalone and compound units are normalized correctly:
- map_unit("%") returns "percent"
- map_unit("m/s") returns "meters_per_second"
- map_unit("By/s") returns "bytes_per_second"
However:
- map_unit("%/s") returns "__per_second"
- map_unit("%/min") returns "__per_min"
- map_unit("%/h") returns "__per_hour"
The percent unit is lost because the ratio-unit path sanitizes "%" into "_" before checking _UNIT_MAPPINGS, even though _UNIT_MAPPINGS contains the mapping "%": "percent".
In an end-to-end Prometheus export, a counter named "cpu.usage.rate" with unit "%/s" is exported as:
cpu_usage_rate___per_second_total
The generated name is syntactically valid for Prometheus, but it is semantically incorrect because the "percent" unit is lost during normalization.
Steps to Reproduce
From the repository root, run:
uv run python -c "from opentelemetry.exporter.prometheus._mapping import map_unit; print('percent:', map_unit('%')); print('meters per second:', map_unit('m/s')); print('percent per second:', map_unit('%/s'))"The output is:
percent: percent
meters per second: meters_per_second
percent per second: __per_second
A minimal failing assertion is:
from opentelemetry.exporter.prometheus._mapping import map_unit
assert map_unit("%/s") == "percent_per_second"The assertion currently fails because the actual value is "__per_second".
The same issue occurs with other percent ratio units:
assert map_unit("%/min") == "percent_per_min"
assert map_unit("%/h") == "percent_per_hour"The current results are "__per_min" and "__per_hour", respectively.
Expected Result
Percent ratio units should preserve the existing percent mapping and normalize both parts of the compound unit.
Expected results:
- map_unit("%/s") returns "percent_per_second"
- map_unit("%/min") returns "percent_per_min"
- map_unit("%/h") returns "percent_per_hour"
For a counter named "cpu.usage.rate" with unit "%/s", the normalized Prometheus metric name should contain the suffix "percent_per_second", for example:
cpu_usage_rate_percent_per_second_total
Actual Result
The percent numerator is replaced with an underscore before the unit mapping is applied.
Actual results:
- map_unit("%/s") returns "__per_second"
- map_unit("%/min") returns "__per_min"
- map_unit("%/h") returns "__per_hour"
For a counter named "cpu.usage.rate" with unit "%/s", the end-to-end Prometheus output contains:
HELP cpu_usage_rate___per_second_total Test metric description
TYPE cpu_usage_rate___per_second_total counter
cpu_usage_rate___per_second_total{otel_scope_name="test_meter",otel_scope_schema_url="",otel_scope_version=""} 42.0
The metric name is syntactically valid, but the normalized unit is semantically incorrect because "percent" has been lost.
Additional context
The issue appears to be in:
exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/_mapping.py
_UNIT_MAPPINGS already contains:
"%": "percent"
The standalone "%" case works because the direct mapping lookup happens before sanitization.
In the ratio-unit path, the numerator is sanitized first. Sanitizing "%" produces "_", so the subsequent lookup no longer finds the "%" mapping. The final value becomes "__per_second".
I ran the full Prometheus exporter test suite:
uv run pytest exporter/opentelemetry-exporter-prometheus/tests/
Result:
41 passed
The current tests cover standalone "%" and ratio units such as "m/s", but they do not appear to cover "%" as the numerator of a ratio unit.
I searched open and closed issues and pull requests using terms including map_unit, %/s, percent_per_second, __per_second, _UNIT_MAPPINGS, and ratio unit. I could not find existing work covering this behavior.
Issue #5766 and PR #5798 relate to annotation stripping in compound units, but they do not appear to address percent-symbol normalization in the ratio-unit path.
I would like to add regression coverage and work on a focused fix if the expected normalization is confirmed.
Would you like to implement a fix?
Yes
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.
Activity
Quantum-Enigma-dot commentedon Nov 1, 2026
I reproduced this behavior on the latest upstream main commit and would like to work on the fix. If percent_per_second is the expected normalization, could this issue please be assigned to me?