diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-common/pyproject.toml index 85226173d58..0b94086be06 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp-proto-common/pyproject.toml @@ -34,7 +34,7 @@ Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/exp Repository = "https://github.com/open-telemetry/opentelemetry-python" [tool.hatch.version] -path = "src/opentelemetry/exporter/otlp/proto/common/version/__init__.py" +path = "src/opentelemetry/exporter/otlp/_proto/common/version/__init__.py" [tool.hatch.build.targets.sdist] include = [ diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/__init__.py similarity index 100% rename from exporter/opentelemetry-exporter-otlp-proto-common/tests/__init__.py rename to exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/__init__.py diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_exporter_metrics.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_exporter_metrics.py new file mode 100644 index 00000000000..6b3d1d76fa3 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_exporter_metrics.py @@ -0,0 +1,153 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from collections import Counter +from collections.abc import Iterator +from contextlib import AbstractContextManager, contextmanager +from dataclasses import dataclass +from time import perf_counter +from typing import TYPE_CHECKING, Protocol + +from opentelemetry.metrics import MeterProvider, get_meter_provider +from opentelemetry.semconv._incubating.attributes.otel_attributes import ( + OTEL_COMPONENT_NAME, + OTEL_COMPONENT_TYPE, + OtelComponentTypeValues, +) +from opentelemetry.semconv._incubating.metrics.otel_metrics import ( + create_otel_sdk_exporter_log_exported, + create_otel_sdk_exporter_log_inflight, + create_otel_sdk_exporter_metric_data_point_exported, + create_otel_sdk_exporter_metric_data_point_inflight, + create_otel_sdk_exporter_operation_duration, + create_otel_sdk_exporter_span_exported, + create_otel_sdk_exporter_span_inflight, +) +from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE +from opentelemetry.semconv.attributes.server_attributes import ( + SERVER_ADDRESS, + SERVER_PORT, +) + +if TYPE_CHECKING: + from typing import Literal + from urllib.parse import ParseResult as UrlParseResult + + from opentelemetry.util.types import Attributes, AttributeValue + +_component_counter = Counter() + + +@dataclass +class ExportResult: + error: Exception | None = None + error_attrs: Attributes = None + + +class ExporterMetricsT(Protocol): + def export_operation( + self, num_items: int + ) -> AbstractContextManager[ExportResult]: ... + + +class NoOpExporterMetrics: + @contextmanager + def export_operation(self, num_items: int) -> Iterator[ExportResult]: + yield ExportResult() + + +class ExporterMetrics: + def __init__( + self, + component_type: OtelComponentTypeValues | None, + signal: Literal["traces", "metrics", "logs"], + endpoint: UrlParseResult, + meter_provider: MeterProvider | None, + ) -> None: + if signal == "traces": + create_exported = create_otel_sdk_exporter_span_exported + create_inflight = create_otel_sdk_exporter_span_inflight + elif signal == "logs": + create_exported = create_otel_sdk_exporter_log_exported + create_inflight = create_otel_sdk_exporter_log_inflight + else: + create_exported = ( + create_otel_sdk_exporter_metric_data_point_exported + ) + create_inflight = ( + create_otel_sdk_exporter_metric_data_point_inflight + ) + + port = endpoint.port + if port is None: + if endpoint.scheme == "https": + port = 443 + elif endpoint.scheme == "http": + port = 80 + + component_type_value = ( + component_type.value if component_type else "unknown_otlp_exporter" + ) + count = _component_counter[component_type_value] + _component_counter[component_type_value] = count + 1 + self._standard_attrs: dict[str, AttributeValue] = { + OTEL_COMPONENT_TYPE: component_type_value, + OTEL_COMPONENT_NAME: f"{component_type_value}/{count}", + } + if endpoint.hostname: + self._standard_attrs[SERVER_ADDRESS] = endpoint.hostname + if port is not None: + self._standard_attrs[SERVER_PORT] = port + + meter_provider = meter_provider or get_meter_provider() + meter = meter_provider.get_meter("opentelemetry-sdk") + self._inflight = create_inflight(meter) + self._exported = create_exported(meter) + self._duration = create_otel_sdk_exporter_operation_duration(meter) + + @contextmanager + def export_operation(self, num_items: int) -> Iterator[ExportResult]: + start_time = perf_counter() + self._inflight.add(num_items, self._standard_attrs) + + result = ExportResult() + try: + yield result + finally: + error = result.error + error_attrs = result.error_attrs + + end_time = perf_counter() + self._inflight.add(-num_items, self._standard_attrs) + exported_attrs = ( + {**self._standard_attrs, ERROR_TYPE: type(error).__qualname__} + if error + else self._standard_attrs + ) + self._exported.add(num_items, exported_attrs) + duration_attrs = ( + {**exported_attrs, **error_attrs} + if error_attrs + else exported_attrs + ) + self._duration.record(end_time - start_time, duration_attrs) + + +def create_exporter_metrics( + component_type: OtelComponentTypeValues | None, + signal: Literal["traces", "metrics", "logs"], + endpoint: UrlParseResult, + meter_provider: MeterProvider | None, + enabled: bool, +) -> ExporterMetricsT: + if not enabled: + return NoOpExporterMetrics() + + return ExporterMetrics( + component_type, + signal, + endpoint, + meter_provider, + ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/__init__.py new file mode 100644 index 00000000000..e91e6115425 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/__init__.py @@ -0,0 +1,114 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from logging import getLogger +from collections.abc import Callable, Mapping, Sequence +from typing import Any, TypeVar + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + ArrayValue, + InstrumentationScope as PB2InstrumentationScope, + KeyValue, + KeyValueList, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource as PB2Resource +from opentelemetry.sdk.trace import Resource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope +from opentelemetry.util.types import _ExtendedAttributes + +_logger = getLogger(__name__) + +_TypingResourceT = TypeVar("_TypingResourceT") +_ResourceDataT = TypeVar("_ResourceDataT") + + +def _encode_instrumentation_scope( + instrumentation_scope: InstrumentationScope, +) -> PB2InstrumentationScope: + if instrumentation_scope is None: + return PB2InstrumentationScope() + return PB2InstrumentationScope( + name=instrumentation_scope.name, + version=instrumentation_scope.version, + attributes=_encode_attributes(instrumentation_scope.attributes), + ) + + +def _encode_resource(resource: Resource) -> PB2Resource: + return PB2Resource(attributes=_encode_attributes(resource.attributes)) + + +def _encode_value(value: Any) -> AnyValue: + if value is None: + return AnyValue() + if isinstance(value, bool): + return AnyValue(bool_value=value) + if isinstance(value, str): + return AnyValue(string_value=value) + if isinstance(value, int): + return AnyValue(int_value=value) + if isinstance(value, float): + return AnyValue(double_value=value) + if isinstance(value, bytes): + return AnyValue(bytes_value=value) + if isinstance(value, Sequence): + return AnyValue( + array_value=ArrayValue(values=[_encode_value(v) for v in value]) + ) + if isinstance(value, Mapping): + return AnyValue( + kvlist_value=KeyValueList( + values=[_encode_key_value(str(k), v) for k, v in value.items()] + ) + ) + raise Exception(f"Invalid type {type(value)} of value {value}") + + +def _encode_key_value(key: str, value: Any) -> KeyValue: + return KeyValue(key=key, value=_encode_value(value)) + + +def _encode_span_id(span_id: int) -> bytes: + return span_id.to_bytes(length=8, byteorder="big", signed=False) + + +def _encode_trace_id(trace_id: int) -> bytes: + return trace_id.to_bytes(length=16, byteorder="big", signed=False) + + +def _encode_attributes( + attributes: _ExtendedAttributes | None, +) -> list[KeyValue]: + if not attributes: + return [] + pb2_attributes = [] + for key, value in attributes.items(): + try: + pb2_attributes.append(_encode_key_value(key, value)) + except Exception as error: + _logger.exception("Failed to encode key %s: %s", key, error) + return pb2_attributes + + +def _get_resource_data( + sdk_resource_scope_data: dict[Resource, _ResourceDataT], + resource_class: Callable[..., _TypingResourceT], + name: str, +) -> list[_TypingResourceT]: + resource_data = [] + for sdk_resource, scope_data in sdk_resource_scope_data.items(): + collector_resource = PB2Resource( + attributes=_encode_attributes(sdk_resource.attributes) + ) + resource_data.append( + resource_class( + **{ + "resource": collector_resource, + f"scope_{name}": scope_data.values(), + } + ) + ) + return resource_data diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/_log_encoder/__init__.py new file mode 100644 index 00000000000..1a1777092b6 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/_log_encoder/__init__.py @@ -0,0 +1,87 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 +from collections import defaultdict +from collections.abc import Sequence + +from opentelemetry.exporter.otlp._proto.common._internal import ( + _encode_attributes, + _encode_instrumentation_scope, + _encode_resource, + _encode_span_id, + _encode_trace_id, + _encode_value, +) +from opentelemetry._proto.collector.logs.v1.logs_service_pb2 import ( + ExportLogsServiceRequest, +) +from opentelemetry._proto.logs.v1.logs_pb2 import ( + LogRecord, + ResourceLogs, + ScopeLogs, +) +from opentelemetry.sdk._logs import ReadableLogRecord + + +def encode_logs(batch: Sequence[ReadableLogRecord]) -> ExportLogsServiceRequest: + return ExportLogsServiceRequest(resource_logs=_encode_resource_logs(batch)) + + +def _encode_log(readable_log_record: ReadableLogRecord) -> LogRecord: + log = readable_log_record.log_record + span_id = ( + b"" + if log.span_id == 0 + else _encode_span_id(log.span_id) + ) + trace_id = ( + b"" + if log.trace_id == 0 + else _encode_trace_id(log.trace_id) + ) + return LogRecord( + time_unix_nano=log.timestamp, + observed_time_unix_nano=log.observed_timestamp, + span_id=span_id, + trace_id=trace_id, + flags=int(log.trace_flags), + body=_encode_value(log.body), + severity_text=log.severity_text or "", + attributes=_encode_attributes(log.attributes), + dropped_attributes_count=readable_log_record.dropped_attributes, + severity_number=getattr(log.severity_number, "value", 0) or 0, + event_name=log.event_name or "", + ) + + +def _encode_resource_logs( + batch: Sequence[ReadableLogRecord], +) -> list[ResourceLogs]: + sdk_resource_logs: dict = defaultdict(lambda: defaultdict(list)) + + for readable_log in batch: + sdk_resource = readable_log.resource + sdk_instrumentation = readable_log.instrumentation_scope or None + pb2_log = _encode_log(readable_log) + sdk_resource_logs[sdk_resource][sdk_instrumentation].append(pb2_log) + + pb2_resource_logs = [] + for sdk_resource, sdk_instrumentations in sdk_resource_logs.items(): + scope_logs = [] + for sdk_instrumentation, pb2_logs in sdk_instrumentations.items(): + scope_logs.append( + ScopeLogs( + scope=_encode_instrumentation_scope(sdk_instrumentation), + log_records=pb2_logs, + schema_url=sdk_instrumentation.schema_url + if sdk_instrumentation + else "", + ) + ) + pb2_resource_logs.append( + ResourceLogs( + resource=_encode_resource(sdk_resource), + scope_logs=scope_logs, + schema_url=sdk_resource.schema_url, + ) + ) + return pb2_resource_logs 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 new file mode 100644 index 00000000000..e299e7dde0f --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/metrics_encoder/__init__.py @@ -0,0 +1,334 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +from logging import getLogger +from os import environ + +from opentelemetry.exporter.otlp._proto.common._internal import ( + _encode_attributes, + _encode_instrumentation_scope, + _encode_span_id, + _encode_trace_id, +) +from opentelemetry._proto.collector.metrics.v1.metrics_service_pb2 import ( + ExportMetricsServiceRequest, +) +from opentelemetry._proto.metrics.v1.metrics_pb2 import ( + Exemplar, + ExponentialHistogram, + ExponentialHistogramDataPoint, + Gauge, + Histogram, + HistogramDataPoint, + Metric, + NumberDataPoint, + ResourceMetrics, + ScopeMetrics, + Sum, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource as PB2Resource +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, +) +from opentelemetry.sdk.metrics import ( + Counter, + Exemplar as SDKExemplar, + Histogram as SDKHistogram, + ObservableCounter, + ObservableGauge, + ObservableUpDownCounter, + UpDownCounter, +) +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + Gauge as SDKGauge, + MetricExporter, + MetricsData, + Sum as SDKSum, +) +from opentelemetry.sdk.metrics.export import ( + ExponentialHistogram as ExponentialHistogramType, +) +from opentelemetry.sdk.metrics.export import ( + Histogram as HistogramType, +) +from opentelemetry.sdk.metrics.view import ( + Aggregation, + ExplicitBucketHistogramAggregation, + ExponentialBucketHistogramAggregation, +) + +_logger = getLogger(__name__) + + +class OTLPMetricExporterMixin: + def _common_configuration( + self, + preferred_temporality: dict[type, AggregationTemporality] | None = None, + preferred_aggregation: dict[type, Aggregation] | None = None, + ) -> None: + 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( + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, + "CUMULATIVE", + ) + .upper() + .strip() + ) + + if otel_exporter_otlp_metrics_temporality_preference == "DELTA": + instrument_class_temporality = { + Counter: AggregationTemporality.DELTA, + UpDownCounter: AggregationTemporality.CUMULATIVE, + SDKHistogram: AggregationTemporality.DELTA, + ObservableCounter: AggregationTemporality.DELTA, + ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, + ObservableGauge: AggregationTemporality.CUMULATIVE, + } + elif otel_exporter_otlp_metrics_temporality_preference == "LOWMEMORY": + instrument_class_temporality = { + Counter: AggregationTemporality.DELTA, + UpDownCounter: AggregationTemporality.CUMULATIVE, + SDKHistogram: AggregationTemporality.DELTA, + ObservableCounter: AggregationTemporality.CUMULATIVE, + ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, + ObservableGauge: AggregationTemporality.CUMULATIVE, + } + else: + if otel_exporter_otlp_metrics_temporality_preference != "CUMULATIVE": + _logger.warning( + "Unrecognized OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" + " value found: %s, using CUMULATIVE", + otel_exporter_otlp_metrics_temporality_preference, + ) + instrument_class_temporality = { + Counter: AggregationTemporality.CUMULATIVE, + UpDownCounter: AggregationTemporality.CUMULATIVE, + SDKHistogram: AggregationTemporality.CUMULATIVE, + ObservableCounter: AggregationTemporality.CUMULATIVE, + ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, + ObservableGauge: AggregationTemporality.CUMULATIVE, + } + + 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", + ) + + if otel_exporter_otlp_metrics_default_histogram_aggregation == ( + "base2_exponential_bucket_histogram" + ): + instrument_class_aggregation: dict[type, Aggregation] = { + SDKHistogram: ExponentialBucketHistogramAggregation(), + } + else: + if otel_exporter_otlp_metrics_default_histogram_aggregation != ( + "explicit_bucket_histogram" + ): + _logger.warning( + "Invalid value for %s: %s, using explicit bucket histogram aggregation", + OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, + otel_exporter_otlp_metrics_default_histogram_aggregation, + ) + instrument_class_aggregation = { + SDKHistogram: ExplicitBucketHistogramAggregation(), + } + + instrument_class_aggregation.update(preferred_aggregation or {}) + return instrument_class_aggregation + + +class EncodingException(Exception): + def __init__(self, original_exception, metric): + super().__init__() + self.original_exception = original_exception + self.metric = metric + + def __str__(self): + return f"{self.metric}\n{self.original_exception}" + + +def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest: + resource_metrics_list = [] + + for resource_metrics in data.resource_metrics: + scope_metrics_list = [] + sdk_resource = resource_metrics.resource + + for scope_metrics in resource_metrics.scope_metrics: + instrumentation_scope = scope_metrics.scope + pb2_scope_metrics = ScopeMetrics( + scope=_encode_instrumentation_scope(instrumentation_scope), + schema_url=instrumentation_scope.schema_url, + ) + + for metric in scope_metrics.metrics: + try: + pb2_metric = _encode_metric(metric) + except Exception as ex: + raise EncodingException(ex, metric) from None + if pb2_metric is not None: + pb2_scope_metrics.metrics.append(pb2_metric) + + scope_metrics_list.append(pb2_scope_metrics) + + resource_metrics_list.append( + ResourceMetrics( + resource=PB2Resource( + attributes=_encode_attributes(sdk_resource.attributes) + ), + scope_metrics=scope_metrics_list, + schema_url=sdk_resource.schema_url, + ) + ) + + return ExportMetricsServiceRequest(resource_metrics=resource_metrics_list) + + +def _encode_metric(metric) -> Metric | None: + kwargs: dict = dict( + name=metric.name, + description=metric.description, + unit=metric.unit, + ) + + if isinstance(metric.data, SDKGauge): + data_points = [] + for dp in metric.data.data_points: + pt_kwargs: dict = dict( + attributes=_encode_attributes(dp.attributes), + time_unix_nano=dp.time_unix_nano, + exemplars=_encode_exemplars(dp.exemplars), + ) + if isinstance(dp.value, int): + pt_kwargs["as_int"] = dp.value + else: + pt_kwargs["as_double"] = dp.value + data_points.append(NumberDataPoint(**pt_kwargs)) + kwargs["gauge"] = Gauge(data_points=data_points) + + elif isinstance(metric.data, HistogramType): + data_points = [] + for dp in metric.data.data_points: + data_points.append( + HistogramDataPoint( + attributes=_encode_attributes(dp.attributes), + time_unix_nano=dp.time_unix_nano, + start_time_unix_nano=dp.start_time_unix_nano, + exemplars=_encode_exemplars(dp.exemplars), + count=dp.count, + sum=dp.sum, + bucket_counts=list(dp.bucket_counts) if dp.bucket_counts else [], + explicit_bounds=list(dp.explicit_bounds) if dp.explicit_bounds else [], + max=dp.max, + min=dp.min, + ) + ) + kwargs["histogram"] = Histogram( + data_points=data_points, + aggregation_temporality=metric.data.aggregation_temporality, + ) + + elif isinstance(metric.data, SDKSum): + data_points = [] + for dp in metric.data.data_points: + pt_kwargs = dict( + attributes=_encode_attributes(dp.attributes), + start_time_unix_nano=dp.start_time_unix_nano, + time_unix_nano=dp.time_unix_nano, + exemplars=_encode_exemplars(dp.exemplars), + ) + if isinstance(dp.value, int): + pt_kwargs["as_int"] = dp.value + else: + pt_kwargs["as_double"] = dp.value + data_points.append(NumberDataPoint(**pt_kwargs)) + kwargs["sum"] = Sum( + data_points=data_points, + aggregation_temporality=metric.data.aggregation_temporality, + is_monotonic=metric.data.is_monotonic, + ) + + elif isinstance(metric.data, ExponentialHistogramType): + data_points = [] + for dp in metric.data.data_points: + positive = negative = None + if dp.positive.bucket_counts: + positive = ExponentialHistogramDataPoint.Buckets( + offset=dp.positive.offset, + bucket_counts=list(dp.positive.bucket_counts), + ) + if dp.negative.bucket_counts: + negative = ExponentialHistogramDataPoint.Buckets( + offset=dp.negative.offset, + bucket_counts=list(dp.negative.bucket_counts), + ) + data_points.append( + ExponentialHistogramDataPoint( + attributes=_encode_attributes(dp.attributes), + time_unix_nano=dp.time_unix_nano, + start_time_unix_nano=dp.start_time_unix_nano, + exemplars=_encode_exemplars(dp.exemplars), + count=dp.count, + sum=dp.sum, + scale=dp.scale, + zero_count=dp.zero_count, + positive=positive, + negative=negative, + flags=dp.flags, + max=dp.max, + min=dp.min, + ) + ) + kwargs["exponential_histogram"] = ExponentialHistogram( + data_points=data_points, + aggregation_temporality=metric.data.aggregation_temporality, + ) + + else: + _logger.warning("unsupported data type %s", metric.data.__class__.__name__) + return None + + return Metric(**kwargs) + + +def _encode_exemplars(sdk_exemplars: list[SDKExemplar]) -> list[Exemplar]: + result = [] + for sdk_exemplar in sdk_exemplars: + ex_kwargs: dict = dict( + time_unix_nano=sdk_exemplar.time_unix_nano, + filtered_attributes=_encode_attributes(sdk_exemplar.filtered_attributes), + ) + if ( + sdk_exemplar.span_id is not None + and sdk_exemplar.trace_id is not None + ): + ex_kwargs["span_id"] = _encode_span_id(sdk_exemplar.span_id) + ex_kwargs["trace_id"] = _encode_trace_id(sdk_exemplar.trace_id) + + if isinstance(sdk_exemplar.value, float): + ex_kwargs["as_double"] = sdk_exemplar.value + elif isinstance(sdk_exemplar.value, int): + ex_kwargs["as_int"] = sdk_exemplar.value + else: + raise ValueError("Exemplar value must be an int or float") + + result.append(Exemplar(**ex_kwargs)) + return result diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/trace_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/trace_encoder/__init__.py new file mode 100644 index 00000000000..f7fb19f6abb --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/_internal/trace_encoder/__init__.py @@ -0,0 +1,160 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from logging import getLogger +from collections import defaultdict +from collections.abc import Sequence + +from opentelemetry.exporter.otlp._proto.common._internal import ( + _encode_attributes, + _encode_instrumentation_scope, + _encode_resource, + _encode_span_id, + _encode_trace_id, +) +from opentelemetry._proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest, +) +from opentelemetry._proto.trace.v1.trace_pb2 import ( + ResourceSpans, + ScopeSpans, + Span, + Status, +) +from opentelemetry.sdk.trace import Event, ReadableSpan +from opentelemetry.trace import Link, SpanKind +from opentelemetry.trace.span import SpanContext, TraceState + +# Map SDK SpanKind (0-4) to proto SpanKind (1-5) +_SPAN_KIND_MAP = { + SpanKind.INTERNAL: 1, + SpanKind.SERVER: 2, + SpanKind.CLIENT: 3, + SpanKind.PRODUCER: 4, + SpanKind.CONSUMER: 5, +} + +# proto SpanFlags values +_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 0x00000100 +_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK = 0x00000200 + +_logger = getLogger(__name__) + + +def encode_spans(sdk_spans: Sequence[ReadableSpan]) -> ExportTraceServiceRequest: + return ExportTraceServiceRequest( + resource_spans=_encode_resource_spans(sdk_spans) + ) + + +def _encode_resource_spans( + sdk_spans: Sequence[ReadableSpan], +) -> list[ResourceSpans]: + sdk_resource_spans: dict = defaultdict(lambda: defaultdict(list)) + + for sdk_span in sdk_spans: + sdk_resource = sdk_span.resource + sdk_instrumentation = sdk_span.instrumentation_scope or None + pb2_span = _encode_span(sdk_span) + sdk_resource_spans[sdk_resource][sdk_instrumentation].append(pb2_span) + + pb2_resource_spans = [] + for sdk_resource, sdk_instrumentations in sdk_resource_spans.items(): + scope_spans = [] + for sdk_instrumentation, pb2_spans in sdk_instrumentations.items(): + scope_spans.append( + ScopeSpans( + scope=_encode_instrumentation_scope(sdk_instrumentation), + spans=pb2_spans, + schema_url=sdk_instrumentation.schema_url + if sdk_instrumentation + else "", + ) + ) + pb2_resource_spans.append( + ResourceSpans( + resource=_encode_resource(sdk_resource), + scope_spans=scope_spans, + schema_url=sdk_resource.schema_url, + ) + ) + return pb2_resource_spans + + +def _span_flags(parent_span_context: SpanContext | None) -> int: + flags = _SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK + if parent_span_context and parent_span_context.is_remote: + flags |= _SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK + return flags + + +def _encode_span(sdk_span: ReadableSpan) -> Span: + span_context = sdk_span.get_span_context() + return Span( + trace_id=_encode_trace_id(span_context.trace_id), + span_id=_encode_span_id(span_context.span_id), + trace_state=_encode_trace_state(span_context.trace_state), + parent_span_id=_encode_parent_id(sdk_span.parent), + name=sdk_span.name, + kind=_SPAN_KIND_MAP[sdk_span.kind], + start_time_unix_nano=sdk_span.start_time, + end_time_unix_nano=sdk_span.end_time, + attributes=_encode_attributes(sdk_span.attributes), + events=_encode_events(sdk_span.events), + links=_encode_links(sdk_span.links), + status=_encode_status(sdk_span.status), + dropped_attributes_count=sdk_span.dropped_attributes, + dropped_events_count=sdk_span.dropped_events, + dropped_links_count=sdk_span.dropped_links, + flags=_span_flags(sdk_span.parent), + ) + + +def _encode_events(events: Sequence[Event]) -> list[Span.Event] | None: + if not events: + return None + return [ + Span.Event( + name=event.name, + time_unix_nano=event.timestamp, + attributes=_encode_attributes(event.attributes), + dropped_attributes_count=event.dropped_attributes, + ) + for event in events + ] + + +def _encode_links(links: Sequence[Link]) -> list[Span.Link] | None: + if not links: + return None + return [ + Span.Link( + trace_id=_encode_trace_id(link.context.trace_id), + span_id=_encode_span_id(link.context.span_id), + attributes=_encode_attributes(link.attributes), + dropped_attributes_count=link.dropped_attributes, + flags=_span_flags(link.context), + ) + for link in links + ] + + +def _encode_status(status) -> Status | None: + if status is None: + return None + return Status( + code=status.status_code.value, + message=status.description or "", + ) + + +def _encode_trace_state(trace_state: TraceState) -> str: + if trace_state is None: + return "" + return ",".join(f"{key}={value}" for key, value in trace_state.items()) + + +def _encode_parent_id(context: SpanContext | None) -> bytes: + if context: + return _encode_span_id(context.span_id) + return b"" diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/version/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/version/__init__.py new file mode 100644 index 00000000000..cdc135bf8ae --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/_proto/common/version/__init__.py @@ -0,0 +1 @@ +__version__ = "1.45.0.dev" diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_exporter_metrics.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_exporter_metrics.py index 6b3d1d76fa3..9cb19bc4099 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_exporter_metrics.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_exporter_metrics.py @@ -1,153 +1,13 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from __future__ import annotations -from collections import Counter -from collections.abc import Iterator -from contextlib import AbstractContextManager, contextmanager -from dataclasses import dataclass -from time import perf_counter -from typing import TYPE_CHECKING, Protocol - -from opentelemetry.metrics import MeterProvider, get_meter_provider -from opentelemetry.semconv._incubating.attributes.otel_attributes import ( - OTEL_COMPONENT_NAME, - OTEL_COMPONENT_TYPE, - OtelComponentTypeValues, -) -from opentelemetry.semconv._incubating.metrics.otel_metrics import ( - create_otel_sdk_exporter_log_exported, - create_otel_sdk_exporter_log_inflight, - create_otel_sdk_exporter_metric_data_point_exported, - create_otel_sdk_exporter_metric_data_point_inflight, - create_otel_sdk_exporter_operation_duration, - create_otel_sdk_exporter_span_exported, - create_otel_sdk_exporter_span_inflight, -) -from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE -from opentelemetry.semconv.attributes.server_attributes import ( - SERVER_ADDRESS, - SERVER_PORT, +from opentelemetry.exporter.otlp._proto.common._exporter_metrics import ( + ExportResult, + ExporterMetricsT, + ExporterMetrics, + NoOpExporterMetrics, + create_exporter_metrics, ) -if TYPE_CHECKING: - from typing import Literal - from urllib.parse import ParseResult as UrlParseResult - - from opentelemetry.util.types import Attributes, AttributeValue - -_component_counter = Counter() - - -@dataclass -class ExportResult: - error: Exception | None = None - error_attrs: Attributes = None - - -class ExporterMetricsT(Protocol): - def export_operation( - self, num_items: int - ) -> AbstractContextManager[ExportResult]: ... - - -class NoOpExporterMetrics: - @contextmanager - def export_operation(self, num_items: int) -> Iterator[ExportResult]: - yield ExportResult() - - -class ExporterMetrics: - def __init__( - self, - component_type: OtelComponentTypeValues | None, - signal: Literal["traces", "metrics", "logs"], - endpoint: UrlParseResult, - meter_provider: MeterProvider | None, - ) -> None: - if signal == "traces": - create_exported = create_otel_sdk_exporter_span_exported - create_inflight = create_otel_sdk_exporter_span_inflight - elif signal == "logs": - create_exported = create_otel_sdk_exporter_log_exported - create_inflight = create_otel_sdk_exporter_log_inflight - else: - create_exported = ( - create_otel_sdk_exporter_metric_data_point_exported - ) - create_inflight = ( - create_otel_sdk_exporter_metric_data_point_inflight - ) - - port = endpoint.port - if port is None: - if endpoint.scheme == "https": - port = 443 - elif endpoint.scheme == "http": - port = 80 - - component_type_value = ( - component_type.value if component_type else "unknown_otlp_exporter" - ) - count = _component_counter[component_type_value] - _component_counter[component_type_value] = count + 1 - self._standard_attrs: dict[str, AttributeValue] = { - OTEL_COMPONENT_TYPE: component_type_value, - OTEL_COMPONENT_NAME: f"{component_type_value}/{count}", - } - if endpoint.hostname: - self._standard_attrs[SERVER_ADDRESS] = endpoint.hostname - if port is not None: - self._standard_attrs[SERVER_PORT] = port - - meter_provider = meter_provider or get_meter_provider() - meter = meter_provider.get_meter("opentelemetry-sdk") - self._inflight = create_inflight(meter) - self._exported = create_exported(meter) - self._duration = create_otel_sdk_exporter_operation_duration(meter) - - @contextmanager - def export_operation(self, num_items: int) -> Iterator[ExportResult]: - start_time = perf_counter() - self._inflight.add(num_items, self._standard_attrs) - - result = ExportResult() - try: - yield result - finally: - error = result.error - error_attrs = result.error_attrs - - end_time = perf_counter() - self._inflight.add(-num_items, self._standard_attrs) - exported_attrs = ( - {**self._standard_attrs, ERROR_TYPE: type(error).__qualname__} - if error - else self._standard_attrs - ) - self._exported.add(num_items, exported_attrs) - duration_attrs = ( - {**exported_attrs, **error_attrs} - if error_attrs - else exported_attrs - ) - self._duration.record(end_time - start_time, duration_attrs) - - -def create_exporter_metrics( - component_type: OtelComponentTypeValues | None, - signal: Literal["traces", "metrics", "logs"], - endpoint: UrlParseResult, - meter_provider: MeterProvider | None, - enabled: bool, -) -> ExporterMetricsT: - if not enabled: - return NoOpExporterMetrics() - - return ExporterMetrics( - component_type, - signal, - endpoint, - meter_provider, - ) +__all__ = ["ExportResult", "ExporterMetricsT", "ExporterMetrics", "NoOpExporterMetrics", "create_exporter_metrics"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py index 7528b5994b8..ef585a5a6ac 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py @@ -2,128 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 -from __future__ import annotations - -import logging -from collections.abc import Callable, Mapping, Sequence -from typing import ( - Any, - TypeVar, -) - -from opentelemetry.proto.common.v1.common_pb2 import AnyValue as PB2AnyValue -from opentelemetry.proto.common.v1.common_pb2 import ( - ArrayValue as PB2ArrayValue, -) -from opentelemetry.proto.common.v1.common_pb2 import ( - InstrumentationScope as PB2InstrumentationScope, -) -from opentelemetry.proto.common.v1.common_pb2 import KeyValue as PB2KeyValue -from opentelemetry.proto.common.v1.common_pb2 import ( - KeyValueList as PB2KeyValueList, -) -from opentelemetry.proto.resource.v1.resource_pb2 import ( - Resource as PB2Resource, +from opentelemetry.exporter.otlp._proto.common._internal import ( + _encode_attributes, + _encode_instrumentation_scope, + _encode_key_value, + _encode_resource, + _encode_span_id, + _encode_trace_id, + _encode_value, + _get_resource_data, ) -from opentelemetry.sdk.trace import Resource -from opentelemetry.sdk.util.instrumentation import InstrumentationScope -from opentelemetry.util.types import _ExtendedAttributes - -_logger = logging.getLogger(__name__) - -_TypingResourceT = TypeVar("_TypingResourceT") -_ResourceDataT = TypeVar("_ResourceDataT") - - -def _encode_instrumentation_scope( - instrumentation_scope: InstrumentationScope, -) -> PB2InstrumentationScope: - if instrumentation_scope is None: - return PB2InstrumentationScope() - return PB2InstrumentationScope( - name=instrumentation_scope.name, - version=instrumentation_scope.version, - attributes=_encode_attributes(instrumentation_scope.attributes), - ) - - -def _encode_resource(resource: Resource) -> PB2Resource: - return PB2Resource(attributes=_encode_attributes(resource.attributes)) - - -def _encode_value(value: Any) -> PB2AnyValue: - if value is None: - return PB2AnyValue() - if isinstance(value, bool): - return PB2AnyValue(bool_value=value) - if isinstance(value, str): - return PB2AnyValue(string_value=value) - if isinstance(value, int): - return PB2AnyValue(int_value=value) - if isinstance(value, float): - return PB2AnyValue(double_value=value) - if isinstance(value, bytes): - return PB2AnyValue(bytes_value=value) - if isinstance(value, Sequence): - return PB2AnyValue( - array_value=PB2ArrayValue(values=[_encode_value(v) for v in value]) - ) - if isinstance(value, Mapping): - return PB2AnyValue( - kvlist_value=PB2KeyValueList( - values=[_encode_key_value(str(k), v) for k, v in value.items()] - ) - ) - raise Exception(f"Invalid type {type(value)} of value {value}") - - -def _encode_key_value(key: str, value: Any) -> PB2KeyValue: - return PB2KeyValue(key=key, value=_encode_value(value)) - - -def _encode_span_id(span_id: int) -> bytes: - return span_id.to_bytes(length=8, byteorder="big", signed=False) - - -def _encode_trace_id(trace_id: int) -> bytes: - return trace_id.to_bytes(length=16, byteorder="big", signed=False) - - -def _encode_attributes( - attributes: _ExtendedAttributes | None, -) -> list[PB2KeyValue]: - if not attributes: - return [] - pb2_attributes = [] - for key, value in attributes.items(): - # pylint: disable=broad-exception-caught - try: - pb2_attributes.append(_encode_key_value(key, value)) - except Exception as error: - _logger.exception("Failed to encode key %s: %s", key, error) - return pb2_attributes - - -def _get_resource_data( - sdk_resource_scope_data: dict[Resource, _ResourceDataT], - resource_class: Callable[..., _TypingResourceT], - name: str, -) -> list[_TypingResourceT]: - resource_data = [] - for ( - sdk_resource, - scope_data, - ) in sdk_resource_scope_data.items(): - collector_resource = PB2Resource( - attributes=_encode_attributes(sdk_resource.attributes) - ) - resource_data.append( - resource_class( - **{ - "resource": collector_resource, - f"scope_{name}": scope_data.values(), - } - ) - ) - return resource_data +__all__ = ["_encode_attributes", "_encode_instrumentation_scope", "_encode_key_value", "_encode_resource", "_encode_span_id", "_encode_trace_id", "_encode_value", "_get_resource_data"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 86b4380bd95..d40dc8fe163 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -1,95 +1,9 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from collections import defaultdict -from collections.abc import Sequence -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_attributes, - _encode_instrumentation_scope, - _encode_resource, - _encode_span_id, - _encode_trace_id, - _encode_value, -) -from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( - ExportLogsServiceRequest, -) -from opentelemetry.proto.logs.v1.logs_pb2 import LogRecord as PB2LogRecord -from opentelemetry.proto.logs.v1.logs_pb2 import ( - ResourceLogs, - ScopeLogs, -) -from opentelemetry.sdk._logs import ReadableLogRecord - - -def encode_logs( - batch: Sequence[ReadableLogRecord], -) -> ExportLogsServiceRequest: - return ExportLogsServiceRequest(resource_logs=_encode_resource_logs(batch)) - - -def _encode_log(readable_log_record: ReadableLogRecord) -> PB2LogRecord: - span_id = ( - None - if readable_log_record.log_record.span_id == 0 - else _encode_span_id(readable_log_record.log_record.span_id) - ) - trace_id = ( - None - if readable_log_record.log_record.trace_id == 0 - else _encode_trace_id(readable_log_record.log_record.trace_id) - ) - return PB2LogRecord( - time_unix_nano=readable_log_record.log_record.timestamp, - observed_time_unix_nano=readable_log_record.log_record.observed_timestamp, - span_id=span_id, - trace_id=trace_id, - flags=int(readable_log_record.log_record.trace_flags), - body=_encode_value(readable_log_record.log_record.body), - severity_text=readable_log_record.log_record.severity_text, - attributes=_encode_attributes( - readable_log_record.log_record.attributes - ), - dropped_attributes_count=readable_log_record.dropped_attributes, - severity_number=getattr( - readable_log_record.log_record.severity_number, "value", None - ), - event_name=readable_log_record.log_record.event_name, - ) +from opentelemetry.exporter.otlp._proto.common._internal._log_encoder import ( + encode_logs, +) -def _encode_resource_logs( - batch: Sequence[ReadableLogRecord], -) -> list[ResourceLogs]: - sdk_resource_logs = defaultdict(lambda: defaultdict(list)) - - for readable_log in batch: - sdk_resource = readable_log.resource - sdk_instrumentation = readable_log.instrumentation_scope or None - pb2_log = _encode_log(readable_log) - - sdk_resource_logs[sdk_resource][sdk_instrumentation].append(pb2_log) - - pb2_resource_logs = [] - - for sdk_resource, sdk_instrumentations in sdk_resource_logs.items(): - scope_logs = [] - for sdk_instrumentation, pb2_logs in sdk_instrumentations.items(): - scope_logs.append( - ScopeLogs( - scope=(_encode_instrumentation_scope(sdk_instrumentation)), - log_records=pb2_logs, - schema_url=sdk_instrumentation.schema_url - if sdk_instrumentation - else None, - ) - ) - pb2_resource_logs.append( - ResourceLogs( - resource=_encode_resource(sdk_resource), - scope_logs=scope_logs, - schema_url=sdk_resource.schema_url, - ) - ) - - return pb2_resource_logs +__all__ = ["encode_logs"] 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 index fad7aaf3dd7..a8ad580f4bc 100644 --- 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 @@ -1,376 +1,11 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from __future__ import annotations -import logging -from os import environ -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_attributes, - _encode_instrumentation_scope, - _encode_span_id, - _encode_trace_id, +from opentelemetry.exporter.otlp._proto.common._internal.metrics_encoder import ( + EncodingException, + OTLPMetricExporterMixin, + encode_metrics, ) -from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( - ExportMetricsServiceRequest, -) -from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 -from opentelemetry.proto.resource.v1.resource_pb2 import ( - Resource as PB2Resource, -) -from opentelemetry.sdk.environment_variables import ( - OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, - OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, -) -from opentelemetry.sdk.metrics import ( - Counter, - Exemplar, - Histogram, - ObservableCounter, - ObservableGauge, - ObservableUpDownCounter, - UpDownCounter, -) -from opentelemetry.sdk.metrics.export import ( - AggregationTemporality, - Gauge, - MetricExporter, - MetricsData, - Sum, -) -from opentelemetry.sdk.metrics.export import ( - ExponentialHistogram as ExponentialHistogramType, -) -from opentelemetry.sdk.metrics.export import ( - Histogram as HistogramType, -) -from opentelemetry.sdk.metrics.view import ( - Aggregation, - ExplicitBucketHistogramAggregation, - ExponentialBucketHistogramAggregation, -) - -_logger = logging.getLogger(__name__) - - -class OTLPMetricExporterMixin: - def _common_configuration( - self, - preferred_temporality: dict[type, AggregationTemporality] - | None = None, - preferred_aggregation: dict[type, Aggregation] | None = None, - ) -> None: - 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( - OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, - "CUMULATIVE", - ) - .upper() - .strip() - ) - - if otel_exporter_otlp_metrics_temporality_preference == "DELTA": - instrument_class_temporality = { - Counter: AggregationTemporality.DELTA, - UpDownCounter: AggregationTemporality.CUMULATIVE, - Histogram: AggregationTemporality.DELTA, - ObservableCounter: AggregationTemporality.DELTA, - ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, - ObservableGauge: AggregationTemporality.CUMULATIVE, - } - - elif otel_exporter_otlp_metrics_temporality_preference == "LOWMEMORY": - instrument_class_temporality = { - Counter: AggregationTemporality.DELTA, - UpDownCounter: AggregationTemporality.CUMULATIVE, - Histogram: AggregationTemporality.DELTA, - ObservableCounter: AggregationTemporality.CUMULATIVE, - ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, - ObservableGauge: AggregationTemporality.CUMULATIVE, - } - - else: - if otel_exporter_otlp_metrics_temporality_preference != ( - "CUMULATIVE" - ): - _logger.warning( - "Unrecognized OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" - " value found: " - "%s, " - "using CUMULATIVE", - otel_exporter_otlp_metrics_temporality_preference, - ) - instrument_class_temporality = { - Counter: AggregationTemporality.CUMULATIVE, - UpDownCounter: AggregationTemporality.CUMULATIVE, - Histogram: AggregationTemporality.CUMULATIVE, - ObservableCounter: AggregationTemporality.CUMULATIVE, - ObservableUpDownCounter: AggregationTemporality.CUMULATIVE, - ObservableGauge: AggregationTemporality.CUMULATIVE, - } - - 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", - ) - - if otel_exporter_otlp_metrics_default_histogram_aggregation == ( - "base2_exponential_bucket_histogram" - ): - instrument_class_aggregation = { - Histogram: ExponentialBucketHistogramAggregation(), - } - - else: - if otel_exporter_otlp_metrics_default_histogram_aggregation != ( - "explicit_bucket_histogram" - ): - _logger.warning( - ( - "Invalid value for %s: %s, using explicit bucket " - "histogram aggregation" - ), - OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, - otel_exporter_otlp_metrics_default_histogram_aggregation, - ) - - instrument_class_aggregation = { - Histogram: ExplicitBucketHistogramAggregation(), - } - - instrument_class_aggregation.update(preferred_aggregation or {}) - - return instrument_class_aggregation - - -class EncodingException(Exception): - """ - Raised by encode_metrics() when an exception is caught during encoding. Contains the problematic metric so - the misbehaving metric name and details can be logged during exception handling. - """ - - def __init__(self, original_exception, metric): - super().__init__() - self.original_exception = original_exception - self.metric = metric - - def __str__(self): - return f"{self.metric}\n{self.original_exception}" - - -def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest: - resource_metrics_dict = {} - - for resource_metrics in data.resource_metrics: - _encode_resource_metrics(resource_metrics, resource_metrics_dict) - - resource_data = [] - for ( - sdk_resource, - scope_data, - ) in resource_metrics_dict.items(): - resource_data.append( - pb2.ResourceMetrics( - resource=PB2Resource( - attributes=_encode_attributes(sdk_resource.attributes) - ), - scope_metrics=scope_data.values(), - schema_url=sdk_resource.schema_url, - ) - ) - return ExportMetricsServiceRequest(resource_metrics=resource_data) - - -def _encode_resource_metrics(resource_metrics, resource_metrics_dict): - resource = resource_metrics.resource - # It is safe to assume that each entry in data.resource_metrics is - # associated with an unique resource. - scope_metrics_dict = {} - resource_metrics_dict[resource] = scope_metrics_dict - for scope_metrics in resource_metrics.scope_metrics: - instrumentation_scope = scope_metrics.scope - - # The SDK groups metrics in instrumentation scopes already so - # there is no need to check for existing instrumentation scopes - # here. - pb2_scope_metrics = pb2.ScopeMetrics( - scope=_encode_instrumentation_scope(instrumentation_scope), - schema_url=instrumentation_scope.schema_url, - ) - - scope_metrics_dict[instrumentation_scope] = pb2_scope_metrics - - for metric in scope_metrics.metrics: - pb2_metric = pb2.Metric( - name=metric.name, - description=metric.description, - unit=metric.unit, - ) - - try: - _encode_metric(metric, pb2_metric) - except Exception as ex: - # `from None` so we don't get "During handling of the above exception, another exception occurred:" - raise EncodingException(ex, metric) from None - - pb2_scope_metrics.metrics.append(pb2_metric) - - -def _encode_metric(metric, pb2_metric): - if isinstance(metric.data, Gauge): - for data_point in metric.data.data_points: - pt = pb2.NumberDataPoint( - attributes=_encode_attributes(data_point.attributes), - time_unix_nano=data_point.time_unix_nano, - exemplars=_encode_exemplars(data_point.exemplars), - ) - if isinstance(data_point.value, int): - pt.as_int = data_point.value - else: - pt.as_double = data_point.value - pb2_metric.gauge.data_points.append(pt) - - elif isinstance(metric.data, HistogramType): - for data_point in metric.data.data_points: - pt = pb2.HistogramDataPoint( - attributes=_encode_attributes(data_point.attributes), - time_unix_nano=data_point.time_unix_nano, - start_time_unix_nano=data_point.start_time_unix_nano, - exemplars=_encode_exemplars(data_point.exemplars), - count=data_point.count, - sum=data_point.sum, - bucket_counts=data_point.bucket_counts, - explicit_bounds=data_point.explicit_bounds, - max=data_point.max, - min=data_point.min, - ) - pb2_metric.histogram.aggregation_temporality = ( - metric.data.aggregation_temporality - ) - pb2_metric.histogram.data_points.append(pt) - - elif isinstance(metric.data, Sum): - for data_point in metric.data.data_points: - pt = pb2.NumberDataPoint( - attributes=_encode_attributes(data_point.attributes), - start_time_unix_nano=data_point.start_time_unix_nano, - time_unix_nano=data_point.time_unix_nano, - exemplars=_encode_exemplars(data_point.exemplars), - ) - if isinstance(data_point.value, int): - pt.as_int = data_point.value - else: - pt.as_double = data_point.value - # note that because sum is a message type, the - # fields must be set individually rather than - # instantiating a pb2.Sum and setting it once - pb2_metric.sum.aggregation_temporality = ( - metric.data.aggregation_temporality - ) - pb2_metric.sum.is_monotonic = metric.data.is_monotonic - pb2_metric.sum.data_points.append(pt) - - elif isinstance(metric.data, ExponentialHistogramType): - for data_point in metric.data.data_points: - if data_point.positive.bucket_counts: - positive = pb2.ExponentialHistogramDataPoint.Buckets( - offset=data_point.positive.offset, - bucket_counts=data_point.positive.bucket_counts, - ) - else: - positive = None - - if data_point.negative.bucket_counts: - negative = pb2.ExponentialHistogramDataPoint.Buckets( - offset=data_point.negative.offset, - bucket_counts=data_point.negative.bucket_counts, - ) - else: - negative = None - - pt = pb2.ExponentialHistogramDataPoint( - attributes=_encode_attributes(data_point.attributes), - time_unix_nano=data_point.time_unix_nano, - start_time_unix_nano=data_point.start_time_unix_nano, - exemplars=_encode_exemplars(data_point.exemplars), - count=data_point.count, - sum=data_point.sum, - scale=data_point.scale, - zero_count=data_point.zero_count, - positive=positive, - negative=negative, - flags=data_point.flags, - max=data_point.max, - min=data_point.min, - ) - pb2_metric.exponential_histogram.aggregation_temporality = ( - metric.data.aggregation_temporality - ) - pb2_metric.exponential_histogram.data_points.append(pt) - - else: - _logger.warning( - "unsupported data type %s", - metric.data.__class__.__name__, - ) - - -def _encode_exemplars(sdk_exemplars: list[Exemplar]) -> list[pb2.Exemplar]: - """ - Converts a list of SDK Exemplars into a list of protobuf Exemplars. - - Args: - sdk_exemplars (list): The list of exemplars from the OpenTelemetry SDK. - - Returns: - list: A list of protobuf exemplars. - """ - pb_exemplars = [] - for sdk_exemplar in sdk_exemplars: - if ( - sdk_exemplar.span_id is not None - and sdk_exemplar.trace_id is not None - ): - pb_exemplar = pb2.Exemplar( - time_unix_nano=sdk_exemplar.time_unix_nano, - span_id=_encode_span_id(sdk_exemplar.span_id), - trace_id=_encode_trace_id(sdk_exemplar.trace_id), - filtered_attributes=_encode_attributes( - sdk_exemplar.filtered_attributes - ), - ) - else: - pb_exemplar = pb2.Exemplar( - time_unix_nano=sdk_exemplar.time_unix_nano, - filtered_attributes=_encode_attributes( - sdk_exemplar.filtered_attributes - ), - ) - - # Assign the value based on its type in the SDK exemplar - if isinstance(sdk_exemplar.value, float): - pb_exemplar.as_double = sdk_exemplar.value - elif isinstance(sdk_exemplar.value, int): - pb_exemplar.as_int = sdk_exemplar.value - else: - raise ValueError("Exemplar value must be an int or float") - pb_exemplars.append(pb_exemplar) - return pb_exemplars +__all__ = ["EncodingException", "OTLPMetricExporterMixin", "encode_metrics"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py index 96276a383e8..bae2ac3609d 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py @@ -1,181 +1,9 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -import logging -from collections import defaultdict -from collections.abc import Sequence -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_attributes, - _encode_instrumentation_scope, - _encode_resource, - _encode_span_id, - _encode_trace_id, +from opentelemetry.exporter.otlp._proto.common._internal.trace_encoder import ( + encode_spans, ) -from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( - ExportTraceServiceRequest as PB2ExportTraceServiceRequest, -) -from opentelemetry.proto.trace.v1.trace_pb2 import ( - ResourceSpans as PB2ResourceSpans, -) -from opentelemetry.proto.trace.v1.trace_pb2 import ScopeSpans as PB2ScopeSpans -from opentelemetry.proto.trace.v1.trace_pb2 import Span as PB2SPan -from opentelemetry.proto.trace.v1.trace_pb2 import SpanFlags as PB2SpanFlags -from opentelemetry.proto.trace.v1.trace_pb2 import Status as PB2Status -from opentelemetry.sdk.trace import Event, ReadableSpan -from opentelemetry.trace import Link, SpanKind -from opentelemetry.trace.span import SpanContext, Status, TraceState - -# pylint: disable=E1101 -_SPAN_KIND_MAP = { - SpanKind.INTERNAL: PB2SPan.SpanKind.SPAN_KIND_INTERNAL, - SpanKind.SERVER: PB2SPan.SpanKind.SPAN_KIND_SERVER, - SpanKind.CLIENT: PB2SPan.SpanKind.SPAN_KIND_CLIENT, - SpanKind.PRODUCER: PB2SPan.SpanKind.SPAN_KIND_PRODUCER, - SpanKind.CONSUMER: PB2SPan.SpanKind.SPAN_KIND_CONSUMER, -} - -_logger = logging.getLogger(__name__) - - -def encode_spans( - sdk_spans: Sequence[ReadableSpan], -) -> PB2ExportTraceServiceRequest: - return PB2ExportTraceServiceRequest( - resource_spans=_encode_resource_spans(sdk_spans) - ) - - -def _encode_resource_spans( - sdk_spans: Sequence[ReadableSpan], -) -> list[PB2ResourceSpans]: - # We need to inspect the spans and group + structure them as: - # - # Resource - # Instrumentation Library - # Spans - # - # First loop organizes the SDK spans in this structure. Protobuf messages - # are not hashable so we stick with SDK data in this phase. - # - # Second loop encodes the data into Protobuf format. - # - sdk_resource_spans = defaultdict(lambda: defaultdict(list)) - - for sdk_span in sdk_spans: - sdk_resource = sdk_span.resource - sdk_instrumentation = sdk_span.instrumentation_scope or None - pb2_span = _encode_span(sdk_span) - - sdk_resource_spans[sdk_resource][sdk_instrumentation].append(pb2_span) - - pb2_resource_spans = [] - - for sdk_resource, sdk_instrumentations in sdk_resource_spans.items(): - scope_spans = [] - for sdk_instrumentation, pb2_spans in sdk_instrumentations.items(): - scope_spans.append( - PB2ScopeSpans( - scope=(_encode_instrumentation_scope(sdk_instrumentation)), - spans=pb2_spans, - schema_url=sdk_instrumentation.schema_url - if sdk_instrumentation - else None, - ) - ) - pb2_resource_spans.append( - PB2ResourceSpans( - resource=_encode_resource(sdk_resource), - scope_spans=scope_spans, - schema_url=sdk_resource.schema_url, - ) - ) - - return pb2_resource_spans - - -def _span_flags(parent_span_context: SpanContext | None) -> int: - flags = PB2SpanFlags.SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK - if parent_span_context and parent_span_context.is_remote: - flags |= PB2SpanFlags.SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK - return flags - - -def _encode_span(sdk_span: ReadableSpan) -> PB2SPan: - span_context = sdk_span.get_span_context() - return PB2SPan( - trace_id=_encode_trace_id(span_context.trace_id), - span_id=_encode_span_id(span_context.span_id), - trace_state=_encode_trace_state(span_context.trace_state), - parent_span_id=_encode_parent_id(sdk_span.parent), - name=sdk_span.name, - kind=_SPAN_KIND_MAP[sdk_span.kind], - start_time_unix_nano=sdk_span.start_time, - end_time_unix_nano=sdk_span.end_time, - attributes=_encode_attributes(sdk_span.attributes), - events=_encode_events(sdk_span.events), - links=_encode_links(sdk_span.links), - status=_encode_status(sdk_span.status), - dropped_attributes_count=sdk_span.dropped_attributes, - dropped_events_count=sdk_span.dropped_events, - dropped_links_count=sdk_span.dropped_links, - flags=_span_flags(sdk_span.parent), - ) - - -def _encode_events( - events: Sequence[Event], -) -> list[PB2SPan.Event] | None: - pb2_events = None - if events: - pb2_events = [] - for event in events: - encoded_event = PB2SPan.Event( - name=event.name, - time_unix_nano=event.timestamp, - attributes=_encode_attributes(event.attributes), - dropped_attributes_count=event.dropped_attributes, - ) - pb2_events.append(encoded_event) - return pb2_events - - -def _encode_links(links: Sequence[Link]) -> Sequence[PB2SPan.Link]: - pb2_links = None - if links: - pb2_links = [] - for link in links: - encoded_link = PB2SPan.Link( - trace_id=_encode_trace_id(link.context.trace_id), - span_id=_encode_span_id(link.context.span_id), - attributes=_encode_attributes(link.attributes), - dropped_attributes_count=link.dropped_attributes, - flags=_span_flags(link.context), - ) - pb2_links.append(encoded_link) - return pb2_links - - -def _encode_status(status: Status) -> PB2Status | None: - pb2_status = None - if status is not None: - pb2_status = PB2Status( - code=status.status_code.value, - message=status.description, - ) - return pb2_status - - -def _encode_trace_state(trace_state: TraceState) -> str | None: - pb2_trace_state = None - if trace_state is not None: - pb2_trace_state = ",".join( - [f"{key}={value}" for key, value in (trace_state.items())] - ) - return pb2_trace_state - -def _encode_parent_id(context: SpanContext | None) -> bytes | None: - if context: - return _encode_span_id(context.span_id) - return None +__all__ = ["encode_spans"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_log_encoder.py index e55fa059673..d40dc8fe163 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_log_encoder.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.exporter.otlp.proto.common._internal._log_encoder import ( +from opentelemetry.exporter.otlp._proto.common._internal._log_encoder import ( encode_logs, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/metrics_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/metrics_encoder.py index be1c83a27b1..31059c0bbd5 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/metrics_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/metrics_encoder.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.exporter.otlp.proto.common._internal.metrics_encoder import ( +from opentelemetry.exporter.otlp._proto.common._internal.metrics_encoder import ( encode_metrics, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/trace_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/trace_encoder.py index 590ea7dfe13..bae2ac3609d 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/trace_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/trace_encoder.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( +from opentelemetry.exporter.otlp._proto.common._internal.trace_encoder import ( encode_spans, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/version/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/version/__init__.py index 524a0260e55..e8b8f0d9807 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/version/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/version/__init__.py @@ -1,4 +1,9 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -__version__ = "1.45.0.dev" + +from opentelemetry.exporter.otlp._proto.common.version import ( + __version__, +) + +__all__ = ["__version__"] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/conftest.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/conftest.py new file mode 100644 index 00000000000..e9ea917f62f --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/conftest.py @@ -0,0 +1,19 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# Equivalence tests import the real (protobuf) encoder via the public +# ``opentelemetry.exporter.otlp.proto.common`` path and the pure-Python encoder +# via the private ``opentelemetry.exporter.otlp._proto.common`` path. Both this +# package and the real ``opentelemetry-exporter-otlp-proto-common`` provide the +# public path, so guard that it resolves to the real protobuf distribution and +# not this package's own re-export shim -- otherwise the equivalence assertions +# would silently compare the pure-Python implementation against itself. +from opentelemetry.exporter.otlp.proto.common import ( + trace_encoder as _public_trace_encoder, +) + +assert "pyproto" not in (_public_trace_encoder.__file__ or ""), ( + "opentelemetry.exporter.otlp.proto.common resolved to the pyproto shim " + f"({_public_trace_encoder.__file__}); the equivalence tests need the real " + "protobuf package to own that path." +) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_internal.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_internal.py new file mode 100644 index 00000000000..39f33806644 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_internal.py @@ -0,0 +1,159 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.exporter.otlp.proto.common._internal import ( + _encode_attributes as proto_encode_attributes, + _encode_instrumentation_scope as proto_encode_instrumentation_scope, + _encode_resource as proto_encode_resource, + _encode_span_id as proto_encode_span_id, + _encode_trace_id as proto_encode_trace_id, + _encode_value as proto_encode_value, +) +from opentelemetry.exporter.otlp._proto.common._internal import ( + _encode_attributes, + _encode_instrumentation_scope, + _encode_resource, + _encode_span_id, + _encode_trace_id, + _encode_value, +) +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope + + +# ── _encode_span_id / _encode_trace_id ─────────────────────────────────────── + +def test_encode_span_id_matches_proto() -> None: + span_id = 0x1234567890ABCDEF + assert _encode_span_id(span_id) == proto_encode_span_id(span_id) + + +def test_encode_trace_id_matches_proto() -> None: + trace_id = 0x3E0C63257DE34C926F9EFCD03927272E + assert _encode_trace_id(trace_id) == proto_encode_trace_id(trace_id) + + +def test_encode_span_id_zero() -> None: + assert _encode_span_id(0) == proto_encode_span_id(0) + + +# ── _encode_value ───────────────────────────────────────────────────────────── + +def test_encode_value_none_matches_proto() -> None: + assert _encode_value(None).SerializeToString() == proto_encode_value(None).SerializeToString() + + +def test_encode_value_string_matches_proto() -> None: + assert _encode_value("hello").SerializeToString() == proto_encode_value("hello").SerializeToString() + + +def test_encode_value_bool_true_matches_proto() -> None: + assert _encode_value(True).SerializeToString() == proto_encode_value(True).SerializeToString() + + +def test_encode_value_bool_false_matches_proto() -> None: + assert _encode_value(False).SerializeToString() == proto_encode_value(False).SerializeToString() + + +def test_encode_value_int_matches_proto() -> None: + assert _encode_value(42).SerializeToString() == proto_encode_value(42).SerializeToString() + + +def test_encode_value_negative_int_matches_proto() -> None: + assert _encode_value(-7).SerializeToString() == proto_encode_value(-7).SerializeToString() + + +def test_encode_value_float_matches_proto() -> None: + assert _encode_value(3.14).SerializeToString() == proto_encode_value(3.14).SerializeToString() + + +def test_encode_value_bytes_matches_proto() -> None: + assert _encode_value(b"\x01\x02\x03").SerializeToString() == proto_encode_value(b"\x01\x02\x03").SerializeToString() + + +def test_encode_value_list_matches_proto() -> None: + assert _encode_value([1, "a", True]).SerializeToString() == proto_encode_value([1, "a", True]).SerializeToString() + + +def test_encode_value_dict_matches_proto() -> None: + assert _encode_value({"k": "v", "n": 1}).SerializeToString() == proto_encode_value({"k": "v", "n": 1}).SerializeToString() + + +def test_encode_value_nested_matches_proto() -> None: + value = {"error": None, "tags": ["a", "b"], "count": 5} + assert _encode_value(value).SerializeToString() == proto_encode_value(value).SerializeToString() + + +# ── _encode_attributes ──────────────────────────────────────────────────────── + +def test_encode_attributes_empty_matches_proto() -> None: + our = _encode_attributes({}) + proto = proto_encode_attributes({}) + our_bytes = b"".join(kv.SerializeToString() for kv in our) + proto_bytes = b"".join(kv.SerializeToString() for kv in proto) + assert our_bytes == proto_bytes + + +def test_encode_attributes_with_values_matches_proto() -> None: + attrs = {"service.name": "my-service", "count": 5, "enabled": True, "ratio": 0.5} + our = _encode_attributes(attrs) + proto = proto_encode_attributes(attrs) + our_bytes = b"".join(kv.SerializeToString() for kv in our) + proto_bytes = b"".join(kv.SerializeToString() for kv in proto) + assert our_bytes == proto_bytes + + +def test_encode_attributes_none_matches_proto() -> None: + our = _encode_attributes(None) + proto = proto_encode_attributes(None) + assert our == proto == [] + + +# ── _encode_instrumentation_scope ───────────────────────────────────────────── + +def test_encode_instrumentation_scope_empty_matches_proto() -> None: + scope = InstrumentationScope(name="") + our = _encode_instrumentation_scope(scope) + proto = proto_encode_instrumentation_scope(scope) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_encode_instrumentation_scope_name_version_matches_proto() -> None: + scope = InstrumentationScope(name="mylib", version="1.2.3") + our = _encode_instrumentation_scope(scope) + proto = proto_encode_instrumentation_scope(scope) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_encode_instrumentation_scope_with_attributes_matches_proto() -> None: + scope = InstrumentationScope( + name="mylib", + version="2.0", + schema_url="https://example.com", + attributes={"env": "prod", "version": 2}, + ) + our = _encode_instrumentation_scope(scope) + proto = proto_encode_instrumentation_scope(scope) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_encode_instrumentation_scope_none_matches_proto() -> None: + our = _encode_instrumentation_scope(None) + proto = proto_encode_instrumentation_scope(None) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── _encode_resource ────────────────────────────────────────────────────────── + +def test_encode_resource_empty_matches_proto() -> None: + resource = Resource({}) + our = _encode_resource(resource) + proto = proto_encode_resource(resource) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_encode_resource_with_attributes_matches_proto() -> None: + resource = Resource({"service.name": "my-service", "host.name": "localhost", "pid": 1234}) + our = _encode_resource(resource) + proto = proto_encode_resource(resource) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_log_encoder.py new file mode 100644 index 00000000000..de7ef321c1a --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_log_encoder.py @@ -0,0 +1,162 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry._logs import LogRecord, SeverityNumber +from opentelemetry.exporter.otlp.proto.common._log_encoder import ( + encode_logs as proto_encode_logs, +) +from opentelemetry.exporter.otlp._proto.common._internal._log_encoder import ( + encode_logs as pyproto_encode_logs, +) +from opentelemetry.sdk._logs import LogRecordLimits, ReadWriteLogRecord +from opentelemetry.sdk.resources import Resource as SDKResource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope +from opentelemetry.trace import ( + NonRecordingSpan, + SpanContext, + TraceFlags, + set_span_in_context, +) + +_CONTEXT = set_span_in_context( + NonRecordingSpan( + SpanContext( + 89564621134313219400156819398935297684, + 1312458408527513268, + False, + TraceFlags(0x01), + ) + ) +) + + +def _make_basic_log() -> ReadWriteLogRecord: + return ReadWriteLogRecord( + LogRecord( + timestamp=1644650195189786880, + observed_timestamp=1644650195189786881, + context=_CONTEXT, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night.", + attributes={"a": 1, "b": "c"}, + ), + resource=SDKResource( + {"first_resource": "value"}, + "resource_schema_url", + ), + instrumentation_scope=InstrumentationScope( + "first_name", "first_version" + ), + ) + + +def test_encode_logs_basic_matches_proto() -> None: + logs = [_make_basic_log()] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() + + +def test_encode_logs_no_instrumentation_scope_matches_proto() -> None: + log = ReadWriteLogRecord( + LogRecord( + timestamp=1644650427658989056, + observed_timestamp=1644650427658989057, + context=_CONTEXT, + severity_text="DEBUG", + severity_number=SeverityNumber.DEBUG, + body={"error": None, "array_with_nones": [1, None, 2]}, + attributes={"a": 1, "b": "c"}, + ), + resource=SDKResource({"second_resource": "CASE"}), + instrumentation_scope=None, + ) + logs = [log] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() + + +def test_encode_logs_empty_resource_with_scope_attributes_matches_proto() -> None: + log = ReadWriteLogRecord( + LogRecord( + timestamp=1644650584292683033, + observed_timestamp=1644650584292683033, + context=_CONTEXT, + severity_text="FATAL", + severity_number=SeverityNumber.FATAL, + body="This instrumentation scope has a schema url and attributes", + attributes={ + "extended": { + "sequence": [{"inner": "mapping", "none": None}] + } + }, + ), + resource=SDKResource({}), + instrumentation_scope=InstrumentationScope( + "scope_with_attributes", + "scope_with_attributes_version", + "instrumentation_schema_url", + {"one": 1, "two": "2"}, + ), + ) + logs = [log] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() + + +def test_encode_logs_dropped_attributes_matches_proto() -> None: + log = ReadWriteLogRecord( + LogRecord( + timestamp=1644650195189786880, + context=_CONTEXT, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="message with dropped attributes", + attributes={"a": 1, "b": "c", "user_id": "B121092"}, + ), + resource=SDKResource({"first_resource": "value"}), + limits=LogRecordLimits(max_attributes=1), + instrumentation_scope=InstrumentationScope( + "first_name", "first_version" + ), + ) + logs = [log] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() + + +def test_encode_logs_multiple_resources_matches_proto() -> None: + log1 = _make_basic_log() + log2 = ReadWriteLogRecord( + LogRecord( + timestamp=1644650249738562048, + observed_timestamp=1644650249738562049, + context=_CONTEXT, + severity_text="ERROR", + severity_number=SeverityNumber.ERROR, + body="second log", + ), + resource=SDKResource({"second_resource": "value2"}), + instrumentation_scope=InstrumentationScope( + "second_name", "second_version" + ), + ) + logs = [log1, log2] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() + + +def test_encode_logs_empty_list_matches_proto() -> None: + assert pyproto_encode_logs([]).SerializeToString() == proto_encode_logs([]).SerializeToString() + + +def test_encode_logs_no_trace_context_matches_proto() -> None: + no_context = set_span_in_context(NonRecordingSpan(SpanContext(0, 0, False))) + log = ReadWriteLogRecord( + LogRecord( + timestamp=1644650195189786880, + context=no_context, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="no trace context", + ), + resource=SDKResource({}), + instrumentation_scope=None, + ) + logs = [log] + assert pyproto_encode_logs(logs).SerializeToString() == proto_encode_logs(logs).SerializeToString() diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_metrics_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_metrics_encoder.py new file mode 100644 index 00000000000..535f9c6d8a4 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_metrics_encoder.py @@ -0,0 +1,332 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.exporter.otlp.proto.common.metrics_encoder import ( + encode_metrics as proto_encode_metrics, +) +from opentelemetry.exporter.otlp._proto.common._internal.metrics_encoder import ( + encode_metrics as pyproto_encode_metrics, +) +from opentelemetry.sdk.metrics import Exemplar +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + Buckets, + ExponentialHistogramDataPoint, + HistogramDataPoint, + Metric, + MetricsData, + ResourceMetrics, + ScopeMetrics, +) +from opentelemetry.sdk.metrics.export import ( + ExponentialHistogram as ExponentialHistogramType, +) +from opentelemetry.sdk.metrics.export import Histogram as HistogramType +from opentelemetry.sdk.metrics.export import Gauge as SDKGauge +from opentelemetry.sdk.metrics.export import Sum as SDKSum +from opentelemetry.sdk.metrics.export import NumberDataPoint +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import ( + InstrumentationScope as SDKInstrumentationScope, +) + + +_SPAN_ID = int("6e0c63257de34c92", 16) +_TRACE_ID = int("d4cda95b652f4a1592b449d5929fda1b", 16) + + +def _wrap(metric: Metric) -> MetricsData: + return MetricsData( + resource_metrics=[ + ResourceMetrics( + resource=Resource({"service.name": "test"}), + scope_metrics=[ + ScopeMetrics( + scope=SDKInstrumentationScope("test_scope", "1.0"), + metrics=[metric], + schema_url="", + ) + ], + schema_url="", + ) + ] + ) + + +# ── Gauge ───────────────────────────────────────────────────────────────────── + +def test_encode_gauge_int_matches_proto() -> None: + data = _wrap(Metric( + name="my.gauge", + description="A gauge", + unit="1", + data=SDKGauge(data_points=[ + NumberDataPoint( + attributes={"host": "localhost"}, + start_time_unix_nano=0, + time_unix_nano=1641946016139533244, + value=42, + exemplars=[], + ) + ]), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +def test_encode_gauge_double_matches_proto() -> None: + data = _wrap(Metric( + name="my.gauge.double", + description="", + unit="s", + data=SDKGauge(data_points=[ + NumberDataPoint( + attributes={"env": "prod"}, + start_time_unix_nano=0, + time_unix_nano=1641946016139533244, + value=3.14, + exemplars=[], + ) + ]), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +def test_encode_gauge_with_exemplar_matches_proto() -> None: + data = _wrap(Metric( + name="gauge.exemplar", + description="", + unit="1", + data=SDKGauge(data_points=[ + NumberDataPoint( + attributes={}, + start_time_unix_nano=0, + time_unix_nano=1641946016139533244, + value=100.0, + exemplars=[ + Exemplar( + {"filtered": "yes"}, + 50.0, + 1641946016139533400, + _SPAN_ID, + _TRACE_ID, + ) + ], + ) + ]), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +# ── Sum ─────────────────────────────────────────────────────────────────────── + +def test_encode_sum_int_matches_proto() -> None: + data = _wrap(Metric( + name="my.counter", + description="A counter", + unit="1", + data=SDKSum( + data_points=[ + NumberDataPoint( + attributes={"a": 1, "b": False}, + start_time_unix_nano=1641946016139533000, + time_unix_nano=1641946016139533244, + value=10, + exemplars=[], + ) + ], + aggregation_temporality=AggregationTemporality.CUMULATIVE, + is_monotonic=True, + ), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +def test_encode_sum_double_delta_matches_proto() -> None: + data = _wrap(Metric( + name="my.updown", + description="", + unit="By", + data=SDKSum( + data_points=[ + NumberDataPoint( + attributes={"direction": "in"}, + start_time_unix_nano=1641946016139533000, + time_unix_nano=1641946016139533244, + value=2.5, + exemplars=[], + ) + ], + aggregation_temporality=AggregationTemporality.DELTA, + is_monotonic=False, + ), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +# ── Histogram ───────────────────────────────────────────────────────────────── + +def test_encode_histogram_matches_proto() -> None: + data = _wrap(Metric( + name="my.histogram", + description="A histogram", + unit="s", + data=HistogramType( + data_points=[ + HistogramDataPoint( + attributes={"a": 1, "b": True}, + start_time_unix_nano=1641946016139533244, + time_unix_nano=1641946016139533244, + exemplars=[ + Exemplar( + {"filtered": "banana"}, + 298.0, + 1641946016139533400, + _SPAN_ID, + _TRACE_ID, + ), + Exemplar( + {"filtered": "banana"}, + 298.0, + 1641946016139533400, + None, + None, + ), + ], + count=5, + sum=67, + bucket_counts=[1, 4], + explicit_bounds=[10.0, 20.0], + min=8, + max=18, + ) + ], + aggregation_temporality=AggregationTemporality.DELTA, + ), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +def test_encode_histogram_no_exemplars_matches_proto() -> None: + data = _wrap(Metric( + name="simple.histogram", + description="", + unit="ms", + data=HistogramType( + data_points=[ + HistogramDataPoint( + attributes={"region": "us-east"}, + start_time_unix_nano=1641946016000000000, + time_unix_nano=1641946016139533244, + exemplars=[], + count=10, + sum=500.0, + bucket_counts=[2, 3, 5], + explicit_bounds=[100.0, 200.0], + min=10.0, + max=200.0, + ) + ], + aggregation_temporality=AggregationTemporality.CUMULATIVE, + ), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +# ── ExponentialHistogram ────────────────────────────────────────────────────── + +def test_encode_exponential_histogram_matches_proto() -> None: + data = _wrap(Metric( + name="exp.histogram", + description="", + unit="1", + data=ExponentialHistogramType( + data_points=[ + ExponentialHistogramDataPoint( + attributes={"host": "node1"}, + start_time_unix_nano=1641946016000000000, + time_unix_nano=1641946016139533244, + exemplars=[], + count=8, + sum=100.0, + scale=2, + zero_count=1, + positive=Buckets(offset=1, bucket_counts=[3, 4]), + negative=Buckets(offset=-1, bucket_counts=[1]), + flags=0, + min=1.0, + max=50.0, + ) + ], + aggregation_temporality=AggregationTemporality.DELTA, + ), + )) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +# ── Multiple metrics and resources ──────────────────────────────────────────── + +def test_encode_multiple_metrics_matches_proto() -> None: + gauge_metric = Metric( + name="gauge", + description="", + unit="1", + data=SDKGauge(data_points=[ + NumberDataPoint( + attributes={}, + start_time_unix_nano=0, + time_unix_nano=1641946016139533244, + value=1, + exemplars=[], + ) + ]), + ) + counter_metric = Metric( + name="counter", + description="", + unit="1", + data=SDKSum( + data_points=[ + NumberDataPoint( + attributes={}, + start_time_unix_nano=1641946016000000000, + time_unix_nano=1641946016139533244, + value=5, + exemplars=[], + ) + ], + aggregation_temporality=AggregationTemporality.CUMULATIVE, + is_monotonic=True, + ), + ) + data = MetricsData( + resource_metrics=[ + ResourceMetrics( + resource=Resource({"service.name": "svc-a"}), + scope_metrics=[ + ScopeMetrics( + scope=SDKInstrumentationScope("lib_a", "1.0"), + metrics=[gauge_metric, counter_metric], + schema_url="", + ) + ], + schema_url="", + ), + ResourceMetrics( + resource=Resource({"service.name": "svc-b"}), + scope_metrics=[ + ScopeMetrics( + scope=SDKInstrumentationScope("lib_b", "2.0"), + metrics=[gauge_metric], + schema_url="", + ) + ], + schema_url="resource_schema_url", + ), + ] + ) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() + + +def test_encode_metrics_empty_matches_proto() -> None: + data = MetricsData(resource_metrics=[]) + assert pyproto_encode_metrics(data).SerializeToString() == proto_encode_metrics(data).SerializeToString() diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_trace_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_trace_encoder.py new file mode 100644 index 00000000000..80ce493c253 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/equivalence/test_trace_encoder.py @@ -0,0 +1,197 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.exporter.otlp.proto.common.trace_encoder import ( + encode_spans as proto_encode_spans, +) +from opentelemetry.exporter.otlp._proto.common._internal.trace_encoder import ( + encode_spans as pyproto_encode_spans, +) +from opentelemetry.sdk.trace import Event as SDKEvent +from opentelemetry.sdk.trace import Resource as SDKResource +from opentelemetry.sdk.trace import SpanContext as SDKSpanContext +from opentelemetry.sdk.trace import _Span as SDKSpan +from opentelemetry.sdk.util.instrumentation import ( + InstrumentationScope as SDKInstrumentationScope, +) +from opentelemetry.trace import Link as SDKLink +from opentelemetry.trace import SpanKind as SDKSpanKind +from opentelemetry.trace import TraceFlags as SDKTraceFlags +from opentelemetry.trace.status import Status as SDKStatus +from opentelemetry.trace.status import StatusCode as SDKStatusCode + + +def _make_exhaustive_spans() -> list[SDKSpan]: + trace_id = 0x3E0C63257DE34C926F9EFCD03927272E + + base_time = 683647322 * 10**9 + start_times = ( + base_time, + base_time + 150 * 10**6, + base_time + 300 * 10**6, + base_time + 400 * 10**6, + base_time + 500 * 10**6, + base_time + 600 * 10**6, + ) + end_times = ( + start_times[0] + (50 * 10**6), + start_times[1] + (100 * 10**6), + start_times[2] + (200 * 10**6), + start_times[3] + (300 * 10**6), + start_times[4] + (400 * 10**6), + start_times[5] + (500 * 10**6), + ) + + parent_span_context = SDKSpanContext( + trace_id, 0x1111111111111111, is_remote=True + ) + other_context = SDKSpanContext( + trace_id, 0x2222222222222222, is_remote=False + ) + + span1 = SDKSpan( + name="test-span-1", + context=SDKSpanContext( + trace_id, + 0x34BF92DEEFC58C92, + is_remote=False, + trace_flags=SDKTraceFlags(SDKTraceFlags.SAMPLED), + ), + parent=parent_span_context, + events=( + SDKEvent( + name="event0", + timestamp=base_time + 50 * 10**6, + attributes={ + "annotation_bool": True, + "annotation_string": "annotation_test", + "key_float": 0.3, + }, + ), + ), + links=( + SDKLink(context=other_context, attributes={"key_bool": True}), + ), + resource=SDKResource({}, "resource_schema_url"), + ) + span1.start(start_time=start_times[0]) + span1.set_attribute("key_bool", False) + span1.set_attribute("key_string", "hello_world") + span1.set_attribute("key_float", 111.22) + span1.set_status(SDKStatus(SDKStatusCode.ERROR, "Example description")) + span1.end(end_time=end_times[0]) + + span2 = SDKSpan( + name="test-span-2", + context=parent_span_context, + parent=None, + resource=SDKResource(attributes={"key_resource": "some_resource"}), + ) + span2.start(start_time=start_times[1]) + span2.end(end_time=end_times[1]) + + span3 = SDKSpan( + name="test-span-3", + context=other_context, + parent=None, + resource=SDKResource(attributes={"key_resource": "some_resource"}), + ) + span3.start(start_time=start_times[2]) + span3.set_attribute("key_string", "hello_world") + span3.end(end_time=end_times[2]) + + span4 = SDKSpan( + name="test-span-4", + context=other_context, + parent=None, + resource=SDKResource({}, "resource_schema_url"), + instrumentation_scope=SDKInstrumentationScope( + name="name", version="version" + ), + ) + span4.start(start_time=start_times[3]) + span4.end(end_time=end_times[3]) + + span5 = SDKSpan( + name="test-span-5", + context=other_context, + parent=None, + resource=SDKResource( + attributes={"key_resource": "another_resource"}, + schema_url="resource_schema_url", + ), + instrumentation_scope=SDKInstrumentationScope( + name="scope_1_name", + version="scope_1_version", + schema_url="scope_1_schema_url", + ), + ) + span5.start(start_time=start_times[4]) + span5.end(end_time=end_times[4]) + + span6 = SDKSpan( + name="test-span-6", + context=other_context, + parent=None, + resource=SDKResource( + attributes={"key_resource": "another_resource"}, + schema_url="resource_schema_url", + ), + instrumentation_scope=SDKInstrumentationScope( + name="scope_2_name", + version="scope_2_version", + schema_url="scope_2_schema_url", + attributes={"one": "1", "two": 2}, + ), + ) + span6.start(start_time=start_times[5]) + span6.end(end_time=end_times[5]) + + return [span1, span2, span3, span4, span5, span6] + + +def test_encode_spans_single_minimal() -> None: + span = SDKSpan( + name="hello", + context=SDKSpanContext( + 0x3E0C63257DE34C926F9EFCD03927272E, + 0x34BF92DEEFC58C92, + is_remote=False, + trace_flags=SDKTraceFlags(SDKTraceFlags.SAMPLED), + ), + parent=None, + resource=SDKResource({}), + ) + span.start(start_time=1_000_000_000) + span.end(end_time=2_000_000_000) + spans = [span] + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() + + +def test_encode_spans_with_attributes_and_status() -> None: + spans = _make_exhaustive_spans()[:1] # span1 has attributes, events, links, status + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() + + +def test_encode_spans_multiple_resources() -> None: + spans = _make_exhaustive_spans() + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() + + +def test_encode_spans_with_instrumentation_scope() -> None: + spans = _make_exhaustive_spans()[3:4] # span4 has instrumentation scope + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() + + +def test_encode_spans_with_scope_attributes() -> None: + spans = _make_exhaustive_spans()[5:6] # span6 has scope with attributes + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() + + +def test_encode_spans_empty_list() -> None: + assert pyproto_encode_spans([]).SerializeToString() == proto_encode_spans([]).SerializeToString() + + +def test_encode_spans_exhaustive_matches_proto() -> None: + spans = _make_exhaustive_spans() + assert pyproto_encode_spans(spans).SerializeToString() == proto_encode_spans(spans).SerializeToString() diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/py.typed b/exporter/opentelemetry-exporter-otlp-proto-common/tests/performance/__init__.py similarity index 100% rename from exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/py.typed rename to exporter/opentelemetry-exporter-otlp-proto-common/tests/performance/__init__.py diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/performance/test_benchmark_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/performance/test_benchmark_encoder.py new file mode 100644 index 00000000000..2cc7dfb1407 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/performance/test_benchmark_encoder.py @@ -0,0 +1,317 @@ +# tests/performance/test_benchmark_encoder.py +# +# Benchmark: the pure-Python OTLP encoders (opentelemetry.exporter.otlp._proto +# .common) vs the real google.protobuf-backed encoders (opentelemetry.exporter +# .otlp.proto.common) on the exporter's actual production hot path — real SDK +# spans / metrics / logs in, serialized OTLP export-request bytes out. +# +# This is the end-to-end counterpart to opentelemetry-pyproto's +# test_benchmark_otlp.py: instead of hand-built proto message trees, it drives +# encode_spans / encode_metrics / encode_logs with genuine SDK objects, so the +# numbers reflect what an exporter pays per export (SDK data -> proto messages +# -> wire bytes). +# +# Both encoder paths share an identical call signature and return objects with +# SerializeToString(); a byte-equality guard asserts they encode identically so +# the comparison is fair. +# +# The real encoders are only present when the upstream +# ``opentelemetry-exporter-otlp-proto-common`` owns the public +# ``opentelemetry.exporter.otlp.proto.common`` path (installed after this +# package so it wins the namespace). Otherwise that path is this package's +# re-export shim and the whole module skips, like the equivalence conftest. +# +# Run (install order matters — real protobuf encoder must win the public path): +# uv pip install . && uv pip install \ +# opentelemetry-sdk protobuf opentelemetry-proto \ +# opentelemetry-exporter-otlp-proto-common pytest-benchmark +# uv run pytest tests/performance/test_benchmark_encoder.py \ +# --benchmark-group-by=param --benchmark-sort=fullname + +from pytest import mark, skip + +# Public path: real upstream encoder when installed, else this package's shim. +from opentelemetry.exporter.otlp.proto.common import ( + trace_encoder as _public_trace_encoder, +) + +if "pyproto" in (_public_trace_encoder.__file__ or ""): + skip( + "opentelemetry.exporter.otlp.proto.common resolves to the pyproto shim " + f"({_public_trace_encoder.__file__}); install the real " + "opentelemetry-exporter-otlp-proto-common (after this package) to " + "benchmark the pure-Python encoders against the real protobuf ones.", + allow_module_level=True, + ) + +from opentelemetry.exporter.otlp.proto.common.trace_encoder import ( + encode_spans as proto_encode_spans, +) +from opentelemetry.exporter.otlp.proto.common.metrics_encoder import ( + encode_metrics as proto_encode_metrics, +) +from opentelemetry.exporter.otlp.proto.common._log_encoder import ( + encode_logs as proto_encode_logs, +) +from opentelemetry.exporter.otlp._proto.common._internal.trace_encoder import ( + encode_spans as pyproto_encode_spans, +) +from opentelemetry.exporter.otlp._proto.common._internal.metrics_encoder import ( + encode_metrics as pyproto_encode_metrics, +) +from opentelemetry.exporter.otlp._proto.common._internal._log_encoder import ( + encode_logs as pyproto_encode_logs, +) + +from opentelemetry._logs import LogRecord, SeverityNumber +from opentelemetry.sdk._logs import ReadWriteLogRecord +from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, + Histogram as HistogramType, + HistogramDataPoint, + Metric, + MetricsData, + ResourceMetrics, + ScopeMetrics, +) +from opentelemetry.sdk.resources import Resource as SDKResource +from opentelemetry.sdk.trace import Event as SDKEvent +from opentelemetry.sdk.trace import SpanContext as SDKSpanContext +from opentelemetry.sdk.trace import _Span as SDKSpan +from opentelemetry.sdk.util.instrumentation import ( + InstrumentationScope as SDKInstrumentationScope, +) +from opentelemetry.trace import Link as SDKLink +from opentelemetry.trace import ( + NonRecordingSpan, + SpanContext, + SpanKind as SDKSpanKind, + TraceFlags as SDKTraceFlags, + set_span_in_context, +) +from opentelemetry.trace.status import Status as SDKStatus +from opentelemetry.trace.status import StatusCode as SDKStatusCode + + +# ── Shared SDK fixtures ───────────────────────────────────────────────────── +# +# One resource + scope shared across all items so a batch collapses into a +# single ResourceSpans/ScopeSpans, as a real per-service export does. Values +# are index-derived (no randomness) for reproducibility. + +_BASE_NS = 1_700_000_000_000_000_000 +_TRACE_ID = 0x3E0C63257DE34C926F9EFCD03927272E +_PARENT_SPAN_ID = 0x1111111111111111 + +_RESOURCE = SDKResource( + { + "service.name": "checkout-service", + "service.version": "1.24.0", + "service.instance.id": "pod-7f9c-abc123", + "process.pid": 42317, + "host.name": "ip-10-0-12-34", + "telemetry.sdk.language": "python", + "telemetry.sdk.version": "1.30.0", + }, + "https://opentelemetry.io/schemas/1.30.0", +) +_TRACE_SCOPE = SDKInstrumentationScope( + "opentelemetry.instrumentation.flask", "0.48b0" +) +_METRIC_SCOPE = SDKInstrumentationScope("opentelemetry.sdk.metrics", "1.30.0") +_LOG_SCOPE = SDKInstrumentationScope("opentelemetry.sdk._logs", "1.30.0") + + +def _make_span(i: int) -> SDKSpan: + start = _BASE_NS + i * 1_000_000 + span = SDKSpan( + name="GET /api/orders/{id}", + context=SDKSpanContext( + _TRACE_ID + i, + 0x34BF92DEEFC58C92 ^ i, + is_remote=False, + trace_flags=SDKTraceFlags(SDKTraceFlags.SAMPLED), + ), + parent=SDKSpanContext(_TRACE_ID + i, _PARENT_SPAN_ID, is_remote=True), + kind=SDKSpanKind.SERVER, + events=( + SDKEvent( + name="cache.miss", + timestamp=start + 1_000_000, + attributes={"cache.key": f"order:{i}"}, + ), + SDKEvent( + name="db.query", + timestamp=start + 2_500_000, + attributes={"db.rows": 17, "db.statement": "SELECT 1"}, + ), + ), + links=( + SDKLink( + context=SDKSpanContext( + _TRACE_ID + i + 100, 0x2222222222222222, is_remote=False + ), + attributes={"link.kind": "follows_from"}, + ), + ), + resource=_RESOURCE, + instrumentation_scope=_TRACE_SCOPE, + ) + span.start(start_time=start) + span.set_attribute("http.request.method", "GET") + span.set_attribute("url.path", f"/api/orders/{i}") + span.set_attribute("http.response.status_code", 200) + span.set_attribute("server.address", "orders.internal") + span.set_attribute("network.protocol.version", "1.1") + span.set_attribute("db.system", "postgresql") + span.set_status(SDKStatus(SDKStatusCode.OK)) + span.end(end_time=start + 4_200_000) + return span + + +def _make_spans(n: int) -> list: + return [_make_span(i) for i in range(n)] + + +def _make_metric(i: int, n_dp: int) -> Metric: + start = _BASE_NS + return Metric( + name=f"http.server.request.duration.{i}", + description="Duration of HTTP server requests.", + unit="s", + data=HistogramType( + data_points=[ + HistogramDataPoint( + attributes={ + "http.request.method": "GET", + "http.response.status_code": 200 + d, + }, + start_time_unix_nano=start, + time_unix_nano=start + 60_000_000_000, + count=1_234, + sum=456.789, + bucket_counts=[0, 12, 145, 402, 388, 210, 61, 14, 2, 0], + explicit_bounds=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5], + min=0.002, + max=3.1, + exemplars=[], + ) + for d in range(n_dp) + ], + aggregation_temporality=AggregationTemporality.CUMULATIVE, + ), + ) + + +def _make_metrics_data(n_metric: int, n_dp: int) -> MetricsData: + return MetricsData( + resource_metrics=[ + ResourceMetrics( + resource=_RESOURCE, + scope_metrics=[ + ScopeMetrics( + scope=_METRIC_SCOPE, + metrics=[_make_metric(i, n_dp) for i in range(n_metric)], + schema_url="", + ) + ], + schema_url="", + ) + ] + ) + + +_LOG_CONTEXT = set_span_in_context( + NonRecordingSpan( + SpanContext(_TRACE_ID, 0x34BF92DEEFC58C92, False, SDKTraceFlags(0x01)) + ) +) + + +def _make_log(i: int) -> ReadWriteLogRecord: + ts = _BASE_NS + i * 1_000_000 + return ReadWriteLogRecord( + LogRecord( + timestamp=ts, + observed_timestamp=ts + 1_000, + context=_LOG_CONTEXT, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body=f"request {i} completed in 4.2ms with status 200", + attributes={ + "log.source": "access", + "http.route": "/api/orders/{id}", + "http.response.status_code": 200, + "thread.id": 140_234_567, + }, + ), + resource=_RESOURCE, + instrumentation_scope=_LOG_SCOPE, + ) + + +def _make_logs(n: int) -> list: + return [_make_log(i) for i in range(n)] + + +# Encoders + payload factories, keyed by signal. Each factory takes the scale +# tuple and returns the encoder input. +_SIGNALS = { + "trace": ( + pyproto_encode_spans, + proto_encode_spans, + lambda dims: _make_spans(*dims), + [("single", (1,)), ("batch_100", (100,)), ("batch_500", (500,))], + ), + "metrics": ( + pyproto_encode_metrics, + proto_encode_metrics, + lambda dims: _make_metrics_data(*dims), + [("single", (1, 1)), ("metrics_50", (50, 1)), ("dp_200", (20, 10))], + ), + "logs": ( + pyproto_encode_logs, + proto_encode_logs, + lambda dims: _make_logs(*dims), + [("single", (1,)), ("batch_100", (100,)), ("batch_500", (500,))], + ), +} + +_ALL_CASES = [ + (signal, label, dims) + for signal, (_py, _pb, _factory, scales) in _SIGNALS.items() + for label, dims in scales +] +_ALL_IDS = [f"{signal}-{label}" for signal, label, _ in _ALL_CASES] + + +# ── Fairness guard: both encoders must produce identical bytes ────────────── + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +def test_encoder_outputs_identical(signal, label, dims) -> None: + py_encode, pb_encode, factory, _ = _SIGNALS[signal] + payload = factory(dims) + py_bytes = py_encode(payload).SerializeToString() + pb_bytes = pb_encode(payload).SerializeToString() + assert py_bytes == pb_bytes, ( + f"{signal}/{label}: pyproto and protobuf encoders disagree " + f"({len(py_bytes)} vs {len(pb_bytes)} bytes)" + ) + + +# ── Encode + serialize: the per-export cost an exporter actually pays ─────── + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +def test_encode_pyproto(benchmark, signal, label, dims) -> None: + py_encode, _pb, factory, _ = _SIGNALS[signal] + payload = factory(dims) + result = benchmark(lambda: py_encode(payload).SerializeToString()) + assert len(result) > 0 + + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +def test_encode_protobuf(benchmark, signal, label, dims) -> None: + _py, pb_encode, factory, _ = _SIGNALS[signal] + payload = factory(dims) + result = benchmark(lambda: pb_encode(payload).SerializeToString()) + assert len(result) > 0 diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_attribute_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_attribute_encoder.py deleted file mode 100644 index 4c2dd7bb427..00000000000 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_attribute_encoder.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -import unittest -from logging import ERROR - -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_attributes, -) -from opentelemetry.proto.common.v1.common_pb2 import AnyValue as PB2AnyValue -from opentelemetry.proto.common.v1.common_pb2 import ( - ArrayValue as PB2ArrayValue, -) -from opentelemetry.proto.common.v1.common_pb2 import KeyValue as PB2KeyValue - - -class CallingStrRaisesException: - def __str__(self): - raise ValueError("Cannot encode") - - -class TestOTLPAttributeEncoder(unittest.TestCase): - def test_encode_attributes_all_kinds(self): - result = _encode_attributes( - { - "a": 1, # int - "b": 3.14, # float - "c": False, # bool - "hello": "world", # str - "greet": ["hola", "bonjour"], # Sequence[str] - "data": [1, 2], # Sequence[int] - "data_granular": [1.4, 2.4], # Sequence[float] - "binary_data": b"x00\x01\x02", # bytes - } - ) - self.assertEqual( - result, - [ - PB2KeyValue(key="a", value=PB2AnyValue(int_value=1)), - PB2KeyValue(key="b", value=PB2AnyValue(double_value=3.14)), - PB2KeyValue(key="c", value=PB2AnyValue(bool_value=False)), - PB2KeyValue( - key="hello", value=PB2AnyValue(string_value="world") - ), - PB2KeyValue( - key="greet", - value=PB2AnyValue( - array_value=PB2ArrayValue( - values=[ - PB2AnyValue(string_value="hola"), - PB2AnyValue(string_value="bonjour"), - ] - ) - ), - ), - PB2KeyValue( - key="data", - value=PB2AnyValue( - array_value=PB2ArrayValue( - values=[ - PB2AnyValue(int_value=1), - PB2AnyValue(int_value=2), - ] - ) - ), - ), - PB2KeyValue( - key="data_granular", - value=PB2AnyValue( - array_value=PB2ArrayValue( - values=[ - PB2AnyValue(double_value=1.4), - PB2AnyValue(double_value=2.4), - ] - ) - ), - ), - PB2KeyValue( - key="binary_data", - value=PB2AnyValue(bytes_value=b"x00\x01\x02"), - ), - ], - ) - - def test_encode_attributes_error_logs_key(self): - with self.assertLogs(level=ERROR) as error: - result = _encode_attributes( - {"a": 1, "bad_key": CallingStrRaisesException(), "b": 2} - ) - - self.assertEqual(len(error.records), 1) - self.assertEqual(error.records[0].msg, "Failed to encode key %s: %s") - self.assertEqual(error.records[0].args[0], "bad_key") - self.assertIsInstance(error.records[0].args[1], Exception) - self.assertEqual( - result, - [ - PB2KeyValue(key="a", value=PB2AnyValue(int_value=1)), - PB2KeyValue(key="b", value=PB2AnyValue(int_value=2)), - ], - ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_exporter_metrics.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_exporter_metrics.py deleted file mode 100644 index e6c1a601c07..00000000000 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_exporter_metrics.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -import unittest -from unittest.mock import Mock, patch -from urllib.parse import urlparse - -from opentelemetry.exporter.otlp.proto.common._exporter_metrics import ( - ExporterMetrics, - NoOpExporterMetrics, - create_exporter_metrics, -) -from opentelemetry.semconv._incubating.attributes.otel_attributes import ( - OtelComponentTypeValues, -) - - -class TestExporterMetrics(unittest.TestCase): - def test_factory_returns_noop_when_disabled(self): - meter_provider = Mock() - - with patch( - "opentelemetry.exporter.otlp.proto.common." - "_exporter_metrics.get_meter_provider" - ) as get_meter_provider: - metrics = create_exporter_metrics( - OtelComponentTypeValues.OTLP_HTTP_SPAN_EXPORTER, - "traces", - urlparse("http://localhost:4318/v1/traces"), - meter_provider, - False, - ) - - self.assertIsInstance(metrics, NoOpExporterMetrics) - meter_provider.get_meter.assert_not_called() - get_meter_provider.assert_not_called() - - def test_factory_returns_exporter_metrics_when_enabled(self): - meter_provider = Mock() - meter_provider.get_meter.return_value = Mock() - - metrics = create_exporter_metrics( - OtelComponentTypeValues.OTLP_HTTP_SPAN_EXPORTER, - "traces", - urlparse("http://localhost:4318/v1/traces"), - meter_provider, - True, - ) - - self.assertIsInstance(metrics, ExporterMetrics) - meter_provider.get_meter.assert_called_once_with("opentelemetry-sdk") - - def test_noop_export_operation_yields_result(self): - metrics = NoOpExporterMetrics() - - with metrics.export_operation(1) as result: - result.error = RuntimeError("error") - - self.assertIsInstance(result.error, RuntimeError) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py deleted file mode 100644 index 6a4e2faa2b6..00000000000 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ /dev/null @@ -1,305 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -import unittest - -from opentelemetry._logs import LogRecord, SeverityNumber -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_attributes, - _encode_span_id, - _encode_trace_id, - _encode_value, -) -from opentelemetry.exporter.otlp.proto.common._log_encoder import encode_logs -from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( - ExportLogsServiceRequest, -) -from opentelemetry.proto.common.v1.common_pb2 import AnyValue as PB2AnyValue -from opentelemetry.proto.common.v1.common_pb2 import ( - InstrumentationScope as PB2InstrumentationScope, -) -from opentelemetry.proto.common.v1.common_pb2 import KeyValue as PB2KeyValue -from opentelemetry.proto.logs.v1.logs_pb2 import LogRecord as PB2LogRecord -from opentelemetry.proto.logs.v1.logs_pb2 import ( - ResourceLogs as PB2ResourceLogs, -) -from opentelemetry.proto.logs.v1.logs_pb2 import ScopeLogs as PB2ScopeLogs -from opentelemetry.proto.resource.v1.resource_pb2 import ( - Resource as PB2Resource, -) -from opentelemetry.sdk._logs import LogRecordLimits, ReadWriteLogRecord -from opentelemetry.sdk.resources import Resource as SDKResource -from opentelemetry.sdk.util.instrumentation import InstrumentationScope -from opentelemetry.trace import ( - NonRecordingSpan, - SpanContext, - TraceFlags, - set_span_in_context, -) - -_CONTEXT_LOG = set_span_in_context( - NonRecordingSpan( - SpanContext( - 89564621134313219400156819398935297684, - 1312458408527513268, - False, - TraceFlags(0x01), - ) - ) -) - - -class TestOTLPLogEncoder(unittest.TestCase): - def test_encode_basic_log_record(self): - basic_log_record = ReadWriteLogRecord( - LogRecord( - timestamp=1644650195189786880, - observed_timestamp=1644650195189786881, - context=_CONTEXT_LOG, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", - attributes={"a": 1, "b": "c"}, - ), - resource=SDKResource( - {"first_resource": "value"}, - "resource_schema_url", - ), - instrumentation_scope=InstrumentationScope( - "first_name", "first_version" - ), - ) - pb2_service_request = ExportLogsServiceRequest( - resource_logs=[ - PB2ResourceLogs( - resource=PB2Resource( - attributes=[ - PB2KeyValue( - key="first_resource", - value=PB2AnyValue(string_value="value"), - ) - ] - ), - scope_logs=[ - PB2ScopeLogs( - scope=PB2InstrumentationScope( - name="first_name", version="first_version" - ), - log_records=[ - PB2LogRecord( - time_unix_nano=1644650195189786880, - observed_time_unix_nano=1644650195189786881, - trace_id=_encode_trace_id( - 89564621134313219400156819398935297684 - ), - span_id=_encode_span_id( - 1312458408527513268 - ), - flags=int(TraceFlags(0x01)), - severity_text="WARN", - severity_number=SeverityNumber.WARN.value, - body=_encode_value( - "Do not go gentle into that good night. Rage, rage against the dying of the light" - ), - attributes=_encode_attributes( - {"a": 1, "b": "c"} - ), - ) - ], - ), - ], - schema_url="resource_schema_url", - ) - ] - ) - self.assertEqual(encode_logs([basic_log_record]), pb2_service_request) - - def test_encode_log_record_with_no_instrumentation_scope_and_dict_body( - self, - ): - log_record_with_no_instrumentation_scope_and_dict_body = ( - ReadWriteLogRecord( - LogRecord( - timestamp=1644650427658989056, - observed_timestamp=1644650427658989057, - context=_CONTEXT_LOG, - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG, - body={"error": None, "array_with_nones": [1, None, 2]}, - attributes={"a": 1, "b": "c"}, - ), - resource=SDKResource({"second_resource": "CASE"}), - instrumentation_scope=None, - ) - ) - pb2_resource_logs = PB2ResourceLogs( - resource=PB2Resource( - attributes=[ - PB2KeyValue( - key="second_resource", - value=PB2AnyValue(string_value="CASE"), - ) - ] - ), - scope_logs=[ - PB2ScopeLogs( - scope=PB2InstrumentationScope(), - log_records=[ - PB2LogRecord( - time_unix_nano=1644650427658989056, - observed_time_unix_nano=1644650427658989057, - trace_id=_encode_trace_id( - 89564621134313219400156819398935297684 - ), - span_id=_encode_span_id(1312458408527513268), - flags=int(TraceFlags(0x01)), - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG.value, - body=_encode_value( - { - "error": None, - "array_with_nones": [1, None, 2], - } - ), - attributes=_encode_attributes({"a": 1, "b": "c"}), - ) - ], - ) - ], - ) - self.assertEqual( - encode_logs( - [log_record_with_no_instrumentation_scope_and_dict_body] - ), - ExportLogsServiceRequest(resource_logs=[pb2_resource_logs]), - ) - - def test_encode_log_record_with_empty_resource_and_dict_attribute_value( - self, - ): - log_record_with_empty_resource_and_dict_attribute_value = ReadWriteLogRecord( - LogRecord( - timestamp=1644650584292683033, - observed_timestamp=1644650584292683033, - context=_CONTEXT_LOG, - severity_text="FATAL", - severity_number=SeverityNumber.FATAL, - body="This instrumentation scope has a schema url and attributes", - attributes={ - "extended": { - "sequence": [{"inner": "mapping", "none": None}] - } - }, - ), - resource=SDKResource({}), - instrumentation_scope=InstrumentationScope( - "scope_with_attributes", - "scope_with_attributes_version", - "instrumentation_schema_url", - {"one": 1, "two": "2"}, - ), - ) - pb2_resource_logs = PB2ResourceLogs( - resource=PB2Resource(attributes=[]), - scope_logs=[ - PB2ScopeLogs( - scope=PB2InstrumentationScope( - name="scope_with_attributes", - version="scope_with_attributes_version", - attributes=_encode_attributes({"one": 1, "two": "2"}), - ), - log_records=[ - PB2LogRecord( - time_unix_nano=1644650584292683033, - observed_time_unix_nano=1644650584292683033, - trace_id=_encode_trace_id( - 89564621134313219400156819398935297684 - ), - span_id=_encode_span_id(1312458408527513268), - flags=int(TraceFlags(0x01)), - severity_text="FATAL", - severity_number=SeverityNumber.FATAL.value, - body=_encode_value( - "This instrumentation scope has a schema url and attributes" - ), - attributes=_encode_attributes( - { - "extended": { - "sequence": [ - {"inner": "mapping", "none": None} - ] - } - } - ), - ) - ], - schema_url="instrumentation_schema_url", - ) - ], - ) - self.assertEqual( - encode_logs( - [log_record_with_empty_resource_and_dict_attribute_value] - ), - ExportLogsServiceRequest(resource_logs=[pb2_resource_logs]), - ) - - def test_dropped_attributes_count(self): - sdk_logs = self._get_test_logs_dropped_attributes() - encoded_logs = encode_logs(sdk_logs) - self.assertTrue(hasattr(sdk_logs[0], "dropped_attributes")) - self.assertEqual( - # pylint:disable=no-member - encoded_logs.resource_logs[0] - .scope_logs[0] - .log_records[0] - .dropped_attributes_count, - 2, - ) - - @staticmethod - def _get_test_logs_dropped_attributes() -> list[ReadWriteLogRecord]: - ctx_log1 = set_span_in_context( - NonRecordingSpan( - SpanContext( - 89564621134313219400156819398935297684, - 1312458408527513268, - False, - TraceFlags(0x01), - ) - ) - ) - log1 = ReadWriteLogRecord( - LogRecord( - timestamp=1644650195189786880, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", - attributes={"a": 1, "b": "c", "user_id": "B121092"}, - ), - resource=SDKResource({"first_resource": "value"}), - limits=LogRecordLimits(max_attributes=1), - instrumentation_scope=InstrumentationScope( - "first_name", "first_version" - ), - ) - ctx_log2 = set_span_in_context( - NonRecordingSpan(SpanContext(0, 0, False)) - ) - log2 = ReadWriteLogRecord( - LogRecord( - timestamp=1644650249738562048, - context=ctx_log2, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", - attributes={}, - ), - resource=SDKResource({"second_resource": "CASE"}), - instrumentation_scope=InstrumentationScope( - "second_name", "second_version" - ), - ) - - return [log1, log2] diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_metrics_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_metrics_encoder.py deleted file mode 100644 index d3b3dc8c5cd..00000000000 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_metrics_encoder.py +++ /dev/null @@ -1,1090 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -# pylint: disable=protected-access,too-many-lines -import unittest - -from opentelemetry.exporter.otlp.proto.common._internal.metrics_encoder import ( - EncodingException, -) -from opentelemetry.exporter.otlp.proto.common.metrics_encoder import ( - encode_metrics, -) -from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( - ExportMetricsServiceRequest, -) -from opentelemetry.proto.common.v1.common_pb2 import ( - AnyValue, - InstrumentationScope, - KeyValue, -) -from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 -from opentelemetry.proto.resource.v1.resource_pb2 import ( - Resource as OTLPResource, -) -from opentelemetry.sdk.metrics import Exemplar -from opentelemetry.sdk.metrics.export import ( - AggregationTemporality, - Buckets, - ExponentialHistogramDataPoint, - HistogramDataPoint, - Metric, - MetricsData, - ResourceMetrics, - ScopeMetrics, -) -from opentelemetry.sdk.metrics.export import ( - ExponentialHistogram as ExponentialHistogramType, -) -from opentelemetry.sdk.metrics.export import Histogram as HistogramType -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.util.instrumentation import ( - InstrumentationScope as SDKInstrumentationScope, -) -from opentelemetry.test.metrictestutil import _generate_gauge, _generate_sum - - -class TestOTLPMetricsEncoder(unittest.TestCase): - span_id = int("6e0c63257de34c92", 16) - trace_id = int("d4cda95b652f4a1592b449d5929fda1b", 16) - - histogram = Metric( - name="histogram", - description="foo", - unit="s", - data=HistogramType( - data_points=[ - HistogramDataPoint( - attributes={"a": 1, "b": True}, - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - exemplars=[ - Exemplar( - {"filtered": "banana"}, - 298.0, - 1641946016139533400, - span_id, - trace_id, - ), - Exemplar( - {"filtered": "banana"}, - 298.0, - 1641946016139533400, - None, - None, - ), - ], - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - min=8, - max=18, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - - def test_encode_sum_int(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[_generate_sum("sum_int", 33)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="sum_int", - unit="s", - description="foo", - sum=pb2.Sum( - data_points=[ - pb2.NumberDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946015139533244, - time_unix_nano=1641946016139533244, - as_int=33, - ) - ], - aggregation_temporality=AggregationTemporality.CUMULATIVE, - is_monotonic=True, - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_sum_double(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[_generate_sum("sum_double", 2.98)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="sum_double", - unit="s", - description="foo", - sum=pb2.Sum( - data_points=[ - pb2.NumberDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946015139533244, - time_unix_nano=1641946016139533244, - as_double=2.98, - ) - ], - aggregation_temporality=AggregationTemporality.CUMULATIVE, - is_monotonic=True, - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_gauge_int(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[_generate_gauge("gauge_int", 9000)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="gauge_int", - unit="s", - description="foo", - gauge=pb2.Gauge( - data_points=[ - pb2.NumberDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - time_unix_nano=1641946016139533244, - start_time_unix_nano=0, - as_int=9000, - ) - ], - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_gauge_double(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[_generate_gauge("gauge_double", 52.028)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="gauge_double", - unit="s", - description="foo", - gauge=pb2.Gauge( - data_points=[ - pb2.NumberDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - time_unix_nano=1641946016139533244, - as_double=52.028, - ) - ], - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_histogram(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[self.histogram], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="histogram", - unit="s", - description="foo", - histogram=pb2.Histogram( - data_points=[ - pb2.HistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - exemplars=[ - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - span_id=b"n\x0cc%}\xe3L\x92", - trace_id=b"\xd4\xcd\xa9[e/J\x15\x92\xb4I\xd5\x92\x9f\xda\x1b", - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - ], - max=18.0, - min=8.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_multiple_scope_histogram(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[self.histogram, self.histogram], - schema_url="instrumentation_scope_schema_url", - ), - ScopeMetrics( - scope=SDKInstrumentationScope( - name="second_name", - version="second_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[self.histogram], - schema_url="instrumentation_scope_schema_url", - ), - ScopeMetrics( - scope=SDKInstrumentationScope( - name="third_name", - version="third_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[self.histogram], - schema_url="instrumentation_scope_schema_url", - ), - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="histogram", - unit="s", - description="foo", - histogram=pb2.Histogram( - data_points=[ - pb2.HistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - exemplars=[ - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - span_id=b"n\x0cc%}\xe3L\x92", - trace_id=b"\xd4\xcd\xa9[e/J\x15\x92\xb4I\xd5\x92\x9f\xda\x1b", - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - ], - max=18.0, - min=8.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ), - pb2.Metric( - name="histogram", - unit="s", - description="foo", - histogram=pb2.Histogram( - data_points=[ - pb2.HistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - exemplars=[ - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - span_id=b"n\x0cc%}\xe3L\x92", - trace_id=b"\xd4\xcd\xa9[e/J\x15\x92\xb4I\xd5\x92\x9f\xda\x1b", - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - ], - max=18.0, - min=8.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ), - ], - ), - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="second_name", version="second_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="histogram", - unit="s", - description="foo", - histogram=pb2.Histogram( - data_points=[ - pb2.HistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - exemplars=[ - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - span_id=b"n\x0cc%}\xe3L\x92", - trace_id=b"\xd4\xcd\xa9[e/J\x15\x92\xb4I\xd5\x92\x9f\xda\x1b", - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - ], - max=18.0, - min=8.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - ], - ), - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="third_name", version="third_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="histogram", - unit="s", - description="foo", - histogram=pb2.Histogram( - data_points=[ - pb2.HistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946016139533244, - time_unix_nano=1641946016139533244, - count=5, - sum=67, - bucket_counts=[1, 4], - explicit_bounds=[10.0, 20.0], - exemplars=[ - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - span_id=b"n\x0cc%}\xe3L\x92", - trace_id=b"\xd4\xcd\xa9[e/J\x15\x92\xb4I\xd5\x92\x9f\xda\x1b", - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - pb2.Exemplar( - time_unix_nano=1641946016139533400, - as_double=298, - filtered_attributes=[ - KeyValue( - key="filtered", - value=AnyValue( - string_value="banana" - ), - ) - ], - ), - ], - max=18.0, - min=8.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - ], - ), - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encode_exponential_histogram(self): - exponential_histogram = Metric( - name="exponential_histogram", - description="description", - unit="unit", - data=ExponentialHistogramType( - data_points=[ - ExponentialHistogramDataPoint( - attributes={"a": 1, "b": True}, - start_time_unix_nano=0, - time_unix_nano=1, - count=2, - sum=3, - scale=4, - zero_count=5, - positive=Buckets(offset=6, bucket_counts=[7, 8]), - negative=Buckets(offset=9, bucket_counts=[10, 11]), - flags=12, - min=13.0, - max=14.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={"a": 1, "b": False}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[exponential_histogram], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource( - attributes=[ - KeyValue(key="a", value=AnyValue(int_value=1)), - KeyValue( - key="b", value=AnyValue(bool_value=False) - ), - ] - ), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", version="first_version" - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="exponential_histogram", - unit="unit", - description="description", - exponential_histogram=pb2.ExponentialHistogram( - data_points=[ - pb2.ExponentialHistogramDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=0, - time_unix_nano=1, - count=2, - sum=3, - scale=4, - zero_count=5, - positive=pb2.ExponentialHistogramDataPoint.Buckets( - offset=6, - bucket_counts=[7, 8], - ), - negative=pb2.ExponentialHistogramDataPoint.Buckets( - offset=9, - bucket_counts=[10, 11], - ), - flags=12, - exemplars=[], - min=13.0, - max=14.0, - ) - ], - aggregation_temporality=AggregationTemporality.DELTA, - ), - ) - ], - ) - ], - ) - ] - ) - # pylint: disable=protected-access - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) - - def test_encoding_exception_reraise(self): - # this number is too big to fit in a signed 64-bit proto field and causes a ValueError - big_number = 2**63 - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes={}, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - ), - metrics=[_generate_sum("sum_double", big_number)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - with self.assertRaises(EncodingException) as context: - encode_metrics(metrics_data) - - # assert that the EncodingException wraps the metric and original exception - assert isinstance(context.exception.metric, Metric) - assert isinstance(context.exception.original_exception, ValueError) - - def test_encode_scope_with_attributes(self): - metrics_data = MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=Resource( - attributes=None, - schema_url="resource_schema_url", - ), - scope_metrics=[ - ScopeMetrics( - scope=SDKInstrumentationScope( - name="first_name", - version="first_version", - schema_url="instrumentation_scope_schema_url", - attributes={"one": 1, "two": "2"}, - ), - metrics=[_generate_sum("sum_int", 88)], - schema_url="instrumentation_scope_schema_url", - ) - ], - schema_url="resource_schema_url", - ) - ] - ) - expected = ExportMetricsServiceRequest( - resource_metrics=[ - pb2.ResourceMetrics( - schema_url="resource_schema_url", - resource=OTLPResource(), - scope_metrics=[ - pb2.ScopeMetrics( - scope=InstrumentationScope( - name="first_name", - version="first_version", - attributes=[ - KeyValue( - key="one", value=AnyValue(int_value=1) - ), - KeyValue( - key="two", - value=AnyValue(string_value="2"), - ), - ], - ), - schema_url="instrumentation_scope_schema_url", - metrics=[ - pb2.Metric( - name="sum_int", - unit="s", - description="foo", - sum=pb2.Sum( - data_points=[ - pb2.NumberDataPoint( - attributes=[ - KeyValue( - key="a", - value=AnyValue( - int_value=1 - ), - ), - KeyValue( - key="b", - value=AnyValue( - bool_value=True - ), - ), - ], - start_time_unix_nano=1641946015139533244, - time_unix_nano=1641946016139533244, - as_int=88, - ) - ], - aggregation_temporality=AggregationTemporality.CUMULATIVE, - is_monotonic=True, - ), - ) - ], - ) - ], - ) - ] - ) - actual = encode_metrics(metrics_data) - self.assertEqual(expected, actual) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py deleted file mode 100644 index 0c685948b75..00000000000 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_trace_encoder.py +++ /dev/null @@ -1,491 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -# pylint: disable=protected-access - -import unittest - -from opentelemetry.exporter.otlp.proto.common._internal import ( - _encode_span_id, - _encode_trace_id, -) -from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import ( - _SPAN_KIND_MAP, - _encode_status, -) -from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans -from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( - ExportTraceServiceRequest as PB2ExportTraceServiceRequest, -) -from opentelemetry.proto.common.v1.common_pb2 import AnyValue as PB2AnyValue -from opentelemetry.proto.common.v1.common_pb2 import ( - InstrumentationScope as PB2InstrumentationScope, -) -from opentelemetry.proto.common.v1.common_pb2 import KeyValue as PB2KeyValue -from opentelemetry.proto.resource.v1.resource_pb2 import ( - Resource as PB2Resource, -) -from opentelemetry.proto.trace.v1.trace_pb2 import ( - ResourceSpans as PB2ResourceSpans, -) -from opentelemetry.proto.trace.v1.trace_pb2 import ScopeSpans as PB2ScopeSpans -from opentelemetry.proto.trace.v1.trace_pb2 import Span as PB2SPan -from opentelemetry.proto.trace.v1.trace_pb2 import Status as PB2Status -from opentelemetry.sdk.trace import Event as SDKEvent -from opentelemetry.sdk.trace import Resource as SDKResource -from opentelemetry.sdk.trace import SpanContext as SDKSpanContext -from opentelemetry.sdk.trace import _Span as SDKSpan -from opentelemetry.sdk.util.instrumentation import ( - InstrumentationScope as SDKInstrumentationScope, -) -from opentelemetry.trace import Link as SDKLink -from opentelemetry.trace import SpanKind as SDKSpanKind -from opentelemetry.trace import TraceFlags as SDKTraceFlags -from opentelemetry.trace.status import Status as SDKStatus -from opentelemetry.trace.status import StatusCode as SDKStatusCode - - -class TestOTLPTraceEncoder(unittest.TestCase): - def test_encode_spans(self): - otel_spans, expected_encoding = self.get_exhaustive_test_spans() - self.assertEqual(encode_spans(otel_spans), expected_encoding) - - @staticmethod - def get_exhaustive_otel_span_list() -> list[SDKSpan]: - trace_id = 0x3E0C63257DE34C926F9EFCD03927272E - - base_time = 683647322 * 10**9 # in ns - start_times = ( - base_time, - base_time + 150 * 10**6, - base_time + 300 * 10**6, - base_time + 400 * 10**6, - base_time + 500 * 10**6, - base_time + 600 * 10**6, - ) - end_times = ( - start_times[0] + (50 * 10**6), - start_times[1] + (100 * 10**6), - start_times[2] + (200 * 10**6), - start_times[3] + (300 * 10**6), - start_times[4] + (400 * 10**6), - start_times[5] + (500 * 10**6), - ) - - parent_span_context = SDKSpanContext( - trace_id, 0x1111111111111111, is_remote=True - ) - - other_context = SDKSpanContext( - trace_id, 0x2222222222222222, is_remote=False - ) - - span1 = SDKSpan( - name="test-span-1", - context=SDKSpanContext( - trace_id, - 0x34BF92DEEFC58C92, - is_remote=False, - trace_flags=SDKTraceFlags(SDKTraceFlags.SAMPLED), - ), - parent=parent_span_context, - events=( - SDKEvent( - name="event0", - timestamp=base_time + 50 * 10**6, - attributes={ - "annotation_bool": True, - "annotation_string": "annotation_test", - "key_float": 0.3, - }, - ), - ), - links=( - SDKLink(context=other_context, attributes={"key_bool": True}), - ), - resource=SDKResource({}, "resource_schema_url"), - ) - span1.start(start_time=start_times[0]) - span1.set_attribute("key_bool", False) - span1.set_attribute("key_string", "hello_world") - span1.set_attribute("key_float", 111.22) - span1.set_status(SDKStatus(SDKStatusCode.ERROR, "Example description")) - span1.end(end_time=end_times[0]) - - span2 = SDKSpan( - name="test-span-2", - context=parent_span_context, - parent=None, - resource=SDKResource(attributes={"key_resource": "some_resource"}), - ) - span2.start(start_time=start_times[1]) - span2.end(end_time=end_times[1]) - - span3 = SDKSpan( - name="test-span-3", - context=other_context, - parent=None, - resource=SDKResource(attributes={"key_resource": "some_resource"}), - ) - span3.start(start_time=start_times[2]) - span3.set_attribute("key_string", "hello_world") - span3.end(end_time=end_times[2]) - - span4 = SDKSpan( - name="test-span-4", - context=other_context, - parent=None, - resource=SDKResource({}, "resource_schema_url"), - instrumentation_scope=SDKInstrumentationScope( - name="name", version="version" - ), - ) - span4.start(start_time=start_times[3]) - span4.end(end_time=end_times[3]) - - span5 = SDKSpan( - name="test-span-5", - context=other_context, - parent=None, - resource=SDKResource( - attributes={"key_resource": "another_resource"}, - schema_url="resource_schema_url", - ), - instrumentation_scope=SDKInstrumentationScope( - name="scope_1_name", - version="scope_1_version", - schema_url="scope_1_schema_url", - ), - ) - span5.start(start_time=start_times[4]) - span5.end(end_time=end_times[4]) - - span6 = SDKSpan( - name="test-span-6", - context=other_context, - parent=None, - resource=SDKResource( - attributes={"key_resource": "another_resource"}, - schema_url="resource_schema_url", - ), - instrumentation_scope=SDKInstrumentationScope( - name="scope_2_name", - version="scope_2_version", - schema_url="scope_2_schema_url", - attributes={"one": "1", "two": 2}, - ), - ) - span6.start(start_time=start_times[5]) - span6.end(end_time=end_times[5]) - - return [span1, span2, span3, span4, span5, span6] - - def get_exhaustive_test_spans( - self, - ) -> tuple[list[SDKSpan], PB2ExportTraceServiceRequest]: - otel_spans = self.get_exhaustive_otel_span_list() - trace_id = _encode_trace_id(otel_spans[0].context.trace_id) - span_kind = _SPAN_KIND_MAP[SDKSpanKind.INTERNAL] - - pb2_service_request = PB2ExportTraceServiceRequest( - resource_spans=[ - PB2ResourceSpans( - schema_url="resource_schema_url", - resource=PB2Resource(), - scope_spans=[ - PB2ScopeSpans( - scope=PB2InstrumentationScope(), - spans=[ - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[0].context.span_id - ), - trace_state=None, - parent_span_id=_encode_span_id( - otel_spans[0].parent.span_id - ), - name=otel_spans[0].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 0 - ].start_time, - end_time_unix_nano=otel_spans[0].end_time, - attributes=[ - PB2KeyValue( - key="key_bool", - value=PB2AnyValue( - bool_value=False - ), - ), - PB2KeyValue( - key="key_string", - value=PB2AnyValue( - string_value="hello_world" - ), - ), - PB2KeyValue( - key="key_float", - value=PB2AnyValue( - double_value=111.22 - ), - ), - ], - events=[ - PB2SPan.Event( - name="event0", - time_unix_nano=otel_spans[0] - .events[0] - .timestamp, - attributes=[ - PB2KeyValue( - key="annotation_bool", - value=PB2AnyValue( - bool_value=True - ), - ), - PB2KeyValue( - key="annotation_string", - value=PB2AnyValue( - string_value="annotation_test" - ), - ), - PB2KeyValue( - key="key_float", - value=PB2AnyValue( - double_value=0.3 - ), - ), - ], - ) - ], - links=[ - PB2SPan.Link( - trace_id=_encode_trace_id( - otel_spans[0] - .links[0] - .context.trace_id - ), - span_id=_encode_span_id( - otel_spans[0] - .links[0] - .context.span_id - ), - attributes=[ - PB2KeyValue( - key="key_bool", - value=PB2AnyValue( - bool_value=True - ), - ), - ], - flags=0x100, - ) - ], - status=PB2Status( - code=SDKStatusCode.ERROR.value, - message="Example description", - ), - flags=0x300, - ) - ], - ), - PB2ScopeSpans( - scope=PB2InstrumentationScope( - name="name", - version="version", - ), - spans=[ - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[3].context.span_id - ), - trace_state=None, - parent_span_id=None, - name=otel_spans[3].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 3 - ].start_time, - end_time_unix_nano=otel_spans[3].end_time, - attributes=None, - events=None, - links=None, - status={}, - flags=0x100, - ) - ], - ), - ], - ), - PB2ResourceSpans( - resource=PB2Resource( - attributes=[ - PB2KeyValue( - key="key_resource", - value=PB2AnyValue( - string_value="some_resource" - ), - ) - ] - ), - scope_spans=[ - PB2ScopeSpans( - scope=PB2InstrumentationScope(), - spans=[ - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[1].context.span_id - ), - trace_state=None, - parent_span_id=None, - name=otel_spans[1].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 1 - ].start_time, - end_time_unix_nano=otel_spans[1].end_time, - attributes=None, - events=None, - links=None, - status={}, - flags=0x100, - ), - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[2].context.span_id - ), - trace_state=None, - parent_span_id=None, - name=otel_spans[2].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 2 - ].start_time, - end_time_unix_nano=otel_spans[2].end_time, - attributes=[ - PB2KeyValue( - key="key_string", - value=PB2AnyValue( - string_value="hello_world" - ), - ), - ], - events=None, - links=None, - status={}, - flags=0x100, - ), - ], - ) - ], - ), - PB2ResourceSpans( - resource=PB2Resource( - attributes=[ - PB2KeyValue( - key="key_resource", - value=PB2AnyValue( - string_value="another_resource" - ), - ), - ], - ), - schema_url="resource_schema_url", - scope_spans=[ - PB2ScopeSpans( - scope=PB2InstrumentationScope( - name="scope_1_name", version="scope_1_version" - ), - schema_url="scope_1_schema_url", - spans=[ - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[4].context.span_id - ), - trace_state=None, - parent_span_id=None, - name=otel_spans[4].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 4 - ].start_time, - end_time_unix_nano=otel_spans[4].end_time, - attributes=None, - events=None, - links=None, - status={}, - flags=0x100, - ), - ], - ), - PB2ScopeSpans( - scope=PB2InstrumentationScope( - name="scope_2_name", - version="scope_2_version", - attributes=[ - PB2KeyValue( - key="one", - value=PB2AnyValue(string_value="1"), - ), - PB2KeyValue( - key="two", - value=PB2AnyValue(int_value=2), - ), - ], - ), - schema_url="scope_2_schema_url", - spans=[ - PB2SPan( - trace_id=trace_id, - span_id=_encode_span_id( - otel_spans[5].context.span_id - ), - trace_state=None, - parent_span_id=None, - name=otel_spans[5].name, - kind=span_kind, - start_time_unix_nano=otel_spans[ - 5 - ].start_time, - end_time_unix_nano=otel_spans[5].end_time, - attributes=None, - events=None, - links=None, - status={}, - flags=0x100, - ), - ], - ), - ], - ), - ] - ) - - return otel_spans, pb2_service_request - - def test_encode_status_code_translations(self): - self.assertEqual( - _encode_status(SDKStatus(status_code=SDKStatusCode.UNSET)), - PB2Status( - code=SDKStatusCode.UNSET.value, - ), - ) - - self.assertEqual( - _encode_status(SDKStatus(status_code=SDKStatusCode.OK)), - PB2Status( - code=SDKStatusCode.OK.value, - ), - ) - - self.assertEqual( - _encode_status(SDKStatus(status_code=SDKStatusCode.ERROR)), - PB2Status( - code=SDKStatusCode.ERROR.value, - ), - ) diff --git a/opentelemetry-proto/pyproject.toml b/opentelemetry-proto/pyproject.toml index 645550627bb..a6482395b96 100644 --- a/opentelemetry-proto/pyproject.toml +++ b/opentelemetry-proto/pyproject.toml @@ -25,7 +25,6 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "protobuf>=5.0, < 8.0", ] [project.urls] @@ -33,7 +32,7 @@ Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/ope Repository = "https://github.com/open-telemetry/opentelemetry-python" [tool.hatch.version] -path = "src/opentelemetry/proto/version/__init__.py" +path = "src/opentelemetry/_proto/version/__init__.py" [tool.hatch.build.targets.sdist] include = [ diff --git a/opentelemetry-proto/src/opentelemetry/proto/py.typed b/opentelemetry-proto/src/opentelemetry/_proto/__init__.py similarity index 100% rename from opentelemetry-proto/src/opentelemetry/proto/py.typed rename to opentelemetry-proto/src/opentelemetry/_proto/__init__.py diff --git a/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/__init__.py new file mode 100644 index 00000000000..9f186998550 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/__init__.py @@ -0,0 +1,39 @@ +from .enum import encode_enum +from .scalars import ( + encode_bool, + encode_bytes, + encode_double, + encode_fixed32, + encode_fixed64, + encode_float, + encode_int, + encode_sfixed32, + encode_sfixed64, + encode_sint32, + encode_sint64, + encode_string, + encode_uint32, + encode_uint64, +) +from .tag import encode_tag +from .varint import encode_varint + +__all__ = [ + "encode_bool", + "encode_bytes", + "encode_double", + "encode_enum", + "encode_fixed32", + "encode_fixed64", + "encode_float", + "encode_int", + "encode_sfixed32", + "encode_sfixed64", + "encode_sint32", + "encode_sint64", + "encode_string", + "encode_tag", + "encode_uint32", + "encode_uint64", + "encode_varint", +] diff --git a/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/enum.py b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/enum.py new file mode 100644 index 00000000000..229b44787b5 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/enum.py @@ -0,0 +1,53 @@ +"""Encoder for protobuf enum field values. + +Protobuf enum fields use wire type 0 (varint) and share the exact same wire +encoding as int32. This module exists as its own file — separate from +_scalars.py — because an enum is not a scalar type. Enum fields carry named +constants, not raw numeric values. The distinction matters at the .proto +language level even though the wire encoding is identical to int32. + +Reference: + https://protobuf.dev/programming-guides/encoding/ +""" + +from .scalars import encode_int + + +def encode_enum(value: int) -> bytes: + """Encode a protobuf enum value. + + Protobuf enum fields use wire type 0 (varint) and share the exact same + wire encoding as int32. The encoding guide states: + + Enum values are always 32-bit integers and the encoding follows the + same rules as int32. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + + Enum values in practice + ----------------------- + In proto3, enum values defined in a .proto file are non-negative named + constants (0, 1, 2, …). The first value of any proto3 enum must be 0. + + In proto2, negative enum values are legal. Negative enum values can also + appear in proto3 messages received from a proto2 sender: the decoder + preserves them as their raw integer value. In both cases the int32 wire + encoding applies, meaning negative enum values produce a 10-byte varint + (via 64-bit sign extension, exactly as encode_int does for negative ints). + + This function is a thin wrapper around encode_int. Its purpose is + readability at call sites: code that encodes an enum field can call + encode_enum to make the intent explicit, rather than encode_int, which + suggests an arbitrary integer. + """ + # Delegate entirely to encode_int, which implements the int32 wire rule: + # + # - Non-negative values → encode_varint directly (compact, 1–5 bytes). + # - Negative values → mask to 64-bit unsigned two's complement, then + # encode_varint (always 10 bytes). + # + # No additional logic is needed: the spec defines enum encoding as + # identical to int32, so encode_int is the correct and complete + # implementation of both. + return encode_int(value) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/fields.py b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/fields.py new file mode 100644 index 00000000000..4b278a50670 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/fields.py @@ -0,0 +1,542 @@ +"""Proto3 field-level encoding helpers for SerializeToString() implementations. + +Why this module exists +---------------------- +The other modules in this package (varint, tag, scalars, enum) provide the +raw encoding kernel: functions that take a Python value and return its bytes +representation in the protobuf wire format. Those functions know nothing about +proto3 messages. They do not know about field numbers, wire-type tags, or the +rule that says a field whose value equals the proto3 default must be omitted +from the serialised output. + +This module sits one level above that kernel. It provides one helper per +proto3 scalar category (uint64, sint32, double, string, bytes, bool, fixed32, +fixed64) plus helpers for embedded messages and packed repeated fields. Each +helper combines three operations that every SerializeToString() method must +perform for every field it writes: + + 1. Apply the proto3 default-omission rule — if the value equals the type's + default (0, 0.0, False, b"", ""), return b"" immediately. + 2. Encode the tag — (field_number << 3) | wire_type, then varint-encoded. + 3. Encode the value — using the appropriate primitive from this package. + +Without these helpers, every SerializeToString() method would repeat that +three-step pattern inline for every field, cluttering the message classes with +low-level wire-format details and making the default-omission logic easy to get +wrong or forget. + +Why it is named "fields" +------------------------ +Each function in this module encodes one proto3 field: it takes a field number +and a value, and returns the complete on-wire bytes for that field — tag plus +encoded value, or b"" when the value is the proto3 default. The word "field" +is the right abstraction because a proto3 field is exactly this: a field number, +a wire type, a default-omission rule, and a value encoding. The other modules +in this package encode raw values; this module encodes fields. + +Layering within _pyprotobuf +---------------------------- + varint.py — encode a varint integer to bytes + tag.py — encode a (field_number, wire_type) tag using varint + scalars.py — encode all proto3 scalar types to bytes + enum.py — encode a proto3 enum value (thin wrapper over scalars) + fields.py — encode a complete proto3 field (tag + default check + value) + +The message classes (common_pb2.py, metrics_pb2.py, etc.) use this +module directly. They import the encode_* primitives from the package root +(__init__.py) and the field helpers from here. + +Wire-type constants +------------------- +The protobuf specification defines six wire types. The wire type is stored in +the three lowest bits of every tag integer. Only four wire types appear in +proto3 messages (wire types 3 and 4, the deprecated group delimiters, do not +appear in new proto3 code): + + WT_VARINT = 0 — one or more bytes with a continuation bit in the MSB of + each byte. Used for: int32, int64, uint32, uint64, sint32, + sint64, bool, enum. + WT_64BIT = 1 — exactly 8 bytes, little-endian. Used for: double, + fixed64, sfixed64. + WT_LEN = 2 — a varint length prefix followed by that many bytes. Used + for: string, bytes, embedded messages, packed repeated + fields. + WT_32BIT = 5 — exactly 4 bytes, little-endian. Used for: float, fixed32, + sfixed32. + +Reference: https://protobuf.dev/programming-guides/encoding/ +""" + +from __future__ import annotations + +from struct import pack + +from .scalars import encode_fixed32, encode_fixed64, encode_sint32 +from .tag import encode_tag +from .varint import encode_varint + +WT_VARINT = 0 # int32, int64, uint32, uint64, bool, enum +WT_64BIT = 1 # double, fixed64, sfixed64 +WT_LEN = 2 # string, bytes, embedded messages, packed arrays +WT_32BIT = 5 # float, fixed32, sfixed32 + + +def msg(field: int, content: bytes) -> bytes: + """Encode an embedded message or group as a length-delimited field. + + In the protobuf wire format, an embedded message is a length-delimited + field (wire type 2). The layout on the wire is: + + tag (varint) | byte_length (varint) | content (bytes) + + The tag encodes the field number and wire type 2. The byte_length is the + number of bytes in the already-serialised sub-message. The content is the + verbatim output of the sub-message's own SerializeToString() call. + + Unlike the scalar helpers, msg does not apply an omission rule for empty + content. An embedded message with no fields set serialises to b"" (zero + bytes), and the caller decides whether to write it at all. In practice, + message fields are either written unconditionally when present or guarded + by an explicit ``if self.field is not None`` check in the caller; the empty + check is the caller's responsibility, not this helper's. + + This helper is also used for oneof sub-messages, repeated message fields + (called once per element), and any other length-delimited payload. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + content: + The serialised bytes of the sub-message (the return value of + sub_message.SerializeToString()). + + Returns + ------- + bytes + tag + varint(len(content)) + content + """ + return encode_tag(field, WT_LEN) + encode_varint(len(content)) + content + + +def string(field: int, value: str) -> bytes: + """Encode a proto3 string field, omitting it when the value is empty. + + Proto3 defines the default value for string fields as the empty string + "". A field equal to its default must not be written to the wire, so this + helper returns b"" when value is "". + + The encoding on the wire is: + + tag (varint) | byte_length (varint) | utf-8 bytes + + The proto3 specification requires that string fields contain valid UTF-8. + Python's str.encode("utf-8") enforces this at the encoding step. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + The string to encode. An empty string causes this helper to return + b"" (field omitted). + + Returns + ------- + bytes + b"" if value is empty, otherwise tag + varint(len(utf8)) + utf8. + """ + if not value: + return b"" + utf8 = value.encode("utf-8") + return encode_tag(field, WT_LEN) + encode_varint(len(utf8)) + utf8 + + +def byt(field: int, value: bytes) -> bytes: + """Encode a proto3 bytes field, omitting it when the value is empty. + + Proto3 defines the default value for bytes fields as the empty byte + string b"". A field equal to its default must not be written, so this + helper returns b"" when value is b"". + + The encoding on the wire is identical to a string field: + + tag (varint) | byte_length (varint) | raw bytes + + Unlike string, no UTF-8 encoding step is needed because the caller already + holds raw bytes. This helper is used for span_id, trace_id, and any other + proto3 bytes field. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + The bytes to encode. An empty bytes object causes this helper to + return b"" (field omitted). + + Returns + ------- + bytes + b"" if value is empty, otherwise tag + varint(len(value)) + value. + """ + if not value: + return b"" + return encode_tag(field, WT_LEN) + encode_varint(len(value)) + value + + +def u64(field: int, value: int) -> bytes: + """Encode a proto3 uint64 (or any varint-encoded integer) field. + + This helper covers all proto3 field types whose wire type is WT_VARINT + and whose value is a non-negative integer: uint32, uint64, int32 (when + non-negative), int64 (when non-negative), bool (encoded as 0 or 1), and + enum (encoded as its integer value). + + Proto3 defines the default value for these types as 0. A zero value must + not be written to the wire. + + The encoding on the wire is: + + tag (varint) | value (varint) + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + A non-negative integer. Zero causes this helper to return b"" + (field omitted). + + Returns + ------- + bytes + b"" if value is 0, otherwise tag + varint(value). + """ + if value == 0: + return b"" + return encode_tag(field, WT_VARINT) + encode_varint(value) + + +def bool_field(field: int, value: bool) -> bytes: + """Encode a proto3 bool field, omitting it when False. + + Proto3 defines the default value for bool as False (encoded as 0). A + False value must not be written to the wire. + + A True value is encoded as varint 1. The wire layout is: + + tag (varint) | 0x01 + + This helper is separate from u64 because booleans carry different + semantic meaning even though they share wire type 0 (varint). Keeping + them distinct makes SerializeToString() implementations easier to read. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + The boolean to encode. False causes this helper to return b"" + (field omitted). True is encoded as varint 1. + + Returns + ------- + bytes + b"" if value is False, otherwise tag + 0x01. + """ + if not value: + return b"" + return encode_tag(field, WT_VARINT) + encode_varint(1) + + +def fix32(field: int, value: int) -> bytes: + """Encode a proto3 fixed32 field (4-byte little-endian uint32). + + fixed32 uses wire type WT_32BIT. The decoder reads exactly 4 bytes after + the tag. This makes fixed32 more efficient than uint32 for values that are + frequently large (close to 2^32), because it avoids varint overhead. + + Proto3 default is 0. A zero value must not be written to the wire. + + The encoding on the wire is: + + tag (varint) | 4-byte little-endian uint32 + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + An unsigned 32-bit integer. Zero causes this helper to return b"" + (field omitted). + + Returns + ------- + bytes + b"" if value is 0, otherwise tag + 4 bytes little-endian. + """ + if value == 0: + return b"" + return encode_tag(field, WT_32BIT) + encode_fixed32(value) + + +def fix64(field: int, value: int) -> bytes: + """Encode a proto3 fixed64 field (8-byte little-endian uint64). + + fixed64 uses wire type WT_64BIT. The decoder reads exactly 8 bytes after + the tag. In the OTel proto schemas, fixed64 is used for nanosecond + timestamps (start_time_unix_nano, time_unix_nano), counts, and bucket + counts that are guaranteed to be non-negative. + + Proto3 default is 0. A zero value must not be written to the wire. + + The encoding on the wire is: + + tag (varint) | 8-byte little-endian uint64 + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + An unsigned 64-bit integer. Zero causes this helper to return b"" + (field omitted). + + Returns + ------- + bytes + b"" if value is 0, otherwise tag + 8 bytes little-endian. + """ + if value == 0: + return b"" + return encode_tag(field, WT_64BIT) + encode_fixed64(value) + + +def dbl(field: int, value: float) -> bytes: + """Encode a proto3 double field (8-byte IEEE 754), omitting when zero. + + double uses wire type WT_64BIT. The decoder reads exactly 8 bytes and + interprets them as an IEEE 754 double-precision floating-point number in + little-endian byte order. + + Proto3 default is 0.0. A zero value must not be written to the wire. + Note that -0.0 compares equal to 0.0 in Python, so dbl(field, -0.0) + returns b"" — if that distinction matters, use opt_dbl instead. + + This helper is used for scalar double fields that have a meaningful zero + default and should be omitted when zero: for example, the `sum` field of + SummaryDataPoint and the `zero_threshold` field of + ExponentialHistogramDataPoint. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + An IEEE 754 double. 0.0 causes this helper to return b"" + (field omitted). Infinity and NaN are encoded as-is. + + Returns + ------- + bytes + b"" if value == 0.0, otherwise tag + 8-byte little-endian double. + """ + if value == 0.0: + return b"" + return encode_tag(field, WT_64BIT) + pack(" bytes: + """Encode an optional proto3 double field, written even when 0.0. + + Some double fields in the OTel proto schemas are declared optional + (proto3 optional syntax), which means the presence of the field is + significant regardless of its value. A value of 0.0 is a meaningful + measurement and must be written; only None (field not set) causes the + field to be omitted. + + This is distinct from dbl, which omits 0.0 as the proto3 default. + opt_dbl is used for fields such as `sum`, `min`, and `max` on histogram + data points, where None means "not present" and 0.0 means "present and + measured as zero". + + The encoding on the wire is identical to dbl when the value is present: + + tag (varint) | 8-byte little-endian double + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + An IEEE 754 double, or None. None causes this helper to return b"" + (field omitted). Any float, including 0.0, -0.0, inf, and nan, is + encoded and written to the wire. + + Returns + ------- + bytes + b"" if value is None, otherwise tag + 8-byte little-endian double. + """ + if value is None: + return b"" + return encode_tag(field, WT_64BIT) + pack(" bytes: + """Encode a proto3 sint32 field (ZigZag varint), omitting when zero. + + sint32 uses wire type WT_VARINT but applies ZigZag encoding before the + varint step. ZigZag maps signed integers to unsigned integers so that + small negative values produce short varints rather than the ten-byte + varints that two's-complement representation would require: + + 0 → 0 + -1 → 1 + 1 → 2 + -2 → 3 + n → 2*n (for n >= 0) + n → -2*n - 1 (for n < 0) + + This is the correct encoding for the proto3 `sint32` type. It is NOT used + for `int32` fields (use u64 for non-negative int32, or encode_int from + the package for signed int32). + + In the OTel proto schemas, sint32 appears on the `scale` field of + ExponentialHistogramDataPoint and the `offset` field of its Buckets + sub-message. These fields represent signed exponents and can be negative. + + Proto3 default is 0. A zero value must not be written to the wire. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + value: + A signed 32-bit integer. Zero causes this helper to return b"" + (field omitted). + + Returns + ------- + bytes + b"" if value is 0, otherwise tag + ZigZag-encoded varint. + """ + if value == 0: + return b"" + return encode_tag(field, WT_VARINT) + encode_sint32(value) + + +def packed_uint64(field: int, values: list[int]) -> bytes: + """Encode a packed repeated uint64 field. + + In proto3, repeated scalar fields are packed by default. "Packed" means + all elements are encoded contiguously without a tag before each one. + Instead, a single tag and a single length prefix wrap the entire payload: + + tag (varint) | payload_length (varint) | element0 (varint) | element1 (varint) | ... + + Each element is encoded as an independent varint, exactly as encode_varint + would encode it if the field were scalar. The decoder knows how many + elements are present by tracking how many bytes it has consumed relative + to payload_length. + + An empty list produces b"" (field omitted). This matches proto3 behaviour: + a repeated field with zero elements is indistinguishable from the field + being absent. + + This helper is used for fields such as bucket_counts in + ExponentialHistogramDataPoint.Buckets. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + values: + A list of non-negative integers. An empty list causes this helper + to return b"" (field omitted). + + Returns + ------- + bytes + b"" if values is empty, otherwise tag + varint(len(payload)) + payload, + where payload is the concatenation of varint-encoded elements. + """ + if not values: + return b"" + payload = b"".join(encode_varint(v) for v in values) + return encode_tag(field, WT_LEN) + encode_varint(len(payload)) + payload + + +def packed_fix64(field: int, values: list[int]) -> bytes: + """Encode a packed repeated fixed64 field. + + Like packed_uint64 but each element is encoded as an 8-byte + little-endian uint64 rather than a varint. The wire layout is: + + tag (varint) | payload_length (varint) | element0 (8 bytes) | element1 (8 bytes) | ... + + fixed64 encoding is efficient for values that are close to 2^64 or are + accessed in bulk (the payload is a flat array of 8-byte little-endian + words, amenable to memcpy). + + An empty list produces b"" (field omitted). + + This helper is used for the bucket_counts field of HistogramDataPoint, + where counts are non-negative 64-bit integers. + + Parameters + ---------- + field: + The field number as declared in the .proto schema. + values: + A list of non-negative 64-bit integers. An empty list causes this + helper to return b"" (field omitted). + + Returns + ------- + bytes + b"" if values is empty, otherwise tag + varint(len(payload)) + payload, + where payload is the concatenation of 8-byte little-endian encodings. + """ + if not values: + return b"" + payload = b"".join(encode_fixed64(v) for v in values) + return encode_tag(field, WT_LEN) + encode_varint(len(payload)) + payload + + +def packed_double(field: int, values: list[float]) -> bytes: + """Encode a packed repeated double field. + + Like packed_uint64 but each element is an IEEE 754 double (8 bytes, + little-endian). The wire layout is: + + tag (varint) | payload_length (varint) | element0 (8 bytes) | element1 (8 bytes) | ... + + struct.pack with format " bytes: + """Encode an unsigned 32-bit integer as a protobuf uint32 field value. + + Protobuf uint32 fields use wire type 0 (varint). The value is encoded + as a plain unsigned varint with no transformation applied. + + Valid range: [0, 2^32 - 1]. + + Unlike int32, uint32 has no signed interpretation, so negative inputs + are not defined. Values in [0, 127] encode to a single byte; larger + values require more bytes as their magnitude grows. + + Example — encoding 0: + + encode_varint(0) == b'\\x00' + + Example — encoding 300: + + 300 = 0b1_0010_1100 + Split into 7-bit groups (little-endian): 0b010_1100, 0b000_0010 + Add continuation bit to first group: 0b1_010_1100 == 0xAC + Second group (no continuation): 0b000_0010 == 0x02 + Result: b'\\xac\\x02' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # uint32 encoding is identical to plain varint encoding — no sign extension, + # no ZigZag, no transformation. encode_varint raises ValueError for negative + # inputs, which correctly rejects values outside the uint32 domain. + return encode_varint(value) + + +def encode_uint64(value: int) -> bytes: + """Encode an unsigned 64-bit integer as a protobuf uint64 field value. + + Protobuf uint64 fields use wire type 0 (varint). The value is encoded + as a plain unsigned varint with no transformation applied. + + Valid range: [0, 2^64 - 1]. + + This is the 64-bit counterpart of encode_uint32. The encoding is + identical — both delegate to encode_varint — but the domain is larger. + Values up to 2^64 - 1 require at most 10 varint bytes. + + Example — encoding 2^32 (first value that exceeds the uint32 domain): + + 2^32 = 4294967296 = 0x1_0000_0000 + Varint encoding requires 5 bytes: + b'\\x80\\x80\\x80\\x80\\x10' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # Identical to encode_uint32 at the implementation level. The separate + # function exists so call sites can express the .proto field type precisely. + return encode_varint(value) + + +def encode_bool(value: bool) -> bytes: + """Encode a Python bool as a protobuf bool field value. + + Protobuf bool fields use wire type 0 (varint). The encoding guide defines + exactly two valid encodings: + + False -> varint 0 -> 0x00 (one byte) + True -> varint 1 -> 0x01 (one byte) + + The spec states that values other than 0 or 1 are not valid on the wire + for a bool field. This function enforces that constraint by mapping any + truthy value to 1 and any falsy value to 0, regardless of the input's + actual numeric value. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # The conditional expression evaluates value in a boolean context. + # + # Using (1 if value else 0) instead of int(value) is intentional. + # int(True) == 1 and int(False) == 0, which would work correctly for + # actual bool inputs. However, int(5) == 5, and varint 5 is not a valid + # protobuf bool encoding. The conditional collapses any truthy integer, + # non-empty string, non-empty list, etc. to exactly 1, and any falsy + # value to exactly 0, maintaining spec compliance regardless of what + # the caller passes. + # + # Both 0 and 1 fit in a single varint byte (no continuation bit needed), + # so encode_varint always returns exactly one byte here: + # + # encode_bool(False) -> b'\x00' + # encode_bool(True) -> b'\x01' + return encode_varint(1 if value else 0) + + +def encode_int(value: int) -> bytes: + """Encode a signed integer as a protobuf varint for int32 and int64 fields. + + Protobuf int32 and int64 fields both use wire type 0 (varint). The two + types share the same wire encoding — int32 values are sign-extended to + 64 bits before encoding, so they produce identical byte sequences to the + equivalent int64 value. + + Non-negative values pass straight through to encode_varint unchanged. + + Negative values are handled by a rule from the encoding guide: + + If you use int32 or int64 as the type for a negative number, the + resulting varint is always ten bytes long — it is, effectively, + treated like a very large unsigned integer. + + The rule comes from how the spec defines the encoding: a negative int32 or + int64 is sign-extended to a full 64-bit two's complement value before being + treated as an unsigned integer for varint encoding. A 64-bit value with + all its high bits set always requires 10 varint bytes regardless of the + original magnitude. + + Concrete example — encoding -1: + + -1 in two's complement 64-bit: + + 0xFFFFFFFFFFFFFFFF (all 64 bits set to 1) + + That unsigned 64-bit integer requires all 10 varint bytes: + + 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x01 + + Each of the first nine bytes has its continuation bit set (0xFF = + 1111_1111: 7 payload bits all 1, plus the continuation bit). The tenth + byte 0x01 carries the final payload bits with no continuation bit. + + This fixed 10-byte cost applies to -1 the same as to the most negative + 64-bit integer -2^63. The magnitude of the negative value does not affect + the byte count. This is why the encoding guide recommends sint32/sint64 + (see encode_sint32 and encode_sint64) for fields that often hold negative + values. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + if value >= 0: + # Non-negative values need no transformation. encode_varint encodes + # them directly as unsigned varints, which matches the int32/int64 + # wire encoding exactly. + return encode_varint(value) + + # For negative values, the protobuf spec requires 64-bit sign extension + # followed by unsigned varint encoding. + # + # Python integers have arbitrary precision — there is no native 64-bit + # boundary. The bitwise AND with 0xFFFFFFFFFFFFFFFF (a mask of 64 ones, + # equal to 2^64 - 1) extracts exactly the lower 64 bits of value. + # + # For any negative integer, Python's two's complement representation means + # the lower 64 bits equal the 64-bit unsigned two's complement value: + # + # -1 & 0xFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF (2^64 - 1) + # -2 & 0xFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFE (2^64 - 2) + # -128 & 0xFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFF80 + # + # Every result is in the range [2^63, 2^64 - 1], so encode_varint will + # always produce exactly 10 bytes for any negative input. + unsigned = value & 0xFFFFFFFFFFFFFFFF + return encode_varint(unsigned) + + +def encode_sint32(value: int) -> bytes: + """Encode a signed 32-bit integer using ZigZag encoding for sint32 fields. + + Protobuf sint32 fields use wire type 0 (varint) but apply a ZigZag + transformation before varint encoding. ZigZag maps signed integers to + non-negative integers by interleaving the negative and positive sequences: + + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + -3 -> 5 + 3 -> 6 + ... + n >= 0 -> 2 * n + n < 0 -> -2 * n - 1 + + The key property is that the ZigZag output is small whenever the input's + absolute magnitude is small, regardless of sign. This makes sint32 much + more efficient than int32 for fields that frequently hold negative values: + + Encoding -1 with encode_int: 10 bytes (64-bit two's complement) + Encoding -1 with encode_sint32: 1 byte (ZigZag maps -1 to 1) + + Encoding -64 with encode_int: 10 bytes + Encoding -64 with encode_sint32: 2 bytes (ZigZag maps -64 to 127) + + The official formula for the 32-bit ZigZag transformation is: + + zigzag32(n) = (n << 1) ^ (n >> 31) + + The right shift (n >> 31) is arithmetic in Python, propagating the sign + bit: it produces 0 for non-negative n and -1 (all bits set) for negative n. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # Step 1: apply the ZigZag transformation. + # + # (value << 1) shifts all bits one position left. This doubles the absolute + # value and frees bit 0 to carry the original sign. For positive n the + # result is 2*n (an even number). For negative n, the shift operates on the + # two's complement representation. + # + # (value >> 31) is an arithmetic right shift by 31 positions. Python + # propagates the sign bit into all vacated positions, so: + # + # non-negative n -> (n >> 31) == 0 (binary: all zeros) + # negative n -> (n >> 31) == -1 (binary: all ones, i.e. 0xFF...FF) + # + # XOR with 0 is a no-op, so positive inputs pass through as 2*n. + # XOR with -1 flips every bit, which is equivalent to bitwise NOT. For + # negative inputs that produces the ZigZag output -2*n - 1. + # + # Worked examples: + # + # n = -1: + # (-1 << 1) == -2 binary: ...1111_1110 + # (-1 >> 31) == -1 binary: ...1111_1111 + # (-2) ^ (-1) == 1 binary: ...0000_0001 ✓ ZigZag(-1) = 1 + # + # n = -2: + # (-2 << 1) == -4 binary: ...1111_1100 + # (-2 >> 31) == -1 binary: ...1111_1111 + # (-4) ^ (-1) == 3 binary: ...0000_0011 ✓ ZigZag(-2) = 3 + # + # n = 1: + # (1 << 1) == 2 binary: ...0000_0010 + # (1 >> 31) == 0 binary: ...0000_0000 + # 2 ^ 0 == 2 binary: ...0000_0010 ✓ ZigZag(1) = 2 + zigzag_value = (value << 1) ^ (value >> 31) + + # Step 2: mask to 32 bits. + # + # Python integers have arbitrary precision, so the XOR result above can + # have more than 32 significant bits for inputs outside the sint32 range + # [-2^31, 2^31 - 1]. The mask clips the result to the 32-bit unsigned + # domain [0, 2^32 - 1], which is the correct output range for sint32 + # ZigZag encoding. + # + # For valid sint32 inputs the ZigZag result already fits in 32 bits and + # the mask has no effect. + # + # 0xFFFFFFFF == 2^32 - 1 == 32 bits of all ones. + zigzag_value &= 0xFFFFFFFF + + # Step 3: varint-encode the non-negative ZigZag result. + # + # The ZigZag value is now in [0, 2^32 - 1]. encode_varint encodes it in + # 1–5 bytes depending on its magnitude, compared to the flat 10 bytes that + # encode_int would use for any negative input. + return encode_varint(zigzag_value) + + +def encode_sint64(value: int) -> bytes: + """Encode a signed 64-bit integer using ZigZag encoding for sint64 fields. + + This is the 64-bit counterpart of encode_sint32. The ZigZag interleaving + and the three-step structure (transform, mask, varint-encode) are identical. + The only differences from encode_sint32 are: + + - The arithmetic right shift uses 63 instead of 31, propagating the + sign bit across all 63 remaining bit positions of a 64-bit value. + - The mask uses 0xFFFFFFFFFFFFFFFF (64 ones) instead of 0xFFFFFFFF + (32 ones), constraining the output to [0, 2^64 - 1]. + - The varint output is at most 10 bytes instead of 5. + + The ZigZag mapping is the same interleaving as sint32, extended to 64 bits: + + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + ... + n >= 0 -> 2 * n + n < 0 -> -2 * n - 1 + + The official formula for the 64-bit ZigZag transformation is: + + zigzag64(n) = (n << 1) ^ (n >> 63) + + Worked examples: + + n = -1: + (-1 << 1) == -2 binary: ...1111_1110 + (-1 >> 63) == -1 binary: ...1111_1111 + (-2) ^ (-1) == 1 binary: ...0000_0001 ✓ ZigZag(-1) = 1 + Encoded: b'\\x01' (1 byte, vs 10 bytes from encode_int) + + n = -(2**63): (most negative sint64 value) + zigzag64(-(2**63)) == 2**64 - 1 (largest 64-bit unsigned integer) + Encoded: 10 bytes (the worst case for sint64) + + n = 2**63 - 1: (most positive sint64 value) + zigzag64(2**63 - 1) == 2**64 - 2 + Encoded: 10 bytes (the worst case alongside the above) + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # Step 1: apply the 64-bit ZigZag transformation. + # + # (value >> 63) produces 0 for non-negative values and -1 for negative + # values, for the same reason as (value >> 31) in encode_sint32: Python + # performs arithmetic right shifts that propagate the sign bit. + # + # The XOR with 0 leaves positive inputs unchanged (result = 2*n). + # The XOR with -1 flips every bit for negative inputs (result = -2*n - 1). + zigzag_value = (value << 1) ^ (value >> 63) + + # Step 2: mask to 64 bits. + # + # Same rationale as in encode_sint32: Python integers are unbounded, so + # the mask constrains the ZigZag result to the valid 64-bit unsigned range + # [0, 2^64 - 1]. For inputs in the sint64 range [-2^63, 2^63 - 1] the + # result already fits and the mask is a no-op. + # + # 0xFFFFFFFFFFFFFFFF == 2^64 - 1 == 64 bits of all ones. + zigzag_value &= 0xFFFFFFFFFFFFFFFF + + # Step 3: varint-encode the non-negative ZigZag result. + # + # The ZigZag value is in [0, 2^64 - 1], which encode_varint encodes in + # 1–10 bytes. The maximum 10-byte output only occurs at the extremes of + # the sint64 range (see worked examples in the docstring above). + return encode_varint(zigzag_value) + + +# ── Wire type 5 — 32-bit fixed-width ───────────────────────────────────────── + + +def encode_float(value: float) -> bytes: + """Encode a Python float as a protobuf float field value. + + Protobuf float fields use wire type 5 (32-bit fixed-width). The value is + stored as a 4-byte IEEE 754 single-precision floating-point number in + little-endian byte order. + + Unlike varint-encoded numbers, wire type 5 always occupies exactly 4 bytes. + This makes float fields predictable in size but more expensive than varint + for small integer-valued floats. + + Precision note + -------------- + Python's float type is a 64-bit IEEE 754 double-precision number. Converting + to single precision loses approximately 7 significant decimal digits of + precision (single has ~7, double has ~15-16). pack handles the conversion + silently; values outside the float32 range become inf or -inf. + + Byte layout — IEEE 754 single precision (32 bits) + -------------------------------------------------- + The 32 bits are arranged as: + + bit 31: sign (0 = positive, 1 = negative) + bits 30–23: exponent, biased by 127 + bits 22–0: mantissa (the fractional part, with an implicit leading 1) + + The four bytes are written in little-endian order: the least significant + byte is first. + + Example — encoding 1.0: + + IEEE 754 single for 1.0: + sign = 0 + exponent = 127 (biased) = 0b0111_1111 + mantissa = 0 (implicit leading 1, no fractional part) + + Combined 32-bit value: 0x3F800000 + + Little-endian bytes: 0x00, 0x00, 0x80, 0x3F + + Result: b'\\x00\\x00\\x80\\x3f' + + Example — encoding -1.0: + + Same as 1.0 but with the sign bit set. + + Combined 32-bit value: 0xBF800000 + + Little-endian bytes: 0x00, 0x00, 0x80, 0xBF + + Result: b'\\x00\\x00\\x80\\xbf' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode an unsigned integer as a protobuf fixed32 field value. + + Protobuf fixed32 fields use wire type 5 (32-bit fixed-width). The value + is stored as a 4-byte unsigned integer in little-endian byte order. + + The valid range is [0, 2^32 - 1]. + + Unlike uint32, which uses a varint and grows with the value's magnitude, + fixed32 always occupies exactly 4 bytes. This makes fixed32 more efficient + than uint32 for values that are consistently large (roughly above 2^28, + where a varint would already require 5 bytes), and less efficient for small + values. + + Example — encoding 1: + + 1 as a 4-byte little-endian unsigned integer: + + byte 0 (LSB): 0x01 + byte 1: 0x00 + byte 2: 0x00 + byte 3 (MSB): 0x00 + + Result: b'\\x01\\x00\\x00\\x00' + + Example — encoding 2^32 - 1 (maximum value): + + 0xFFFFFFFF as little-endian bytes: 0xFF, 0xFF, 0xFF, 0xFF + + Result: b'\\xff\\xff\\xff\\xff' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode a signed integer as a protobuf sfixed32 field value. + + Protobuf sfixed32 fields use wire type 5 (32-bit fixed-width). The value + is stored as a 4-byte signed integer in little-endian two's complement byte + order. + + The valid range is [-2^31, 2^31 - 1]. + + Unlike int32, which uses a varint and always costs 10 bytes for negative + values, sfixed32 always occupies exactly 4 bytes regardless of sign. This + makes sfixed32 more efficient than int32 for negative values and for large + positive values near 2^31. + + Example — encoding -1: + + -1 in 32-bit two's complement: 0xFFFFFFFF + + Little-endian bytes: 0xFF, 0xFF, 0xFF, 0xFF + + Result: b'\\xff\\xff\\xff\\xff' + + Example — encoding -2^31 (minimum value): + + -2147483648 in 32-bit two's complement: 0x80000000 + + Little-endian bytes: 0x00, 0x00, 0x00, 0x80 + + Result: b'\\x00\\x00\\x00\\x80' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode a Python float as a protobuf double field value. + + Protobuf double fields use wire type 1 (64-bit fixed-width). The value is + stored as an 8-byte IEEE 754 double-precision floating-point number in + little-endian byte order. + + Unlike wire type 5 (float), wire type 1 uses 8 bytes and matches Python's + native float precision exactly — no precision is lost in the conversion. + + Byte layout — IEEE 754 double precision (64 bits) + -------------------------------------------------- + The 64 bits are arranged as: + + bit 63: sign (0 = positive, 1 = negative) + bits 62–52: exponent, biased by 1023 + bits 51–0: mantissa (the fractional part, with an implicit leading 1) + + The eight bytes are written in little-endian order. + + Example — encoding 1.0: + + IEEE 754 double for 1.0: + sign = 0 + exponent = 1023 (biased) = 0b011_1111_1111 + mantissa = 0 + + Combined 64-bit value: 0x3FF0000000000000 + + Little-endian bytes: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F + + Result: b'\\x00\\x00\\x00\\x00\\x00\\x00\\xf0\\x3f' + + Special values (inf, -inf, nan) are represented in their standard IEEE 754 + double-precision forms and are encoded without error. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode an unsigned integer as a protobuf fixed64 field value. + + Protobuf fixed64 fields use wire type 1 (64-bit fixed-width). The value + is stored as an 8-byte unsigned integer in little-endian byte order. + + The valid range is [0, 2^64 - 1]. + + fixed64 always occupies exactly 8 bytes. It is more efficient than uint64 + (varint) for values consistently above 2^56, where a varint would already + require 9–10 bytes. For smaller values, uint64's variable length is more + compact. + + Example — encoding 1: + + 1 as an 8-byte little-endian unsigned integer: + + bytes: 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + + Result: b'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00' + + Example — encoding 2^64 - 1 (maximum value): + + 0xFFFFFFFFFFFFFFFF as little-endian bytes: eight 0xFF bytes. + + Result: b'\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode a signed integer as a protobuf sfixed64 field value. + + Protobuf sfixed64 fields use wire type 1 (64-bit fixed-width). The value + is stored as an 8-byte signed integer in little-endian two's complement + byte order. + + The valid range is [-2^63, 2^63 - 1]. + + sfixed64 always occupies exactly 8 bytes regardless of sign. Unlike int64 + (varint), which always costs 10 bytes for negative values, sfixed64 saves + 2 bytes for negative inputs and for large positive values near 2^63. + + Example — encoding -1: + + -1 in 64-bit two's complement: 0xFFFFFFFFFFFFFFFF + + Little-endian bytes: eight 0xFF bytes. + + Result: b'\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff' + + Example — encoding -2^63 (minimum value): + + -9223372036854775808 in 64-bit two's complement: 0x8000000000000000 + + Little-endian bytes: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 + + Result: b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # pack with format ' bytes: + """Encode a Python str as a protobuf string field value. + + Protobuf string fields use wire type 2 (length-delimited). The encoding + is a two-part sequence: + + 1. A varint giving the number of bytes in the UTF-8 representation of + the string. This is the byte count, not the character count — they + differ for any character outside ASCII. + 2. The UTF-8 encoded bytes of the string. + + The protobuf spec requires that string fields contain valid UTF-8. Python + str objects are always valid Unicode, so encoding to UTF-8 always succeeds + (Python's str cannot represent unpaired surrogates in normal use). + + The length prefix allows the decoder to know exactly how many bytes to read + for the string value without scanning for a terminator. + + Example — encoding "hi": + + UTF-8 bytes: b'hi' (2 bytes, both ASCII) + Length varint: encode_varint(2) == b'\\x02' + Result: b'\\x02hi' + + Example — encoding "é" (U+00E9, LATIN SMALL LETTER E WITH ACUTE): + + UTF-8 bytes: b'\\xc3\\xa9' (2 bytes; this character needs 2 UTF-8 bytes) + Length varint: encode_varint(2) == b'\\x02' + Result: b'\\x02\\xc3\\xa9' + + Example — encoding "" (empty string): + + UTF-8 bytes: b'' (0 bytes) + Length varint: encode_varint(0) == b'\\x00' + Result: b'\\x00' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # Encode the string to UTF-8 bytes. + # + # UTF-8 is the only encoding the protobuf spec allows for string fields. + # The result is a bytes object whose length may be greater than len(value) + # if the string contains non-ASCII characters (each such character encodes + # to 2–4 UTF-8 bytes). + utf8_bytes = value.encode("utf-8") + + # The length prefix is the number of UTF-8 bytes, encoded as a varint. + # + # The decoder reads this varint first to know how many bytes to consume + # for the field value. Without the length prefix, the decoder would have + # no way to find where the string ends in the byte stream. + length_prefix = encode_varint(len(utf8_bytes)) + + # Concatenate the length prefix and the UTF-8 bytes. + # + # The + operator on bytes objects produces a new bytes object containing + # the bytes of length_prefix followed immediately by the bytes of + # utf8_bytes. This is the complete wire encoding for the field value. + return length_prefix + utf8_bytes + + +def encode_bytes(value: bytes) -> bytes: + """Encode a Python bytes object as a protobuf bytes field value. + + Protobuf bytes fields use wire type 2 (length-delimited). The encoding + is a two-part sequence: + + 1. A varint giving the number of raw bytes in the payload. + 2. The raw bytes themselves, copied verbatim. + + Unlike string fields, bytes fields impose no encoding constraint on their + content — any byte sequence is valid, including sequences that are not + valid UTF-8. + + The length prefix allows the decoder to know exactly how many bytes to read + for the field value without scanning for a terminator. + + Example — encoding b'\\x00\\x01\\x02': + + Payload length: 3 bytes + Length varint: encode_varint(3) == b'\\x03' + Result: b'\\x03\\x00\\x01\\x02' + + Example — encoding b'' (empty bytes): + + Payload length: 0 bytes + Length varint: encode_varint(0) == b'\\x00' + Result: b'\\x00' + + Example — encoding b'\\xff\\xff': + + Payload length: 2 bytes + Length varint: encode_varint(2) == b'\\x02' + Result: b'\\x02\\xff\\xff' + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # The length prefix is the number of bytes in value, encoded as a varint. + # + # For large payloads (e.g. 128 bytes or more), encode_varint returns a + # multi-byte varint for the length prefix. For payloads up to 127 bytes + # the length fits in a single varint byte. + length_prefix = encode_varint(len(value)) + + # Concatenate the length prefix and the raw payload bytes. + # + # value is copied verbatim — no transformation is applied to the content. + return length_prefix + value diff --git a/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/tag.py b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/tag.py new file mode 100644 index 00000000000..70b081f6ffe --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/tag.py @@ -0,0 +1,151 @@ +"""Tag encoding for the protobuf wire format. + +Every protobuf message on the wire is a flat sequence of records. Each record +has exactly two parts: + + 1. A tag — a varint that encodes the field number and wire type. + 2. A value — the encoded field value, whose byte layout depends on the wire + type stored in the preceding tag. + +The tag is the reader's only guide to what follows. Without it, the decoder +would not know where one field ends and the next begins. + +Wire types +---------- +The protobuf spec defines six wire types. The wire type tells the decoder how +many bytes to consume for the value — not what the logical type is, just how +to delimit it on the wire: + + 0 Varint — one or more bytes, continuation bit in MSB of each byte. + 1 64-bit — exactly 8 bytes (used for fixed64, sfixed64, double). + 2 Length-delimited — a varint length prefix followed by that many bytes + (used for string, bytes, embedded messages, packed + repeated fields). + 3 Start group — deprecated; marks the start of a group (proto2 only). + 4 End group — deprecated; marks the end of a group (proto2 only). + 5 32-bit — exactly 4 bytes (used for fixed32, sfixed32, float). + +Wire types 3 and 4 are not used in proto3 and should not appear in new code. + +Tag bit layout +-------------- +The tag integer packs the field number and wire type into a single value: + + bits [2:0] — wire type (3 bits, enough for values 0–7) + bits [N:3] — field number (all remaining higher bits) + +The formula is: + + tag = (field_number << 3) | wire_type + +Three bits are reserved for the wire type because there are only six defined +wire type values (0–5), which fit comfortably in 3 bits (range 0–7). + +Example — field 1, wire type 0 (varint): + + field_number = 1 -> binary 0000_0001 + field_number << 3 -> binary 0000_1000 (decimal 8) + wire_type = 0 -> binary 0000_0000 + tag = 8 | 0 -> binary 0000_1000 (decimal 8) + + Varint-encoded: 0x08 (fits in one byte, no continuation bit needed) + +Example — field 2, wire type 2 (length-delimited): + + field_number = 2 -> binary 0000_0010 + field_number << 3 -> binary 0001_0000 (decimal 16) + wire_type = 2 -> binary 0000_0010 + tag = 16|2 -> binary 0001_0010 (decimal 18) + + Varint-encoded: 0x12 (one byte) + +Example — field 16, wire type 0 (varint): + + field_number = 16 -> binary 0001_0000 + field_number << 3 -> binary 1000_0000 (decimal 128) + wire_type = 0 -> binary 0000_0000 + tag = 128 -> varint requires two bytes: 0x80 0x01 + + The first byte 0x80 has its continuation bit set, meaning another byte + follows. The second byte 0x01 carries the remaining bits with no + continuation bit. This is the standard varint encoding for 128. + +Reference: + https://protobuf.dev/programming-guides/encoding/ +""" + +from .varint import encode_varint + + +def encode_tag(field_number: int, wire_type: int) -> bytes: + """Encode a protobuf record tag. + + The tag is the varint-encoded integer produced by: + + tag = (field_number << 3) | wire_type + + It is written before every field value in a serialised protobuf message. + The decoder reads this tag first to learn the field number (which .proto + field this value belongs to) and the wire type (how many bytes to read for + the value). + + Parameters + ---------- + field_number: + The field number as declared in the .proto schema. Must be a positive + integer. Field numbers 1–15 produce a one-byte tag for wire types 0–5; + field numbers 16–2047 produce a two-byte tag. + wire_type: + One of the six protobuf wire type constants (0–5). See the module + docstring for the full list and their meanings. + + Reference: + https://protobuf.dev/programming-guides/encoding/ + """ + # field_number << 3 shifts the field number left by three bit positions. + # + # This makes room in the three lowest bits of the tag integer for the wire + # type. The three-bit reservation comes directly from the protobuf spec: + # wire types 0–5 fit in 3 bits, so the spec dedicates exactly 3 bits to + # the wire type in every tag. + # + # Example: + # + # field_number = 1 + # field_number in binary = 0000_0001 + # field_number << 3 = 0000_1000 (decimal 8) + # + # After the shift, bits [2:0] are always zero, leaving space for + # the wire type to be OR'd in below. + field_number_bits = field_number << 3 + + # The bitwise OR writes the wire type into the lowest three bits. + # + # Because field_number_bits always has its lowest three bits set to zero + # (from the shift above), and wire_type is always in the range 0–5 (which + # fits in three bits), the OR simply places the wire type value into those + # three vacated bit positions without disturbing the field number bits. + # + # Example (field 1, wire type 2): + # + # field_number_bits = 0b0000_1000 (8) + # wire_type = 0b0000_0010 (2) + # tag = 0b0000_1010 (10) + # + # Example (field 15, wire type 0): + # + # field_number_bits = 0b0111_1000 (120) + # wire_type = 0b0000_0000 (0) + # tag = 0b0111_1000 (120) + # + # Field numbers 1–15 combined with any wire type 0–5 always produce a tag + # integer of at most 127, which encodes as a single varint byte. This is + # why the protobuf style guide recommends reserving field numbers 1–15 for + # the most frequently used fields: their tags are one byte instead of two. + tag = field_number_bits | wire_type + + # The tag integer is itself encoded as a varint before being written to the + # wire. For field numbers 1–15 the tag integer is at most 127, fitting in + # one varint byte. For field number 16 and above the tag integer exceeds + # 127 and requires two or more varint bytes. + return encode_varint(tag) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/varint.py b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/varint.py new file mode 100644 index 00000000000..d1d478f82cc --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/_pyprotobuf/varint.py @@ -0,0 +1,135 @@ +"""Varint encoding for the protobuf wire format. + +Reference: + https://protobuf.dev/programming-guides/encoding/ + +The protobuf encoding guide explains the wire format mostly from the point of +view of inspecting or decoding bytes that already exist. For example, it shows +how a varint byte sequence can be read by removing each byte's continuation bit, +then combining the remaining 7-bit payload groups. + +This module implements the encoder side of the same algorithm. Instead of +starting with bytes and recovering the integer, we start with the integer and +produce the bytes. That means the operations appear in the opposite direction: + + documented explanation / decoding view: + bytes -> remove continuation bits -> collect 7-bit groups -> integer + + implementation / encoding view: + integer -> extract 7-bit groups -> add continuation bits -> bytes + +The bit layout is the same in both directions. +""" + + +def encode_varint(value: int) -> bytes: + """Encode a non-negative integer as a protobuf varint. + + A protobuf varint stores an integer as one or more bytes. + + Each byte has two parts: + + bit 7: continuation bit + bits 0-6: seven payload bits + + The continuation bit is the most significant bit of the byte: + + 0b1000_0000 == 0x80 + + The payload bits are the lower seven bits of the byte: + + 0b0111_1111 == 0x7F + + The official protobuf encoding guide describes this format here: + + https://protobuf.dev/programming-guides/encoding/ + + The guide explains that the payload is split into 7-bit groups and that the + groups are stored in little-endian order. In this context, little-endian + means the least significant 7-bit group is written first. + + This implementation directly performs that encoder-side operation: + + 1. Take the least significant 7 bits of the integer. + 2. Write those 7 bits into the next output byte. + 3. If more integer bits remain, set the byte's continuation bit. + 4. Shift the integer right by 7 bits so the next group becomes the new + least significant group. + 5. Repeat until the remaining integer fits in one final 7-bit payload. + + This is different from the document's presentation because the document is + mostly showing how to interpret already-encoded bytes. Here we are producing + those bytes. The two views are inverse operations of the same wire-format + rule. + """ + # Protobuf varints, as implemented by this function, encode unsigned integer + # payloads. Signed integer fields need additional field-specific handling + # before varint encoding. For example, sint32/sint64 use ZigZag encoding + # before the resulting non-negative integer is encoded as a varint. + if value < 0: + raise ValueError("varint values must be non-negative") + + # bytearray is used because we build the encoded byte sequence one byte at a + # time. A bytearray is mutable, so appending to it is clearer and cheaper + # than repeatedly concatenating immutable bytes objects. + output = bytearray() + + # 0x7F is binary 0111_1111. + # + # If value is greater than 0x7F, it cannot fit in one protobuf varint byte, + # because one varint byte has only seven payload bits. In that case, we must + # emit one byte containing the current least significant 7-bit group and then + # continue encoding the remaining higher bits. + while value > 0x7F: + # value & 0x7F keeps only the lower seven bits of value. + # + # This extracts exactly one protobuf varint payload group. + # + # Example: + # + # value = 0b1001_0110 # decimal 150 + # 0x7F = 0b0111_1111 + # value & 0x7F = 0b0001_0110 # decimal 22 + # + # This corresponds to the guide's 7-bit payload group, but from the + # encoder direction. The guide often starts from encoded bytes and strips + # the continuation bit. Here we start from the integer and extract the + # payload bits before adding the continuation bit. + payload_bits = value & 0x7F + + # 0x80 is binary 1000_0000. + # + # payload_bits | 0x80 sets the most significant bit of the output byte. + # That most significant bit is the protobuf varint continuation bit. + # + # Setting it to 1 means: + # + # "this is not the final varint byte; another byte follows" + # + # This byte needs the continuation bit because the while condition has + # already proven that value has more than seven bits left to encode. + output.append(payload_bits | 0x80) + + # Shift value right by seven bits. + # + # This discards the payload group we just emitted and moves the next + # higher 7-bit group into the lowest seven bits, ready for the next loop + # iteration. + # + # This is why protobuf varints are little-endian at the 7-bit-group + # level: we emit the least significant group first, then move toward more + # significant groups. + value >>= 7 + + # When the loop ends, value is between 0 and 0x7F inclusive, so it fits in a + # single 7-bit payload group. + # + # This is the final varint byte, so we do NOT set the continuation bit. + # A most significant bit of 0 means: + # + # "this is the final byte of the varint" + output.append(value) + + # Convert the mutable bytearray into immutable bytes, which is the natural + # representation for encoded wire-format data in Python. + return bytes(output) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2.py new file mode 100644 index 00000000000..99d98932004 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2.py @@ -0,0 +1,32 @@ +"""Pure-Python equivalents of collector/logs/v1/logs_service_pb2.py. + +Field numbers: + ExportLogsServiceRequest resource_logs=1 + ExportLogsServiceResponse (empty — no fields used in export path) +""" + +from __future__ import annotations + +from opentelemetry._proto.logs.v1.logs_pb2 import ResourceLogs +from opentelemetry._proto._pyprotobuf.fields import msg + + +class ExportLogsServiceRequest: + def __init__(self, resource_logs: list[ResourceLogs] | None = None): + self.resource_logs: list[ResourceLogs] = ( + list(resource_logs) if resource_logs else [] + ) + + def SerializeToString(self) -> bytes: + return b"".join( + msg(1, rl.SerializeToString()) for rl in self.resource_logs + ) + + +class ExportLogsServiceResponse: + @classmethod + def FromString(cls, data: bytes) -> 'ExportLogsServiceResponse': + return cls() + + def SerializeToString(self) -> bytes: + return b"" diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2_grpc.py new file mode 100644 index 00000000000..51e1224144f --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/logs/v1/logs_service_pb2_grpc.py @@ -0,0 +1,16 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry._proto.collector.logs.v1.logs_service_pb2 import ( + ExportLogsServiceRequest, + ExportLogsServiceResponse, +) + + +class LogsServiceStub: + def __init__(self, channel): + self.Export = channel.unary_unary( + '/opentelemetry.proto.collector.logs.v1.LogsService/Export', + request_serializer=ExportLogsServiceRequest.SerializeToString, + response_deserializer=ExportLogsServiceResponse.FromString, + ) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2.py new file mode 100644 index 00000000000..f9cdc448420 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2.py @@ -0,0 +1,32 @@ +"""Pure-Python equivalents of collector/metrics/v1/metrics_service_pb2.py. + +Field numbers: + ExportMetricsServiceRequest resource_metrics=1 + ExportMetricsServiceResponse (empty — no fields used in export path) +""" + +from __future__ import annotations + +from opentelemetry._proto.metrics.v1.metrics_pb2 import ResourceMetrics +from opentelemetry._proto._pyprotobuf.fields import msg + + +class ExportMetricsServiceRequest: + def __init__(self, resource_metrics: list[ResourceMetrics] | None = None): + self.resource_metrics: list[ResourceMetrics] = ( + list(resource_metrics) if resource_metrics else [] + ) + + def SerializeToString(self) -> bytes: + return b"".join( + msg(1, rm.SerializeToString()) for rm in self.resource_metrics + ) + + +class ExportMetricsServiceResponse: + @classmethod + def FromString(cls, data: bytes) -> 'ExportMetricsServiceResponse': + return cls() + + def SerializeToString(self) -> bytes: + return b"" diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2_grpc.py new file mode 100644 index 00000000000..4c523b4286e --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/metrics/v1/metrics_service_pb2_grpc.py @@ -0,0 +1,16 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry._proto.collector.metrics.v1.metrics_service_pb2 import ( + ExportMetricsServiceRequest, + ExportMetricsServiceResponse, +) + + +class MetricsServiceStub: + def __init__(self, channel): + self.Export = channel.unary_unary( + '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', + request_serializer=ExportMetricsServiceRequest.SerializeToString, + response_deserializer=ExportMetricsServiceResponse.FromString, + ) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2.py new file mode 100644 index 00000000000..de8801b934e --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2.py @@ -0,0 +1,32 @@ +"""Pure-Python equivalents of collector/trace/v1/trace_service_pb2.py. + +Field numbers: + ExportTraceServiceRequest resource_spans=1 + ExportTraceServiceResponse (empty — no fields used in export path) +""" + +from __future__ import annotations + +from opentelemetry._proto.trace.v1.trace_pb2 import ResourceSpans +from opentelemetry._proto._pyprotobuf.fields import msg + + +class ExportTraceServiceRequest: + def __init__(self, resource_spans: list[ResourceSpans] | None = None): + self.resource_spans: list[ResourceSpans] = ( + list(resource_spans) if resource_spans else [] + ) + + def SerializeToString(self) -> bytes: + return b"".join( + msg(1, rs.SerializeToString()) for rs in self.resource_spans + ) + + +class ExportTraceServiceResponse: + @classmethod + def FromString(cls, data: bytes) -> 'ExportTraceServiceResponse': + return cls() + + def SerializeToString(self) -> bytes: + return b"" diff --git a/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2_grpc.py new file mode 100644 index 00000000000..106455b1bba --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/collector/trace/v1/trace_service_pb2_grpc.py @@ -0,0 +1,16 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry._proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest, + ExportTraceServiceResponse, +) + + +class TraceServiceStub: + def __init__(self, channel): + self.Export = channel.unary_unary( + '/opentelemetry.proto.collector.trace.v1.TraceService/Export', + request_serializer=ExportTraceServiceRequest.SerializeToString, + response_deserializer=ExportTraceServiceResponse.FromString, + ) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/common/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/common/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/common/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/common/v1/common_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/common/v1/common_pb2.py new file mode 100644 index 00000000000..bb07a82c9a0 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/common/v1/common_pb2.py @@ -0,0 +1,132 @@ +"""Pure-Python equivalents of opentelemetry/proto/common/v1/common_pb2.py. + +Field numbers: + AnyValue (oneof value) string_value=1 bool_value=2 int_value=3 + double_value=4 array_value=5 kvlist_value=6 + bytes_value=7 + ArrayValue values=1 + KeyValueList values=1 + KeyValue key=1 value=2 + InstrumentationScope name=1 version=2 attributes=3 + dropped_attributes_count=4 +""" + +from __future__ import annotations + +from struct import pack + +from opentelemetry._proto._pyprotobuf import encode_int, encode_tag, encode_varint + +from opentelemetry._proto._pyprotobuf.fields import msg, string, u64, WT_LEN, WT_VARINT, WT_64BIT + + +class AnyValue: + """Oneof value container — exactly one field is set.""" + + def __init__( + self, + string_value: str | None = None, + bool_value: bool | None = None, + int_value: int | None = None, + double_value: float | None = None, + array_value: "ArrayValue | None" = None, + kvlist_value: "KeyValueList | None" = None, + bytes_value: bytes | None = None, + ): + self._which: str | None = None + if string_value is not None: + self.string_value = string_value + self._which = "string_value" + elif bool_value is not None: + self.bool_value = bool_value + self._which = "bool_value" + elif int_value is not None: + self.int_value = int_value + self._which = "int_value" + elif double_value is not None: + self.double_value = double_value + self._which = "double_value" + elif array_value is not None: + self.array_value = array_value + self._which = "array_value" + elif kvlist_value is not None: + self.kvlist_value = kvlist_value + self._which = "kvlist_value" + elif bytes_value is not None: + self.bytes_value = bytes_value + self._which = "bytes_value" + + def WhichOneof(self, oneof_name: str) -> str | None: + if oneof_name == "value": + return self._which + return None + + def SerializeToString(self) -> bytes: + # oneof: always written even when the value equals the proto3 default. + if self._which == "string_value": + utf8 = self.string_value.encode("utf-8") + return encode_tag(1, WT_LEN) + encode_varint(len(utf8)) + utf8 + if self._which == "bool_value": + return encode_tag(2, WT_VARINT) + encode_varint(1 if self.bool_value else 0) + if self._which == "int_value": + return encode_tag(3, WT_VARINT) + encode_int(self.int_value) + if self._which == "double_value": + return encode_tag(4, WT_64BIT) + pack(" bytes: + return b"".join(msg(1, v.SerializeToString()) for v in self.values) + + +class KeyValueList: + def __init__(self, values: "list[KeyValue] | None" = None): + self.values: list[KeyValue] = list(values) if values else [] + + def SerializeToString(self) -> bytes: + return b"".join(msg(1, kv.SerializeToString()) for kv in self.values) + + +class KeyValue: + def __init__(self, key: str = "", value: AnyValue | None = None): + self.key = key + self.value = value + + def SerializeToString(self) -> bytes: + result = string(1, self.key) + if self.value is not None: + result += msg(2, self.value.SerializeToString()) + return result + + +class InstrumentationScope: + def __init__( + self, + name: str = "", + version: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + ): + self.name = name + self.version = version + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + + def SerializeToString(self) -> bytes: + return ( + string(1, self.name) + + string(2, self.version) + + b"".join(msg(3, kv.SerializeToString()) for kv in self.attributes) + + u64(4, self.dropped_attributes_count) + ) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/logs/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/logs/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/logs/v1/logs_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/logs/v1/logs_pb2.py new file mode 100644 index 00000000000..29c06607ed7 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/logs/v1/logs_pb2.py @@ -0,0 +1,114 @@ +"""Pure-Python equivalents of opentelemetry/proto/logs/v1/logs_pb2.py. + +Field numbers: + LogRecord time_unix_nano=1 severity_number=2 severity_text=3 + body=5 attributes=6 dropped_attrs_count=7 flags=8 + trace_id=9 span_id=10 observed_time_unix_nano=11 + event_name=12 + ScopeLogs scope=1 log_records=2 schema_url=3 + ResourceLogs resource=1 scope_logs=2 schema_url=3 +""" + +from __future__ import annotations + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource +from opentelemetry._proto._pyprotobuf.fields import ( + byt, + fix32, + fix64, + msg, + string, + u64, +) + + +class LogRecord: + def __init__( + self, + time_unix_nano: int = 0, + severity_number: int = 0, + severity_text: str = "", + body: AnyValue | None = None, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + flags: int = 0, + trace_id: bytes = b"", + span_id: bytes = b"", + observed_time_unix_nano: int = 0, + event_name: str = "", + ): + self.time_unix_nano = time_unix_nano + self.severity_number = severity_number + self.severity_text = severity_text + self.body = body + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + self.flags = flags + self.trace_id = trace_id + self.span_id = span_id + self.observed_time_unix_nano = observed_time_unix_nano + self.event_name = event_name + + def SerializeToString(self) -> bytes: + result = ( + fix64(1, self.time_unix_nano) + + u64(2, self.severity_number) + + string(3, self.severity_text) + ) + if self.body is not None: + result += msg(5, self.body.SerializeToString()) + result += ( + b"".join(msg(6, kv.SerializeToString()) for kv in self.attributes) + + u64(7, self.dropped_attributes_count) + + fix32(8, self.flags) + + byt(9, self.trace_id) + + byt(10, self.span_id) + + fix64(11, self.observed_time_unix_nano) + + string(12, self.event_name) + ) + return result + + +class ScopeLogs: + def __init__( + self, + scope: InstrumentationScope | None = None, + log_records: list[LogRecord] | None = None, + schema_url: str = "", + ): + self.scope = scope + self.log_records: list[LogRecord] = list(log_records) if log_records else [] + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.scope is not None: + result += msg(1, self.scope.SerializeToString()) + result += b"".join(msg(2, lr.SerializeToString()) for lr in self.log_records) + result += string(3, self.schema_url) + return result + + +class ResourceLogs: + def __init__( + self, + resource: Resource | None = None, + scope_logs: list[ScopeLogs] | None = None, + schema_url: str = "", + ): + self.resource = resource + self.scope_logs: list[ScopeLogs] = list(scope_logs) if scope_logs else [] + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.resource is not None: + result += msg(1, self.resource.SerializeToString()) + result += b"".join(msg(2, sl.SerializeToString()) for sl in self.scope_logs) + result += string(3, self.schema_url) + return result diff --git a/opentelemetry-proto/src/opentelemetry/_proto/metrics/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/metrics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/metrics/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/metrics/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/metrics/v1/metrics_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/metrics/v1/metrics_pb2.py new file mode 100644 index 00000000000..f479ac44a6b --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/metrics/v1/metrics_pb2.py @@ -0,0 +1,500 @@ +"""Pure-Python equivalents of opentelemetry/proto/metrics/v1/metrics_pb2.py. + +Field numbers: + Exemplar filtered_attributes=7 time_unix_nano=2 + as_double=3(wire_64bit) as_int=6(sfixed64) + span_id=4 trace_id=5 + NumberDataPoint attributes=7 start_time_unix_nano=2 time_unix_nano=3 + as_double=4(wire_64bit) as_int=6(sfixed64) + exemplars=5 flags=8 + HistogramDataPoint attributes=9 start_time_unix_nano=2 time_unix_nano=3 + count=4(fixed64) sum=5(opt_dbl) + bucket_counts=6(packed_fix64) + explicit_bounds=7(packed_dbl) exemplars=8 + flags=10 min=11(opt_dbl) max=12(opt_dbl) + ExponentialHistogramDataPoint + attributes=1 start_time_unix_nano=2 time_unix_nano=3 + count=4(fixed64) sum=5(opt_dbl) scale=6(sint32) + zero_count=7(fixed64) positive=8 negative=9 + flags=10 exemplars=11 min=12(opt_dbl) + max=13(opt_dbl) zero_threshold=14(dbl) + ExponentialHistogramDataPoint.Buckets + offset=1(sint32) bucket_counts=2(packed_uint64) + SummaryDataPoint attributes=7 start_time_unix_nano=2 time_unix_nano=3 + count=4(fixed64) sum=5(dbl) quantile_values=6 flags=8 + SummaryDataPoint.ValueAtQuantile quantile=1(dbl) value=2(dbl) + Gauge data_points=1 + Sum data_points=1 aggregation_temporality=2 is_monotonic=3 + Histogram data_points=1 aggregation_temporality=2 + ExponentialHistogram data_points=1 aggregation_temporality=2 + Summary data_points=1 + Metric name=1 description=2 unit=3 + oneof data: gauge=5 sum=7 histogram=9 + exponential_histogram=10 summary=11 + ScopeMetrics scope=1 metrics=2 schema_url=3 + ResourceMetrics resource=1 scope_metrics=2 schema_url=3 +""" + +from __future__ import annotations + +from struct import pack + +from opentelemetry._proto.common.v1.common_pb2 import ( + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource +from opentelemetry._proto._pyprotobuf.fields import ( + byt, + bool_field, + dbl, + fix64, + msg, + opt_dbl, + packed_double, + packed_fix64, + packed_uint64, + sint32, + string, + u64, + WT_64BIT, +) +from opentelemetry._proto._pyprotobuf import encode_tag + + +def _sfixed64(field: int, value: int) -> bytes: + """sfixed64 field (little-endian signed 64-bit int).""" + if value == 0: + return b"" + return encode_tag(field, WT_64BIT) + pack(" bytes: + """Oneof as_double: always write, even if 0.0.""" + return encode_tag(field, WT_64BIT) + pack(" bytes: + """Oneof as_int: always write, even if 0.""" + return encode_tag(field, WT_64BIT) + pack(" str | None: + if oneof_name == "value": + return self._which + return None + + def SerializeToString(self) -> bytes: + result = fix64(2, self.time_unix_nano) + if self._which == "as_double": + result += _as_double(3, self._as_double) # type: ignore[arg-type] + result += byt(4, self.span_id) + byt(5, self.trace_id) + if self._which == "as_int": + result += _as_int(6, self._as_int) # type: ignore[arg-type] + result += b"".join( + msg(7, kv.SerializeToString()) for kv in self.filtered_attributes + ) + return result + + +class NumberDataPoint: + def __init__( + self, + attributes: list[KeyValue] | None = None, + start_time_unix_nano: int = 0, + time_unix_nano: int = 0, + as_double: float | None = None, + as_int: int | None = None, + exemplars: list[Exemplar] | None = None, + flags: int = 0, + ): + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.start_time_unix_nano = start_time_unix_nano + self.time_unix_nano = time_unix_nano + self._as_double = as_double + self._as_int = as_int + self.exemplars: list[Exemplar] = list(exemplars) if exemplars else [] + self.flags = flags + self._which = ( + "as_double" if as_double is not None else + "as_int" if as_int is not None else None + ) + + def WhichOneof(self, oneof_name: str) -> str | None: + if oneof_name == "value": + return self._which + return None + + def SerializeToString(self) -> bytes: + result = fix64(2, self.start_time_unix_nano) + fix64(3, self.time_unix_nano) + if self._which == "as_double": + result += _as_double(4, self._as_double) # type: ignore[arg-type] + result += b"".join(msg(5, ex.SerializeToString()) for ex in self.exemplars) + if self._which == "as_int": + result += _as_int(6, self._as_int) # type: ignore[arg-type] + result += b"".join(msg(7, kv.SerializeToString()) for kv in self.attributes) + result += u64(8, self.flags) + return result + + +class HistogramDataPoint: + def __init__( + self, + attributes: list[KeyValue] | None = None, + start_time_unix_nano: int = 0, + time_unix_nano: int = 0, + count: int = 0, + sum: float | None = None, + bucket_counts: list[int] | None = None, + explicit_bounds: list[float] | None = None, + exemplars: list[Exemplar] | None = None, + flags: int = 0, + min: float | None = None, + max: float | None = None, + ): + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.start_time_unix_nano = start_time_unix_nano + self.time_unix_nano = time_unix_nano + self.count = count + self.sum = sum + self.bucket_counts: list[int] = list(bucket_counts) if bucket_counts else [] + self.explicit_bounds: list[float] = ( + list(explicit_bounds) if explicit_bounds else [] + ) + self.exemplars: list[Exemplar] = list(exemplars) if exemplars else [] + self.flags = flags + self.min = min + self.max = max + + def SerializeToString(self) -> bytes: + return ( + fix64(2, self.start_time_unix_nano) + + fix64(3, self.time_unix_nano) + + fix64(4, self.count) + + opt_dbl(5, self.sum) + + packed_fix64(6, self.bucket_counts) + + packed_double(7, self.explicit_bounds) + + b"".join(msg(8, ex.SerializeToString()) for ex in self.exemplars) + + b"".join(msg(9, kv.SerializeToString()) for kv in self.attributes) + + u64(10, self.flags) + + opt_dbl(11, self.min) + + opt_dbl(12, self.max) + ) + + +class ExponentialHistogramDataPoint: + class Buckets: + def __init__( + self, + offset: int = 0, + bucket_counts: list[int] | None = None, + ): + self.offset = offset + self.bucket_counts: list[int] = ( + list(bucket_counts) if bucket_counts else [] + ) + + def SerializeToString(self) -> bytes: + return ( + sint32(1, self.offset) + + packed_uint64(2, self.bucket_counts) + ) + + def __init__( + self, + attributes: list[KeyValue] | None = None, + start_time_unix_nano: int = 0, + time_unix_nano: int = 0, + count: int = 0, + sum: float | None = None, + scale: int = 0, + zero_count: int = 0, + positive: "ExponentialHistogramDataPoint.Buckets | None" = None, + negative: "ExponentialHistogramDataPoint.Buckets | None" = None, + flags: int = 0, + exemplars: list[Exemplar] | None = None, + min: float | None = None, + max: float | None = None, + zero_threshold: float = 0.0, + ): + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.start_time_unix_nano = start_time_unix_nano + self.time_unix_nano = time_unix_nano + self.count = count + self.sum = sum + self.scale = scale + self.zero_count = zero_count + self.positive = positive + self.negative = negative + self.flags = flags + self.exemplars: list[Exemplar] = list(exemplars) if exemplars else [] + self.min = min + self.max = max + self.zero_threshold = zero_threshold + + def SerializeToString(self) -> bytes: + result = b"".join(msg(1, kv.SerializeToString()) for kv in self.attributes) + result += ( + fix64(2, self.start_time_unix_nano) + + fix64(3, self.time_unix_nano) + + fix64(4, self.count) + + opt_dbl(5, self.sum) + + sint32(6, self.scale) + + fix64(7, self.zero_count) + ) + if self.positive is not None: + result += msg(8, self.positive.SerializeToString()) + if self.negative is not None: + result += msg(9, self.negative.SerializeToString()) + result += ( + u64(10, self.flags) + + b"".join(msg(11, ex.SerializeToString()) for ex in self.exemplars) + + opt_dbl(12, self.min) + + opt_dbl(13, self.max) + + dbl(14, self.zero_threshold) + ) + return result + + +class SummaryDataPoint: + class ValueAtQuantile: + def __init__(self, quantile: float = 0.0, value: float = 0.0): + self.quantile = quantile + self.value = value + + def SerializeToString(self) -> bytes: + return dbl(1, self.quantile) + dbl(2, self.value) + + def __init__( + self, + attributes: list[KeyValue] | None = None, + start_time_unix_nano: int = 0, + time_unix_nano: int = 0, + count: int = 0, + sum: float = 0.0, + quantile_values: list["SummaryDataPoint.ValueAtQuantile"] | None = None, + flags: int = 0, + ): + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.start_time_unix_nano = start_time_unix_nano + self.time_unix_nano = time_unix_nano + self.count = count + self.sum = sum + self.quantile_values: list[SummaryDataPoint.ValueAtQuantile] = ( + list(quantile_values) if quantile_values else [] + ) + self.flags = flags + + def SerializeToString(self) -> bytes: + return ( + fix64(2, self.start_time_unix_nano) + + fix64(3, self.time_unix_nano) + + fix64(4, self.count) + + dbl(5, self.sum) + + b"".join(msg(6, qv.SerializeToString()) for qv in self.quantile_values) + + b"".join(msg(7, kv.SerializeToString()) for kv in self.attributes) + + u64(8, self.flags) + ) + + +class Gauge: + def __init__(self, data_points: list[NumberDataPoint] | None = None): + self.data_points: list[NumberDataPoint] = ( + list(data_points) if data_points else [] + ) + + def SerializeToString(self) -> bytes: + return b"".join(msg(1, dp.SerializeToString()) for dp in self.data_points) + + +class Sum: + def __init__( + self, + data_points: list[NumberDataPoint] | None = None, + aggregation_temporality: int = 0, + is_monotonic: bool = False, + ): + self.data_points: list[NumberDataPoint] = ( + list(data_points) if data_points else [] + ) + self.aggregation_temporality = aggregation_temporality + self.is_monotonic = is_monotonic + + def SerializeToString(self) -> bytes: + temp = self.aggregation_temporality + temp_int = temp.value if hasattr(temp, "value") else int(temp) + return ( + b"".join(msg(1, dp.SerializeToString()) for dp in self.data_points) + + u64(2, temp_int) + + bool_field(3, self.is_monotonic) + ) + + +class Histogram: + def __init__( + self, + data_points: list[HistogramDataPoint] | None = None, + aggregation_temporality: int = 0, + ): + self.data_points: list[HistogramDataPoint] = ( + list(data_points) if data_points else [] + ) + self.aggregation_temporality = aggregation_temporality + + def SerializeToString(self) -> bytes: + temp = self.aggregation_temporality + temp_int = temp.value if hasattr(temp, "value") else int(temp) + return ( + b"".join(msg(1, dp.SerializeToString()) for dp in self.data_points) + + u64(2, temp_int) + ) + + +class ExponentialHistogram: + def __init__( + self, + data_points: list[ExponentialHistogramDataPoint] | None = None, + aggregation_temporality: int = 0, + ): + self.data_points: list[ExponentialHistogramDataPoint] = ( + list(data_points) if data_points else [] + ) + self.aggregation_temporality = aggregation_temporality + + def SerializeToString(self) -> bytes: + temp = self.aggregation_temporality + temp_int = temp.value if hasattr(temp, "value") else int(temp) + return ( + b"".join(msg(1, dp.SerializeToString()) for dp in self.data_points) + + u64(2, temp_int) + ) + + +class Summary: + def __init__(self, data_points: list[SummaryDataPoint] | None = None): + self.data_points: list[SummaryDataPoint] = ( + list(data_points) if data_points else [] + ) + + def SerializeToString(self) -> bytes: + return b"".join(msg(1, dp.SerializeToString()) for dp in self.data_points) + + +class Metric: + def __init__( + self, + name: str = "", + description: str = "", + unit: str = "", + gauge: Gauge | None = None, + sum: Sum | None = None, + histogram: Histogram | None = None, + exponential_histogram: ExponentialHistogram | None = None, + summary: Summary | None = None, + ): + self.name = name + self.description = description + self.unit = unit + self.gauge = gauge + self.sum = sum + self.histogram = histogram + self.exponential_histogram = exponential_histogram + self.summary = summary + + # Resolve oneof data field name + if gauge is not None: + self._which_data = "gauge" + elif sum is not None: + self._which_data = "sum" + elif histogram is not None: + self._which_data = "histogram" + elif exponential_histogram is not None: + self._which_data = "exponential_histogram" + elif summary is not None: + self._which_data = "summary" + else: + self._which_data = None + + def WhichOneof(self, oneof_name: str) -> str | None: + if oneof_name == "data": + return self._which_data + return None + + def SerializeToString(self) -> bytes: + result = string(1, self.name) + string(2, self.description) + string(3, self.unit) + if self.gauge is not None: + result += msg(5, self.gauge.SerializeToString()) + elif self.sum is not None: + result += msg(7, self.sum.SerializeToString()) + elif self.histogram is not None: + result += msg(9, self.histogram.SerializeToString()) + elif self.exponential_histogram is not None: + result += msg(10, self.exponential_histogram.SerializeToString()) + elif self.summary is not None: + result += msg(11, self.summary.SerializeToString()) + return result + + +class ScopeMetrics: + def __init__( + self, + scope: InstrumentationScope | None = None, + metrics: list[Metric] | None = None, + schema_url: str = "", + ): + self.scope = scope + self.metrics: list[Metric] = list(metrics) if metrics else [] + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.scope is not None: + result += msg(1, self.scope.SerializeToString()) + result += b"".join(msg(2, m.SerializeToString()) for m in self.metrics) + result += string(3, self.schema_url) + return result + + +class ResourceMetrics: + def __init__( + self, + resource: Resource | None = None, + scope_metrics: list[ScopeMetrics] | None = None, + schema_url: str = "", + ): + self.resource = resource + self.scope_metrics: list[ScopeMetrics] = ( + list(scope_metrics) if scope_metrics else [] + ) + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.resource is not None: + result += msg(1, self.resource.SerializeToString()) + result += b"".join(msg(2, sm.SerializeToString()) for sm in self.scope_metrics) + result += string(3, self.schema_url) + return result diff --git a/opentelemetry-proto/src/opentelemetry/_proto/resource/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/resource/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/resource/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/resource/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/resource/v1/resource_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/resource/v1/resource_pb2.py new file mode 100644 index 00000000000..2fb21980e13 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/resource/v1/resource_pb2.py @@ -0,0 +1,26 @@ +"""Pure-Python equivalents of opentelemetry/proto/resource/v1/resource_pb2.py. + +Field numbers: + Resource attributes=1 dropped_attributes_count=2 +""" + +from __future__ import annotations + +from opentelemetry._proto.common.v1.common_pb2 import KeyValue +from opentelemetry._proto._pyprotobuf.fields import msg, u64 + + +class Resource: + def __init__( + self, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + ): + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + + def SerializeToString(self) -> bytes: + return ( + b"".join(msg(1, kv.SerializeToString()) for kv in self.attributes) + + u64(2, self.dropped_attributes_count) + ) diff --git a/opentelemetry-proto/src/opentelemetry/_proto/trace/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/trace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/trace/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/trace/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/_proto/trace/v1/trace_pb2.py b/opentelemetry-proto/src/opentelemetry/_proto/trace/v1/trace_pb2.py new file mode 100644 index 00000000000..2c63cbd9cf9 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/trace/v1/trace_pb2.py @@ -0,0 +1,188 @@ +"""Pure-Python equivalents of opentelemetry/proto/trace/v1/trace_pb2.py. + +Field numbers: + Status message=2 code=3 + Span.Event time_unix_nano=1 name=2 attributes=3 dropped_attrs_count=4 + Span.Link trace_id=1 span_id=2 trace_state=3 attributes=4 + dropped_attrs_count=5 flags=6 + Span trace_id=1 span_id=2 trace_state=3 parent_span_id=4 + flags=16 name=5 kind=6 start_time_unix_nano=7 + end_time_unix_nano=8 attributes=9 dropped_attrs_count=10 + events=11 dropped_events_count=12 links=13 + dropped_links_count=14 status=15 + ScopeSpans scope=1 spans=2 schema_url=3 + ResourceSpans resource=1 scope_spans=2 schema_url=3 +""" + +from __future__ import annotations + +from opentelemetry._proto.common.v1.common_pb2 import ( + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource +from opentelemetry._proto._pyprotobuf.fields import ( + byt, + fix32, + fix64, + msg, + string, + u64, +) + + +class Status: + def __init__(self, message: str = "", code: int = 0): + self.message = message + self.code = code + + def SerializeToString(self) -> bytes: + return string(2, self.message) + u64(3, self.code) + + +class Span: + class Event: + def __init__( + self, + time_unix_nano: int = 0, + name: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + ): + self.time_unix_nano = time_unix_nano + self.name = name + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + + def SerializeToString(self) -> bytes: + return ( + fix64(1, self.time_unix_nano) + + string(2, self.name) + + b"".join(msg(3, kv.SerializeToString()) for kv in self.attributes) + + u64(4, self.dropped_attributes_count) + ) + + class Link: + def __init__( + self, + trace_id: bytes = b"", + span_id: bytes = b"", + trace_state: str = "", + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + flags: int = 0, + ): + self.trace_id = trace_id + self.span_id = span_id + self.trace_state = trace_state + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + self.flags = flags + + def SerializeToString(self) -> bytes: + return ( + byt(1, self.trace_id) + + byt(2, self.span_id) + + string(3, self.trace_state) + + b"".join(msg(4, kv.SerializeToString()) for kv in self.attributes) + + u64(5, self.dropped_attributes_count) + + fix32(6, self.flags) + ) + + def __init__( + self, + trace_id: bytes = b"", + span_id: bytes = b"", + trace_state: str = "", + parent_span_id: bytes = b"", + flags: int = 0, + name: str = "", + kind: int = 0, + start_time_unix_nano: int = 0, + end_time_unix_nano: int = 0, + attributes: list[KeyValue] | None = None, + dropped_attributes_count: int = 0, + events: list["Span.Event"] | None = None, + dropped_events_count: int = 0, + links: list["Span.Link"] | None = None, + dropped_links_count: int = 0, + status: Status | None = None, + ): + self.trace_id = trace_id + self.span_id = span_id + self.trace_state = trace_state + self.parent_span_id = parent_span_id + self.flags = flags + self.name = name + self.kind = kind + self.start_time_unix_nano = start_time_unix_nano + self.end_time_unix_nano = end_time_unix_nano + self.attributes: list[KeyValue] = list(attributes) if attributes else [] + self.dropped_attributes_count = dropped_attributes_count + self.events: list[Span.Event] = list(events) if events else [] + self.dropped_events_count = dropped_events_count + self.links: list[Span.Link] = list(links) if links else [] + self.dropped_links_count = dropped_links_count + self.status = status + + def SerializeToString(self) -> bytes: + result = ( + byt(1, self.trace_id) + + byt(2, self.span_id) + + string(3, self.trace_state) + + byt(4, self.parent_span_id) + + string(5, self.name) + + u64(6, self.kind) + + fix64(7, self.start_time_unix_nano) + + fix64(8, self.end_time_unix_nano) + + b"".join(msg(9, kv.SerializeToString()) for kv in self.attributes) + + u64(10, self.dropped_attributes_count) + + b"".join(msg(11, ev.SerializeToString()) for ev in self.events) + + u64(12, self.dropped_events_count) + + b"".join(msg(13, lk.SerializeToString()) for lk in self.links) + + u64(14, self.dropped_links_count) + ) + if self.status is not None: + result += msg(15, self.status.SerializeToString()) + result += fix32(16, self.flags) + return result + + +class ScopeSpans: + def __init__( + self, + scope: InstrumentationScope | None = None, + spans: list[Span] | None = None, + schema_url: str = "", + ): + self.scope = scope + self.spans: list[Span] = list(spans) if spans else [] + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.scope is not None: + result += msg(1, self.scope.SerializeToString()) + result += b"".join(msg(2, sp.SerializeToString()) for sp in self.spans) + result += string(3, self.schema_url) + return result + + +class ResourceSpans: + def __init__( + self, + resource: Resource | None = None, + scope_spans: list[ScopeSpans] | None = None, + schema_url: str = "", + ): + self.resource = resource + self.scope_spans: list[ScopeSpans] = list(scope_spans) if scope_spans else [] + self.schema_url = schema_url + + def SerializeToString(self) -> bytes: + result = b"" + if self.resource is not None: + result += msg(1, self.resource.SerializeToString()) + result += b"".join(msg(2, ss.SerializeToString()) for ss in self.scope_spans) + result += string(3, self.schema_url) + return result diff --git a/opentelemetry-proto/src/opentelemetry/_proto/version/__init__.py b/opentelemetry-proto/src/opentelemetry/_proto/version/__init__.py new file mode 100644 index 00000000000..cdc135bf8ae --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/_proto/version/__init__.py @@ -0,0 +1 @@ +__version__ = "1.45.0.dev" diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.py index 81f124f6303..5cab986ec39 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.py @@ -1,34 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/collector/logs/v1/logs_service.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.logs.v1 import logs_pb2 as opentelemetry_dot_proto_dot_logs_dot_v1_dot_logs__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n8opentelemetry/proto/collector/logs/v1/logs_service.proto\x12%opentelemetry.proto.collector.logs.v1\x1a&opentelemetry/proto/logs/v1/logs.proto\"\\\n\x18\x45xportLogsServiceRequest\x12@\n\rresource_logs\x18\x01 \x03(\x0b\x32).opentelemetry.proto.logs.v1.ResourceLogs\"u\n\x19\x45xportLogsServiceResponse\x12X\n\x0fpartial_success\x18\x01 \x01(\x0b\x32?.opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess\"O\n\x18\x45xportLogsPartialSuccess\x12\x1c\n\x14rejected_log_records\x18\x01 \x01(\x03\x12\x15\n\rerror_message\x18\x02 \x01(\t2\x9d\x01\n\x0bLogsService\x12\x8d\x01\n\x06\x45xport\x12?.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest\x1a@.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse\"\x00\x42\x98\x01\n(io.opentelemetry.proto.collector.logs.v1B\x10LogsServiceProtoP\x01Z0go.opentelemetry.io/proto/otlp/collector/logs/v1\xaa\x02%OpenTelemetry.Proto.Collector.Logs.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.collector.logs.v1.logs_service_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n(io.opentelemetry.proto.collector.logs.v1B\020LogsServiceProtoP\001Z0go.opentelemetry.io/proto/otlp/collector/logs/v1\252\002%OpenTelemetry.Proto.Collector.Logs.V1' - _globals['_EXPORTLOGSSERVICEREQUEST']._serialized_start=139 - _globals['_EXPORTLOGSSERVICEREQUEST']._serialized_end=231 - _globals['_EXPORTLOGSSERVICERESPONSE']._serialized_start=233 - _globals['_EXPORTLOGSSERVICERESPONSE']._serialized_end=350 - _globals['_EXPORTLOGSPARTIALSUCCESS']._serialized_start=352 - _globals['_EXPORTLOGSPARTIALSUCCESS']._serialized_end=431 - _globals['_LOGSSERVICE']._serialized_start=434 - _globals['_LOGSSERVICE']._serialized_end=591 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.collector.logs.v1.logs_service_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.pyi deleted file mode 100644 index 99e2a0ac101..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2.pyi +++ /dev/null @@ -1,117 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2020, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.logs.v1.logs_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class ExportLogsServiceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_LOGS_FIELD_NUMBER: builtins.int - @property - def resource_logs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.logs.v1.logs_pb2.ResourceLogs]: - """An array of ResourceLogs. - For data coming from a single resource this array will typically contain one - element. Intermediary nodes (such as OpenTelemetry Collector) that receive - data from multiple origins typically batch the data before forwarding further and - in that case this array will contain multiple elements. - """ - def __init__( - self, - *, - resource_logs: collections.abc.Iterable[opentelemetry.proto.logs.v1.logs_pb2.ResourceLogs] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_logs", b"resource_logs"]) -> None: ... - -global___ExportLogsServiceRequest = ExportLogsServiceRequest - -@typing_extensions.final -class ExportLogsServiceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_SUCCESS_FIELD_NUMBER: builtins.int - @property - def partial_success(self) -> global___ExportLogsPartialSuccess: - """The details of a partially successful export request. - - If the request is only partially accepted - (i.e. when the server accepts only parts of the data and rejects the rest) - the server MUST initialize the `partial_success` field and MUST - set the `rejected_` with the number of items it rejected. - - Servers MAY also make use of the `partial_success` field to convey - warnings/suggestions to senders even when the request was fully accepted. - In such cases, the `rejected_` MUST have a value of `0` and - the `error_message` MUST be non-empty. - - A `partial_success` message with an empty value (rejected_ = 0 and - `error_message` = "") is equivalent to it not being set/present. Senders - SHOULD interpret it the same way as in the full success case. - """ - def __init__( - self, - *, - partial_success: global___ExportLogsPartialSuccess | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> None: ... - -global___ExportLogsServiceResponse = ExportLogsServiceResponse - -@typing_extensions.final -class ExportLogsPartialSuccess(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - REJECTED_LOG_RECORDS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - rejected_log_records: builtins.int - """The number of rejected log records. - - A `rejected_` field holding a `0` value indicates that the - request was fully accepted. - """ - error_message: builtins.str - """A developer-facing human-readable message in English. It should be used - either to explain why the server rejected parts of the data during a partial - success or to convey warnings/suggestions during a full success. The message - should offer guidance on how users can address such issues. - - error_message is an optional field. An error_message with an empty value - is equivalent to it not being set. - """ - def __init__( - self, - *, - rejected_log_records: builtins.int = ..., - error_message: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["error_message", b"error_message", "rejected_log_records", b"rejected_log_records"]) -> None: ... - -global___ExportLogsPartialSuccess = ExportLogsPartialSuccess diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2_grpc.py index bb64c98fa25..da80a79f0c0 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2_grpc.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/logs/v1/logs_service_pb2_grpc.py @@ -1,110 +1,5 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -from opentelemetry.proto.collector.logs.v1 import logs_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2 -GRPC_GENERATED_VERSION = '1.63.2' -GRPC_VERSION = grpc.__version__ -EXPECTED_ERROR_RELEASE = '1.65.0' -SCHEDULED_RELEASE_DATE = 'June 25, 2024' -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - warnings.warn( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in opentelemetry/proto/collector/logs/v1/logs_service_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},' - + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.', - RuntimeWarning - ) - - -class LogsServiceStub(object): - """Service that can be used to push logs between one Application instrumented with - OpenTelemetry and an collector, or between an collector and a central collector (in this - case logs are sent/received to/from multiple Applications). - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Export = channel.unary_unary( - '/opentelemetry.proto.collector.logs.v1.LogsService/Export', - request_serializer=opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceRequest.SerializeToString, - response_deserializer=opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceResponse.FromString, - _registered_method=True) - - -class LogsServiceServicer(object): - """Service that can be used to push logs between one Application instrumented with - OpenTelemetry and an collector, or between an collector and a central collector (in this - case logs are sent/received to/from multiple Applications). - """ - - def Export(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_LogsServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Export': grpc.unary_unary_rpc_method_handler( - servicer.Export, - request_deserializer=opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceRequest.FromString, - response_serializer=opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'opentelemetry.proto.collector.logs.v1.LogsService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class LogsService(object): - """Service that can be used to push logs between one Application instrumented with - OpenTelemetry and an collector, or between an collector and a central collector (in this - case logs are sent/received to/from multiple Applications). - """ - - @staticmethod - def Export(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/opentelemetry.proto.collector.logs.v1.LogsService/Export', - opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceRequest.SerializeToString, - opentelemetry_dot_proto_dot_collector_dot_logs_dot_v1_dot_logs__service__pb2.ExportLogsServiceResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) +from opentelemetry._proto.collector.logs.v1.logs_service_pb2_grpc import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py index 6083655c882..d111e2ce3df 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py @@ -1,34 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/collector/metrics/v1/metrics_service.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.metrics.v1 import metrics_pb2 as opentelemetry_dot_proto_dot_metrics_dot_v1_dot_metrics__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n>opentelemetry/proto/collector/metrics/v1/metrics_service.proto\x12(opentelemetry.proto.collector.metrics.v1\x1a,opentelemetry/proto/metrics/v1/metrics.proto\"h\n\x1b\x45xportMetricsServiceRequest\x12I\n\x10resource_metrics\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.ResourceMetrics\"~\n\x1c\x45xportMetricsServiceResponse\x12^\n\x0fpartial_success\x18\x01 \x01(\x0b\x32\x45.opentelemetry.proto.collector.metrics.v1.ExportMetricsPartialSuccess\"R\n\x1b\x45xportMetricsPartialSuccess\x12\x1c\n\x14rejected_data_points\x18\x01 \x01(\x03\x12\x15\n\rerror_message\x18\x02 \x01(\t2\xac\x01\n\x0eMetricsService\x12\x99\x01\n\x06\x45xport\x12\x45.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest\x1a\x46.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse\"\x00\x42\xa4\x01\n+io.opentelemetry.proto.collector.metrics.v1B\x13MetricsServiceProtoP\x01Z3go.opentelemetry.io/proto/otlp/collector/metrics/v1\xaa\x02(OpenTelemetry.Proto.Collector.Metrics.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.collector.metrics.v1.metrics_service_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n+io.opentelemetry.proto.collector.metrics.v1B\023MetricsServiceProtoP\001Z3go.opentelemetry.io/proto/otlp/collector/metrics/v1\252\002(OpenTelemetry.Proto.Collector.Metrics.V1' - _globals['_EXPORTMETRICSSERVICEREQUEST']._serialized_start=154 - _globals['_EXPORTMETRICSSERVICEREQUEST']._serialized_end=258 - _globals['_EXPORTMETRICSSERVICERESPONSE']._serialized_start=260 - _globals['_EXPORTMETRICSSERVICERESPONSE']._serialized_end=386 - _globals['_EXPORTMETRICSPARTIALSUCCESS']._serialized_start=388 - _globals['_EXPORTMETRICSPARTIALSUCCESS']._serialized_end=470 - _globals['_METRICSSERVICE']._serialized_start=473 - _globals['_METRICSSERVICE']._serialized_end=645 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.collector.metrics.v1.metrics_service_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.pyi deleted file mode 100644 index fe3c44f3c37..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.pyi +++ /dev/null @@ -1,117 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.metrics.v1.metrics_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class ExportMetricsServiceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_METRICS_FIELD_NUMBER: builtins.int - @property - def resource_metrics(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.metrics.v1.metrics_pb2.ResourceMetrics]: - """An array of ResourceMetrics. - For data coming from a single resource this array will typically contain one - element. Intermediary nodes (such as OpenTelemetry Collector) that receive - data from multiple origins typically batch the data before forwarding further and - in that case this array will contain multiple elements. - """ - def __init__( - self, - *, - resource_metrics: collections.abc.Iterable[opentelemetry.proto.metrics.v1.metrics_pb2.ResourceMetrics] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_metrics", b"resource_metrics"]) -> None: ... - -global___ExportMetricsServiceRequest = ExportMetricsServiceRequest - -@typing_extensions.final -class ExportMetricsServiceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_SUCCESS_FIELD_NUMBER: builtins.int - @property - def partial_success(self) -> global___ExportMetricsPartialSuccess: - """The details of a partially successful export request. - - If the request is only partially accepted - (i.e. when the server accepts only parts of the data and rejects the rest) - the server MUST initialize the `partial_success` field and MUST - set the `rejected_` with the number of items it rejected. - - Servers MAY also make use of the `partial_success` field to convey - warnings/suggestions to senders even when the request was fully accepted. - In such cases, the `rejected_` MUST have a value of `0` and - the `error_message` MUST be non-empty. - - A `partial_success` message with an empty value (rejected_ = 0 and - `error_message` = "") is equivalent to it not being set/present. Senders - SHOULD interpret it the same way as in the full success case. - """ - def __init__( - self, - *, - partial_success: global___ExportMetricsPartialSuccess | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> None: ... - -global___ExportMetricsServiceResponse = ExportMetricsServiceResponse - -@typing_extensions.final -class ExportMetricsPartialSuccess(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - REJECTED_DATA_POINTS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - rejected_data_points: builtins.int - """The number of rejected data points. - - A `rejected_` field holding a `0` value indicates that the - request was fully accepted. - """ - error_message: builtins.str - """A developer-facing human-readable message in English. It should be used - either to explain why the server rejected parts of the data during a partial - success or to convey warnings/suggestions during a full success. The message - should offer guidance on how users can address such issues. - - error_message is an optional field. An error_message with an empty value - is equivalent to it not being set. - """ - def __init__( - self, - *, - rejected_data_points: builtins.int = ..., - error_message: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["error_message", b"error_message", "rejected_data_points", b"rejected_data_points"]) -> None: ... - -global___ExportMetricsPartialSuccess = ExportMetricsPartialSuccess diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py index f124bfe4adc..e7ee94e209e 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py @@ -1,110 +1,5 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -from opentelemetry.proto.collector.metrics.v1 import metrics_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2 -GRPC_GENERATED_VERSION = '1.63.2' -GRPC_VERSION = grpc.__version__ -EXPECTED_ERROR_RELEASE = '1.65.0' -SCHEDULED_RELEASE_DATE = 'June 25, 2024' -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - warnings.warn( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},' - + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.', - RuntimeWarning - ) - - -class MetricsServiceStub(object): - """Service that can be used to push metrics between one Application - instrumented with OpenTelemetry and a collector, or between a collector and a - central collector. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Export = channel.unary_unary( - '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', - request_serializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.SerializeToString, - response_deserializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.FromString, - _registered_method=True) - - -class MetricsServiceServicer(object): - """Service that can be used to push metrics between one Application - instrumented with OpenTelemetry and a collector, or between a collector and a - central collector. - """ - - def Export(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_MetricsServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Export': grpc.unary_unary_rpc_method_handler( - servicer.Export, - request_deserializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.FromString, - response_serializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'opentelemetry.proto.collector.metrics.v1.MetricsService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class MetricsService(object): - """Service that can be used to push metrics between one Application - instrumented with OpenTelemetry and a collector, or between a collector and a - central collector. - """ - - @staticmethod - def Export(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', - opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.SerializeToString, - opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) +from opentelemetry._proto.collector.metrics.v1.metrics_service_pb2_grpc import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.py deleted file mode 100644 index 9e2f6198299..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/collector/profiles/v1development/profiles_service.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from opentelemetry.proto.profiles.v1development import profiles_pb2 as opentelemetry_dot_proto_dot_profiles_dot_v1development_dot_profiles__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nKopentelemetry/proto/collector/profiles/v1development/profiles_service.proto\x12\x34opentelemetry.proto.collector.profiles.v1development\x1a\x39opentelemetry/proto/profiles/v1development/profiles.proto\"\xcb\x01\n\x1c\x45xportProfilesServiceRequest\x12W\n\x11resource_profiles\x18\x01 \x03(\x0b\x32<.opentelemetry.proto.profiles.v1development.ResourceProfiles\x12R\n\ndictionary\x18\x02 \x01(\x0b\x32>.opentelemetry.proto.profiles.v1development.ProfilesDictionary\"\x8c\x01\n\x1d\x45xportProfilesServiceResponse\x12k\n\x0fpartial_success\x18\x01 \x01(\x0b\x32R.opentelemetry.proto.collector.profiles.v1development.ExportProfilesPartialSuccess\"P\n\x1c\x45xportProfilesPartialSuccess\x12\x19\n\x11rejected_profiles\x18\x01 \x01(\x03\x12\x15\n\rerror_message\x18\x02 \x01(\t2\xc7\x01\n\x0fProfilesService\x12\xb3\x01\n\x06\x45xport\x12R.opentelemetry.proto.collector.profiles.v1development.ExportProfilesServiceRequest\x1aS.opentelemetry.proto.collector.profiles.v1development.ExportProfilesServiceResponse\"\x00\x42\xc9\x01\n7io.opentelemetry.proto.collector.profiles.v1developmentB\x14ProfilesServiceProtoP\x01Z?go.opentelemetry.io/proto/otlp/collector/profiles/v1development\xaa\x02\x34OpenTelemetry.Proto.Collector.Profiles.V1Developmentb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.collector.profiles.v1development.profiles_service_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n7io.opentelemetry.proto.collector.profiles.v1developmentB\024ProfilesServiceProtoP\001Z?go.opentelemetry.io/proto/otlp/collector/profiles/v1development\252\0024OpenTelemetry.Proto.Collector.Profiles.V1Development' - _globals['_EXPORTPROFILESSERVICEREQUEST']._serialized_start=193 - _globals['_EXPORTPROFILESSERVICEREQUEST']._serialized_end=396 - _globals['_EXPORTPROFILESSERVICERESPONSE']._serialized_start=399 - _globals['_EXPORTPROFILESSERVICERESPONSE']._serialized_end=539 - _globals['_EXPORTPROFILESPARTIALSUCCESS']._serialized_start=541 - _globals['_EXPORTPROFILESPARTIALSUCCESS']._serialized_end=621 - _globals['_PROFILESSERVICE']._serialized_start=624 - _globals['_PROFILESSERVICE']._serialized_end=823 -# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.pyi deleted file mode 100644 index e8b7a82095c..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2.pyi +++ /dev/null @@ -1,123 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2023, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.profiles.v1development.profiles_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class ExportProfilesServiceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_PROFILES_FIELD_NUMBER: builtins.int - DICTIONARY_FIELD_NUMBER: builtins.int - @property - def resource_profiles(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.profiles.v1development.profiles_pb2.ResourceProfiles]: - """An array of ResourceProfiles. - For data coming from a single resource this array will typically contain one - element. Intermediary nodes (such as OpenTelemetry Collector) that receive - data from multiple origins typically batch the data before forwarding further and - in that case this array will contain multiple elements. - """ - @property - def dictionary(self) -> opentelemetry.proto.profiles.v1development.profiles_pb2.ProfilesDictionary: - """The reference table containing all data shared by profiles across the message being sent.""" - def __init__( - self, - *, - resource_profiles: collections.abc.Iterable[opentelemetry.proto.profiles.v1development.profiles_pb2.ResourceProfiles] | None = ..., - dictionary: opentelemetry.proto.profiles.v1development.profiles_pb2.ProfilesDictionary | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["dictionary", b"dictionary"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["dictionary", b"dictionary", "resource_profiles", b"resource_profiles"]) -> None: ... - -global___ExportProfilesServiceRequest = ExportProfilesServiceRequest - -@typing_extensions.final -class ExportProfilesServiceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_SUCCESS_FIELD_NUMBER: builtins.int - @property - def partial_success(self) -> global___ExportProfilesPartialSuccess: - """The details of a partially successful export request. - - If the request is only partially accepted - (i.e. when the server accepts only parts of the data and rejects the rest) - the server MUST initialize the `partial_success` field and MUST - set the `rejected_` with the number of items it rejected. - - Servers MAY also make use of the `partial_success` field to convey - warnings/suggestions to senders even when the request was fully accepted. - In such cases, the `rejected_` MUST have a value of `0` and - the `error_message` MUST be non-empty. - - A `partial_success` message with an empty value (rejected_ = 0 and - `error_message` = "") is equivalent to it not being set/present. Senders - SHOULD interpret it the same way as in the full success case. - """ - def __init__( - self, - *, - partial_success: global___ExportProfilesPartialSuccess | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> None: ... - -global___ExportProfilesServiceResponse = ExportProfilesServiceResponse - -@typing_extensions.final -class ExportProfilesPartialSuccess(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - REJECTED_PROFILES_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - rejected_profiles: builtins.int - """The number of rejected profiles. - - A `rejected_` field holding a `0` value indicates that the - request was fully accepted. - """ - error_message: builtins.str - """A developer-facing human-readable message in English. It should be used - either to explain why the server rejected parts of the data during a partial - success or to convey warnings/suggestions during a full success. The message - should offer guidance on how users can address such issues. - - error_message is an optional field. An error_message with an empty value - is equivalent to it not being set. - """ - def __init__( - self, - *, - rejected_profiles: builtins.int = ..., - error_message: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["error_message", b"error_message", "rejected_profiles", b"rejected_profiles"]) -> None: ... - -global___ExportProfilesPartialSuccess = ExportProfilesPartialSuccess diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2_grpc.py deleted file mode 100644 index 3742ae591e3..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2_grpc.py +++ /dev/null @@ -1,107 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from opentelemetry.proto.collector.profiles.v1development import profiles_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2 - -GRPC_GENERATED_VERSION = '1.63.2' -GRPC_VERSION = grpc.__version__ -EXPECTED_ERROR_RELEASE = '1.65.0' -SCHEDULED_RELEASE_DATE = 'June 25, 2024' -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - warnings.warn( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in opentelemetry/proto/collector/profiles/v1development/profiles_service_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},' - + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.', - RuntimeWarning - ) - - -class ProfilesServiceStub(object): - """Service that can be used to push profiles between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Export = channel.unary_unary( - '/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export', - request_serializer=opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceRequest.SerializeToString, - response_deserializer=opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceResponse.FromString, - _registered_method=True) - - -class ProfilesServiceServicer(object): - """Service that can be used to push profiles between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector. - """ - - def Export(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ProfilesServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Export': grpc.unary_unary_rpc_method_handler( - servicer.Export, - request_deserializer=opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceRequest.FromString, - response_serializer=opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'opentelemetry.proto.collector.profiles.v1development.ProfilesService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class ProfilesService(object): - """Service that can be used to push profiles between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector. - """ - - @staticmethod - def Export(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export', - opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceRequest.SerializeToString, - opentelemetry_dot_proto_dot_collector_dot_profiles_dot_v1development_dot_profiles__service__pb2.ExportProfilesServiceResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py index c0ad62bfdbd..c86e73d64fb 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py @@ -1,34 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/collector/trace/v1/trace_service.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.trace.v1 import trace_pb2 as opentelemetry_dot_proto_dot_trace_dot_v1_dot_trace__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n:opentelemetry/proto/collector/trace/v1/trace_service.proto\x12&opentelemetry.proto.collector.trace.v1\x1a(opentelemetry/proto/trace/v1/trace.proto\"`\n\x19\x45xportTraceServiceRequest\x12\x43\n\x0eresource_spans\x18\x01 \x03(\x0b\x32+.opentelemetry.proto.trace.v1.ResourceSpans\"x\n\x1a\x45xportTraceServiceResponse\x12Z\n\x0fpartial_success\x18\x01 \x01(\x0b\x32\x41.opentelemetry.proto.collector.trace.v1.ExportTracePartialSuccess\"J\n\x19\x45xportTracePartialSuccess\x12\x16\n\x0erejected_spans\x18\x01 \x01(\x03\x12\x15\n\rerror_message\x18\x02 \x01(\t2\xa2\x01\n\x0cTraceService\x12\x91\x01\n\x06\x45xport\x12\x41.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest\x1a\x42.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse\"\x00\x42\x9c\x01\n)io.opentelemetry.proto.collector.trace.v1B\x11TraceServiceProtoP\x01Z1go.opentelemetry.io/proto/otlp/collector/trace/v1\xaa\x02&OpenTelemetry.Proto.Collector.Trace.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.collector.trace.v1.trace_service_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n)io.opentelemetry.proto.collector.trace.v1B\021TraceServiceProtoP\001Z1go.opentelemetry.io/proto/otlp/collector/trace/v1\252\002&OpenTelemetry.Proto.Collector.Trace.V1' - _globals['_EXPORTTRACESERVICEREQUEST']._serialized_start=144 - _globals['_EXPORTTRACESERVICEREQUEST']._serialized_end=240 - _globals['_EXPORTTRACESERVICERESPONSE']._serialized_start=242 - _globals['_EXPORTTRACESERVICERESPONSE']._serialized_end=362 - _globals['_EXPORTTRACEPARTIALSUCCESS']._serialized_start=364 - _globals['_EXPORTTRACEPARTIALSUCCESS']._serialized_end=438 - _globals['_TRACESERVICE']._serialized_start=441 - _globals['_TRACESERVICE']._serialized_end=603 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.collector.trace.v1.trace_service_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.pyi deleted file mode 100644 index ceb4db5213f..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.pyi +++ /dev/null @@ -1,117 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.trace.v1.trace_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class ExportTraceServiceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_SPANS_FIELD_NUMBER: builtins.int - @property - def resource_spans(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.trace.v1.trace_pb2.ResourceSpans]: - """An array of ResourceSpans. - For data coming from a single resource this array will typically contain one - element. Intermediary nodes (such as OpenTelemetry Collector) that receive - data from multiple origins typically batch the data before forwarding further and - in that case this array will contain multiple elements. - """ - def __init__( - self, - *, - resource_spans: collections.abc.Iterable[opentelemetry.proto.trace.v1.trace_pb2.ResourceSpans] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_spans", b"resource_spans"]) -> None: ... - -global___ExportTraceServiceRequest = ExportTraceServiceRequest - -@typing_extensions.final -class ExportTraceServiceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PARTIAL_SUCCESS_FIELD_NUMBER: builtins.int - @property - def partial_success(self) -> global___ExportTracePartialSuccess: - """The details of a partially successful export request. - - If the request is only partially accepted - (i.e. when the server accepts only parts of the data and rejects the rest) - the server MUST initialize the `partial_success` field and MUST - set the `rejected_` with the number of items it rejected. - - Servers MAY also make use of the `partial_success` field to convey - warnings/suggestions to senders even when the request was fully accepted. - In such cases, the `rejected_` MUST have a value of `0` and - the `error_message` MUST be non-empty. - - A `partial_success` message with an empty value (rejected_ = 0 and - `error_message` = "") is equivalent to it not being set/present. Senders - SHOULD interpret it the same way as in the full success case. - """ - def __init__( - self, - *, - partial_success: global___ExportTracePartialSuccess | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["partial_success", b"partial_success"]) -> None: ... - -global___ExportTraceServiceResponse = ExportTraceServiceResponse - -@typing_extensions.final -class ExportTracePartialSuccess(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - REJECTED_SPANS_FIELD_NUMBER: builtins.int - ERROR_MESSAGE_FIELD_NUMBER: builtins.int - rejected_spans: builtins.int - """The number of rejected spans. - - A `rejected_` field holding a `0` value indicates that the - request was fully accepted. - """ - error_message: builtins.str - """A developer-facing human-readable message in English. It should be used - either to explain why the server rejected parts of the data during a partial - success or to convey warnings/suggestions during a full success. The message - should offer guidance on how users can address such issues. - - error_message is an optional field. An error_message with an empty value - is equivalent to it not being set. - """ - def __init__( - self, - *, - rejected_spans: builtins.int = ..., - error_message: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["error_message", b"error_message", "rejected_spans", b"rejected_spans"]) -> None: ... - -global___ExportTracePartialSuccess = ExportTracePartialSuccess diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py index f1cdf0355b4..bba0e2ebede 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py @@ -1,110 +1,5 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -from opentelemetry.proto.collector.trace.v1 import trace_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2 -GRPC_GENERATED_VERSION = '1.63.2' -GRPC_VERSION = grpc.__version__ -EXPECTED_ERROR_RELEASE = '1.65.0' -SCHEDULED_RELEASE_DATE = 'June 25, 2024' -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - warnings.warn( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - + f' This warning will become an error in {EXPECTED_ERROR_RELEASE},' - + f' scheduled for release on {SCHEDULED_RELEASE_DATE}.', - RuntimeWarning - ) - - -class TraceServiceStub(object): - """Service that can be used to push spans between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector (in this - case spans are sent/received to/from multiple Applications). - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Export = channel.unary_unary( - '/opentelemetry.proto.collector.trace.v1.TraceService/Export', - request_serializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.SerializeToString, - response_deserializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.FromString, - _registered_method=True) - - -class TraceServiceServicer(object): - """Service that can be used to push spans between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector (in this - case spans are sent/received to/from multiple Applications). - """ - - def Export(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TraceServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Export': grpc.unary_unary_rpc_method_handler( - servicer.Export, - request_deserializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.FromString, - response_serializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'opentelemetry.proto.collector.trace.v1.TraceService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class TraceService(object): - """Service that can be used to push spans between one Application instrumented with - OpenTelemetry and a collector, or between a collector and a central collector (in this - case spans are sent/received to/from multiple Applications). - """ - - @staticmethod - def Export(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/opentelemetry.proto.collector.trace.v1.TraceService/Export', - opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.SerializeToString, - opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) +from opentelemetry._proto.collector.trace.v1.trace_service_pb2_grpc import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py index 1e816f201f8..04ce80a2aed 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py @@ -1,37 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/common/v1/common.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n*opentelemetry/proto/common/v1/common.proto\x12\x1dopentelemetry.proto.common.v1\"\xad\x02\n\x08\x41nyValue\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x02 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12@\n\x0b\x61rray_value\x18\x05 \x01(\x0b\x32).opentelemetry.proto.common.v1.ArrayValueH\x00\x12\x43\n\x0ckvlist_value\x18\x06 \x01(\x0b\x32+.opentelemetry.proto.common.v1.KeyValueListH\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x12\x1f\n\x15string_value_strindex\x18\x08 \x01(\x05H\x00\x42\x07\n\x05value\"E\n\nArrayValue\x12\x37\n\x06values\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\"G\n\x0cKeyValueList\x12\x37\n\x06values\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\"e\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\x12\x14\n\x0ckey_strindex\x18\x03 \x01(\x05\"\x94\x01\n\x14InstrumentationScope\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12;\n\nattributes\x18\x03 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x04 \x01(\r\"X\n\tEntityRef\x12\x12\n\nschema_url\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0f\n\x07id_keys\x18\x03 \x03(\t\x12\x18\n\x10\x64\x65scription_keys\x18\x04 \x03(\tB{\n io.opentelemetry.proto.common.v1B\x0b\x43ommonProtoP\x01Z(go.opentelemetry.io/proto/otlp/common/v1\xaa\x02\x1dOpenTelemetry.Proto.Common.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.common.v1.common_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n io.opentelemetry.proto.common.v1B\013CommonProtoP\001Z(go.opentelemetry.io/proto/otlp/common/v1\252\002\035OpenTelemetry.Proto.Common.V1' - _globals['_ANYVALUE']._serialized_start=78 - _globals['_ANYVALUE']._serialized_end=379 - _globals['_ARRAYVALUE']._serialized_start=381 - _globals['_ARRAYVALUE']._serialized_end=450 - _globals['_KEYVALUELIST']._serialized_start=452 - _globals['_KEYVALUELIST']._serialized_end=523 - _globals['_KEYVALUE']._serialized_start=525 - _globals['_KEYVALUE']._serialized_end=626 - _globals['_INSTRUMENTATIONSCOPE']._serialized_start=629 - _globals['_INSTRUMENTATIONSCOPE']._serialized_end=777 - _globals['_ENTITYREF']._serialized_start=779 - _globals['_ENTITYREF']._serialized_end=867 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.common.v1.common_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.pyi deleted file mode 100644 index 205bba7510f..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.pyi +++ /dev/null @@ -1,280 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class AnyValue(google.protobuf.message.Message): - """Represents any type of attribute value. AnyValue may contain a - primitive value such as a string or integer or it may contain an arbitrary nested - object containing arrays, key-value lists and primitives. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STRING_VALUE_FIELD_NUMBER: builtins.int - BOOL_VALUE_FIELD_NUMBER: builtins.int - INT_VALUE_FIELD_NUMBER: builtins.int - DOUBLE_VALUE_FIELD_NUMBER: builtins.int - ARRAY_VALUE_FIELD_NUMBER: builtins.int - KVLIST_VALUE_FIELD_NUMBER: builtins.int - BYTES_VALUE_FIELD_NUMBER: builtins.int - STRING_VALUE_STRINDEX_FIELD_NUMBER: builtins.int - string_value: builtins.str - bool_value: builtins.bool - int_value: builtins.int - double_value: builtins.float - @property - def array_value(self) -> global___ArrayValue: ... - @property - def kvlist_value(self) -> global___KeyValueList: ... - bytes_value: builtins.bytes - string_value_strindex: builtins.int - """Reference to the string value in ProfilesDictionary.string_table. - - Note: This is currently used exclusively in the Profiling signal. - Implementers of OTLP receivers for signals other than Profiling should - treat the presence of this value as a non-fatal issue. - Log an error or warning indicating an unexpected field intended for the - Profiling signal and process the data as if this value were absent or - empty, ignoring its semantic content for the non-Profiling signal. - - Status: [Development] - """ - def __init__( - self, - *, - string_value: builtins.str = ..., - bool_value: builtins.bool = ..., - int_value: builtins.int = ..., - double_value: builtins.float = ..., - array_value: global___ArrayValue | None = ..., - kvlist_value: global___KeyValueList | None = ..., - bytes_value: builtins.bytes = ..., - string_value_strindex: builtins.int = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["array_value", b"array_value", "bool_value", b"bool_value", "bytes_value", b"bytes_value", "double_value", b"double_value", "int_value", b"int_value", "kvlist_value", b"kvlist_value", "string_value", b"string_value", "string_value_strindex", b"string_value_strindex", "value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["array_value", b"array_value", "bool_value", b"bool_value", "bytes_value", b"bytes_value", "double_value", b"double_value", "int_value", b"int_value", "kvlist_value", b"kvlist_value", "string_value", b"string_value", "string_value_strindex", b"string_value_strindex", "value", b"value"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["value", b"value"]) -> typing_extensions.Literal["string_value", "bool_value", "int_value", "double_value", "array_value", "kvlist_value", "bytes_value", "string_value_strindex"] | None: ... - -global___AnyValue = AnyValue - -@typing_extensions.final -class ArrayValue(google.protobuf.message.Message): - """ArrayValue is a list of AnyValue messages. We need ArrayValue as a message - since oneof in AnyValue does not allow repeated fields. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VALUES_FIELD_NUMBER: builtins.int - @property - def values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___AnyValue]: - """Array of values. The array may be empty (contain 0 elements).""" - def __init__( - self, - *, - values: collections.abc.Iterable[global___AnyValue] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["values", b"values"]) -> None: ... - -global___ArrayValue = ArrayValue - -@typing_extensions.final -class KeyValueList(google.protobuf.message.Message): - """KeyValueList is a list of KeyValue messages. We need KeyValueList as a message - since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need - a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to - avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches - are semantically equivalent. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VALUES_FIELD_NUMBER: builtins.int - @property - def values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___KeyValue]: - """A collection of key/value pairs of key-value pairs. The list may be empty (may - contain 0 elements). - - The keys MUST be unique (it is not allowed to have more than one - value with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - def __init__( - self, - *, - values: collections.abc.Iterable[global___KeyValue] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["values", b"values"]) -> None: ... - -global___KeyValueList = KeyValueList - -@typing_extensions.final -class KeyValue(google.protobuf.message.Message): - """Represents a key-value pair that is used to store Span attributes, Link - attributes, etc. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - KEY_STRINDEX_FIELD_NUMBER: builtins.int - key: builtins.str - """The key name of the pair. - key_ref MUST NOT be set if key is used. - """ - @property - def value(self) -> global___AnyValue: - """The value of the pair.""" - key_strindex: builtins.int - """Reference to the string key in ProfilesDictionary.string_table. - key MUST NOT be set if key_strindex is used. - - Note: This is currently used exclusively in the Profiling signal. - Implementers of OTLP receivers for signals other than Profiling should - treat the presence of this key as a non-fatal issue. - Log an error or warning indicating an unexpected field intended for the - Profiling signal and process the data as if this value were absent or - empty, ignoring its semantic content for the non-Profiling signal. - - Status: [Development] - """ - def __init__( - self, - *, - key: builtins.str = ..., - value: global___AnyValue | None = ..., - key_strindex: builtins.int = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "key_strindex", b"key_strindex", "value", b"value"]) -> None: ... - -global___KeyValue = KeyValue - -@typing_extensions.final -class InstrumentationScope(google.protobuf.message.Message): - """InstrumentationScope is a message representing the instrumentation scope information - such as the fully qualified name and version. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - VERSION_FIELD_NUMBER: builtins.int - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - name: builtins.str - """A name denoting the Instrumentation scope. - An empty instrumentation scope name means the name is unknown. - """ - version: builtins.str - """Defines the version of the instrumentation scope. - An empty instrumentation scope version means the version is unknown. - """ - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___KeyValue]: - """Additional attributes that describe the scope. [Optional]. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - """The number of attributes that were discarded. Attributes - can be discarded because their keys are too long or because there are too many - attributes. If this value is 0, then no attributes were dropped. - """ - def __init__( - self, - *, - name: builtins.str = ..., - version: builtins.str = ..., - attributes: collections.abc.Iterable[global___KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "dropped_attributes_count", b"dropped_attributes_count", "name", b"name", "version", b"version"]) -> None: ... - -global___InstrumentationScope = InstrumentationScope - -@typing_extensions.final -class EntityRef(google.protobuf.message.Message): - """A reference to an Entity. - Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs. - - Status: [Development] - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SCHEMA_URL_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - ID_KEYS_FIELD_NUMBER: builtins.int - DESCRIPTION_KEYS_FIELD_NUMBER: builtins.int - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the entity data - is recorded in. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - - This schema_url applies to the data in this message and to the Resource attributes - referenced by id_keys and description_keys. - TODO: discuss if we are happy with this somewhat complicated definition of what - the schema_url applies to. - - This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs. - """ - type: builtins.str - """Defines the type of the entity. MUST not change during the lifetime of the entity. - For example: "service" or "host". This field is required and MUST not be empty - for valid entities. - """ - @property - def id_keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Attribute Keys that identify the entity. - MUST not change during the lifetime of the entity. The Id must contain at least one attribute. - These keys MUST exist in the containing {message}.attributes. - """ - @property - def description_keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Descriptive (non-identifying) attribute keys of the entity. - MAY change over the lifetime of the entity. MAY be empty. - These attribute keys are not part of entity's identity. - These keys MUST exist in the containing {message}.attributes. - """ - def __init__( - self, - *, - schema_url: builtins.str = ..., - type: builtins.str = ..., - id_keys: collections.abc.Iterable[builtins.str] | None = ..., - description_keys: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["description_keys", b"description_keys", "id_keys", b"id_keys", "schema_url", b"schema_url", "type", b"type"]) -> None: ... - -global___EntityRef = EntityRef diff --git a/opentelemetry-proto/src/opentelemetry/proto/logs/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/proto/logs/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.py index 3fe64e28961..5d2cc3ed509 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.py @@ -1,39 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/logs/v1/logs.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 -from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&opentelemetry/proto/logs/v1/logs.proto\x12\x1bopentelemetry.proto.logs.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"L\n\x08LogsData\x12@\n\rresource_logs\x18\x01 \x03(\x0b\x32).opentelemetry.proto.logs.v1.ResourceLogs\"\xa3\x01\n\x0cResourceLogs\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12:\n\nscope_logs\x18\x02 \x03(\x0b\x32&.opentelemetry.proto.logs.v1.ScopeLogs\x12\x12\n\nschema_url\x18\x03 \x01(\tJ\x06\x08\xe8\x07\x10\xe9\x07\"\xa0\x01\n\tScopeLogs\x12\x42\n\x05scope\x18\x01 \x01(\x0b\x32\x33.opentelemetry.proto.common.v1.InstrumentationScope\x12;\n\x0blog_records\x18\x02 \x03(\x0b\x32&.opentelemetry.proto.logs.v1.LogRecord\x12\x12\n\nschema_url\x18\x03 \x01(\t\"\x83\x03\n\tLogRecord\x12\x16\n\x0etime_unix_nano\x18\x01 \x01(\x06\x12\x1f\n\x17observed_time_unix_nano\x18\x0b \x01(\x06\x12\x44\n\x0fseverity_number\x18\x02 \x01(\x0e\x32+.opentelemetry.proto.logs.v1.SeverityNumber\x12\x15\n\rseverity_text\x18\x03 \x01(\t\x12\x35\n\x04\x62ody\x18\x05 \x01(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\x12;\n\nattributes\x18\x06 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x07 \x01(\r\x12\r\n\x05\x66lags\x18\x08 \x01(\x07\x12\x10\n\x08trace_id\x18\t \x01(\x0c\x12\x0f\n\x07span_id\x18\n \x01(\x0c\x12\x12\n\nevent_name\x18\x0c \x01(\tJ\x04\x08\x04\x10\x05*\xc3\x05\n\x0eSeverityNumber\x12\x1f\n\x1bSEVERITY_NUMBER_UNSPECIFIED\x10\x00\x12\x19\n\x15SEVERITY_NUMBER_TRACE\x10\x01\x12\x1a\n\x16SEVERITY_NUMBER_TRACE2\x10\x02\x12\x1a\n\x16SEVERITY_NUMBER_TRACE3\x10\x03\x12\x1a\n\x16SEVERITY_NUMBER_TRACE4\x10\x04\x12\x19\n\x15SEVERITY_NUMBER_DEBUG\x10\x05\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG2\x10\x06\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG3\x10\x07\x12\x1a\n\x16SEVERITY_NUMBER_DEBUG4\x10\x08\x12\x18\n\x14SEVERITY_NUMBER_INFO\x10\t\x12\x19\n\x15SEVERITY_NUMBER_INFO2\x10\n\x12\x19\n\x15SEVERITY_NUMBER_INFO3\x10\x0b\x12\x19\n\x15SEVERITY_NUMBER_INFO4\x10\x0c\x12\x18\n\x14SEVERITY_NUMBER_WARN\x10\r\x12\x19\n\x15SEVERITY_NUMBER_WARN2\x10\x0e\x12\x19\n\x15SEVERITY_NUMBER_WARN3\x10\x0f\x12\x19\n\x15SEVERITY_NUMBER_WARN4\x10\x10\x12\x19\n\x15SEVERITY_NUMBER_ERROR\x10\x11\x12\x1a\n\x16SEVERITY_NUMBER_ERROR2\x10\x12\x12\x1a\n\x16SEVERITY_NUMBER_ERROR3\x10\x13\x12\x1a\n\x16SEVERITY_NUMBER_ERROR4\x10\x14\x12\x19\n\x15SEVERITY_NUMBER_FATAL\x10\x15\x12\x1a\n\x16SEVERITY_NUMBER_FATAL2\x10\x16\x12\x1a\n\x16SEVERITY_NUMBER_FATAL3\x10\x17\x12\x1a\n\x16SEVERITY_NUMBER_FATAL4\x10\x18*Y\n\x0eLogRecordFlags\x12\x1f\n\x1bLOG_RECORD_FLAGS_DO_NOT_USE\x10\x00\x12&\n!LOG_RECORD_FLAGS_TRACE_FLAGS_MASK\x10\xff\x01\x42s\n\x1eio.opentelemetry.proto.logs.v1B\tLogsProtoP\x01Z&go.opentelemetry.io/proto/otlp/logs/v1\xaa\x02\x1bOpenTelemetry.Proto.Logs.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.logs.v1.logs_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\036io.opentelemetry.proto.logs.v1B\tLogsProtoP\001Z&go.opentelemetry.io/proto/otlp/logs/v1\252\002\033OpenTelemetry.Proto.Logs.V1' - _globals['_SEVERITYNUMBER']._serialized_start=961 - _globals['_SEVERITYNUMBER']._serialized_end=1668 - _globals['_LOGRECORDFLAGS']._serialized_start=1670 - _globals['_LOGRECORDFLAGS']._serialized_end=1759 - _globals['_LOGSDATA']._serialized_start=163 - _globals['_LOGSDATA']._serialized_end=239 - _globals['_RESOURCELOGS']._serialized_start=242 - _globals['_RESOURCELOGS']._serialized_end=405 - _globals['_SCOPELOGS']._serialized_start=408 - _globals['_SCOPELOGS']._serialized_end=568 - _globals['_LOGRECORD']._serialized_start=571 - _globals['_LOGRECORD']._serialized_end=958 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.logs.v1.logs_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.pyi deleted file mode 100644 index 03edd535b7c..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/logs/v1/logs_pb2.pyi +++ /dev/null @@ -1,365 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2020, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import opentelemetry.proto.common.v1.common_pb2 -import opentelemetry.proto.resource.v1.resource_pb2 -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -class _SeverityNumber: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _SeverityNumberEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_SeverityNumber.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SEVERITY_NUMBER_UNSPECIFIED: _SeverityNumber.ValueType # 0 - SEVERITY_NUMBER_TRACE: _SeverityNumber.ValueType # 1 - SEVERITY_NUMBER_TRACE2: _SeverityNumber.ValueType # 2 - SEVERITY_NUMBER_TRACE3: _SeverityNumber.ValueType # 3 - SEVERITY_NUMBER_TRACE4: _SeverityNumber.ValueType # 4 - SEVERITY_NUMBER_DEBUG: _SeverityNumber.ValueType # 5 - SEVERITY_NUMBER_DEBUG2: _SeverityNumber.ValueType # 6 - SEVERITY_NUMBER_DEBUG3: _SeverityNumber.ValueType # 7 - SEVERITY_NUMBER_DEBUG4: _SeverityNumber.ValueType # 8 - SEVERITY_NUMBER_INFO: _SeverityNumber.ValueType # 9 - SEVERITY_NUMBER_INFO2: _SeverityNumber.ValueType # 10 - SEVERITY_NUMBER_INFO3: _SeverityNumber.ValueType # 11 - SEVERITY_NUMBER_INFO4: _SeverityNumber.ValueType # 12 - SEVERITY_NUMBER_WARN: _SeverityNumber.ValueType # 13 - SEVERITY_NUMBER_WARN2: _SeverityNumber.ValueType # 14 - SEVERITY_NUMBER_WARN3: _SeverityNumber.ValueType # 15 - SEVERITY_NUMBER_WARN4: _SeverityNumber.ValueType # 16 - SEVERITY_NUMBER_ERROR: _SeverityNumber.ValueType # 17 - SEVERITY_NUMBER_ERROR2: _SeverityNumber.ValueType # 18 - SEVERITY_NUMBER_ERROR3: _SeverityNumber.ValueType # 19 - SEVERITY_NUMBER_ERROR4: _SeverityNumber.ValueType # 20 - SEVERITY_NUMBER_FATAL: _SeverityNumber.ValueType # 21 - SEVERITY_NUMBER_FATAL2: _SeverityNumber.ValueType # 22 - SEVERITY_NUMBER_FATAL3: _SeverityNumber.ValueType # 23 - SEVERITY_NUMBER_FATAL4: _SeverityNumber.ValueType # 24 - -class SeverityNumber(_SeverityNumber, metaclass=_SeverityNumberEnumTypeWrapper): - """Possible values for LogRecord.SeverityNumber.""" - -SEVERITY_NUMBER_UNSPECIFIED: SeverityNumber.ValueType # 0 -SEVERITY_NUMBER_TRACE: SeverityNumber.ValueType # 1 -SEVERITY_NUMBER_TRACE2: SeverityNumber.ValueType # 2 -SEVERITY_NUMBER_TRACE3: SeverityNumber.ValueType # 3 -SEVERITY_NUMBER_TRACE4: SeverityNumber.ValueType # 4 -SEVERITY_NUMBER_DEBUG: SeverityNumber.ValueType # 5 -SEVERITY_NUMBER_DEBUG2: SeverityNumber.ValueType # 6 -SEVERITY_NUMBER_DEBUG3: SeverityNumber.ValueType # 7 -SEVERITY_NUMBER_DEBUG4: SeverityNumber.ValueType # 8 -SEVERITY_NUMBER_INFO: SeverityNumber.ValueType # 9 -SEVERITY_NUMBER_INFO2: SeverityNumber.ValueType # 10 -SEVERITY_NUMBER_INFO3: SeverityNumber.ValueType # 11 -SEVERITY_NUMBER_INFO4: SeverityNumber.ValueType # 12 -SEVERITY_NUMBER_WARN: SeverityNumber.ValueType # 13 -SEVERITY_NUMBER_WARN2: SeverityNumber.ValueType # 14 -SEVERITY_NUMBER_WARN3: SeverityNumber.ValueType # 15 -SEVERITY_NUMBER_WARN4: SeverityNumber.ValueType # 16 -SEVERITY_NUMBER_ERROR: SeverityNumber.ValueType # 17 -SEVERITY_NUMBER_ERROR2: SeverityNumber.ValueType # 18 -SEVERITY_NUMBER_ERROR3: SeverityNumber.ValueType # 19 -SEVERITY_NUMBER_ERROR4: SeverityNumber.ValueType # 20 -SEVERITY_NUMBER_FATAL: SeverityNumber.ValueType # 21 -SEVERITY_NUMBER_FATAL2: SeverityNumber.ValueType # 22 -SEVERITY_NUMBER_FATAL3: SeverityNumber.ValueType # 23 -SEVERITY_NUMBER_FATAL4: SeverityNumber.ValueType # 24 -global___SeverityNumber = SeverityNumber - -class _LogRecordFlags: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _LogRecordFlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_LogRecordFlags.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - LOG_RECORD_FLAGS_DO_NOT_USE: _LogRecordFlags.ValueType # 0 - """The zero value for the enum. Should not be used for comparisons. - Instead use bitwise "and" with the appropriate mask as shown above. - """ - LOG_RECORD_FLAGS_TRACE_FLAGS_MASK: _LogRecordFlags.ValueType # 255 - """Bits 0-7 are used for trace flags.""" - -class LogRecordFlags(_LogRecordFlags, metaclass=_LogRecordFlagsEnumTypeWrapper): - """LogRecordFlags represents constants used to interpret the - LogRecord.flags field, which is protobuf 'fixed32' type and is to - be used as bit-fields. Each non-zero value defined in this enum is - a bit-mask. To extract the bit-field, for example, use an - expression like: - - (logRecord.flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK) - """ - -LOG_RECORD_FLAGS_DO_NOT_USE: LogRecordFlags.ValueType # 0 -"""The zero value for the enum. Should not be used for comparisons. -Instead use bitwise "and" with the appropriate mask as shown above. -""" -LOG_RECORD_FLAGS_TRACE_FLAGS_MASK: LogRecordFlags.ValueType # 255 -"""Bits 0-7 are used for trace flags.""" -global___LogRecordFlags = LogRecordFlags - -@typing_extensions.final -class LogsData(google.protobuf.message.Message): - """LogsData represents the logs data that can be stored in a persistent storage, - OR can be embedded by other protocols that transfer OTLP logs data but do not - implement the OTLP protocol. - - The main difference between this message and collector protocol is that - in this message there will not be any "control" or "metadata" specific to - OTLP protocol. - - When new fields are added into this message, the OTLP request MUST be updated - as well. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_LOGS_FIELD_NUMBER: builtins.int - @property - def resource_logs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ResourceLogs]: - """An array of ResourceLogs. - For data coming from a single resource this array will typically contain - one element. Intermediary nodes that receive data from multiple origins - typically batch the data before forwarding further and in that case this - array will contain multiple elements. - """ - def __init__( - self, - *, - resource_logs: collections.abc.Iterable[global___ResourceLogs] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_logs", b"resource_logs"]) -> None: ... - -global___LogsData = LogsData - -@typing_extensions.final -class ResourceLogs(google.protobuf.message.Message): - """A collection of ScopeLogs from a Resource.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_FIELD_NUMBER: builtins.int - SCOPE_LOGS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def resource(self) -> opentelemetry.proto.resource.v1.resource_pb2.Resource: - """The resource for the logs in this message. - If this field is not set then resource info is unknown. - """ - @property - def scope_logs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ScopeLogs]: - """A list of ScopeLogs that originate from a resource.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the resource data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "resource" field. It does not apply - to the data in the "scope_logs" field which have their own schema_url field. - """ - def __init__( - self, - *, - resource: opentelemetry.proto.resource.v1.resource_pb2.Resource | None = ..., - scope_logs: collections.abc.Iterable[global___ScopeLogs] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["resource", b"resource"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["resource", b"resource", "schema_url", b"schema_url", "scope_logs", b"scope_logs"]) -> None: ... - -global___ResourceLogs = ResourceLogs - -@typing_extensions.final -class ScopeLogs(google.protobuf.message.Message): - """A collection of Logs produced by a Scope.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SCOPE_FIELD_NUMBER: builtins.int - LOG_RECORDS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def scope(self) -> opentelemetry.proto.common.v1.common_pb2.InstrumentationScope: - """The instrumentation scope information for the logs in this message. - Semantically when InstrumentationScope isn't set, it is equivalent with - an empty instrumentation scope name (unknown). - """ - @property - def log_records(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___LogRecord]: - """A list of log records.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the log data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "scope" field and all logs in the - "log_records" field. - """ - def __init__( - self, - *, - scope: opentelemetry.proto.common.v1.common_pb2.InstrumentationScope | None = ..., - log_records: collections.abc.Iterable[global___LogRecord] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["scope", b"scope"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["log_records", b"log_records", "schema_url", b"schema_url", "scope", b"scope"]) -> None: ... - -global___ScopeLogs = ScopeLogs - -@typing_extensions.final -class LogRecord(google.protobuf.message.Message): - """A log record according to OpenTelemetry Log Data Model: - https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - OBSERVED_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - SEVERITY_NUMBER_FIELD_NUMBER: builtins.int - SEVERITY_TEXT_FIELD_NUMBER: builtins.int - BODY_FIELD_NUMBER: builtins.int - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - TRACE_ID_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - EVENT_NAME_FIELD_NUMBER: builtins.int - time_unix_nano: builtins.int - """time_unix_nano is the time when the event occurred. - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - Value of 0 indicates unknown or missing timestamp. - """ - observed_time_unix_nano: builtins.int - """Time when the event was observed by the collection system. - For events that originate in OpenTelemetry (e.g. using OpenTelemetry Logging SDK) - this timestamp is typically set at the generation time and is equal to Timestamp. - For events originating externally and collected by OpenTelemetry (e.g. using - Collector) this is the time when OpenTelemetry's code observed the event measured - by the clock of the OpenTelemetry code. This field MUST be set once the event is - observed by OpenTelemetry. - - For converting OpenTelemetry log data to formats that support only one timestamp or - when receiving OpenTelemetry log data by recipients that support only one timestamp - internally the following logic is recommended: - - Use time_unix_nano if it is present, otherwise use observed_time_unix_nano. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - Value of 0 indicates unknown or missing timestamp. - """ - severity_number: global___SeverityNumber.ValueType - """Numerical value of the severity, normalized to values described in Log Data Model. - [Optional]. - """ - severity_text: builtins.str - """The severity text (also known as log level). The original string representation as - it is known at the source. [Optional]. - """ - @property - def body(self) -> opentelemetry.proto.common.v1.common_pb2.AnyValue: - """A value containing the body of the log record. Can be for example a human-readable - string message (including multi-line) describing the event in a free form or it can - be a structured data composed of arrays and maps of other values. [Optional]. - """ - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """Additional attributes that describe the specific event occurrence. [Optional]. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - flags: builtins.int - """Flags, a bit field. 8 least significant bits are the trace flags as - defined in W3C Trace Context specification. 24 most significant bits are reserved - and must be set to 0. Readers must not assume that 24 most significant bits - will be zero and must correctly mask the bits when reading 8-bit trace flag (use - flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK). [Optional]. - """ - trace_id: builtins.bytes - """A unique identifier for a trace. All logs from the same trace share - the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR - of length other than 16 bytes is considered invalid (empty string in OTLP/JSON - is zero-length and thus is also invalid). - - This field is optional. - - The receivers SHOULD assume that the log record is not associated with a - trace if any of the following is true: - - the field is not present, - - the field contains an invalid value. - """ - span_id: builtins.bytes - """A unique identifier for a span within a trace, assigned when the span - is created. The ID is an 8-byte array. An ID with all zeroes OR of length - other than 8 bytes is considered invalid (empty string in OTLP/JSON - is zero-length and thus is also invalid). - - This field is optional. If the sender specifies a valid span_id then it SHOULD also - specify a valid trace_id. - - The receivers SHOULD assume that the log record is not associated with a - span if any of the following is true: - - the field is not present, - - the field contains an invalid value. - """ - event_name: builtins.str - """A unique identifier of event category/type. - All events with the same event_name are expected to conform to the same - schema for both their attributes and their body. - - Recommended to be fully qualified and short (no longer than 256 characters). - - Presence of event_name on the log record identifies this record - as an event. - - [Optional]. - """ - def __init__( - self, - *, - time_unix_nano: builtins.int = ..., - observed_time_unix_nano: builtins.int = ..., - severity_number: global___SeverityNumber.ValueType = ..., - severity_text: builtins.str = ..., - body: opentelemetry.proto.common.v1.common_pb2.AnyValue | None = ..., - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - flags: builtins.int = ..., - trace_id: builtins.bytes = ..., - span_id: builtins.bytes = ..., - event_name: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["body", b"body"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "body", b"body", "dropped_attributes_count", b"dropped_attributes_count", "event_name", b"event_name", "flags", b"flags", "observed_time_unix_nano", b"observed_time_unix_nano", "severity_number", b"severity_number", "severity_text", b"severity_text", "span_id", b"span_id", "time_unix_nano", b"time_unix_nano", "trace_id", b"trace_id"]) -> None: ... - -global___LogRecord = LogRecord diff --git a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py index a337a58476b..198854f8d36 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py @@ -1,63 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/metrics/v1/metrics.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 -from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,opentelemetry/proto/metrics/v1/metrics.proto\x12\x1eopentelemetry.proto.metrics.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"X\n\x0bMetricsData\x12I\n\x10resource_metrics\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.ResourceMetrics\"\xaf\x01\n\x0fResourceMetrics\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12\x43\n\rscope_metrics\x18\x02 \x03(\x0b\x32,.opentelemetry.proto.metrics.v1.ScopeMetrics\x12\x12\n\nschema_url\x18\x03 \x01(\tJ\x06\x08\xe8\x07\x10\xe9\x07\"\x9f\x01\n\x0cScopeMetrics\x12\x42\n\x05scope\x18\x01 \x01(\x0b\x32\x33.opentelemetry.proto.common.v1.InstrumentationScope\x12\x37\n\x07metrics\x18\x02 \x03(\x0b\x32&.opentelemetry.proto.metrics.v1.Metric\x12\x12\n\nschema_url\x18\x03 \x01(\t\"\xcd\x03\n\x06Metric\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04unit\x18\x03 \x01(\t\x12\x36\n\x05gauge\x18\x05 \x01(\x0b\x32%.opentelemetry.proto.metrics.v1.GaugeH\x00\x12\x32\n\x03sum\x18\x07 \x01(\x0b\x32#.opentelemetry.proto.metrics.v1.SumH\x00\x12>\n\thistogram\x18\t \x01(\x0b\x32).opentelemetry.proto.metrics.v1.HistogramH\x00\x12U\n\x15\x65xponential_histogram\x18\n \x01(\x0b\x32\x34.opentelemetry.proto.metrics.v1.ExponentialHistogramH\x00\x12:\n\x07summary\x18\x0b \x01(\x0b\x32\'.opentelemetry.proto.metrics.v1.SummaryH\x00\x12\x39\n\x08metadata\x18\x0c \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValueB\x06\n\x04\x64\x61taJ\x04\x08\x04\x10\x05J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\t\"M\n\x05Gauge\x12\x44\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.NumberDataPoint\"\xba\x01\n\x03Sum\x12\x44\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.NumberDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\x12\x14\n\x0cis_monotonic\x18\x03 \x01(\x08\"\xad\x01\n\tHistogram\x12G\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32\x32.opentelemetry.proto.metrics.v1.HistogramDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\"\xc3\x01\n\x14\x45xponentialHistogram\x12R\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32=.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\"P\n\x07Summary\x12\x45\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32\x30.opentelemetry.proto.metrics.v1.SummaryDataPoint\"\x86\x02\n\x0fNumberDataPoint\x12;\n\nattributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\x13\n\tas_double\x18\x04 \x01(\x01H\x00\x12\x10\n\x06\x61s_int\x18\x06 \x01(\x10H\x00\x12;\n\texemplars\x18\x05 \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\r\n\x05\x66lags\x18\x08 \x01(\rB\x07\n\x05valueJ\x04\x08\x01\x10\x02\"\xe6\x02\n\x12HistogramDataPoint\x12;\n\nattributes\x18\t \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x10\n\x03sum\x18\x05 \x01(\x01H\x00\x88\x01\x01\x12\x15\n\rbucket_counts\x18\x06 \x03(\x06\x12\x17\n\x0f\x65xplicit_bounds\x18\x07 \x03(\x01\x12;\n\texemplars\x18\x08 \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\r\n\x05\x66lags\x18\n \x01(\r\x12\x10\n\x03min\x18\x0b \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03max\x18\x0c \x01(\x01H\x02\x88\x01\x01\x42\x06\n\x04_sumB\x06\n\x04_minB\x06\n\x04_maxJ\x04\x08\x01\x10\x02\"\xda\x04\n\x1d\x45xponentialHistogramDataPoint\x12;\n\nattributes\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x10\n\x03sum\x18\x05 \x01(\x01H\x00\x88\x01\x01\x12\r\n\x05scale\x18\x06 \x01(\x11\x12\x12\n\nzero_count\x18\x07 \x01(\x06\x12W\n\x08positive\x18\x08 \x01(\x0b\x32\x45.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint.Buckets\x12W\n\x08negative\x18\t \x01(\x0b\x32\x45.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint.Buckets\x12\r\n\x05\x66lags\x18\n \x01(\r\x12;\n\texemplars\x18\x0b \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\x10\n\x03min\x18\x0c \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03max\x18\r \x01(\x01H\x02\x88\x01\x01\x12\x16\n\x0ezero_threshold\x18\x0e \x01(\x01\x1a\x30\n\x07\x42uckets\x12\x0e\n\x06offset\x18\x01 \x01(\x11\x12\x15\n\rbucket_counts\x18\x02 \x03(\x04\x42\x06\n\x04_sumB\x06\n\x04_minB\x06\n\x04_max\"\xc5\x02\n\x10SummaryDataPoint\x12;\n\nattributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x0b\n\x03sum\x18\x05 \x01(\x01\x12Y\n\x0fquantile_values\x18\x06 \x03(\x0b\x32@.opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtQuantile\x12\r\n\x05\x66lags\x18\x08 \x01(\r\x1a\x32\n\x0fValueAtQuantile\x12\x10\n\x08quantile\x18\x01 \x01(\x01\x12\r\n\x05value\x18\x02 \x01(\x01J\x04\x08\x01\x10\x02\"\xc1\x01\n\x08\x45xemplar\x12\x44\n\x13\x66iltered_attributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x16\n\x0etime_unix_nano\x18\x02 \x01(\x06\x12\x13\n\tas_double\x18\x03 \x01(\x01H\x00\x12\x10\n\x06\x61s_int\x18\x06 \x01(\x10H\x00\x12\x0f\n\x07span_id\x18\x04 \x01(\x0c\x12\x10\n\x08trace_id\x18\x05 \x01(\x0c\x42\x07\n\x05valueJ\x04\x08\x01\x10\x02*\x8c\x01\n\x16\x41ggregationTemporality\x12\'\n#AGGREGATION_TEMPORALITY_UNSPECIFIED\x10\x00\x12!\n\x1d\x41GGREGATION_TEMPORALITY_DELTA\x10\x01\x12&\n\"AGGREGATION_TEMPORALITY_CUMULATIVE\x10\x02*^\n\x0e\x44\x61taPointFlags\x12\x1f\n\x1b\x44\x41TA_POINT_FLAGS_DO_NOT_USE\x10\x00\x12+\n\'DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK\x10\x01\x42\x7f\n!io.opentelemetry.proto.metrics.v1B\x0cMetricsProtoP\x01Z)go.opentelemetry.io/proto/otlp/metrics/v1\xaa\x02\x1eOpenTelemetry.Proto.Metrics.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.metrics.v1.metrics_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n!io.opentelemetry.proto.metrics.v1B\014MetricsProtoP\001Z)go.opentelemetry.io/proto/otlp/metrics/v1\252\002\036OpenTelemetry.Proto.Metrics.V1' - _globals['_AGGREGATIONTEMPORALITY']._serialized_start=3546 - _globals['_AGGREGATIONTEMPORALITY']._serialized_end=3686 - _globals['_DATAPOINTFLAGS']._serialized_start=3688 - _globals['_DATAPOINTFLAGS']._serialized_end=3782 - _globals['_METRICSDATA']._serialized_start=172 - _globals['_METRICSDATA']._serialized_end=260 - _globals['_RESOURCEMETRICS']._serialized_start=263 - _globals['_RESOURCEMETRICS']._serialized_end=438 - _globals['_SCOPEMETRICS']._serialized_start=441 - _globals['_SCOPEMETRICS']._serialized_end=600 - _globals['_METRIC']._serialized_start=603 - _globals['_METRIC']._serialized_end=1064 - _globals['_GAUGE']._serialized_start=1066 - _globals['_GAUGE']._serialized_end=1143 - _globals['_SUM']._serialized_start=1146 - _globals['_SUM']._serialized_end=1332 - _globals['_HISTOGRAM']._serialized_start=1335 - _globals['_HISTOGRAM']._serialized_end=1508 - _globals['_EXPONENTIALHISTOGRAM']._serialized_start=1511 - _globals['_EXPONENTIALHISTOGRAM']._serialized_end=1706 - _globals['_SUMMARY']._serialized_start=1708 - _globals['_SUMMARY']._serialized_end=1788 - _globals['_NUMBERDATAPOINT']._serialized_start=1791 - _globals['_NUMBERDATAPOINT']._serialized_end=2053 - _globals['_HISTOGRAMDATAPOINT']._serialized_start=2056 - _globals['_HISTOGRAMDATAPOINT']._serialized_end=2414 - _globals['_EXPONENTIALHISTOGRAMDATAPOINT']._serialized_start=2417 - _globals['_EXPONENTIALHISTOGRAMDATAPOINT']._serialized_end=3019 - _globals['_EXPONENTIALHISTOGRAMDATAPOINT_BUCKETS']._serialized_start=2947 - _globals['_EXPONENTIALHISTOGRAMDATAPOINT_BUCKETS']._serialized_end=2995 - _globals['_SUMMARYDATAPOINT']._serialized_start=3022 - _globals['_SUMMARYDATAPOINT']._serialized_end=3347 - _globals['_SUMMARYDATAPOINT_VALUEATQUANTILE']._serialized_start=3291 - _globals['_SUMMARYDATAPOINT_VALUEATQUANTILE']._serialized_end=3341 - _globals['_EXEMPLAR']._serialized_start=3350 - _globals['_EXEMPLAR']._serialized_end=3543 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.metrics.v1.metrics_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.pyi deleted file mode 100644 index 0f374be93ee..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.pyi +++ /dev/null @@ -1,1177 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import opentelemetry.proto.common.v1.common_pb2 -import opentelemetry.proto.resource.v1.resource_pb2 -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -class _AggregationTemporality: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _AggregationTemporalityEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_AggregationTemporality.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - AGGREGATION_TEMPORALITY_UNSPECIFIED: _AggregationTemporality.ValueType # 0 - """UNSPECIFIED is the default AggregationTemporality, it MUST not be used.""" - AGGREGATION_TEMPORALITY_DELTA: _AggregationTemporality.ValueType # 1 - """DELTA is an AggregationTemporality for a metric aggregator which reports - changes since last report time. Successive metrics contain aggregation of - values from continuous and non-overlapping intervals. - - The values for a DELTA metric are based only on the time interval - associated with one measurement cycle. There is no dependency on - previous measurements like is the case for CUMULATIVE metrics. - - For example, consider a system measuring the number of requests that - it receives and reports the sum of these requests every second as a - DELTA metric: - - 1. The system starts receiving at time=t_0. - 2. A request is received, the system measures 1 request. - 3. A request is received, the system measures 1 request. - 4. A request is received, the system measures 1 request. - 5. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+1 with a value of 3. - 6. A request is received, the system measures 1 request. - 7. A request is received, the system measures 1 request. - 8. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0+1 to - t_0+2 with a value of 2. - """ - AGGREGATION_TEMPORALITY_CUMULATIVE: _AggregationTemporality.ValueType # 2 - """CUMULATIVE is an AggregationTemporality for a metric aggregator which - reports changes since a fixed start time. This means that current values - of a CUMULATIVE metric depend on all previous measurements since the - start time. Because of this, the sender is required to retain this state - in some form. If this state is lost or invalidated, the CUMULATIVE metric - values MUST be reset and a new fixed start time following the last - reported measurement time sent MUST be used. - - For example, consider a system measuring the number of requests that - it receives and reports the sum of these requests every second as a - CUMULATIVE metric: - - 1. The system starts receiving at time=t_0. - 2. A request is received, the system measures 1 request. - 3. A request is received, the system measures 1 request. - 4. A request is received, the system measures 1 request. - 5. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+1 with a value of 3. - 6. A request is received, the system measures 1 request. - 7. A request is received, the system measures 1 request. - 8. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+2 with a value of 5. - 9. The system experiences a fault and loses state. - 10. The system recovers and resumes receiving at time=t_1. - 11. A request is received, the system measures 1 request. - 12. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_1 to - t_0+1 with a value of 1. - - Note: Even though, when reporting changes since last report time, using - CUMULATIVE is valid, it is not recommended. This may cause problems for - systems that do not use start_time to determine when the aggregation - value was reset (e.g. Prometheus). - """ - -class AggregationTemporality(_AggregationTemporality, metaclass=_AggregationTemporalityEnumTypeWrapper): - """AggregationTemporality defines how a metric aggregator reports aggregated - values. It describes how those values relate to the time interval over - which they are aggregated. - """ - -AGGREGATION_TEMPORALITY_UNSPECIFIED: AggregationTemporality.ValueType # 0 -"""UNSPECIFIED is the default AggregationTemporality, it MUST not be used.""" -AGGREGATION_TEMPORALITY_DELTA: AggregationTemporality.ValueType # 1 -"""DELTA is an AggregationTemporality for a metric aggregator which reports -changes since last report time. Successive metrics contain aggregation of -values from continuous and non-overlapping intervals. - -The values for a DELTA metric are based only on the time interval -associated with one measurement cycle. There is no dependency on -previous measurements like is the case for CUMULATIVE metrics. - -For example, consider a system measuring the number of requests that -it receives and reports the sum of these requests every second as a -DELTA metric: - - 1. The system starts receiving at time=t_0. - 2. A request is received, the system measures 1 request. - 3. A request is received, the system measures 1 request. - 4. A request is received, the system measures 1 request. - 5. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+1 with a value of 3. - 6. A request is received, the system measures 1 request. - 7. A request is received, the system measures 1 request. - 8. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0+1 to - t_0+2 with a value of 2. -""" -AGGREGATION_TEMPORALITY_CUMULATIVE: AggregationTemporality.ValueType # 2 -"""CUMULATIVE is an AggregationTemporality for a metric aggregator which -reports changes since a fixed start time. This means that current values -of a CUMULATIVE metric depend on all previous measurements since the -start time. Because of this, the sender is required to retain this state -in some form. If this state is lost or invalidated, the CUMULATIVE metric -values MUST be reset and a new fixed start time following the last -reported measurement time sent MUST be used. - -For example, consider a system measuring the number of requests that -it receives and reports the sum of these requests every second as a -CUMULATIVE metric: - - 1. The system starts receiving at time=t_0. - 2. A request is received, the system measures 1 request. - 3. A request is received, the system measures 1 request. - 4. A request is received, the system measures 1 request. - 5. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+1 with a value of 3. - 6. A request is received, the system measures 1 request. - 7. A request is received, the system measures 1 request. - 8. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_0 to - t_0+2 with a value of 5. - 9. The system experiences a fault and loses state. - 10. The system recovers and resumes receiving at time=t_1. - 11. A request is received, the system measures 1 request. - 12. The 1 second collection cycle ends. A metric is exported for the - number of requests received over the interval of time t_1 to - t_0+1 with a value of 1. - -Note: Even though, when reporting changes since last report time, using -CUMULATIVE is valid, it is not recommended. This may cause problems for -systems that do not use start_time to determine when the aggregation -value was reset (e.g. Prometheus). -""" -global___AggregationTemporality = AggregationTemporality - -class _DataPointFlags: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _DataPointFlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_DataPointFlags.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - DATA_POINT_FLAGS_DO_NOT_USE: _DataPointFlags.ValueType # 0 - """The zero value for the enum. Should not be used for comparisons. - Instead use bitwise "and" with the appropriate mask as shown above. - """ - DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK: _DataPointFlags.ValueType # 1 - """This DataPoint is valid but has no recorded value. This value - SHOULD be used to reflect explicitly missing data in a series, as - for an equivalent to the Prometheus "staleness marker". - """ - -class DataPointFlags(_DataPointFlags, metaclass=_DataPointFlagsEnumTypeWrapper): - """DataPointFlags is defined as a protobuf 'uint32' type and is to be used as a - bit-field representing 32 distinct boolean flags. Each flag defined in this - enum is a bit-mask. To test the presence of a single flag in the flags of - a data point, for example, use an expression like: - - (point.flags & DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK) == DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK - """ - -DATA_POINT_FLAGS_DO_NOT_USE: DataPointFlags.ValueType # 0 -"""The zero value for the enum. Should not be used for comparisons. -Instead use bitwise "and" with the appropriate mask as shown above. -""" -DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK: DataPointFlags.ValueType # 1 -"""This DataPoint is valid but has no recorded value. This value -SHOULD be used to reflect explicitly missing data in a series, as -for an equivalent to the Prometheus "staleness marker". -""" -global___DataPointFlags = DataPointFlags - -@typing_extensions.final -class MetricsData(google.protobuf.message.Message): - """MetricsData represents the metrics data that can be stored in a persistent - storage, OR can be embedded by other protocols that transfer OTLP metrics - data but do not implement the OTLP protocol. - - MetricsData - └─── ResourceMetrics - ├── Resource - ├── SchemaURL - └── ScopeMetrics - ├── Scope - ├── SchemaURL - └── Metric - ├── Name - ├── Description - ├── Unit - └── data - ├── Gauge - ├── Sum - ├── Histogram - ├── ExponentialHistogram - └── Summary - - The main difference between this message and collector protocol is that - in this message there will not be any "control" or "metadata" specific to - OTLP protocol. - - When new fields are added into this message, the OTLP request MUST be updated - as well. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_METRICS_FIELD_NUMBER: builtins.int - @property - def resource_metrics(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ResourceMetrics]: - """An array of ResourceMetrics. - For data coming from a single resource this array will typically contain - one element. Intermediary nodes that receive data from multiple origins - typically batch the data before forwarding further and in that case this - array will contain multiple elements. - """ - def __init__( - self, - *, - resource_metrics: collections.abc.Iterable[global___ResourceMetrics] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_metrics", b"resource_metrics"]) -> None: ... - -global___MetricsData = MetricsData - -@typing_extensions.final -class ResourceMetrics(google.protobuf.message.Message): - """A collection of ScopeMetrics from a Resource.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_FIELD_NUMBER: builtins.int - SCOPE_METRICS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def resource(self) -> opentelemetry.proto.resource.v1.resource_pb2.Resource: - """The resource for the metrics in this message. - If this field is not set then no resource info is known. - """ - @property - def scope_metrics(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ScopeMetrics]: - """A list of metrics that originate from a resource.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the resource data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "resource" field. It does not apply - to the data in the "scope_metrics" field which have their own schema_url field. - """ - def __init__( - self, - *, - resource: opentelemetry.proto.resource.v1.resource_pb2.Resource | None = ..., - scope_metrics: collections.abc.Iterable[global___ScopeMetrics] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["resource", b"resource"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["resource", b"resource", "schema_url", b"schema_url", "scope_metrics", b"scope_metrics"]) -> None: ... - -global___ResourceMetrics = ResourceMetrics - -@typing_extensions.final -class ScopeMetrics(google.protobuf.message.Message): - """A collection of Metrics produced by an Scope.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SCOPE_FIELD_NUMBER: builtins.int - METRICS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def scope(self) -> opentelemetry.proto.common.v1.common_pb2.InstrumentationScope: - """The instrumentation scope information for the metrics in this message. - Semantically when InstrumentationScope isn't set, it is equivalent with - an empty instrumentation scope name (unknown). - """ - @property - def metrics(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Metric]: - """A list of metrics that originate from an instrumentation library.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the metric data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "scope" field and all metrics in the - "metrics" field. - """ - def __init__( - self, - *, - scope: opentelemetry.proto.common.v1.common_pb2.InstrumentationScope | None = ..., - metrics: collections.abc.Iterable[global___Metric] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["scope", b"scope"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["metrics", b"metrics", "schema_url", b"schema_url", "scope", b"scope"]) -> None: ... - -global___ScopeMetrics = ScopeMetrics - -@typing_extensions.final -class Metric(google.protobuf.message.Message): - """Defines a Metric which has one or more timeseries. The following is a - brief summary of the Metric data model. For more details, see: - - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md - - The data model and relation between entities is shown in the - diagram below. Here, "DataPoint" is the term used to refer to any - one of the specific data point value types, and "points" is the term used - to refer to any one of the lists of points contained in the Metric. - - - Metric is composed of a metadata and data. - - Metadata part contains a name, description, unit. - - Data is one of the possible types (Sum, Gauge, Histogram, Summary). - - DataPoint contains timestamps, attributes, and one of the possible value type - fields. - - Metric - +------------+ - |name | - |description | - |unit | +------------------------------------+ - |data |---> |Gauge, Sum, Histogram, Summary, ... | - +------------+ +------------------------------------+ - - Data [One of Gauge, Sum, Histogram, Summary, ...] - +-----------+ - |... | // Metadata about the Data. - |points |--+ - +-----------+ | - | +---------------------------+ - | |DataPoint 1 | - v |+------+------+ +------+ | - +-----+ ||label |label |...|label | | - | 1 |-->||value1|value2|...|valueN| | - +-----+ |+------+------+ +------+ | - | . | |+-----+ | - | . | ||value| | - | . | |+-----+ | - | . | +---------------------------+ - | . | . - | . | . - | . | . - | . | +---------------------------+ - | . | |DataPoint M | - +-----+ |+------+------+ +------+ | - | M |-->||label |label |...|label | | - +-----+ ||value1|value2|...|valueN| | - |+------+------+ +------+ | - |+-----+ | - ||value| | - |+-----+ | - +---------------------------+ - - Each distinct type of DataPoint represents the output of a specific - aggregation function, the result of applying the DataPoint's - associated function of to one or more measurements. - - All DataPoint types have three common fields: - - Attributes includes key-value pairs associated with the data point - - TimeUnixNano is required, set to the end time of the aggregation - - StartTimeUnixNano is optional, but strongly encouraged for DataPoints - having an AggregationTemporality field, as discussed below. - - Both TimeUnixNano and StartTimeUnixNano values are expressed as - UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - - # TimeUnixNano - - This field is required, having consistent interpretation across - DataPoint types. TimeUnixNano is the moment corresponding to when - the data point's aggregate value was captured. - - Data points with the 0 value for TimeUnixNano SHOULD be rejected - by consumers. - - # StartTimeUnixNano - - StartTimeUnixNano in general allows detecting when a sequence of - observations is unbroken. This field indicates to consumers the - start time for points with cumulative and delta - AggregationTemporality, and it should be included whenever possible - to support correct rate calculation. Although it may be omitted - when the start time is truly unknown, setting StartTimeUnixNano is - strongly encouraged. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - UNIT_FIELD_NUMBER: builtins.int - GAUGE_FIELD_NUMBER: builtins.int - SUM_FIELD_NUMBER: builtins.int - HISTOGRAM_FIELD_NUMBER: builtins.int - EXPONENTIAL_HISTOGRAM_FIELD_NUMBER: builtins.int - SUMMARY_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - name: builtins.str - """The name of the metric.""" - description: builtins.str - """A description of the metric, which can be used in documentation.""" - unit: builtins.str - """The unit in which the metric value is reported. Follows the format - described by https://unitsofmeasure.org/ucum.html. - """ - @property - def gauge(self) -> global___Gauge: ... - @property - def sum(self) -> global___Sum: ... - @property - def histogram(self) -> global___Histogram: ... - @property - def exponential_histogram(self) -> global___ExponentialHistogram: ... - @property - def summary(self) -> global___Summary: ... - @property - def metadata(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """Additional metadata attributes that describe the metric. [Optional]. - Attributes are non-identifying. - Consumers SHOULD NOT need to be aware of these attributes. - These attributes MAY be used to encode information allowing - for lossless roundtrip translation to / from another data model. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - def __init__( - self, - *, - name: builtins.str = ..., - description: builtins.str = ..., - unit: builtins.str = ..., - gauge: global___Gauge | None = ..., - sum: global___Sum | None = ..., - histogram: global___Histogram | None = ..., - exponential_histogram: global___ExponentialHistogram | None = ..., - summary: global___Summary | None = ..., - metadata: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["data", b"data", "exponential_histogram", b"exponential_histogram", "gauge", b"gauge", "histogram", b"histogram", "sum", b"sum", "summary", b"summary"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "description", b"description", "exponential_histogram", b"exponential_histogram", "gauge", b"gauge", "histogram", b"histogram", "metadata", b"metadata", "name", b"name", "sum", b"sum", "summary", b"summary", "unit", b"unit"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["data", b"data"]) -> typing_extensions.Literal["gauge", "sum", "histogram", "exponential_histogram", "summary"] | None: ... - -global___Metric = Metric - -@typing_extensions.final -class Gauge(google.protobuf.message.Message): - """Gauge represents the type of a scalar metric that always exports the - "current value" for every data point. It should be used for an "unknown" - aggregation. - - A Gauge does not support different aggregation temporalities. Given the - aggregation is unknown, points cannot be combined using the same - aggregation, regardless of aggregation temporalities. Therefore, - AggregationTemporality is not included. Consequently, this also means - "StartTimeUnixNano" is ignored for all data points. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_POINTS_FIELD_NUMBER: builtins.int - @property - def data_points(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NumberDataPoint]: - """The time series data points. - Note: Multiple time series may be included (same timestamp, different attributes). - """ - def __init__( - self, - *, - data_points: collections.abc.Iterable[global___NumberDataPoint] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data_points", b"data_points"]) -> None: ... - -global___Gauge = Gauge - -@typing_extensions.final -class Sum(google.protobuf.message.Message): - """Sum represents the type of a scalar metric that is calculated as a sum of all - reported measurements over a time interval. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_POINTS_FIELD_NUMBER: builtins.int - AGGREGATION_TEMPORALITY_FIELD_NUMBER: builtins.int - IS_MONOTONIC_FIELD_NUMBER: builtins.int - @property - def data_points(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NumberDataPoint]: - """The time series data points. - Note: Multiple time series may be included (same timestamp, different attributes). - """ - aggregation_temporality: global___AggregationTemporality.ValueType - """aggregation_temporality describes if the aggregator reports delta changes - since last report time, or cumulative changes since a fixed start time. - """ - is_monotonic: builtins.bool - """Represents whether the sum is monotonic.""" - def __init__( - self, - *, - data_points: collections.abc.Iterable[global___NumberDataPoint] | None = ..., - aggregation_temporality: global___AggregationTemporality.ValueType = ..., - is_monotonic: builtins.bool = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["aggregation_temporality", b"aggregation_temporality", "data_points", b"data_points", "is_monotonic", b"is_monotonic"]) -> None: ... - -global___Sum = Sum - -@typing_extensions.final -class Histogram(google.protobuf.message.Message): - """Histogram represents the type of a metric that is calculated by aggregating - as a Histogram of all reported measurements over a time interval. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_POINTS_FIELD_NUMBER: builtins.int - AGGREGATION_TEMPORALITY_FIELD_NUMBER: builtins.int - @property - def data_points(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___HistogramDataPoint]: - """The time series data points. - Note: Multiple time series may be included (same timestamp, different attributes). - """ - aggregation_temporality: global___AggregationTemporality.ValueType - """aggregation_temporality describes if the aggregator reports delta changes - since last report time, or cumulative changes since a fixed start time. - """ - def __init__( - self, - *, - data_points: collections.abc.Iterable[global___HistogramDataPoint] | None = ..., - aggregation_temporality: global___AggregationTemporality.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["aggregation_temporality", b"aggregation_temporality", "data_points", b"data_points"]) -> None: ... - -global___Histogram = Histogram - -@typing_extensions.final -class ExponentialHistogram(google.protobuf.message.Message): - """ExponentialHistogram represents the type of a metric that is calculated by aggregating - as a ExponentialHistogram of all reported double measurements over a time interval. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_POINTS_FIELD_NUMBER: builtins.int - AGGREGATION_TEMPORALITY_FIELD_NUMBER: builtins.int - @property - def data_points(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ExponentialHistogramDataPoint]: - """The time series data points. - Note: Multiple time series may be included (same timestamp, different attributes). - """ - aggregation_temporality: global___AggregationTemporality.ValueType - """aggregation_temporality describes if the aggregator reports delta changes - since last report time, or cumulative changes since a fixed start time. - """ - def __init__( - self, - *, - data_points: collections.abc.Iterable[global___ExponentialHistogramDataPoint] | None = ..., - aggregation_temporality: global___AggregationTemporality.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["aggregation_temporality", b"aggregation_temporality", "data_points", b"data_points"]) -> None: ... - -global___ExponentialHistogram = ExponentialHistogram - -@typing_extensions.final -class Summary(google.protobuf.message.Message): - """Summary metric data are used to convey quantile summaries, - a Prometheus (see: https://prometheus.io/docs/concepts/metric_types/#summary) - and OpenMetrics (see: https://github.com/prometheus/OpenMetrics/blob/4dbf6075567ab43296eed941037c12951faafb92/protos/prometheus.proto#L45) - data type. These data points cannot always be merged in a meaningful way. - While they can be useful in some applications, histogram data points are - recommended for new applications. - Summary metrics do not have an aggregation temporality field. This is - because the count and sum fields of a SummaryDataPoint are assumed to be - cumulative values. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_POINTS_FIELD_NUMBER: builtins.int - @property - def data_points(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___SummaryDataPoint]: - """The time series data points. - Note: Multiple time series may be included (same timestamp, different attributes). - """ - def __init__( - self, - *, - data_points: collections.abc.Iterable[global___SummaryDataPoint] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data_points", b"data_points"]) -> None: ... - -global___Summary = Summary - -@typing_extensions.final -class NumberDataPoint(google.protobuf.message.Message): - """NumberDataPoint is a single data point in a timeseries that describes the - time-varying scalar value of a metric. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ATTRIBUTES_FIELD_NUMBER: builtins.int - START_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - AS_DOUBLE_FIELD_NUMBER: builtins.int - AS_INT_FIELD_NUMBER: builtins.int - EXEMPLARS_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """The set of key/value pairs that uniquely identify the timeseries from - where this point belongs. The list may be empty (may contain 0 elements). - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - start_time_unix_nano: builtins.int - """StartTimeUnixNano is optional but strongly encouraged, see the - the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - time_unix_nano: builtins.int - """TimeUnixNano is required, see the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - as_double: builtins.float - as_int: builtins.int - @property - def exemplars(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Exemplar]: - """(Optional) List of exemplars collected from - measurements that were used to form the data point - """ - flags: builtins.int - """Flags that apply to this specific data point. See DataPointFlags - for the available flags and their meaning. - """ - def __init__( - self, - *, - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - start_time_unix_nano: builtins.int = ..., - time_unix_nano: builtins.int = ..., - as_double: builtins.float = ..., - as_int: builtins.int = ..., - exemplars: collections.abc.Iterable[global___Exemplar] | None = ..., - flags: builtins.int = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["as_double", b"as_double", "as_int", b"as_int", "value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["as_double", b"as_double", "as_int", b"as_int", "attributes", b"attributes", "exemplars", b"exemplars", "flags", b"flags", "start_time_unix_nano", b"start_time_unix_nano", "time_unix_nano", b"time_unix_nano", "value", b"value"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["value", b"value"]) -> typing_extensions.Literal["as_double", "as_int"] | None: ... - -global___NumberDataPoint = NumberDataPoint - -@typing_extensions.final -class HistogramDataPoint(google.protobuf.message.Message): - """HistogramDataPoint is a single data point in a timeseries that describes the - time-varying values of a Histogram. A Histogram contains summary statistics - for a population of values, it may optionally contain the distribution of - those values across a set of buckets. - - If the histogram contains the distribution of values, then both - "explicit_bounds" and "bucket counts" fields must be defined. - If the histogram does not contain the distribution of values, then both - "explicit_bounds" and "bucket_counts" must be omitted and only "count" and - "sum" are known. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ATTRIBUTES_FIELD_NUMBER: builtins.int - START_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - COUNT_FIELD_NUMBER: builtins.int - SUM_FIELD_NUMBER: builtins.int - BUCKET_COUNTS_FIELD_NUMBER: builtins.int - EXPLICIT_BOUNDS_FIELD_NUMBER: builtins.int - EXEMPLARS_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - MIN_FIELD_NUMBER: builtins.int - MAX_FIELD_NUMBER: builtins.int - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """The set of key/value pairs that uniquely identify the timeseries from - where this point belongs. The list may be empty (may contain 0 elements). - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - start_time_unix_nano: builtins.int - """StartTimeUnixNano is optional but strongly encouraged, see the - the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - time_unix_nano: builtins.int - """TimeUnixNano is required, see the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - count: builtins.int - """count is the number of values in the population. Must be non-negative. This - value must be equal to the sum of the "count" fields in buckets if a - histogram is provided. - """ - sum: builtins.float - """sum of the values in the population. If count is zero then this field - must be zero. - - Note: Sum should only be filled out when measuring non-negative discrete - events, and is assumed to be monotonic over the values of these events. - Negative events *can* be recorded, but sum should not be filled out when - doing so. This is specifically to enforce compatibility w/ OpenMetrics, - see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#histogram - """ - @property - def bucket_counts(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """bucket_counts is an optional field contains the count values of histogram - for each bucket. - - The sum of the bucket_counts must equal the value in the count field. - - The number of elements in bucket_counts array must be by one greater than - the number of elements in explicit_bounds array. The exception to this rule - is when the length of bucket_counts is 0, then the length of explicit_bounds - must also be 0. - """ - @property - def explicit_bounds(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: - """explicit_bounds specifies buckets with explicitly defined bounds for values. - - The boundaries for bucket at index i are: - - (-infinity, explicit_bounds[i]] for i == 0 - (explicit_bounds[i-1], explicit_bounds[i]] for 0 < i < size(explicit_bounds) - (explicit_bounds[i-1], +infinity) for i == size(explicit_bounds) - - The values in the explicit_bounds array must be strictly increasing. - - Histogram buckets are inclusive of their upper boundary, except the last - bucket where the boundary is at infinity. This format is intentionally - compatible with the OpenMetrics histogram definition. - - If bucket_counts length is 0 then explicit_bounds length must also be 0, - otherwise the data point is invalid. - """ - @property - def exemplars(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Exemplar]: - """(Optional) List of exemplars collected from - measurements that were used to form the data point - """ - flags: builtins.int - """Flags that apply to this specific data point. See DataPointFlags - for the available flags and their meaning. - """ - min: builtins.float - """min is the minimum value over (start_time, end_time].""" - max: builtins.float - """max is the maximum value over (start_time, end_time].""" - def __init__( - self, - *, - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - start_time_unix_nano: builtins.int = ..., - time_unix_nano: builtins.int = ..., - count: builtins.int = ..., - sum: builtins.float | None = ..., - bucket_counts: collections.abc.Iterable[builtins.int] | None = ..., - explicit_bounds: collections.abc.Iterable[builtins.float] | None = ..., - exemplars: collections.abc.Iterable[global___Exemplar] | None = ..., - flags: builtins.int = ..., - min: builtins.float | None = ..., - max: builtins.float | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_max", b"_max", "_min", b"_min", "_sum", b"_sum", "max", b"max", "min", b"min", "sum", b"sum"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_max", b"_max", "_min", b"_min", "_sum", b"_sum", "attributes", b"attributes", "bucket_counts", b"bucket_counts", "count", b"count", "exemplars", b"exemplars", "explicit_bounds", b"explicit_bounds", "flags", b"flags", "max", b"max", "min", b"min", "start_time_unix_nano", b"start_time_unix_nano", "sum", b"sum", "time_unix_nano", b"time_unix_nano"]) -> None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_max", b"_max"]) -> typing_extensions.Literal["max"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_min", b"_min"]) -> typing_extensions.Literal["min"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_sum", b"_sum"]) -> typing_extensions.Literal["sum"] | None: ... - -global___HistogramDataPoint = HistogramDataPoint - -@typing_extensions.final -class ExponentialHistogramDataPoint(google.protobuf.message.Message): - """ExponentialHistogramDataPoint is a single data point in a timeseries that describes the - time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains - summary statistics for a population of values, it may optionally contain the - distribution of those values across a set of buckets. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class Buckets(google.protobuf.message.Message): - """Buckets are a set of bucket counts, encoded in a contiguous array - of counts. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - OFFSET_FIELD_NUMBER: builtins.int - BUCKET_COUNTS_FIELD_NUMBER: builtins.int - offset: builtins.int - """The bucket index of the first entry in the bucket_counts array. - - Note: This uses a varint encoding as a simple form of compression. - """ - @property - def bucket_counts(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """An array of count values, where bucket_counts[i] carries - the count of the bucket at index (offset+i). bucket_counts[i] is the count - of values greater than base^(offset+i) and less than or equal to - base^(offset+i+1). - - Note: By contrast, the explicit HistogramDataPoint uses - fixed64. This field is expected to have many buckets, - especially zeros, so uint64 has been selected to ensure - varint encoding. - """ - def __init__( - self, - *, - offset: builtins.int = ..., - bucket_counts: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["bucket_counts", b"bucket_counts", "offset", b"offset"]) -> None: ... - - ATTRIBUTES_FIELD_NUMBER: builtins.int - START_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - COUNT_FIELD_NUMBER: builtins.int - SUM_FIELD_NUMBER: builtins.int - SCALE_FIELD_NUMBER: builtins.int - ZERO_COUNT_FIELD_NUMBER: builtins.int - POSITIVE_FIELD_NUMBER: builtins.int - NEGATIVE_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - EXEMPLARS_FIELD_NUMBER: builtins.int - MIN_FIELD_NUMBER: builtins.int - MAX_FIELD_NUMBER: builtins.int - ZERO_THRESHOLD_FIELD_NUMBER: builtins.int - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """The set of key/value pairs that uniquely identify the timeseries from - where this point belongs. The list may be empty (may contain 0 elements). - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - start_time_unix_nano: builtins.int - """StartTimeUnixNano is optional but strongly encouraged, see the - the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - time_unix_nano: builtins.int - """TimeUnixNano is required, see the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - count: builtins.int - """The number of values in the population. Must be - non-negative. This value must be equal to the sum of the "bucket_counts" - values in the positive and negative Buckets plus the "zero_count" field. - """ - sum: builtins.float - """The sum of the values in the population. If count is zero then this field - must be zero. - - Note: Sum should only be filled out when measuring non-negative discrete - events, and is assumed to be monotonic over the values of these events. - Negative events *can* be recorded, but sum should not be filled out when - doing so. This is specifically to enforce compatibility w/ OpenMetrics, - see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#histogram - """ - scale: builtins.int - """scale describes the resolution of the histogram. Boundaries are - located at powers of the base, where: - - base = (2^(2^-scale)) - - The histogram bucket identified by `index`, a signed integer, - contains values that are greater than (base^index) and - less than or equal to (base^(index+1)). - - The positive and negative ranges of the histogram are expressed - separately. Negative values are mapped by their absolute value - into the negative range using the same scale as the positive range. - - scale is not restricted by the protocol, as the permissible - values depend on the range of the data. - """ - zero_count: builtins.int - """The count of values that are either exactly zero or - within the region considered zero by the instrumentation at the - tolerated degree of precision. This bucket stores values that - cannot be expressed using the standard exponential formula as - well as values that have been rounded to zero. - - Implementations MAY consider the zero bucket to have probability - mass equal to (zero_count / count). - """ - @property - def positive(self) -> global___ExponentialHistogramDataPoint.Buckets: - """positive carries the positive range of exponential bucket counts.""" - @property - def negative(self) -> global___ExponentialHistogramDataPoint.Buckets: - """negative carries the negative range of exponential bucket counts.""" - flags: builtins.int - """Flags that apply to this specific data point. See DataPointFlags - for the available flags and their meaning. - """ - @property - def exemplars(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Exemplar]: - """(Optional) List of exemplars collected from - measurements that were used to form the data point - """ - min: builtins.float - """The minimum value over (start_time, end_time].""" - max: builtins.float - """The maximum value over (start_time, end_time].""" - zero_threshold: builtins.float - """ZeroThreshold may be optionally set to convey the width of the zero - region. Where the zero region is defined as the closed interval - [-ZeroThreshold, ZeroThreshold]. - When ZeroThreshold is 0, zero count bucket stores values that cannot be - expressed using the standard exponential formula as well as values that - have been rounded to zero. - """ - def __init__( - self, - *, - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - start_time_unix_nano: builtins.int = ..., - time_unix_nano: builtins.int = ..., - count: builtins.int = ..., - sum: builtins.float | None = ..., - scale: builtins.int = ..., - zero_count: builtins.int = ..., - positive: global___ExponentialHistogramDataPoint.Buckets | None = ..., - negative: global___ExponentialHistogramDataPoint.Buckets | None = ..., - flags: builtins.int = ..., - exemplars: collections.abc.Iterable[global___Exemplar] | None = ..., - min: builtins.float | None = ..., - max: builtins.float | None = ..., - zero_threshold: builtins.float = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_max", b"_max", "_min", b"_min", "_sum", b"_sum", "max", b"max", "min", b"min", "negative", b"negative", "positive", b"positive", "sum", b"sum"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_max", b"_max", "_min", b"_min", "_sum", b"_sum", "attributes", b"attributes", "count", b"count", "exemplars", b"exemplars", "flags", b"flags", "max", b"max", "min", b"min", "negative", b"negative", "positive", b"positive", "scale", b"scale", "start_time_unix_nano", b"start_time_unix_nano", "sum", b"sum", "time_unix_nano", b"time_unix_nano", "zero_count", b"zero_count", "zero_threshold", b"zero_threshold"]) -> None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_max", b"_max"]) -> typing_extensions.Literal["max"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_min", b"_min"]) -> typing_extensions.Literal["min"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_sum", b"_sum"]) -> typing_extensions.Literal["sum"] | None: ... - -global___ExponentialHistogramDataPoint = ExponentialHistogramDataPoint - -@typing_extensions.final -class SummaryDataPoint(google.protobuf.message.Message): - """SummaryDataPoint is a single data point in a timeseries that describes the - time-varying values of a Summary metric. The count and sum fields represent - cumulative values. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ValueAtQuantile(google.protobuf.message.Message): - """Represents the value at a given quantile of a distribution. - - To record Min and Max values following conventions are used: - - The 1.0 quantile is equivalent to the maximum value observed. - - The 0.0 quantile is equivalent to the minimum value observed. - - See the following issue for more context: - https://github.com/open-telemetry/opentelemetry-proto/issues/125 - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - QUANTILE_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - quantile: builtins.float - """The quantile of a distribution. Must be in the interval - [0.0, 1.0]. - """ - value: builtins.float - """The value at the given quantile of a distribution. - - Quantile values must NOT be negative. - """ - def __init__( - self, - *, - quantile: builtins.float = ..., - value: builtins.float = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["quantile", b"quantile", "value", b"value"]) -> None: ... - - ATTRIBUTES_FIELD_NUMBER: builtins.int - START_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - COUNT_FIELD_NUMBER: builtins.int - SUM_FIELD_NUMBER: builtins.int - QUANTILE_VALUES_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """The set of key/value pairs that uniquely identify the timeseries from - where this point belongs. The list may be empty (may contain 0 elements). - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - start_time_unix_nano: builtins.int - """StartTimeUnixNano is optional but strongly encouraged, see the - the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - time_unix_nano: builtins.int - """TimeUnixNano is required, see the detailed comments above Metric. - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - count: builtins.int - """count is the number of values in the population. Must be non-negative.""" - sum: builtins.float - """sum of the values in the population. If count is zero then this field - must be zero. - - Note: Sum should only be filled out when measuring non-negative discrete - events, and is assumed to be monotonic over the values of these events. - Negative events *can* be recorded, but sum should not be filled out when - doing so. This is specifically to enforce compatibility w/ OpenMetrics, - see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#summary - """ - @property - def quantile_values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___SummaryDataPoint.ValueAtQuantile]: - """(Optional) list of values at different quantiles of the distribution calculated - from the current snapshot. The quantiles must be strictly increasing. - """ - flags: builtins.int - """Flags that apply to this specific data point. See DataPointFlags - for the available flags and their meaning. - """ - def __init__( - self, - *, - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - start_time_unix_nano: builtins.int = ..., - time_unix_nano: builtins.int = ..., - count: builtins.int = ..., - sum: builtins.float = ..., - quantile_values: collections.abc.Iterable[global___SummaryDataPoint.ValueAtQuantile] | None = ..., - flags: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "count", b"count", "flags", b"flags", "quantile_values", b"quantile_values", "start_time_unix_nano", b"start_time_unix_nano", "sum", b"sum", "time_unix_nano", b"time_unix_nano"]) -> None: ... - -global___SummaryDataPoint = SummaryDataPoint - -@typing_extensions.final -class Exemplar(google.protobuf.message.Message): - """A representation of an exemplar, which is a sample input measurement. - Exemplars also hold information about the environment when the measurement - was recorded, for example the span and trace ID of the active span when the - exemplar was recorded. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - FILTERED_ATTRIBUTES_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - AS_DOUBLE_FIELD_NUMBER: builtins.int - AS_INT_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - TRACE_ID_FIELD_NUMBER: builtins.int - @property - def filtered_attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """The set of key/value pairs that were filtered out by the aggregator, but - recorded alongside the original measurement. Only key/value pairs that were - filtered out by the aggregator should be included - """ - time_unix_nano: builtins.int - """time_unix_nano is the exact time when this exemplar was recorded - - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January - 1970. - """ - as_double: builtins.float - as_int: builtins.int - span_id: builtins.bytes - """(Optional) Span ID of the exemplar trace. - span_id may be missing if the measurement is not recorded inside a trace - or if the trace is not sampled. - """ - trace_id: builtins.bytes - """(Optional) Trace ID of the exemplar trace. - trace_id may be missing if the measurement is not recorded inside a trace - or if the trace is not sampled. - """ - def __init__( - self, - *, - filtered_attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - time_unix_nano: builtins.int = ..., - as_double: builtins.float = ..., - as_int: builtins.int = ..., - span_id: builtins.bytes = ..., - trace_id: builtins.bytes = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["as_double", b"as_double", "as_int", b"as_int", "value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["as_double", b"as_double", "as_int", b"as_int", "filtered_attributes", b"filtered_attributes", "span_id", b"span_id", "time_unix_nano", b"time_unix_nano", "trace_id", b"trace_id", "value", b"value"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["value", b"value"]) -> typing_extensions.Literal["as_double", "as_int"] | None: ... - -global___Exemplar = Exemplar diff --git a/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.py deleted file mode 100644 index f78c6abd713..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/profiles/v1development/profiles.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 -from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n9opentelemetry/proto/profiles/v1development/profiles.proto\x12*opentelemetry.proto.profiles.v1development\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"\xf6\x03\n\x12ProfilesDictionary\x12J\n\rmapping_table\x18\x01 \x03(\x0b\x32\x33.opentelemetry.proto.profiles.v1development.Mapping\x12L\n\x0elocation_table\x18\x02 \x03(\x0b\x32\x34.opentelemetry.proto.profiles.v1development.Location\x12L\n\x0e\x66unction_table\x18\x03 \x03(\x0b\x32\x34.opentelemetry.proto.profiles.v1development.Function\x12\x44\n\nlink_table\x18\x04 \x03(\x0b\x32\x30.opentelemetry.proto.profiles.v1development.Link\x12\x14\n\x0cstring_table\x18\x05 \x03(\t\x12T\n\x0f\x61ttribute_table\x18\x06 \x03(\x0b\x32;.opentelemetry.proto.profiles.v1development.KeyValueAndUnit\x12\x46\n\x0bstack_table\x18\x07 \x03(\x0b\x32\x31.opentelemetry.proto.profiles.v1development.Stack\"\xbb\x01\n\x0cProfilesData\x12W\n\x11resource_profiles\x18\x01 \x03(\x0b\x32<.opentelemetry.proto.profiles.v1development.ResourceProfiles\x12R\n\ndictionary\x18\x02 \x01(\x0b\x32>.opentelemetry.proto.profiles.v1development.ProfilesDictionary\"\xbe\x01\n\x10ResourceProfiles\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12Q\n\x0escope_profiles\x18\x02 \x03(\x0b\x32\x39.opentelemetry.proto.profiles.v1development.ScopeProfiles\x12\x12\n\nschema_url\x18\x03 \x01(\tJ\x06\x08\xe8\x07\x10\xe9\x07\"\xae\x01\n\rScopeProfiles\x12\x42\n\x05scope\x18\x01 \x01(\x0b\x32\x33.opentelemetry.proto.common.v1.InstrumentationScope\x12\x45\n\x08profiles\x18\x02 \x03(\x0b\x32\x33.opentelemetry.proto.profiles.v1development.Profile\x12\x12\n\nschema_url\x18\x03 \x01(\t\"\xb1\x03\n\x07Profile\x12J\n\x0bsample_type\x18\x01 \x01(\x0b\x32\x35.opentelemetry.proto.profiles.v1development.ValueType\x12\x43\n\x07samples\x18\x02 \x03(\x0b\x32\x32.opentelemetry.proto.profiles.v1development.Sample\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\x15\n\rduration_nano\x18\x04 \x01(\x04\x12J\n\x0bperiod_type\x18\x05 \x01(\x0b\x32\x35.opentelemetry.proto.profiles.v1development.ValueType\x12\x0e\n\x06period\x18\x06 \x01(\x03\x12\x12\n\nprofile_id\x18\x07 \x01(\x0c\x12 \n\x18\x64ropped_attributes_count\x18\x08 \x01(\r\x12\x1f\n\x17original_payload_format\x18\t \x01(\t\x12\x18\n\x10original_payload\x18\n \x01(\x0c\x12\x19\n\x11\x61ttribute_indices\x18\x0b \x03(\x05\")\n\x04Link\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x0f\n\x07span_id\x18\x02 \x01(\x0c\"9\n\tValueType\x12\x15\n\rtype_strindex\x18\x01 \x01(\x05\x12\x15\n\runit_strindex\x18\x02 \x01(\x05\"z\n\x06Sample\x12\x13\n\x0bstack_index\x18\x01 \x01(\x05\x12\x19\n\x11\x61ttribute_indices\x18\x02 \x03(\x05\x12\x12\n\nlink_index\x18\x03 \x01(\x05\x12\x0e\n\x06values\x18\x04 \x03(\x03\x12\x1c\n\x14timestamps_unix_nano\x18\x05 \x03(\x06\"\x80\x01\n\x07Mapping\x12\x14\n\x0cmemory_start\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x02 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x03 \x01(\x04\x12\x19\n\x11\x66ilename_strindex\x18\x04 \x01(\x05\x12\x19\n\x11\x61ttribute_indices\x18\x05 \x03(\x05\"!\n\x05Stack\x12\x18\n\x10location_indices\x18\x01 \x03(\x05\"\x8e\x01\n\x08Location\x12\x15\n\rmapping_index\x18\x01 \x01(\x05\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\x04\x12?\n\x05lines\x18\x03 \x03(\x0b\x32\x30.opentelemetry.proto.profiles.v1development.Line\x12\x19\n\x11\x61ttribute_indices\x18\x04 \x03(\x05\"<\n\x04Line\x12\x16\n\x0e\x66unction_index\x18\x01 \x01(\x05\x12\x0c\n\x04line\x18\x02 \x01(\x03\x12\x0e\n\x06\x63olumn\x18\x03 \x01(\x03\"n\n\x08\x46unction\x12\x15\n\rname_strindex\x18\x01 \x01(\x05\x12\x1c\n\x14system_name_strindex\x18\x02 \x01(\x05\x12\x19\n\x11\x66ilename_strindex\x18\x03 \x01(\x05\x12\x12\n\nstart_line\x18\x04 \x01(\x03\"v\n\x0fKeyValueAndUnit\x12\x14\n\x0ckey_strindex\x18\x01 \x01(\x05\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\x12\x15\n\runit_strindex\x18\x03 \x01(\x05\x42\xa4\x01\n-io.opentelemetry.proto.profiles.v1developmentB\rProfilesProtoP\x01Z5go.opentelemetry.io/proto/otlp/profiles/v1development\xaa\x02*OpenTelemetry.Proto.Profiles.V1Developmentb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.profiles.v1development.profiles_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n-io.opentelemetry.proto.profiles.v1developmentB\rProfilesProtoP\001Z5go.opentelemetry.io/proto/otlp/profiles/v1development\252\002*OpenTelemetry.Proto.Profiles.V1Development' - _globals['_PROFILESDICTIONARY']._serialized_start=198 - _globals['_PROFILESDICTIONARY']._serialized_end=700 - _globals['_PROFILESDATA']._serialized_start=703 - _globals['_PROFILESDATA']._serialized_end=890 - _globals['_RESOURCEPROFILES']._serialized_start=893 - _globals['_RESOURCEPROFILES']._serialized_end=1083 - _globals['_SCOPEPROFILES']._serialized_start=1086 - _globals['_SCOPEPROFILES']._serialized_end=1260 - _globals['_PROFILE']._serialized_start=1263 - _globals['_PROFILE']._serialized_end=1696 - _globals['_LINK']._serialized_start=1698 - _globals['_LINK']._serialized_end=1739 - _globals['_VALUETYPE']._serialized_start=1741 - _globals['_VALUETYPE']._serialized_end=1798 - _globals['_SAMPLE']._serialized_start=1800 - _globals['_SAMPLE']._serialized_end=1922 - _globals['_MAPPING']._serialized_start=1925 - _globals['_MAPPING']._serialized_end=2053 - _globals['_STACK']._serialized_start=2055 - _globals['_STACK']._serialized_end=2088 - _globals['_LOCATION']._serialized_start=2091 - _globals['_LOCATION']._serialized_end=2233 - _globals['_LINE']._serialized_start=2235 - _globals['_LINE']._serialized_end=2295 - _globals['_FUNCTION']._serialized_start=2297 - _globals['_FUNCTION']._serialized_end=2407 - _globals['_KEYVALUEANDUNIT']._serialized_start=2409 - _globals['_KEYVALUEANDUNIT']._serialized_end=2527 -# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.pyi deleted file mode 100644 index 01938eded00..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/profiles/v1development/profiles_pb2.pyi +++ /dev/null @@ -1,800 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2023, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -This file includes work covered by the following copyright and permission notices: - -Copyright 2016 Google Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.common.v1.common_pb2 -import opentelemetry.proto.resource.v1.resource_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class ProfilesDictionary(google.protobuf.message.Message): - """ Relationships Diagram - - ┌──────────────────┐ LEGEND - │ ProfilesData │ ─────┐ - └──────────────────┘ │ ─────▶ embedded - │ │ - │ 1-n │ ─────▷ referenced by index - ▼ ▼ - ┌──────────────────┐ ┌────────────────────┐ - │ ResourceProfiles │ │ ProfilesDictionary │ - └──────────────────┘ └────────────────────┘ - │ - │ 1-n - ▼ - ┌──────────────────┐ - │ ScopeProfiles │ - └──────────────────┘ - │ - │ 1-n - ▼ - ┌──────────────────┐ - │ Profile │ - └──────────────────┘ - │ n-1 - │ 1-n ┌───────────────────────────────────────┐ - ▼ │ ▽ - ┌──────────────────┐ 1-n ┌─────────────────┐ ┌──────────┐ - │ Sample │ ──────▷ │ KeyValueAndUnit │ │ Link │ - └──────────────────┘ └─────────────────┘ └──────────┘ - │ △ △ - │ n-1 │ │ 1-n - ▽ │ │ - ┌──────────────────┐ │ │ - │ Stack │ │ │ - └──────────────────┘ │ │ - │ 1-n │ │ - │ 1-n ┌────────────────┘ │ - ▽ │ │ - ┌──────────────────┐ n-1 ┌─────────────┐ - │ Location │ ──────▷ │ Mapping │ - └──────────────────┘ └─────────────┘ - │ - │ 1-n - ▼ - ┌──────────────────┐ - │ Line │ - └──────────────────┘ - │ - │ 1-1 - ▽ - ┌──────────────────┐ - │ Function │ - └──────────────────┘ - - ProfilesDictionary represents the profiles data shared across the - entire message being sent. The following applies to all fields in this - message: - - - A dictionary is an array of dictionary items. Users of the dictionary - compactly reference the items using the index within the array. - - - A dictionary MUST have a zero value encoded as the first element. This - allows for _index fields pointing into the dictionary to use a 0 pointer - value to indicate 'null' / 'not set'. Unless otherwise defined, a 'zero - value' message value is one with all default field values, so as to - minimize wire encoded size. - - - There SHOULD NOT be dupes in a dictionary. The identity of dictionary - items is based on their value, recursively as needed. If a particular - implementation does emit duplicated items, it MUST NOT attempt to give them - meaning based on the index or order. A profile processor may remove - duplicate items and this MUST NOT have any observable effects for - consumers. - - - There SHOULD NOT be orphaned (unreferenced) items in a dictionary. A - profile processor may remove ("garbage-collect") orphaned items and this - MUST NOT have any observable effects for consumers. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MAPPING_TABLE_FIELD_NUMBER: builtins.int - LOCATION_TABLE_FIELD_NUMBER: builtins.int - FUNCTION_TABLE_FIELD_NUMBER: builtins.int - LINK_TABLE_FIELD_NUMBER: builtins.int - STRING_TABLE_FIELD_NUMBER: builtins.int - ATTRIBUTE_TABLE_FIELD_NUMBER: builtins.int - STACK_TABLE_FIELD_NUMBER: builtins.int - @property - def mapping_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Mapping]: - """Mappings from address ranges to the image/binary/library mapped - into that address range referenced by locations via Location.mapping_index. - - mapping_table[0] must always be zero value (Mapping{}) and present. - """ - @property - def location_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Location]: - """Locations referenced by samples via Stack.location_indices. - - location_table[0] must always be zero value (Location{}) and present. - """ - @property - def function_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Function]: - """Functions referenced by locations via Line.function_index. - - function_table[0] must always be zero value (Function{}) and present. - """ - @property - def link_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Link]: - """Links referenced by samples via Sample.link_index. - - link_table[0] must always be zero value (Link{}) and present. - """ - @property - def string_table(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """A common table for strings referenced by various messages. - - string_table[0] must always be "" and present. - """ - @property - def attribute_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___KeyValueAndUnit]: - """A common table for attributes referenced by the Profile, Sample, Mapping - and Location messages below through attribute_indices field. Each entry is - a key/value pair with an optional unit. Since this is a dictionary table, - multiple entries with the same key may be present, unlike direct attribute - tables like Resource.attributes. The referencing attribute_indices fields, - though, do maintain the key uniqueness requirement. - - It's recommended to use attributes for variables with bounded cardinality, - such as categorical variables - (https://en.wikipedia.org/wiki/Categorical_variable). Using an attribute of - a floating point type (e.g., CPU time) in a sample can quickly make every - attribute value unique, defeating the purpose of the dictionary and - impractically increasing the profile size. - - Examples of attributes: - "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" - "abc.com/myattribute": true - "allocation_size": 128 bytes - - attribute_table[0] must always be zero value (KeyValueAndUnit{}) and present. - """ - @property - def stack_table(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Stack]: - """Stacks referenced by samples via Sample.stack_index. - - stack_table[0] must always be zero value (Stack{}) and present. - """ - def __init__( - self, - *, - mapping_table: collections.abc.Iterable[global___Mapping] | None = ..., - location_table: collections.abc.Iterable[global___Location] | None = ..., - function_table: collections.abc.Iterable[global___Function] | None = ..., - link_table: collections.abc.Iterable[global___Link] | None = ..., - string_table: collections.abc.Iterable[builtins.str] | None = ..., - attribute_table: collections.abc.Iterable[global___KeyValueAndUnit] | None = ..., - stack_table: collections.abc.Iterable[global___Stack] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attribute_table", b"attribute_table", "function_table", b"function_table", "link_table", b"link_table", "location_table", b"location_table", "mapping_table", b"mapping_table", "stack_table", b"stack_table", "string_table", b"string_table"]) -> None: ... - -global___ProfilesDictionary = ProfilesDictionary - -@typing_extensions.final -class ProfilesData(google.protobuf.message.Message): - """ProfilesData represents the profiles data that can be stored in persistent storage, - OR can be embedded by other protocols that transfer OTLP profiles data but do not - implement the OTLP protocol. - - The main difference between this message and collector protocol is that - in this message there will not be any "control" or "metadata" specific to - OTLP protocol. - - When new fields are added into this message, the OTLP request MUST be updated - as well. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_PROFILES_FIELD_NUMBER: builtins.int - DICTIONARY_FIELD_NUMBER: builtins.int - @property - def resource_profiles(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ResourceProfiles]: - """An array of ResourceProfiles. - For data coming from an SDK profiler, this array will typically contain one - element. Host-level profilers will usually create one ResourceProfile per - container, as well as one additional ResourceProfile grouping all samples - from non-containerized processes. - Other resource groupings are possible as well and clarified via - Resource.attributes and semantic conventions. - Tools that visualize profiles should prefer displaying - resources_profiles[0].scope_profiles[0].profiles[0] by default. - """ - @property - def dictionary(self) -> global___ProfilesDictionary: - """One instance of ProfilesDictionary""" - def __init__( - self, - *, - resource_profiles: collections.abc.Iterable[global___ResourceProfiles] | None = ..., - dictionary: global___ProfilesDictionary | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["dictionary", b"dictionary"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["dictionary", b"dictionary", "resource_profiles", b"resource_profiles"]) -> None: ... - -global___ProfilesData = ProfilesData - -@typing_extensions.final -class ResourceProfiles(google.protobuf.message.Message): - """A collection of ScopeProfiles from a Resource.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_FIELD_NUMBER: builtins.int - SCOPE_PROFILES_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def resource(self) -> opentelemetry.proto.resource.v1.resource_pb2.Resource: - """The resource for the profiles in this message. - If this field is not set then no resource info is known. - """ - @property - def scope_profiles(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ScopeProfiles]: - """A list of ScopeProfiles that originate from a resource.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the resource data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "resource" field. It does not apply - to the data in the "scope_profiles" field which have their own schema_url field. - """ - def __init__( - self, - *, - resource: opentelemetry.proto.resource.v1.resource_pb2.Resource | None = ..., - scope_profiles: collections.abc.Iterable[global___ScopeProfiles] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["resource", b"resource"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["resource", b"resource", "schema_url", b"schema_url", "scope_profiles", b"scope_profiles"]) -> None: ... - -global___ResourceProfiles = ResourceProfiles - -@typing_extensions.final -class ScopeProfiles(google.protobuf.message.Message): - """A collection of Profiles produced by an InstrumentationScope.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SCOPE_FIELD_NUMBER: builtins.int - PROFILES_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def scope(self) -> opentelemetry.proto.common.v1.common_pb2.InstrumentationScope: - """The instrumentation scope information for the profiles in this message. - Semantically when InstrumentationScope isn't set, it is equivalent with - an empty instrumentation scope name (unknown). - """ - @property - def profiles(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Profile]: - """A list of Profiles that originate from an instrumentation scope.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the profile data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "scope" field and all profiles in the - "profiles" field. - """ - def __init__( - self, - *, - scope: opentelemetry.proto.common.v1.common_pb2.InstrumentationScope | None = ..., - profiles: collections.abc.Iterable[global___Profile] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["scope", b"scope"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["profiles", b"profiles", "schema_url", b"schema_url", "scope", b"scope"]) -> None: ... - -global___ScopeProfiles = ScopeProfiles - -@typing_extensions.final -class Profile(google.protobuf.message.Message): - """Profile is a common stacktrace profile format. - - Measurements represented with this format should follow the - following conventions: - - - Consumers should treat unset optional fields as if they had been - set with their default value. - - - When possible, measurements should be stored in "unsampled" form - that is most useful to humans. There should be enough - information present to determine the original sampled values. - - - The profile is represented as a set of samples, where each sample - references a stack trace which is a list of locations, each belonging - to a mapping. - - There is a N->1 relationship from Stack.location_indices entries to - locations. For every Stack.location_indices entry there must be a - unique Location with that index. - - There is an optional N->1 relationship from locations to - mappings. For every nonzero Location.mapping_id there must be a - unique Mapping with that index. - - Represents a complete profile, including sample types, samples, mappings to - binaries, stacks, locations, functions, string table, and additional - metadata. It modifies and annotates pprof Profile with OpenTelemetry - specific fields. - - Note that whilst fields in this message retain the name and field id from pprof in most cases - for ease of understanding data migration, it is not intended that pprof:Profile and - OpenTelemetry:Profile encoding be wire compatible. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SAMPLE_TYPE_FIELD_NUMBER: builtins.int - SAMPLES_FIELD_NUMBER: builtins.int - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - DURATION_NANO_FIELD_NUMBER: builtins.int - PERIOD_TYPE_FIELD_NUMBER: builtins.int - PERIOD_FIELD_NUMBER: builtins.int - PROFILE_ID_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - ORIGINAL_PAYLOAD_FORMAT_FIELD_NUMBER: builtins.int - ORIGINAL_PAYLOAD_FIELD_NUMBER: builtins.int - ATTRIBUTE_INDICES_FIELD_NUMBER: builtins.int - @property - def sample_type(self) -> global___ValueType: - """The type and unit of all Sample.values in this profile. - For a cpu or off-cpu profile this might be: - ["cpu","nanoseconds"] or ["off_cpu","nanoseconds"] - For a heap profile, this might be: - ["allocated_objects","count"] or ["allocated_space","bytes"], - """ - @property - def samples(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Sample]: - """The set of samples recorded in this profile.""" - time_unix_nano: builtins.int - """The following fields 3-12 are informational, do not affect - interpretation of results. - - Time of collection. Value is UNIX Epoch time in nanoseconds since 00:00:00 - UTC on 1 January 1970. - """ - duration_nano: builtins.int - """Duration of the profile. For instant profiles like live heap snapshot, the - duration can be zero but it may be preferable to set time_unix_nano to the - process start time and duration_nano to the relative time when the profile - was gathered. This ensures Sample.timestamps_unix_nano values such as - allocation timestamp fall into the profile time range. - """ - @property - def period_type(self) -> global___ValueType: - """The kind of events between sampled occurrences. - e.g [ "cpu","cycles" ] or [ "heap","bytes" ] - """ - period: builtins.int - """The number of events between sampled occurrences.""" - profile_id: builtins.bytes - """A globally unique identifier for a profile. The ID is a 16-byte array. An ID with - all zeroes is considered invalid. It may be used for deduplication and signal - correlation purposes. It is acceptable to treat two profiles with different values - in this field as not equal, even if they represented the same object at an earlier - time. - This field is optional; an ID may be assigned to an ID-less profile in a later step. - """ - dropped_attributes_count: builtins.int - """The number of attributes that were discarded. Attributes - can be discarded because their keys are too long or because there are too many - attributes. If this value is 0, then no attributes were dropped. - """ - original_payload_format: builtins.str - """The original payload format. See also original_payload. Optional, but the - format and the bytes must be set or unset together. - - The allowed values for the format string are defined by the OpenTelemetry - specification. Some examples are "jfr", "pprof", "linux_perf". - - The original payload may be optionally provided when the conversion to the - OLTP format was done from a different format with some loss of the fidelity - and the receiver may want to store the original payload to allow future - lossless export or reinterpretation. Some examples of the original format - are JFR (Java Flight Recorder), pprof, Linux perf. - - Even when the original payload is in a format that is semantically close to - OTLP, such as pprof, a conversion may still be lossy in some cases (e.g. if - the pprof file contains custom extensions or conventions). - - The original payload can be large in size, so including the original - payload should be configurable by the profiler or collector options. The - default behavior should be to not include the original payload. - """ - original_payload: builtins.bytes - """The original payload bytes. See also original_payload_format. Optional, but - format and the bytes must be set or unset together. - """ - @property - def attribute_indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """References to attributes in attribute_table. [optional]""" - def __init__( - self, - *, - sample_type: global___ValueType | None = ..., - samples: collections.abc.Iterable[global___Sample] | None = ..., - time_unix_nano: builtins.int = ..., - duration_nano: builtins.int = ..., - period_type: global___ValueType | None = ..., - period: builtins.int = ..., - profile_id: builtins.bytes = ..., - dropped_attributes_count: builtins.int = ..., - original_payload_format: builtins.str = ..., - original_payload: builtins.bytes = ..., - attribute_indices: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["period_type", b"period_type", "sample_type", b"sample_type"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["attribute_indices", b"attribute_indices", "dropped_attributes_count", b"dropped_attributes_count", "duration_nano", b"duration_nano", "original_payload", b"original_payload", "original_payload_format", b"original_payload_format", "period", b"period", "period_type", b"period_type", "profile_id", b"profile_id", "sample_type", b"sample_type", "samples", b"samples", "time_unix_nano", b"time_unix_nano"]) -> None: ... - -global___Profile = Profile - -@typing_extensions.final -class Link(google.protobuf.message.Message): - """A pointer from a profile Sample to a trace Span. - Connects a profile sample to a trace span, identified by unique trace and span IDs. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TRACE_ID_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - trace_id: builtins.bytes - """A unique identifier of a trace that this linked span is part of. The ID is a - 16-byte array. - """ - span_id: builtins.bytes - """A unique identifier for the linked span. The ID is an 8-byte array.""" - def __init__( - self, - *, - trace_id: builtins.bytes = ..., - span_id: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["span_id", b"span_id", "trace_id", b"trace_id"]) -> None: ... - -global___Link = Link - -@typing_extensions.final -class ValueType(google.protobuf.message.Message): - """ValueType describes the type and units of a value.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TYPE_STRINDEX_FIELD_NUMBER: builtins.int - UNIT_STRINDEX_FIELD_NUMBER: builtins.int - type_strindex: builtins.int - """Index into ProfilesDictionary.string_table.""" - unit_strindex: builtins.int - """Index into ProfilesDictionary.string_table.""" - def __init__( - self, - *, - type_strindex: builtins.int = ..., - unit_strindex: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["type_strindex", b"type_strindex", "unit_strindex", b"unit_strindex"]) -> None: ... - -global___ValueType = ValueType - -@typing_extensions.final -class Sample(google.protobuf.message.Message): - """Each Sample records values encountered in some program context. The program - context is typically a stack trace, perhaps augmented with auxiliary - information like the thread-id, some indicator of a higher level request - being handled etc. - - A Sample MUST have have at least one values or timestamps_unix_nano entry. If - both fields are populated, they MUST contain the same number of elements, and - the elements at the same index MUST refer to the same event. - - For the purposes of efficiently representing aggregated data observations, a Sample is regarded - as having a shared identity and an associated collection of per-observation data points. - Samples having the same identity SHOULD be combined by inserting timestamps and values to the data arrays. - - Examples of different ways ('shapes') of representing a sample with the total value of 10: - - Report of a stacktrace at 10 timestamps (consumers must assume the value is 1 for each point): - values: [] - timestamps_unix_nano: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - - Report of a stacktrace with an aggregated value without timestamps: - values: [10] - timestamps_unix_nano: [] - - Report of a stacktrace at 4 timestamps where each point records a specific value: - values: [2, 2, 3, 3] - timestamps_unix_nano: [1, 2, 3, 4] - - All Samples for a Profile SHOULD have the same shape, i.e. all data observation series should consistently - adopt the same data recording style. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STACK_INDEX_FIELD_NUMBER: builtins.int - ATTRIBUTE_INDICES_FIELD_NUMBER: builtins.int - LINK_INDEX_FIELD_NUMBER: builtins.int - VALUES_FIELD_NUMBER: builtins.int - TIMESTAMPS_UNIX_NANO_FIELD_NUMBER: builtins.int - stack_index: builtins.int - """A Sample's identity (i.e. 'primary key') is the tuple of {stack_index, set_of(attribute_indices), link_index} - - Reference to stack in ProfilesDictionary.stack_table. - """ - @property - def attribute_indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """References to attributes in ProfilesDictionary.attribute_table. [optional]""" - link_index: builtins.int - """Reference to link in ProfilesDictionary.link_table. [optional] - It can be unset / set to 0 if no link exists, as link_table[0] is always a 'null' default value. - """ - @property - def values(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """The following fields may contain per-observation data and do not form part of the Sample's identity. - - The type and unit of each value is defined by Profile.sample_type. - """ - @property - def timestamps_unix_nano(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """Timestamps associated with Sample. Value is UNIX Epoch time in nanoseconds - since 00:00:00 UTC on 1 January 1970. The timestamps should fall within the - [Profile.time_unix_nano, Profile.time_unix_nano + Profile.duration_nano) - time range. - """ - def __init__( - self, - *, - stack_index: builtins.int = ..., - attribute_indices: collections.abc.Iterable[builtins.int] | None = ..., - link_index: builtins.int = ..., - values: collections.abc.Iterable[builtins.int] | None = ..., - timestamps_unix_nano: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attribute_indices", b"attribute_indices", "link_index", b"link_index", "stack_index", b"stack_index", "timestamps_unix_nano", b"timestamps_unix_nano", "values", b"values"]) -> None: ... - -global___Sample = Sample - -@typing_extensions.final -class Mapping(google.protobuf.message.Message): - """Describes the mapping of a binary in memory, including its address range, - file offset, and metadata like build ID - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MEMORY_START_FIELD_NUMBER: builtins.int - MEMORY_LIMIT_FIELD_NUMBER: builtins.int - FILE_OFFSET_FIELD_NUMBER: builtins.int - FILENAME_STRINDEX_FIELD_NUMBER: builtins.int - ATTRIBUTE_INDICES_FIELD_NUMBER: builtins.int - memory_start: builtins.int - """Address at which the binary (or DLL) is loaded into memory.""" - memory_limit: builtins.int - """The limit of the address range occupied by this mapping.""" - file_offset: builtins.int - """Offset in the binary that corresponds to the first mapped address.""" - filename_strindex: builtins.int - """The object this entry is loaded from. This can be a filename on - disk for the main binary and shared libraries, or virtual - abstractions like "[vdso]". - Index into ProfilesDictionary.string_table. - """ - @property - def attribute_indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """References to attributes in ProfilesDictionary.attribute_table. [optional]""" - def __init__( - self, - *, - memory_start: builtins.int = ..., - memory_limit: builtins.int = ..., - file_offset: builtins.int = ..., - filename_strindex: builtins.int = ..., - attribute_indices: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attribute_indices", b"attribute_indices", "file_offset", b"file_offset", "filename_strindex", b"filename_strindex", "memory_limit", b"memory_limit", "memory_start", b"memory_start"]) -> None: ... - -global___Mapping = Mapping - -@typing_extensions.final -class Stack(google.protobuf.message.Message): - """A Stack represents a stack trace as a list of locations.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - LOCATION_INDICES_FIELD_NUMBER: builtins.int - @property - def location_indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """References to locations in ProfilesDictionary.location_table. - The first location is the leaf frame. - """ - def __init__( - self, - *, - location_indices: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["location_indices", b"location_indices"]) -> None: ... - -global___Stack = Stack - -@typing_extensions.final -class Location(google.protobuf.message.Message): - """Describes function and line table debug information.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MAPPING_INDEX_FIELD_NUMBER: builtins.int - ADDRESS_FIELD_NUMBER: builtins.int - LINES_FIELD_NUMBER: builtins.int - ATTRIBUTE_INDICES_FIELD_NUMBER: builtins.int - mapping_index: builtins.int - """Reference to mapping in ProfilesDictionary.mapping_table. - It can be unset / set to 0 if the mapping is unknown or not applicable for - this profile type, as mapping_table[0] is always a 'null' default mapping. - """ - address: builtins.int - """The instruction address for this location, if available. It - should be within [Mapping.memory_start...Mapping.memory_limit] - for the corresponding mapping. A non-leaf address may be in the - middle of a call instruction. It is up to display tools to find - the beginning of the instruction if necessary. - """ - @property - def lines(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Line]: - """Multiple line indicates this location has inlined functions, - where the last entry represents the caller into which the - preceding entries were inlined. - - E.g., if memcpy() is inlined into printf: - lines[0].function_name == "memcpy" - lines[1].function_name == "printf" - """ - @property - def attribute_indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: - """References to attributes in ProfilesDictionary.attribute_table. [optional]""" - def __init__( - self, - *, - mapping_index: builtins.int = ..., - address: builtins.int = ..., - lines: collections.abc.Iterable[global___Line] | None = ..., - attribute_indices: collections.abc.Iterable[builtins.int] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "attribute_indices", b"attribute_indices", "lines", b"lines", "mapping_index", b"mapping_index"]) -> None: ... - -global___Location = Location - -@typing_extensions.final -class Line(google.protobuf.message.Message): - """Details a specific line in a source code, linked to a function.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - FUNCTION_INDEX_FIELD_NUMBER: builtins.int - LINE_FIELD_NUMBER: builtins.int - COLUMN_FIELD_NUMBER: builtins.int - function_index: builtins.int - """Reference to function in ProfilesDictionary.function_table.""" - line: builtins.int - """Line number in source code. 0 means unset.""" - column: builtins.int - """Column number in source code. 0 means unset.""" - def __init__( - self, - *, - function_index: builtins.int = ..., - line: builtins.int = ..., - column: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["column", b"column", "function_index", b"function_index", "line", b"line"]) -> None: ... - -global___Line = Line - -@typing_extensions.final -class Function(google.protobuf.message.Message): - """Describes a function, including its human-readable name, system name, - source file, and starting line number in the source. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_STRINDEX_FIELD_NUMBER: builtins.int - SYSTEM_NAME_STRINDEX_FIELD_NUMBER: builtins.int - FILENAME_STRINDEX_FIELD_NUMBER: builtins.int - START_LINE_FIELD_NUMBER: builtins.int - name_strindex: builtins.int - """The function name. Empty string if not available.""" - system_name_strindex: builtins.int - """Function name, as identified by the system. For instance, - it can be a C++ mangled name. Empty string if not available. - """ - filename_strindex: builtins.int - """Source file containing the function. Empty string if not available.""" - start_line: builtins.int - """Line number in source file. 0 means unset.""" - def __init__( - self, - *, - name_strindex: builtins.int = ..., - system_name_strindex: builtins.int = ..., - filename_strindex: builtins.int = ..., - start_line: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["filename_strindex", b"filename_strindex", "name_strindex", b"name_strindex", "start_line", b"start_line", "system_name_strindex", b"system_name_strindex"]) -> None: ... - -global___Function = Function - -@typing_extensions.final -class KeyValueAndUnit(google.protobuf.message.Message): - """A custom 'dictionary native' style of encoding attributes which is more convenient - for profiles than opentelemetry.proto.common.v1.KeyValue - Specifically, uses the string table for keys and allows optional unit information. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_STRINDEX_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - UNIT_STRINDEX_FIELD_NUMBER: builtins.int - key_strindex: builtins.int - """The index into the string table for the attribute's key.""" - @property - def value(self) -> opentelemetry.proto.common.v1.common_pb2.AnyValue: - """The value of the attribute.""" - unit_strindex: builtins.int - """The index into the string table for the attribute's unit. - zero indicates implicit (by semconv) or non-defined unit. - """ - def __init__( - self, - *, - key_strindex: builtins.int = ..., - value: opentelemetry.proto.common.v1.common_pb2.AnyValue | None = ..., - unit_strindex: builtins.int = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key_strindex", b"key_strindex", "unit_strindex", b"unit_strindex", "value", b"value"]) -> None: ... - -global___KeyValueAndUnit = KeyValueAndUnit diff --git a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py index f7066fcf7ac..a63c51ed52e 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py @@ -1,28 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/resource/v1/resource.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.opentelemetry/proto/resource/v1/resource.proto\x12\x1fopentelemetry.proto.resource.v1\x1a*opentelemetry/proto/common/v1/common.proto\"\xa8\x01\n\x08Resource\x12;\n\nattributes\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x02 \x01(\r\x12=\n\x0b\x65ntity_refs\x18\x03 \x03(\x0b\x32(.opentelemetry.proto.common.v1.EntityRefB\x83\x01\n\"io.opentelemetry.proto.resource.v1B\rResourceProtoP\x01Z*go.opentelemetry.io/proto/otlp/resource/v1\xaa\x02\x1fOpenTelemetry.Proto.Resource.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.resource.v1.resource_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\"io.opentelemetry.proto.resource.v1B\rResourceProtoP\001Z*go.opentelemetry.io/proto/otlp/resource/v1\252\002\037OpenTelemetry.Proto.Resource.V1' - _globals['_RESOURCE']._serialized_start=128 - _globals['_RESOURCE']._serialized_end=296 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.resource.v1.resource_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.pyi deleted file mode 100644 index 61472c538e1..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.pyi +++ /dev/null @@ -1,70 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.message -import opentelemetry.proto.common.v1.common_pb2 -import sys - -if sys.version_info >= (3, 8): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class Resource(google.protobuf.message.Message): - """Resource information.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - ENTITY_REFS_FIELD_NUMBER: builtins.int - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """Set of attributes that describe the resource. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - """The number of dropped attributes. If the value is 0, then - no attributes were dropped. - """ - @property - def entity_refs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.EntityRef]: - """Set of entities that participate in this Resource. - - Note: keys in the references MUST exist in attributes of this message. - - Status: [Development] - """ - def __init__( - self, - *, - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - entity_refs: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.EntityRef] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "dropped_attributes_count", b"dropped_attributes_count", "entity_refs", b"entity_refs"]) -> None: ... - -global___Resource = Resource diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py index 61a2d0fadd1..8e2aeea213d 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py +++ b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py @@ -1,47 +1,5 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opentelemetry/proto/trace/v1/trace.proto -# Protobuf Python Version: 5.26.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 -_sym_db = _symbol_database.Default() - -from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 -from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(opentelemetry/proto/trace/v1/trace.proto\x12\x1copentelemetry.proto.trace.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"Q\n\nTracesData\x12\x43\n\x0eresource_spans\x18\x01 \x03(\x0b\x32+.opentelemetry.proto.trace.v1.ResourceSpans\"\xa7\x01\n\rResourceSpans\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12=\n\x0bscope_spans\x18\x02 \x03(\x0b\x32(.opentelemetry.proto.trace.v1.ScopeSpans\x12\x12\n\nschema_url\x18\x03 \x01(\tJ\x06\x08\xe8\x07\x10\xe9\x07\"\x97\x01\n\nScopeSpans\x12\x42\n\x05scope\x18\x01 \x01(\x0b\x32\x33.opentelemetry.proto.common.v1.InstrumentationScope\x12\x31\n\x05spans\x18\x02 \x03(\x0b\x32\".opentelemetry.proto.trace.v1.Span\x12\x12\n\nschema_url\x18\x03 \x01(\t\"\x84\x08\n\x04Span\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x0f\n\x07span_id\x18\x02 \x01(\x0c\x12\x13\n\x0btrace_state\x18\x03 \x01(\t\x12\x16\n\x0eparent_span_id\x18\x04 \x01(\x0c\x12\r\n\x05\x66lags\x18\x10 \x01(\x07\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x39\n\x04kind\x18\x06 \x01(\x0e\x32+.opentelemetry.proto.trace.v1.Span.SpanKind\x12\x1c\n\x14start_time_unix_nano\x18\x07 \x01(\x06\x12\x1a\n\x12\x65nd_time_unix_nano\x18\x08 \x01(\x06\x12;\n\nattributes\x18\t \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\n \x01(\r\x12\x38\n\x06\x65vents\x18\x0b \x03(\x0b\x32(.opentelemetry.proto.trace.v1.Span.Event\x12\x1c\n\x14\x64ropped_events_count\x18\x0c \x01(\r\x12\x36\n\x05links\x18\r \x03(\x0b\x32\'.opentelemetry.proto.trace.v1.Span.Link\x12\x1b\n\x13\x64ropped_links_count\x18\x0e \x01(\r\x12\x34\n\x06status\x18\x0f \x01(\x0b\x32$.opentelemetry.proto.trace.v1.Status\x1a\x8c\x01\n\x05\x45vent\x12\x16\n\x0etime_unix_nano\x18\x01 \x01(\x06\x12\x0c\n\x04name\x18\x02 \x01(\t\x12;\n\nattributes\x18\x03 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x04 \x01(\r\x1a\xac\x01\n\x04Link\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x0f\n\x07span_id\x18\x02 \x01(\x0c\x12\x13\n\x0btrace_state\x18\x03 \x01(\t\x12;\n\nattributes\x18\x04 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x05 \x01(\r\x12\r\n\x05\x66lags\x18\x06 \x01(\x07\"\x99\x01\n\x08SpanKind\x12\x19\n\x15SPAN_KIND_UNSPECIFIED\x10\x00\x12\x16\n\x12SPAN_KIND_INTERNAL\x10\x01\x12\x14\n\x10SPAN_KIND_SERVER\x10\x02\x12\x14\n\x10SPAN_KIND_CLIENT\x10\x03\x12\x16\n\x12SPAN_KIND_PRODUCER\x10\x04\x12\x16\n\x12SPAN_KIND_CONSUMER\x10\x05\"\xae\x01\n\x06Status\x12\x0f\n\x07message\x18\x02 \x01(\t\x12=\n\x04\x63ode\x18\x03 \x01(\x0e\x32/.opentelemetry.proto.trace.v1.Status.StatusCode\"N\n\nStatusCode\x12\x15\n\x11STATUS_CODE_UNSET\x10\x00\x12\x12\n\x0eSTATUS_CODE_OK\x10\x01\x12\x15\n\x11STATUS_CODE_ERROR\x10\x02J\x04\x08\x01\x10\x02*\x9c\x01\n\tSpanFlags\x12\x19\n\x15SPAN_FLAGS_DO_NOT_USE\x10\x00\x12 \n\x1bSPAN_FLAGS_TRACE_FLAGS_MASK\x10\xff\x01\x12*\n%SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK\x10\x80\x02\x12&\n!SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK\x10\x80\x04\x42w\n\x1fio.opentelemetry.proto.trace.v1B\nTraceProtoP\x01Z\'go.opentelemetry.io/proto/otlp/trace/v1\xaa\x02\x1cOpenTelemetry.Proto.Trace.V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opentelemetry.proto.trace.v1.trace_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\n\037io.opentelemetry.proto.trace.v1B\nTraceProtoP\001Z\'go.opentelemetry.io/proto/otlp/trace/v1\252\002\034OpenTelemetry.Proto.Trace.V1' - _globals['_SPANFLAGS']._serialized_start=1782 - _globals['_SPANFLAGS']._serialized_end=1938 - _globals['_TRACESDATA']._serialized_start=166 - _globals['_TRACESDATA']._serialized_end=247 - _globals['_RESOURCESPANS']._serialized_start=250 - _globals['_RESOURCESPANS']._serialized_end=417 - _globals['_SCOPESPANS']._serialized_start=420 - _globals['_SCOPESPANS']._serialized_end=571 - _globals['_SPAN']._serialized_start=574 - _globals['_SPAN']._serialized_end=1602 - _globals['_SPAN_EVENT']._serialized_start=1131 - _globals['_SPAN_EVENT']._serialized_end=1271 - _globals['_SPAN_LINK']._serialized_start=1274 - _globals['_SPAN_LINK']._serialized_end=1446 - _globals['_SPAN_SPANKIND']._serialized_start=1449 - _globals['_SPAN_SPANKIND']._serialized_end=1602 - _globals['_STATUS']._serialized_start=1605 - _globals['_STATUS']._serialized_end=1779 - _globals['_STATUS_STATUSCODE']._serialized_start=1695 - _globals['_STATUS_STATUSCODE']._serialized_end=1773 -# @@protoc_insertion_point(module_scope) +from opentelemetry._proto.trace.v1.trace_pb2 import * # noqa: F401,F403 diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.pyi b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.pyi deleted file mode 100644 index e21336f03c6..00000000000 --- a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.pyi +++ /dev/null @@ -1,586 +0,0 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file -Copyright 2019, OpenTelemetry Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import opentelemetry.proto.common.v1.common_pb2 -import opentelemetry.proto.resource.v1.resource_pb2 -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -class _SpanFlags: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _SpanFlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_SpanFlags.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SPAN_FLAGS_DO_NOT_USE: _SpanFlags.ValueType # 0 - """The zero value for the enum. Should not be used for comparisons. - Instead use bitwise "and" with the appropriate mask as shown above. - """ - SPAN_FLAGS_TRACE_FLAGS_MASK: _SpanFlags.ValueType # 255 - """Bits 0-7 are used for trace flags.""" - SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK: _SpanFlags.ValueType # 256 - """Bits 8 and 9 are used to indicate that the parent span or link span is remote. - Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known. - Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote. - """ - SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK: _SpanFlags.ValueType # 512 - -class SpanFlags(_SpanFlags, metaclass=_SpanFlagsEnumTypeWrapper): - """SpanFlags represents constants used to interpret the - Span.flags field, which is protobuf 'fixed32' type and is to - be used as bit-fields. Each non-zero value defined in this enum is - a bit-mask. To extract the bit-field, for example, use an - expression like: - - (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK) - - See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. - - Note that Span flags were introduced in version 1.1 of the - OpenTelemetry protocol. Older Span producers do not set this - field, consequently consumers should not rely on the absence of a - particular flag bit to indicate the presence of a particular feature. - """ - -SPAN_FLAGS_DO_NOT_USE: SpanFlags.ValueType # 0 -"""The zero value for the enum. Should not be used for comparisons. -Instead use bitwise "and" with the appropriate mask as shown above. -""" -SPAN_FLAGS_TRACE_FLAGS_MASK: SpanFlags.ValueType # 255 -"""Bits 0-7 are used for trace flags.""" -SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK: SpanFlags.ValueType # 256 -"""Bits 8 and 9 are used to indicate that the parent span or link span is remote. -Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known. -Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote. -""" -SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK: SpanFlags.ValueType # 512 -global___SpanFlags = SpanFlags - -@typing_extensions.final -class TracesData(google.protobuf.message.Message): - """TracesData represents the traces data that can be stored in a persistent storage, - OR can be embedded by other protocols that transfer OTLP traces data but do - not implement the OTLP protocol. - - The main difference between this message and collector protocol is that - in this message there will not be any "control" or "metadata" specific to - OTLP protocol. - - When new fields are added into this message, the OTLP request MUST be updated - as well. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_SPANS_FIELD_NUMBER: builtins.int - @property - def resource_spans(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ResourceSpans]: - """An array of ResourceSpans. - For data coming from a single resource this array will typically contain - one element. Intermediary nodes that receive data from multiple origins - typically batch the data before forwarding further and in that case this - array will contain multiple elements. - """ - def __init__( - self, - *, - resource_spans: collections.abc.Iterable[global___ResourceSpans] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["resource_spans", b"resource_spans"]) -> None: ... - -global___TracesData = TracesData - -@typing_extensions.final -class ResourceSpans(google.protobuf.message.Message): - """A collection of ScopeSpans from a Resource.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RESOURCE_FIELD_NUMBER: builtins.int - SCOPE_SPANS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def resource(self) -> opentelemetry.proto.resource.v1.resource_pb2.Resource: - """The resource for the spans in this message. - If this field is not set then no resource info is known. - """ - @property - def scope_spans(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ScopeSpans]: - """A list of ScopeSpans that originate from a resource.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the resource data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "resource" field. It does not apply - to the data in the "scope_spans" field which have their own schema_url field. - """ - def __init__( - self, - *, - resource: opentelemetry.proto.resource.v1.resource_pb2.Resource | None = ..., - scope_spans: collections.abc.Iterable[global___ScopeSpans] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["resource", b"resource"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["resource", b"resource", "schema_url", b"schema_url", "scope_spans", b"scope_spans"]) -> None: ... - -global___ResourceSpans = ResourceSpans - -@typing_extensions.final -class ScopeSpans(google.protobuf.message.Message): - """A collection of Spans produced by an InstrumentationScope.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SCOPE_FIELD_NUMBER: builtins.int - SPANS_FIELD_NUMBER: builtins.int - SCHEMA_URL_FIELD_NUMBER: builtins.int - @property - def scope(self) -> opentelemetry.proto.common.v1.common_pb2.InstrumentationScope: - """The instrumentation scope information for the spans in this message. - Semantically when InstrumentationScope isn't set, it is equivalent with - an empty instrumentation scope name (unknown). - """ - @property - def spans(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Span]: - """A list of Spans that originate from an instrumentation scope.""" - schema_url: builtins.str - """The Schema URL, if known. This is the identifier of the Schema that the span data - is recorded in. Notably, the last part of the URL path is the version number of the - schema: http[s]://server[:port]/path/. To learn more about Schema URL see - https://opentelemetry.io/docs/specs/otel/schemas/#schema-url - This schema_url applies to the data in the "scope" field and all spans and span - events in the "spans" field. - """ - def __init__( - self, - *, - scope: opentelemetry.proto.common.v1.common_pb2.InstrumentationScope | None = ..., - spans: collections.abc.Iterable[global___Span] | None = ..., - schema_url: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["scope", b"scope"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["schema_url", b"schema_url", "scope", b"scope", "spans", b"spans"]) -> None: ... - -global___ScopeSpans = ScopeSpans - -@typing_extensions.final -class Span(google.protobuf.message.Message): - """A Span represents a single operation performed by a single component of the system. - - The next available field id is 17. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _SpanKind: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _SpanKindEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Span._SpanKind.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SPAN_KIND_UNSPECIFIED: Span._SpanKind.ValueType # 0 - """Unspecified. Do NOT use as default. - Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. - """ - SPAN_KIND_INTERNAL: Span._SpanKind.ValueType # 1 - """Indicates that the span represents an internal operation within an application, - as opposed to an operation happening at the boundaries. Default value. - """ - SPAN_KIND_SERVER: Span._SpanKind.ValueType # 2 - """Indicates that the span covers server-side handling of an RPC or other - remote network request. - """ - SPAN_KIND_CLIENT: Span._SpanKind.ValueType # 3 - """Indicates that the span describes a request to some remote service.""" - SPAN_KIND_PRODUCER: Span._SpanKind.ValueType # 4 - """Indicates that the span describes a producer sending a message to a broker. - Unlike CLIENT and SERVER, there is often no direct critical path latency relationship - between producer and consumer spans. A PRODUCER span ends when the message was accepted - by the broker while the logical processing of the message might span a much longer time. - """ - SPAN_KIND_CONSUMER: Span._SpanKind.ValueType # 5 - """Indicates that the span describes consumer receiving a message from a broker. - Like the PRODUCER kind, there is often no direct critical path latency relationship - between producer and consumer spans. - """ - - class SpanKind(_SpanKind, metaclass=_SpanKindEnumTypeWrapper): - """SpanKind is the type of span. Can be used to specify additional relationships between spans - in addition to a parent/child relationship. - """ - - SPAN_KIND_UNSPECIFIED: Span.SpanKind.ValueType # 0 - """Unspecified. Do NOT use as default. - Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. - """ - SPAN_KIND_INTERNAL: Span.SpanKind.ValueType # 1 - """Indicates that the span represents an internal operation within an application, - as opposed to an operation happening at the boundaries. Default value. - """ - SPAN_KIND_SERVER: Span.SpanKind.ValueType # 2 - """Indicates that the span covers server-side handling of an RPC or other - remote network request. - """ - SPAN_KIND_CLIENT: Span.SpanKind.ValueType # 3 - """Indicates that the span describes a request to some remote service.""" - SPAN_KIND_PRODUCER: Span.SpanKind.ValueType # 4 - """Indicates that the span describes a producer sending a message to a broker. - Unlike CLIENT and SERVER, there is often no direct critical path latency relationship - between producer and consumer spans. A PRODUCER span ends when the message was accepted - by the broker while the logical processing of the message might span a much longer time. - """ - SPAN_KIND_CONSUMER: Span.SpanKind.ValueType # 5 - """Indicates that the span describes consumer receiving a message from a broker. - Like the PRODUCER kind, there is often no direct critical path latency relationship - between producer and consumer spans. - """ - - @typing_extensions.final - class Event(google.protobuf.message.Message): - """Event is a time-stamped annotation of the span, consisting of user-supplied - text description and key-value pairs. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - time_unix_nano: builtins.int - """The time the event occurred.""" - name: builtins.str - """The name of the event. - This field is semantically required to be set to non-empty string. - """ - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """A collection of attribute key/value pairs on the event. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - """The number of dropped attributes. If the value is 0, - then no attributes were dropped. - """ - def __init__( - self, - *, - time_unix_nano: builtins.int = ..., - name: builtins.str = ..., - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "dropped_attributes_count", b"dropped_attributes_count", "name", b"name", "time_unix_nano", b"time_unix_nano"]) -> None: ... - - @typing_extensions.final - class Link(google.protobuf.message.Message): - """A pointer from the current span to another span in the same trace or in a - different trace. For example, this can be used in batching operations, - where a single batch handler processes multiple requests from different - traces or when the handler receives a request from a different project. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TRACE_ID_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - TRACE_STATE_FIELD_NUMBER: builtins.int - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - trace_id: builtins.bytes - """A unique identifier of a trace that this linked span is part of. The ID is a - 16-byte array. - """ - span_id: builtins.bytes - """A unique identifier for the linked span. The ID is an 8-byte array.""" - trace_state: builtins.str - """The trace_state associated with the link.""" - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """A collection of attribute key/value pairs on the link. - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - """The number of dropped attributes. If the value is 0, - then no attributes were dropped. - """ - flags: builtins.int - """Flags, a bit field. - - Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace - Context specification. To read the 8-bit W3C trace flag, use - `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. - - See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. - - Bits 8 and 9 represent the 3 states of whether the link is remote. - The states are (unknown, is not remote, is remote). - To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. - To read whether the link is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. - - Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. - When creating new spans, bits 10-31 (most-significant 22-bits) MUST be zero. - - [Optional]. - """ - def __init__( - self, - *, - trace_id: builtins.bytes = ..., - span_id: builtins.bytes = ..., - trace_state: builtins.str = ..., - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - flags: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "dropped_attributes_count", b"dropped_attributes_count", "flags", b"flags", "span_id", b"span_id", "trace_id", b"trace_id", "trace_state", b"trace_state"]) -> None: ... - - TRACE_ID_FIELD_NUMBER: builtins.int - SPAN_ID_FIELD_NUMBER: builtins.int - TRACE_STATE_FIELD_NUMBER: builtins.int - PARENT_SPAN_ID_FIELD_NUMBER: builtins.int - FLAGS_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - KIND_FIELD_NUMBER: builtins.int - START_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - END_TIME_UNIX_NANO_FIELD_NUMBER: builtins.int - ATTRIBUTES_FIELD_NUMBER: builtins.int - DROPPED_ATTRIBUTES_COUNT_FIELD_NUMBER: builtins.int - EVENTS_FIELD_NUMBER: builtins.int - DROPPED_EVENTS_COUNT_FIELD_NUMBER: builtins.int - LINKS_FIELD_NUMBER: builtins.int - DROPPED_LINKS_COUNT_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - trace_id: builtins.bytes - """A unique identifier for a trace. All spans from the same trace share - the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR - of length other than 16 bytes is considered invalid (empty string in OTLP/JSON - is zero-length and thus is also invalid). - - This field is required. - """ - span_id: builtins.bytes - """A unique identifier for a span within a trace, assigned when the span - is created. The ID is an 8-byte array. An ID with all zeroes OR of length - other than 8 bytes is considered invalid (empty string in OTLP/JSON - is zero-length and thus is also invalid). - - This field is required. - """ - trace_state: builtins.str - """trace_state conveys information about request position in multiple distributed tracing graphs. - It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header - See also https://github.com/w3c/distributed-tracing for more details about this field. - """ - parent_span_id: builtins.bytes - """The `span_id` of this span's parent span. If this is a root span, then this - field must be empty. The ID is an 8-byte array. - """ - flags: builtins.int - """Flags, a bit field. - - Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace - Context specification. To read the 8-bit W3C trace flag, use - `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. - - See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. - - Bits 8 and 9 represent the 3 states of whether a span's parent - is remote. The states are (unknown, is not remote, is remote). - To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. - To read whether the span is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. - - When creating span messages, if the message is logically forwarded from another source - with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD - be copied as-is. If creating from a source that does not have an equivalent flags field - (such as a runtime representation of an OpenTelemetry span), the high 22 bits MUST - be set to zero. - Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. - - [Optional]. - """ - name: builtins.str - """A description of the span's operation. - - For example, the name can be a qualified method name or a file name - and a line number where the operation is called. A best practice is to use - the same display name at the same call point in an application. - This makes it easier to correlate spans in different traces. - - This field is semantically required to be set to non-empty string. - Empty value is equivalent to an unknown span name. - - This field is required. - """ - kind: global___Span.SpanKind.ValueType - """Distinguishes between spans generated in a particular context. For example, - two spans with the same name may be distinguished using `CLIENT` (caller) - and `SERVER` (callee) to identify queueing latency associated with the span. - """ - start_time_unix_nano: builtins.int - """The start time of the span. On the client side, this is the time - kept by the local machine where the span execution starts. On the server side, this - is the time when the server's application handler starts running. - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - - This field is semantically required and it is expected that end_time >= start_time. - """ - end_time_unix_nano: builtins.int - """The end time of the span. On the client side, this is the time - kept by the local machine where the span execution ends. On the server side, this - is the time when the server application handler stops running. - Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - - This field is semantically required and it is expected that end_time >= start_time. - """ - @property - def attributes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[opentelemetry.proto.common.v1.common_pb2.KeyValue]: - """A collection of key/value pairs. Note, global attributes - like server name can be set using the resource API. Examples of attributes: - - "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" - "/http/server_latency": 300 - "example.com/myattribute": true - "example.com/score": 10.239 - - Attribute keys MUST be unique (it is not allowed to have more than one - attribute with the same key). - The behavior of software that receives duplicated keys can be unpredictable. - """ - dropped_attributes_count: builtins.int - """The number of attributes that were discarded. Attributes - can be discarded because their keys are too long or because there are too many - attributes. If this value is 0, then no attributes were dropped. - """ - @property - def events(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Span.Event]: - """A collection of Event items.""" - dropped_events_count: builtins.int - """The number of dropped events. If the value is 0, then no - events were dropped. - """ - @property - def links(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Span.Link]: - """A collection of Links, which are references from this span to a span - in the same or different trace. - """ - dropped_links_count: builtins.int - """The number of dropped links after the maximum size was - enforced. If this value is 0, then no links were dropped. - """ - @property - def status(self) -> global___Status: - """An optional final status for this span. Semantically when Status isn't set, it means - span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). - """ - def __init__( - self, - *, - trace_id: builtins.bytes = ..., - span_id: builtins.bytes = ..., - trace_state: builtins.str = ..., - parent_span_id: builtins.bytes = ..., - flags: builtins.int = ..., - name: builtins.str = ..., - kind: global___Span.SpanKind.ValueType = ..., - start_time_unix_nano: builtins.int = ..., - end_time_unix_nano: builtins.int = ..., - attributes: collections.abc.Iterable[opentelemetry.proto.common.v1.common_pb2.KeyValue] | None = ..., - dropped_attributes_count: builtins.int = ..., - events: collections.abc.Iterable[global___Span.Event] | None = ..., - dropped_events_count: builtins.int = ..., - links: collections.abc.Iterable[global___Span.Link] | None = ..., - dropped_links_count: builtins.int = ..., - status: global___Status | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["status", b"status"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["attributes", b"attributes", "dropped_attributes_count", b"dropped_attributes_count", "dropped_events_count", b"dropped_events_count", "dropped_links_count", b"dropped_links_count", "end_time_unix_nano", b"end_time_unix_nano", "events", b"events", "flags", b"flags", "kind", b"kind", "links", b"links", "name", b"name", "parent_span_id", b"parent_span_id", "span_id", b"span_id", "start_time_unix_nano", b"start_time_unix_nano", "status", b"status", "trace_id", b"trace_id", "trace_state", b"trace_state"]) -> None: ... - -global___Span = Span - -@typing_extensions.final -class Status(google.protobuf.message.Message): - """The Status type defines a logical error model that is suitable for different - programming environments, including REST APIs and RPC APIs. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _StatusCode: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _StatusCodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Status._StatusCode.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - STATUS_CODE_UNSET: Status._StatusCode.ValueType # 0 - """The default status.""" - STATUS_CODE_OK: Status._StatusCode.ValueType # 1 - """The Span has been validated by an Application developer or Operator to - have completed successfully. - """ - STATUS_CODE_ERROR: Status._StatusCode.ValueType # 2 - """The Span contains an error.""" - - class StatusCode(_StatusCode, metaclass=_StatusCodeEnumTypeWrapper): - """For the semantics of status codes see - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status - """ - - STATUS_CODE_UNSET: Status.StatusCode.ValueType # 0 - """The default status.""" - STATUS_CODE_OK: Status.StatusCode.ValueType # 1 - """The Span has been validated by an Application developer or Operator to - have completed successfully. - """ - STATUS_CODE_ERROR: Status.StatusCode.ValueType # 2 - """The Span contains an error.""" - - MESSAGE_FIELD_NUMBER: builtins.int - CODE_FIELD_NUMBER: builtins.int - message: builtins.str - """A developer-facing human readable error message.""" - code: global___Status.StatusCode.ValueType - """The status code.""" - def __init__( - self, - *, - message: builtins.str = ..., - code: global___Status.StatusCode.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["code", b"code", "message", b"message"]) -> None: ... - -global___Status = Status diff --git a/opentelemetry-proto/src/opentelemetry/proto/version/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/version/__init__.py index 524a0260e55..1d05b21972c 100644 --- a/opentelemetry-proto/src/opentelemetry/proto/version/__init__.py +++ b/opentelemetry-proto/src/opentelemetry/proto/version/__init__.py @@ -1,4 +1,7 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -__version__ = "1.45.0.dev" + +from opentelemetry._proto.version import __version__ + +__all__ = ["__version__"] diff --git a/opentelemetry-proto/tests/equivalence/__init__.py b/opentelemetry-proto/tests/equivalence/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/__init__.py b/opentelemetry-proto/tests/equivalence/collector/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/logs/__init__.py b/opentelemetry-proto/tests/equivalence/collector/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/logs/v1/__init__.py b/opentelemetry-proto/tests/equivalence/collector/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/logs/v1/test_logs_service_pypb2.py b/opentelemetry-proto/tests/equivalence/collector/logs/v1/test_logs_service_pypb2.py new file mode 100644 index 00000000000..6b44cfc66a8 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/collector/logs/v1/test_logs_service_pypb2.py @@ -0,0 +1,67 @@ +from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( + ExportLogsServiceRequest as ProtoExportLogsServiceRequest, + ExportLogsServiceResponse as ProtoExportLogsServiceResponse, +) +from opentelemetry.proto.logs.v1.logs_pb2 import ( + ResourceLogs as ProtoResourceLogs, +) + +from opentelemetry._proto.collector.logs.v1.logs_service_pb2 import ( + ExportLogsServiceRequest, + ExportLogsServiceResponse, +) +from opentelemetry._proto.logs.v1.logs_pb2 import ResourceLogs + + +def test_export_response_empty() -> None: + assert ExportLogsServiceResponse().SerializeToString() == b"" + + +def test_export_response_matches_proto() -> None: + assert ( + ExportLogsServiceResponse().SerializeToString() + == ProtoExportLogsServiceResponse().SerializeToString() + ) + + +def test_export_request_empty() -> None: + assert ExportLogsServiceRequest().SerializeToString() == b"" + + +def test_export_request_empty_matches_proto() -> None: + assert ( + ExportLogsServiceRequest().SerializeToString() + == ProtoExportLogsServiceRequest().SerializeToString() + ) + + +def test_export_request_with_empty_resource_logs() -> None: + our = ExportLogsServiceRequest(resource_logs=[ResourceLogs()]) + proto = ProtoExportLogsServiceRequest(resource_logs=[ProtoResourceLogs()]) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_with_schema_url() -> None: + our = ExportLogsServiceRequest( + resource_logs=[ResourceLogs(schema_url="https://example.com/schema")] + ) + proto = ProtoExportLogsServiceRequest( + resource_logs=[ProtoResourceLogs(schema_url="https://example.com/schema")] + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_multiple_resource_logs() -> None: + our = ExportLogsServiceRequest( + resource_logs=[ + ResourceLogs(schema_url="schema-a"), + ResourceLogs(schema_url="schema-b"), + ] + ) + proto = ProtoExportLogsServiceRequest( + resource_logs=[ + ProtoResourceLogs(schema_url="schema-a"), + ProtoResourceLogs(schema_url="schema-b"), + ] + ) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/collector/metrics/__init__.py b/opentelemetry-proto/tests/equivalence/collector/metrics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/metrics/v1/__init__.py b/opentelemetry-proto/tests/equivalence/collector/metrics/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/metrics/v1/test_metrics_service_pypb2.py b/opentelemetry-proto/tests/equivalence/collector/metrics/v1/test_metrics_service_pypb2.py new file mode 100644 index 00000000000..3171161ef12 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/collector/metrics/v1/test_metrics_service_pypb2.py @@ -0,0 +1,67 @@ +from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( + ExportMetricsServiceRequest as ProtoExportMetricsServiceRequest, + ExportMetricsServiceResponse as ProtoExportMetricsServiceResponse, +) +from opentelemetry.proto.metrics.v1.metrics_pb2 import ( + ResourceMetrics as ProtoResourceMetrics, +) + +from opentelemetry._proto.collector.metrics.v1.metrics_service_pb2 import ( + ExportMetricsServiceRequest, + ExportMetricsServiceResponse, +) +from opentelemetry._proto.metrics.v1.metrics_pb2 import ResourceMetrics + + +def test_export_response_empty() -> None: + assert ExportMetricsServiceResponse().SerializeToString() == b"" + + +def test_export_response_matches_proto() -> None: + assert ( + ExportMetricsServiceResponse().SerializeToString() + == ProtoExportMetricsServiceResponse().SerializeToString() + ) + + +def test_export_request_empty() -> None: + assert ExportMetricsServiceRequest().SerializeToString() == b"" + + +def test_export_request_empty_matches_proto() -> None: + assert ( + ExportMetricsServiceRequest().SerializeToString() + == ProtoExportMetricsServiceRequest().SerializeToString() + ) + + +def test_export_request_with_empty_resource_metrics() -> None: + our = ExportMetricsServiceRequest(resource_metrics=[ResourceMetrics()]) + proto = ProtoExportMetricsServiceRequest(resource_metrics=[ProtoResourceMetrics()]) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_with_schema_url() -> None: + our = ExportMetricsServiceRequest( + resource_metrics=[ResourceMetrics(schema_url="https://example.com/schema")] + ) + proto = ProtoExportMetricsServiceRequest( + resource_metrics=[ProtoResourceMetrics(schema_url="https://example.com/schema")] + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_multiple_resource_metrics() -> None: + our = ExportMetricsServiceRequest( + resource_metrics=[ + ResourceMetrics(schema_url="schema-a"), + ResourceMetrics(schema_url="schema-b"), + ] + ) + proto = ProtoExportMetricsServiceRequest( + resource_metrics=[ + ProtoResourceMetrics(schema_url="schema-a"), + ProtoResourceMetrics(schema_url="schema-b"), + ] + ) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/collector/trace/__init__.py b/opentelemetry-proto/tests/equivalence/collector/trace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/trace/v1/__init__.py b/opentelemetry-proto/tests/equivalence/collector/trace/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/collector/trace/v1/test_trace_service_pypb2.py b/opentelemetry-proto/tests/equivalence/collector/trace/v1/test_trace_service_pypb2.py new file mode 100644 index 00000000000..691c2ae2478 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/collector/trace/v1/test_trace_service_pypb2.py @@ -0,0 +1,67 @@ +from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest as ProtoExportTraceServiceRequest, + ExportTraceServiceResponse as ProtoExportTraceServiceResponse, +) +from opentelemetry.proto.trace.v1.trace_pb2 import ( + ResourceSpans as ProtoResourceSpans, +) + +from opentelemetry._proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest, + ExportTraceServiceResponse, +) +from opentelemetry._proto.trace.v1.trace_pb2 import ResourceSpans + + +def test_export_response_empty() -> None: + assert ExportTraceServiceResponse().SerializeToString() == b"" + + +def test_export_response_matches_proto() -> None: + assert ( + ExportTraceServiceResponse().SerializeToString() + == ProtoExportTraceServiceResponse().SerializeToString() + ) + + +def test_export_request_empty() -> None: + assert ExportTraceServiceRequest().SerializeToString() == b"" + + +def test_export_request_empty_matches_proto() -> None: + assert ( + ExportTraceServiceRequest().SerializeToString() + == ProtoExportTraceServiceRequest().SerializeToString() + ) + + +def test_export_request_with_empty_resource_spans() -> None: + our = ExportTraceServiceRequest(resource_spans=[ResourceSpans()]) + proto = ProtoExportTraceServiceRequest(resource_spans=[ProtoResourceSpans()]) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_with_schema_url() -> None: + our = ExportTraceServiceRequest( + resource_spans=[ResourceSpans(schema_url="https://example.com/schema")] + ) + proto = ProtoExportTraceServiceRequest( + resource_spans=[ProtoResourceSpans(schema_url="https://example.com/schema")] + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_export_request_multiple_resource_spans() -> None: + our = ExportTraceServiceRequest( + resource_spans=[ + ResourceSpans(schema_url="schema-a"), + ResourceSpans(schema_url="schema-b"), + ] + ) + proto = ProtoExportTraceServiceRequest( + resource_spans=[ + ProtoResourceSpans(schema_url="schema-a"), + ProtoResourceSpans(schema_url="schema-b"), + ] + ) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/common/__init__.py b/opentelemetry-proto/tests/equivalence/common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/common/v1/__init__.py b/opentelemetry-proto/tests/equivalence/common/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/common/v1/test_common_pypb2.py b/opentelemetry-proto/tests/equivalence/common/v1/test_common_pypb2.py new file mode 100644 index 00000000000..67063117176 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/common/v1/test_common_pypb2.py @@ -0,0 +1,188 @@ +from opentelemetry.proto.common.v1.common_pb2 import ( + AnyValue as ProtoAnyValue, + ArrayValue as ProtoArrayValue, + InstrumentationScope as ProtoInstrumentationScope, + KeyValue as ProtoKeyValue, + KeyValueList as ProtoKeyValueList, +) + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + ArrayValue, + InstrumentationScope, + KeyValue, + KeyValueList, +) + + +# ── AnyValue ────────────────────────────────────────────────────────────────── + +def test_any_value_empty() -> None: + assert AnyValue().SerializeToString() == b"" + + +def test_any_value_empty_matches_proto() -> None: + assert AnyValue().SerializeToString() == ProtoAnyValue().SerializeToString() + + +def test_any_value_string() -> None: + our = AnyValue(string_value="hello") + proto = ProtoAnyValue(string_value="hello") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_bool_true() -> None: + our = AnyValue(bool_value=True) + proto = ProtoAnyValue(bool_value=True) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_bool_false() -> None: + our = AnyValue(bool_value=False) + proto = ProtoAnyValue(bool_value=False) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_int() -> None: + our = AnyValue(int_value=42) + proto = ProtoAnyValue(int_value=42) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_int_negative() -> None: + our = AnyValue(int_value=-1) + proto = ProtoAnyValue(int_value=-1) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_double() -> None: + our = AnyValue(double_value=3.14) + proto = ProtoAnyValue(double_value=3.14) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_bytes() -> None: + our = AnyValue(bytes_value=b"\x01\x02\x03") + proto = ProtoAnyValue(bytes_value=b"\x01\x02\x03") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_array_empty() -> None: + our = AnyValue(array_value=ArrayValue()) + proto = ProtoAnyValue(array_value=ProtoArrayValue()) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_any_value_kvlist_empty() -> None: + our = AnyValue(kvlist_value=KeyValueList()) + proto = ProtoAnyValue(kvlist_value=ProtoKeyValueList()) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ArrayValue ──────────────────────────────────────────────────────────────── + +def test_array_value_empty() -> None: + assert ArrayValue().SerializeToString() == b"" + + +def test_array_value_empty_matches_proto() -> None: + assert ArrayValue().SerializeToString() == ProtoArrayValue().SerializeToString() + + +def test_array_value_with_elements() -> None: + our = ArrayValue(values=[AnyValue(string_value="a"), AnyValue(int_value=1)]) + proto = ProtoArrayValue(values=[ProtoAnyValue(string_value="a"), ProtoAnyValue(int_value=1)]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── KeyValueList ────────────────────────────────────────────────────────────── + +def test_kvlist_empty() -> None: + assert KeyValueList().SerializeToString() == b"" + + +def test_kvlist_empty_matches_proto() -> None: + assert KeyValueList().SerializeToString() == ProtoKeyValueList().SerializeToString() + + +def test_kvlist_with_values() -> None: + our = KeyValueList(values=[KeyValue(key="k", value=AnyValue(string_value="v"))]) + proto = ProtoKeyValueList(values=[ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v"))]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── KeyValue ────────────────────────────────────────────────────────────────── + +def test_key_value_empty() -> None: + assert KeyValue().SerializeToString() == b"" + + +def test_key_value_empty_matches_proto() -> None: + assert KeyValue().SerializeToString() == ProtoKeyValue().SerializeToString() + + +def test_key_value_key_only() -> None: + our = KeyValue(key="mykey") + proto = ProtoKeyValue(key="mykey") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_key_value_string() -> None: + our = KeyValue(key="k", value=AnyValue(string_value="v")) + proto = ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v")) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_key_value_int() -> None: + our = KeyValue(key="count", value=AnyValue(int_value=42)) + proto = ProtoKeyValue(key="count", value=ProtoAnyValue(int_value=42)) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_key_value_double() -> None: + our = KeyValue(key="ratio", value=AnyValue(double_value=0.5)) + proto = ProtoKeyValue(key="ratio", value=ProtoAnyValue(double_value=0.5)) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── InstrumentationScope ────────────────────────────────────────────────────── + +def test_instrumentation_scope_empty() -> None: + assert InstrumentationScope().SerializeToString() == b"" + + +def test_instrumentation_scope_empty_matches_proto() -> None: + assert ( + InstrumentationScope().SerializeToString() + == ProtoInstrumentationScope().SerializeToString() + ) + + +def test_instrumentation_scope_name() -> None: + our = InstrumentationScope(name="mylib") + proto = ProtoInstrumentationScope(name="mylib") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_instrumentation_scope_name_version() -> None: + our = InstrumentationScope(name="mylib", version="1.2.3") + proto = ProtoInstrumentationScope(name="mylib", version="1.2.3") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_instrumentation_scope_with_attributes() -> None: + attr = KeyValue(key="env", value=AnyValue(string_value="prod")) + proto_attr = ProtoKeyValue(key="env", value=ProtoAnyValue(string_value="prod")) + our = InstrumentationScope( + name="mylib", + version="1.0", + attributes=[attr], + dropped_attributes_count=3, + ) + proto = ProtoInstrumentationScope( + name="mylib", + version="1.0", + attributes=[proto_attr], + dropped_attributes_count=3, + ) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/conftest.py b/opentelemetry-proto/tests/equivalence/conftest.py new file mode 100644 index 00000000000..c556071941f --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/conftest.py @@ -0,0 +1,16 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# The equivalence tests import the real (google.protobuf) message classes via +# ``opentelemetry.proto.*`` and the pure-Python classes via +# ``opentelemetry._proto.*``. Both this distribution (as a re-export shim) and +# the real ``opentelemetry-proto`` provide ``opentelemetry.proto``; guard that +# the public path resolves to the real package, not this package's shim, so the +# comparisons are genuinely real-vs-pure-Python. +from opentelemetry.proto.trace.v1 import trace_pb2 as _public_trace_pb2 + +assert "pyproto" not in (_public_trace_pb2.__file__ or ""), ( + "opentelemetry.proto resolved to the pyproto shim " + f"({_public_trace_pb2.__file__}); the equivalence tests need the real " + "opentelemetry-proto package to own that path." +) diff --git a/opentelemetry-proto/tests/equivalence/logs/__init__.py b/opentelemetry-proto/tests/equivalence/logs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/logs/v1/__init__.py b/opentelemetry-proto/tests/equivalence/logs/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/logs/v1/test_logs_pypb2.py b/opentelemetry-proto/tests/equivalence/logs/v1/test_logs_pypb2.py new file mode 100644 index 00000000000..0bf2871ad44 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/logs/v1/test_logs_pypb2.py @@ -0,0 +1,155 @@ +from opentelemetry.proto.common.v1.common_pb2 import ( + AnyValue as ProtoAnyValue, + InstrumentationScope as ProtoInstrumentationScope, + KeyValue as ProtoKeyValue, +) +from opentelemetry.proto.logs.v1.logs_pb2 import ( + LogRecord as ProtoLogRecord, + ResourceLogs as ProtoResourceLogs, + ScopeLogs as ProtoScopeLogs, +) +from opentelemetry.proto.resource.v1.resource_pb2 import Resource as ProtoResource + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.logs.v1.logs_pb2 import LogRecord, ResourceLogs, ScopeLogs +from opentelemetry._proto.resource.v1.resource_pb2 import Resource + + +# ── LogRecord ───────────────────────────────────────────────────────────────── + +def test_log_record_empty() -> None: + assert LogRecord().SerializeToString() == b"" + + +def test_log_record_empty_matches_proto() -> None: + assert LogRecord().SerializeToString() == ProtoLogRecord().SerializeToString() + + +def test_log_record_severity() -> None: + our = LogRecord(severity_number=9, severity_text="INFO") + proto = ProtoLogRecord(severity_number=9, severity_text="INFO") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_log_record_with_body() -> None: + our = LogRecord(body=AnyValue(string_value="hello world")) + proto = ProtoLogRecord(body=ProtoAnyValue(string_value="hello world")) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_log_record_with_trace_context() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + our = LogRecord( + time_unix_nano=1_000_000_000, + trace_id=trace_id, + span_id=span_id, + observed_time_unix_nano=2_000_000_000, + ) + proto = ProtoLogRecord( + time_unix_nano=1_000_000_000, + trace_id=trace_id, + span_id=span_id, + observed_time_unix_nano=2_000_000_000, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_log_record_with_attributes() -> None: + attr = KeyValue(key="env", value=AnyValue(string_value="prod")) + proto_attr = ProtoKeyValue(key="env", value=ProtoAnyValue(string_value="prod")) + our = LogRecord( + body=AnyValue(string_value="msg"), + attributes=[attr], + dropped_attributes_count=1, + ) + proto = ProtoLogRecord( + body=ProtoAnyValue(string_value="msg"), + attributes=[proto_attr], + dropped_attributes_count=1, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_log_record_event_name() -> None: + our = LogRecord(event_name="user.click") + proto = ProtoLogRecord(event_name="user.click") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_log_record_flags() -> None: + our = LogRecord(flags=1) + proto = ProtoLogRecord(flags=1) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ScopeLogs ───────────────────────────────────────────────────────────────── + +def test_scope_logs_empty() -> None: + assert ScopeLogs().SerializeToString() == b"" + + +def test_scope_logs_empty_matches_proto() -> None: + assert ScopeLogs().SerializeToString() == ProtoScopeLogs().SerializeToString() + + +def test_scope_logs_schema_url() -> None: + our = ScopeLogs(schema_url="https://example.com/schema") + proto = ProtoScopeLogs(schema_url="https://example.com/schema") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_scope_logs_with_scope() -> None: + scope = InstrumentationScope(name="mylib", version="1.0") + proto_scope = ProtoInstrumentationScope(name="mylib", version="1.0") + our = ScopeLogs(scope=scope) + proto = ProtoScopeLogs(scope=proto_scope) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_scope_logs_with_scope_and_records() -> None: + scope = InstrumentationScope(name="mylib", version="1.0") + proto_scope = ProtoInstrumentationScope(name="mylib", version="1.0") + rec = LogRecord(severity_text="WARN", severity_number=13) + proto_rec = ProtoLogRecord(severity_text="WARN", severity_number=13) + our = ScopeLogs(scope=scope, log_records=[rec], schema_url="s") + proto = ProtoScopeLogs(scope=proto_scope, log_records=[proto_rec], schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ResourceLogs ────────────────────────────────────────────────────────────── + +def test_resource_logs_empty() -> None: + assert ResourceLogs().SerializeToString() == b"" + + +def test_resource_logs_empty_matches_proto() -> None: + assert ResourceLogs().SerializeToString() == ProtoResourceLogs().SerializeToString() + + +def test_resource_logs_schema_url() -> None: + our = ResourceLogs(schema_url="https://example.com") + proto = ProtoResourceLogs(schema_url="https://example.com") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_logs_with_resource() -> None: + res = Resource(attributes=[KeyValue(key="k", value=AnyValue(string_value="v"))]) + proto_res = ProtoResource(attributes=[ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v"))]) + our = ResourceLogs(resource=res, schema_url="s") + proto = ProtoResourceLogs(resource=proto_res, schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_logs_with_scope_logs() -> None: + rec = LogRecord(severity_text="INFO", severity_number=9) + proto_rec = ProtoLogRecord(severity_text="INFO", severity_number=9) + sl = ScopeLogs(log_records=[rec]) + proto_sl = ProtoScopeLogs(log_records=[proto_rec]) + our = ResourceLogs(scope_logs=[sl]) + proto = ProtoResourceLogs(scope_logs=[proto_sl]) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/metrics/__init__.py b/opentelemetry-proto/tests/equivalence/metrics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/metrics/v1/__init__.py b/opentelemetry-proto/tests/equivalence/metrics/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/metrics/v1/test_metrics_pypb2.py b/opentelemetry-proto/tests/equivalence/metrics/v1/test_metrics_pypb2.py new file mode 100644 index 00000000000..ef3a850c43e --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/metrics/v1/test_metrics_pypb2.py @@ -0,0 +1,527 @@ +from opentelemetry.proto.common.v1.common_pb2 import ( + AnyValue as ProtoAnyValue, + InstrumentationScope as ProtoInstrumentationScope, + KeyValue as ProtoKeyValue, +) +from opentelemetry.proto.metrics.v1.metrics_pb2 import ( + Exemplar as ProtoExemplar, + ExponentialHistogram as ProtoExponentialHistogram, + ExponentialHistogramDataPoint as ProtoExponentialHistogramDataPoint, + Gauge as ProtoGauge, + Histogram as ProtoHistogram, + HistogramDataPoint as ProtoHistogramDataPoint, + Metric as ProtoMetric, + NumberDataPoint as ProtoNumberDataPoint, + ResourceMetrics as ProtoResourceMetrics, + ScopeMetrics as ProtoScopeMetrics, + Sum as ProtoSum, + Summary as ProtoSummary, + SummaryDataPoint as ProtoSummaryDataPoint, +) +from opentelemetry.proto.resource.v1.resource_pb2 import Resource as ProtoResource + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.metrics.v1.metrics_pb2 import ( + Exemplar, + ExponentialHistogram, + ExponentialHistogramDataPoint, + Gauge, + Histogram, + HistogramDataPoint, + Metric, + NumberDataPoint, + ResourceMetrics, + ScopeMetrics, + Sum, + Summary, + SummaryDataPoint, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource + + +# ── Exemplar ────────────────────────────────────────────────────────────────── + +def test_exemplar_empty() -> None: + assert Exemplar().SerializeToString() == b"" + + +def test_exemplar_empty_matches_proto() -> None: + assert Exemplar().SerializeToString() == ProtoExemplar().SerializeToString() + + +def test_exemplar_as_double() -> None: + our = Exemplar(as_double=1.5, time_unix_nano=1_000) + proto = ProtoExemplar(as_double=1.5, time_unix_nano=1_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_exemplar_as_int() -> None: + our = Exemplar(as_int=42, time_unix_nano=2_000) + proto = ProtoExemplar(as_int=42, time_unix_nano=2_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_exemplar_with_span_trace_id() -> None: + our = Exemplar(as_double=3.0, span_id=b"\x01" * 8, trace_id=b"\x02" * 16) + proto = ProtoExemplar(as_double=3.0, span_id=b"\x01" * 8, trace_id=b"\x02" * 16) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_exemplar_with_filtered_attributes() -> None: + # filtered_attributes is field 7; test it alone to avoid field-ordering + # differences between our fixed-order encoding and proto's ascending order. + attr = KeyValue(key="k", value=AnyValue(string_value="v")) + proto_attr = ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v")) + our = Exemplar(filtered_attributes=[attr]) + proto = ProtoExemplar(filtered_attributes=[proto_attr]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── NumberDataPoint ─────────────────────────────────────────────────────────── + +def test_number_data_point_empty() -> None: + assert NumberDataPoint().SerializeToString() == b"" + + +def test_number_data_point_empty_matches_proto() -> None: + assert NumberDataPoint().SerializeToString() == ProtoNumberDataPoint().SerializeToString() + + +def test_number_data_point_as_double() -> None: + our = NumberDataPoint(as_double=1.5, time_unix_nano=1_000_000) + proto = ProtoNumberDataPoint(as_double=1.5, time_unix_nano=1_000_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_number_data_point_as_int() -> None: + our = NumberDataPoint(as_int=42, time_unix_nano=1_000_000) + proto = ProtoNumberDataPoint(as_int=42, time_unix_nano=1_000_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_number_data_point_with_attributes() -> None: + # attributes is field 7, flags is field 8 — both high-numbered and in the + # right relative order in our encoding, so no ordering difference with proto. + attr = KeyValue(key="env", value=AnyValue(string_value="prod")) + proto_attr = ProtoKeyValue(key="env", value=ProtoAnyValue(string_value="prod")) + our = NumberDataPoint(attributes=[attr], flags=1) + proto = ProtoNumberDataPoint(attributes=[proto_attr], flags=1) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_number_data_point_with_exemplar() -> None: + our = NumberDataPoint( + as_double=5.0, + start_time_unix_nano=1_000, + time_unix_nano=2_000, + exemplars=[Exemplar(as_double=5.0)], + ) + proto = ProtoNumberDataPoint( + as_double=5.0, + start_time_unix_nano=1_000, + time_unix_nano=2_000, + exemplars=[ProtoExemplar(as_double=5.0)], + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Gauge ───────────────────────────────────────────────────────────────────── + +def test_gauge_empty() -> None: + assert Gauge().SerializeToString() == b"" + + +def test_gauge_empty_matches_proto() -> None: + assert Gauge().SerializeToString() == ProtoGauge().SerializeToString() + + +def test_gauge_with_data_point() -> None: + our = Gauge(data_points=[NumberDataPoint(as_double=1.5)]) + proto = ProtoGauge(data_points=[ProtoNumberDataPoint(as_double=1.5)]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Sum ─────────────────────────────────────────────────────────────────────── + +def test_sum_empty() -> None: + assert Sum().SerializeToString() == b"" + + +def test_sum_empty_matches_proto() -> None: + assert Sum().SerializeToString() == ProtoSum().SerializeToString() + + +def test_sum_with_data_and_temporality() -> None: + our = Sum( + data_points=[NumberDataPoint(as_int=10)], + aggregation_temporality=1, + is_monotonic=True, + ) + proto = ProtoSum( + data_points=[ProtoNumberDataPoint(as_int=10)], + aggregation_temporality=1, + is_monotonic=True, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── HistogramDataPoint ──────────────────────────────────────────────────────── + +def test_histogram_data_point_empty() -> None: + assert HistogramDataPoint().SerializeToString() == b"" + + +def test_histogram_data_point_empty_matches_proto() -> None: + assert ( + HistogramDataPoint().SerializeToString() + == ProtoHistogramDataPoint().SerializeToString() + ) + + +def test_histogram_data_point_with_count_and_buckets() -> None: + our = HistogramDataPoint( + time_unix_nano=1_000, + count=5, + sum=100.0, + bucket_counts=[2, 3], + explicit_bounds=[50.0], + ) + proto = ProtoHistogramDataPoint( + time_unix_nano=1_000, + count=5, + sum=100.0, + bucket_counts=[2, 3], + explicit_bounds=[50.0], + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_histogram_data_point_with_min_max() -> None: + our = HistogramDataPoint(time_unix_nano=1_000, count=3, min=0.0, max=10.0) + proto = ProtoHistogramDataPoint(time_unix_nano=1_000, count=3, min=0.0, max=10.0) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Histogram ───────────────────────────────────────────────────────────────── + +def test_histogram_empty() -> None: + assert Histogram().SerializeToString() == b"" + + +def test_histogram_empty_matches_proto() -> None: + assert Histogram().SerializeToString() == ProtoHistogram().SerializeToString() + + +def test_histogram_with_data_and_temporality() -> None: + our = Histogram( + data_points=[HistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=2, + ) + proto = ProtoHistogram( + data_points=[ProtoHistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=2, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ExponentialHistogramDataPoint.Buckets ───────────────────────────────────── + +def test_exp_histogram_buckets_empty() -> None: + assert ExponentialHistogramDataPoint.Buckets().SerializeToString() == b"" + + +def test_exp_histogram_buckets_empty_matches_proto() -> None: + assert ( + ExponentialHistogramDataPoint.Buckets().SerializeToString() + == ProtoExponentialHistogramDataPoint.Buckets().SerializeToString() + ) + + +def test_exp_histogram_buckets_with_data() -> None: + our = ExponentialHistogramDataPoint.Buckets(offset=2, bucket_counts=[1, 2, 3]) + proto = ProtoExponentialHistogramDataPoint.Buckets(offset=2, bucket_counts=[1, 2, 3]) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_exp_histogram_buckets_negative_offset() -> None: + our = ExponentialHistogramDataPoint.Buckets(offset=-3, bucket_counts=[4, 5]) + proto = ProtoExponentialHistogramDataPoint.Buckets(offset=-3, bucket_counts=[4, 5]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ExponentialHistogramDataPoint ───────────────────────────────────────────── + +def test_exp_histogram_data_point_empty() -> None: + assert ExponentialHistogramDataPoint().SerializeToString() == b"" + + +def test_exp_histogram_data_point_empty_matches_proto() -> None: + assert ( + ExponentialHistogramDataPoint().SerializeToString() + == ProtoExponentialHistogramDataPoint().SerializeToString() + ) + + +def test_exp_histogram_data_point_with_scale_and_count() -> None: + our = ExponentialHistogramDataPoint( + time_unix_nano=1_000, + count=10, + scale=-1, + zero_count=2, + ) + proto = ProtoExponentialHistogramDataPoint( + time_unix_nano=1_000, + count=10, + scale=-1, + zero_count=2, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_exp_histogram_data_point_with_buckets() -> None: + pos = ExponentialHistogramDataPoint.Buckets(offset=1, bucket_counts=[3, 4]) + neg = ExponentialHistogramDataPoint.Buckets(offset=-1, bucket_counts=[1]) + proto_pos = ProtoExponentialHistogramDataPoint.Buckets(offset=1, bucket_counts=[3, 4]) + proto_neg = ProtoExponentialHistogramDataPoint.Buckets(offset=-1, bucket_counts=[1]) + our = ExponentialHistogramDataPoint( + count=8, scale=2, positive=pos, negative=neg, time_unix_nano=1_000 + ) + proto = ProtoExponentialHistogramDataPoint( + count=8, scale=2, positive=proto_pos, negative=proto_neg, time_unix_nano=1_000 + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ExponentialHistogram ────────────────────────────────────────────────────── + +def test_exp_histogram_empty() -> None: + assert ExponentialHistogram().SerializeToString() == b"" + + +def test_exp_histogram_empty_matches_proto() -> None: + assert ( + ExponentialHistogram().SerializeToString() + == ProtoExponentialHistogram().SerializeToString() + ) + + +def test_exp_histogram_with_data() -> None: + our = ExponentialHistogram( + data_points=[ExponentialHistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=1, + ) + proto = ProtoExponentialHistogram( + data_points=[ProtoExponentialHistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=1, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── SummaryDataPoint.ValueAtQuantile ────────────────────────────────────────── + +def test_value_at_quantile_empty() -> None: + assert SummaryDataPoint.ValueAtQuantile().SerializeToString() == b"" + + +def test_value_at_quantile_empty_matches_proto() -> None: + assert ( + SummaryDataPoint.ValueAtQuantile().SerializeToString() + == ProtoSummaryDataPoint.ValueAtQuantile().SerializeToString() + ) + + +def test_value_at_quantile() -> None: + our = SummaryDataPoint.ValueAtQuantile(quantile=0.5, value=100.0) + proto = ProtoSummaryDataPoint.ValueAtQuantile(quantile=0.5, value=100.0) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_value_at_quantile_p99() -> None: + our = SummaryDataPoint.ValueAtQuantile(quantile=0.99, value=500.0) + proto = ProtoSummaryDataPoint.ValueAtQuantile(quantile=0.99, value=500.0) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── SummaryDataPoint ────────────────────────────────────────────────────────── + +def test_summary_data_point_empty() -> None: + assert SummaryDataPoint().SerializeToString() == b"" + + +def test_summary_data_point_empty_matches_proto() -> None: + assert ( + SummaryDataPoint().SerializeToString() + == ProtoSummaryDataPoint().SerializeToString() + ) + + +def test_summary_data_point_with_count_and_sum() -> None: + our = SummaryDataPoint(count=10, sum=500.0, time_unix_nano=1_000) + proto = ProtoSummaryDataPoint(count=10, sum=500.0, time_unix_nano=1_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_summary_data_point_with_quantiles() -> None: + our = SummaryDataPoint( + count=100, + sum=5000.0, + quantile_values=[ + SummaryDataPoint.ValueAtQuantile(quantile=0.5, value=45.0), + SummaryDataPoint.ValueAtQuantile(quantile=0.99, value=200.0), + ], + ) + proto = ProtoSummaryDataPoint( + count=100, + sum=5000.0, + quantile_values=[ + ProtoSummaryDataPoint.ValueAtQuantile(quantile=0.5, value=45.0), + ProtoSummaryDataPoint.ValueAtQuantile(quantile=0.99, value=200.0), + ], + ) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Summary ─────────────────────────────────────────────────────────────────── + +def test_summary_empty() -> None: + assert Summary().SerializeToString() == b"" + + +def test_summary_empty_matches_proto() -> None: + assert Summary().SerializeToString() == ProtoSummary().SerializeToString() + + +def test_summary_with_data_point() -> None: + our = Summary(data_points=[SummaryDataPoint(count=10, sum=500.0)]) + proto = ProtoSummary(data_points=[ProtoSummaryDataPoint(count=10, sum=500.0)]) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Metric ──────────────────────────────────────────────────────────────────── + +def test_metric_empty() -> None: + assert Metric().SerializeToString() == b"" + + +def test_metric_empty_matches_proto() -> None: + assert Metric().SerializeToString() == ProtoMetric().SerializeToString() + + +def test_metric_gauge() -> None: + our = Metric(name="cpu", description="CPU usage", unit="%", gauge=Gauge( + data_points=[NumberDataPoint(as_double=0.75)] + )) + proto = ProtoMetric(name="cpu", description="CPU usage", unit="%", gauge=ProtoGauge( + data_points=[ProtoNumberDataPoint(as_double=0.75)] + )) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_metric_sum() -> None: + our = Metric(name="requests", unit="1", sum=Sum( + data_points=[NumberDataPoint(as_int=100)], + aggregation_temporality=2, + is_monotonic=True, + )) + proto = ProtoMetric(name="requests", unit="1", sum=ProtoSum( + data_points=[ProtoNumberDataPoint(as_int=100)], + aggregation_temporality=2, + is_monotonic=True, + )) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_metric_histogram() -> None: + our = Metric(name="latency", histogram=Histogram( + data_points=[HistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=1, + )) + proto = ProtoMetric(name="latency", histogram=ProtoHistogram( + data_points=[ProtoHistogramDataPoint(count=5, time_unix_nano=1_000)], + aggregation_temporality=1, + )) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_metric_summary() -> None: + our = Metric(name="duration", summary=Summary( + data_points=[SummaryDataPoint(count=10, sum=500.0)], + )) + proto = ProtoMetric(name="duration", summary=ProtoSummary( + data_points=[ProtoSummaryDataPoint(count=10, sum=500.0)], + )) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_metric_exponential_histogram() -> None: + our = Metric(name="exp_lat", exponential_histogram=ExponentialHistogram( + data_points=[ExponentialHistogramDataPoint(count=3, time_unix_nano=1_000)], + aggregation_temporality=2, + )) + proto = ProtoMetric(name="exp_lat", exponential_histogram=ProtoExponentialHistogram( + data_points=[ProtoExponentialHistogramDataPoint(count=3, time_unix_nano=1_000)], + aggregation_temporality=2, + )) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ScopeMetrics ────────────────────────────────────────────────────────────── + +def test_scope_metrics_empty() -> None: + assert ScopeMetrics().SerializeToString() == b"" + + +def test_scope_metrics_empty_matches_proto() -> None: + assert ScopeMetrics().SerializeToString() == ProtoScopeMetrics().SerializeToString() + + +def test_scope_metrics_schema_url() -> None: + our = ScopeMetrics(schema_url="https://example.com") + proto = ProtoScopeMetrics(schema_url="https://example.com") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_scope_metrics_with_scope_and_metric() -> None: + scope = InstrumentationScope(name="mylib", version="1.0") + proto_scope = ProtoInstrumentationScope(name="mylib", version="1.0") + metric = Metric(name="cpu", gauge=Gauge(data_points=[NumberDataPoint(as_double=0.5)])) + proto_metric = ProtoMetric(name="cpu", gauge=ProtoGauge(data_points=[ProtoNumberDataPoint(as_double=0.5)])) + our = ScopeMetrics(scope=scope, metrics=[metric], schema_url="s") + proto = ProtoScopeMetrics(scope=proto_scope, metrics=[proto_metric], schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ResourceMetrics ─────────────────────────────────────────────────────────── + +def test_resource_metrics_empty() -> None: + assert ResourceMetrics().SerializeToString() == b"" + + +def test_resource_metrics_empty_matches_proto() -> None: + assert ResourceMetrics().SerializeToString() == ProtoResourceMetrics().SerializeToString() + + +def test_resource_metrics_schema_url() -> None: + our = ResourceMetrics(schema_url="https://example.com") + proto = ProtoResourceMetrics(schema_url="https://example.com") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_metrics_with_resource() -> None: + res = Resource(attributes=[KeyValue(key="k", value=AnyValue(string_value="v"))]) + proto_res = ProtoResource(attributes=[ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v"))]) + our = ResourceMetrics(resource=res, schema_url="s") + proto = ProtoResourceMetrics(resource=proto_res, schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_metrics_with_scope_metrics() -> None: + sm = ScopeMetrics(metrics=[Metric(name="cpu", gauge=Gauge())]) + proto_sm = ProtoScopeMetrics(metrics=[ProtoMetric(name="cpu", gauge=ProtoGauge())]) + our = ResourceMetrics(scope_metrics=[sm]) + proto = ProtoResourceMetrics(scope_metrics=[proto_sm]) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/resource/__init__.py b/opentelemetry-proto/tests/equivalence/resource/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/resource/v1/__init__.py b/opentelemetry-proto/tests/equivalence/resource/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/resource/v1/test_resource_pypb2.py b/opentelemetry-proto/tests/equivalence/resource/v1/test_resource_pypb2.py new file mode 100644 index 00000000000..5ee8ffc5b4f --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/resource/v1/test_resource_pypb2.py @@ -0,0 +1,64 @@ +from opentelemetry.proto.common.v1.common_pb2 import ( + AnyValue as ProtoAnyValue, + KeyValue as ProtoKeyValue, +) +from opentelemetry.proto.resource.v1.resource_pb2 import Resource as ProtoResource + +from opentelemetry._proto.common.v1.common_pb2 import AnyValue, KeyValue +from opentelemetry._proto.resource.v1.resource_pb2 import Resource + + +def test_resource_empty() -> None: + assert Resource().SerializeToString() == b"" + + +def test_resource_empty_matches_proto() -> None: + assert Resource().SerializeToString() == ProtoResource().SerializeToString() + + +def test_resource_dropped_attributes_count() -> None: + our = Resource(dropped_attributes_count=5) + proto = ProtoResource(dropped_attributes_count=5) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_with_one_attribute() -> None: + attr = KeyValue(key="service.name", value=AnyValue(string_value="my-service")) + proto_attr = ProtoKeyValue(key="service.name", value=ProtoAnyValue(string_value="my-service")) + our = Resource(attributes=[attr]) + proto = ProtoResource(attributes=[proto_attr]) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_with_multiple_attributes() -> None: + attrs = [ + KeyValue(key="service.name", value=AnyValue(string_value="my-service")), + KeyValue(key="service.version", value=AnyValue(string_value="1.0.0")), + KeyValue(key="deployment.environment", value=AnyValue(string_value="prod")), + ] + proto_attrs = [ + ProtoKeyValue(key="service.name", value=ProtoAnyValue(string_value="my-service")), + ProtoKeyValue(key="service.version", value=ProtoAnyValue(string_value="1.0.0")), + ProtoKeyValue(key="deployment.environment", value=ProtoAnyValue(string_value="prod")), + ] + our = Resource(attributes=attrs) + proto = ProtoResource(attributes=proto_attrs) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_full() -> None: + attrs = [ + KeyValue(key="k", value=AnyValue(string_value="v")), + ] + proto_attrs = [ + ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v")), + ] + our = Resource(attributes=attrs, dropped_attributes_count=2) + proto = ProtoResource(attributes=proto_attrs, dropped_attributes_count=2) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_with_int_attribute() -> None: + our = Resource(attributes=[KeyValue(key="pid", value=AnyValue(int_value=1234))]) + proto = ProtoResource(attributes=[ProtoKeyValue(key="pid", value=ProtoAnyValue(int_value=1234))]) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/equivalence/trace/__init__.py b/opentelemetry-proto/tests/equivalence/trace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/trace/v1/__init__.py b/opentelemetry-proto/tests/equivalence/trace/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/equivalence/trace/v1/test_trace_pypb2.py b/opentelemetry-proto/tests/equivalence/trace/v1/test_trace_pypb2.py new file mode 100644 index 00000000000..3c4f7cd2e22 --- /dev/null +++ b/opentelemetry-proto/tests/equivalence/trace/v1/test_trace_pypb2.py @@ -0,0 +1,245 @@ +from opentelemetry.proto.common.v1.common_pb2 import ( + AnyValue as ProtoAnyValue, + InstrumentationScope as ProtoInstrumentationScope, + KeyValue as ProtoKeyValue, +) +from opentelemetry.proto.resource.v1.resource_pb2 import Resource as ProtoResource +from opentelemetry.proto.trace.v1.trace_pb2 import ( + ResourceSpans as ProtoResourceSpans, + ScopeSpans as ProtoScopeSpans, + Span as ProtoSpan, + Status as ProtoStatus, +) + +from opentelemetry._proto.common.v1.common_pb2 import ( + AnyValue, + InstrumentationScope, + KeyValue, +) +from opentelemetry._proto.resource.v1.resource_pb2 import Resource +from opentelemetry._proto.trace.v1.trace_pb2 import ( + ResourceSpans, + ScopeSpans, + Span, + Status, +) + + +# ── Status ──────────────────────────────────────────────────────────────────── + +def test_status_empty() -> None: + assert Status().SerializeToString() == b"" + + +def test_status_empty_matches_proto() -> None: + assert Status().SerializeToString() == ProtoStatus().SerializeToString() + + +def test_status_code() -> None: + our = Status(code=2) + proto = ProtoStatus(code=2) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_status_code_and_message() -> None: + our = Status(code=2, message="internal error") + proto = ProtoStatus(code=2, message="internal error") + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Span.Event ──────────────────────────────────────────────────────────────── + +def test_span_event_empty() -> None: + assert Span.Event().SerializeToString() == b"" + + +def test_span_event_empty_matches_proto() -> None: + assert Span.Event().SerializeToString() == ProtoSpan.Event().SerializeToString() + + +def test_span_event_name_and_time() -> None: + our = Span.Event(name="button.click", time_unix_nano=1_000_000) + proto = ProtoSpan.Event(name="button.click", time_unix_nano=1_000_000) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_event_with_attributes() -> None: + attr = KeyValue(key="k", value=AnyValue(string_value="v")) + proto_attr = ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v")) + our = Span.Event(name="evt", attributes=[attr], dropped_attributes_count=1) + proto = ProtoSpan.Event(name="evt", attributes=[proto_attr], dropped_attributes_count=1) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Span.Link ───────────────────────────────────────────────────────────────── + +def test_span_link_empty() -> None: + assert Span.Link().SerializeToString() == b"" + + +def test_span_link_empty_matches_proto() -> None: + assert Span.Link().SerializeToString() == ProtoSpan.Link().SerializeToString() + + +def test_span_link_with_ids() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + our = Span.Link(trace_id=trace_id, span_id=span_id, trace_state="k=v") + proto = ProtoSpan.Link(trace_id=trace_id, span_id=span_id, trace_state="k=v") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_link_with_flags() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + our = Span.Link(trace_id=trace_id, span_id=span_id, flags=1) + proto = ProtoSpan.Link(trace_id=trace_id, span_id=span_id, flags=1) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── Span ────────────────────────────────────────────────────────────────────── + +def test_span_empty() -> None: + assert Span().SerializeToString() == b"" + + +def test_span_empty_matches_proto() -> None: + assert Span().SerializeToString() == ProtoSpan().SerializeToString() + + +def test_span_basic() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + our = Span( + trace_id=trace_id, + span_id=span_id, + name="my-span", + start_time_unix_nano=1_000_000_000, + end_time_unix_nano=2_000_000_000, + ) + proto = ProtoSpan( + trace_id=trace_id, + span_id=span_id, + name="my-span", + start_time_unix_nano=1_000_000_000, + end_time_unix_nano=2_000_000_000, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_with_status() -> None: + our = Span(name="err-span", status=Status(code=2, message="error")) + proto = ProtoSpan(name="err-span", status=ProtoStatus(code=2, message="error")) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_with_attributes() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + attr = KeyValue(key="http.method", value=AnyValue(string_value="GET")) + proto_attr = ProtoKeyValue(key="http.method", value=ProtoAnyValue(string_value="GET")) + our = Span( + trace_id=trace_id, + span_id=span_id, + name="http-span", + attributes=[attr], + dropped_attributes_count=2, + ) + proto = ProtoSpan( + trace_id=trace_id, + span_id=span_id, + name="http-span", + attributes=[proto_attr], + dropped_attributes_count=2, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_with_events_and_links() -> None: + trace_id = b"\x01" * 16 + span_id = b"\x02" * 8 + our = Span( + trace_id=trace_id, + span_id=span_id, + name="parent", + events=[Span.Event(name="click", time_unix_nano=500)], + links=[Span.Link(trace_id=trace_id, span_id=span_id)], + dropped_events_count=1, + dropped_links_count=2, + ) + proto = ProtoSpan( + trace_id=trace_id, + span_id=span_id, + name="parent", + events=[ProtoSpan.Event(name="click", time_unix_nano=500)], + links=[ProtoSpan.Link(trace_id=trace_id, span_id=span_id)], + dropped_events_count=1, + dropped_links_count=2, + ) + assert our.SerializeToString() == proto.SerializeToString() + + +def test_span_kind() -> None: + our = Span(name="server", kind=2) + proto = ProtoSpan(name="server", kind=2) + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ScopeSpans ──────────────────────────────────────────────────────────────── + +def test_scope_spans_empty() -> None: + assert ScopeSpans().SerializeToString() == b"" + + +def test_scope_spans_empty_matches_proto() -> None: + assert ScopeSpans().SerializeToString() == ProtoScopeSpans().SerializeToString() + + +def test_scope_spans_schema_url() -> None: + our = ScopeSpans(schema_url="https://example.com") + proto = ProtoScopeSpans(schema_url="https://example.com") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_scope_spans_with_scope_and_span() -> None: + scope = InstrumentationScope(name="mylib", version="1.0") + proto_scope = ProtoInstrumentationScope(name="mylib", version="1.0") + span = Span(name="test-span") + proto_span = ProtoSpan(name="test-span") + our = ScopeSpans(scope=scope, spans=[span], schema_url="s") + proto = ProtoScopeSpans(scope=proto_scope, spans=[proto_span], schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +# ── ResourceSpans ───────────────────────────────────────────────────────────── + +def test_resource_spans_empty() -> None: + assert ResourceSpans().SerializeToString() == b"" + + +def test_resource_spans_empty_matches_proto() -> None: + assert ResourceSpans().SerializeToString() == ProtoResourceSpans().SerializeToString() + + +def test_resource_spans_schema_url() -> None: + our = ResourceSpans(schema_url="https://example.com") + proto = ProtoResourceSpans(schema_url="https://example.com") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_spans_with_resource() -> None: + res = Resource(attributes=[KeyValue(key="k", value=AnyValue(string_value="v"))]) + proto_res = ProtoResource(attributes=[ProtoKeyValue(key="k", value=ProtoAnyValue(string_value="v"))]) + our = ResourceSpans(resource=res, schema_url="s") + proto = ProtoResourceSpans(resource=proto_res, schema_url="s") + assert our.SerializeToString() == proto.SerializeToString() + + +def test_resource_spans_with_scope_spans() -> None: + span = Span(name="s") + proto_span = ProtoSpan(name="s") + ss = ScopeSpans(spans=[span]) + proto_ss = ProtoScopeSpans(spans=[proto_span]) + our = ResourceSpans(scope_spans=[ss]) + proto = ProtoResourceSpans(scope_spans=[proto_ss]) + assert our.SerializeToString() == proto.SerializeToString() diff --git a/opentelemetry-proto/tests/performance/__init__.py b/opentelemetry-proto/tests/performance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/performance/test_benchmark.py b/opentelemetry-proto/tests/performance/test_benchmark.py new file mode 100644 index 00000000000..f562b001505 --- /dev/null +++ b/opentelemetry-proto/tests/performance/test_benchmark.py @@ -0,0 +1,569 @@ +# tests/_pyprotobuf/test_benchmark.py +# +# Benchmark: _pyprotobuf (pure-Python) vs google.protobuf (C extension) +# encoding speed. Four benchmark categories: +# +# Full message — one round-trip of a Record containing every field type. +# Per field — each field helper in isolation against google.protobuf. +# Scaling — string, bytes, and packed-repeated at 3 payload sizes. +# Varint — encode_varint at 1-, 2-, 3-, and 5-byte bit widths. +# +# All proto schemas are built at import time via the descriptor / message- +# factory API — no .proto files or protoc step required. +# +# Run: +# uv run pytest tests/_pyprotobuf/test_benchmark.py -v --benchmark-sort=mean + +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory +from pytest import mark + +from opentelemetry._proto._pyprotobuf import encode_varint +from opentelemetry._proto._pyprotobuf.fields import ( + bool_field, + byt, + dbl, + fix32, + fix64, + msg, + packed_double, + packed_uint64, + sint32, + string, + u64, +) + +_T = descriptor_pb2.FieldDescriptorProto + + +# ── Proto builders ───────────────────────────────────────────────────────────── + +def _build_full_record_classes(): + """Record message with one field of every type (full-message benchmark).""" + fp = descriptor_pb2.FileDescriptorProto() + fp.name = "pyproto_benchmark.proto" + fp.syntax = "proto3" + + inner = fp.message_type.add() + inner.name = "Inner" + for name, number, tid in (("label", 1, _T.TYPE_STRING), ("seq", 2, _T.TYPE_UINT64)): + f = inner.field.add() + f.name = name; f.number = number; f.type = tid; f.label = _T.LABEL_OPTIONAL + + rec = fp.message_type.add() + rec.name = "Record" + for name, number, tid, lbl in ( + ("name", 1, _T.TYPE_STRING, _T.LABEL_OPTIONAL), + ("count", 2, _T.TYPE_UINT64, _T.LABEL_OPTIONAL), + ("value", 3, _T.TYPE_DOUBLE, _T.LABEL_OPTIONAL), + ("active", 4, _T.TYPE_BOOL, _T.LABEL_OPTIONAL), + ("data", 5, _T.TYPE_BYTES, _T.LABEL_OPTIONAL), + ("timestamp_ns", 6, _T.TYPE_FIXED64, _T.LABEL_OPTIONAL), + ("flags", 7, _T.TYPE_FIXED32, _T.LABEL_OPTIONAL), + ("bucket_counts", 9, _T.TYPE_UINT64, _T.LABEL_REPEATED), + ("bounds", 10, _T.TYPE_DOUBLE, _T.LABEL_REPEATED), + ): + f = rec.field.add() + f.name = name; f.number = number; f.type = tid; f.label = lbl + + mf = rec.field.add() + mf.name = "inner"; mf.number = 8; mf.type = _T.TYPE_MESSAGE + mf.label = _T.LABEL_OPTIONAL; mf.type_name = "Inner" + + pool = descriptor_pool.DescriptorPool() + pool.Add(fp) + return ( + message_factory.GetMessageClass(pool.FindMessageTypeByName("Inner")), + message_factory.GetMessageClass(pool.FindMessageTypeByName("Record")), + ) + + +def _build_field_msg_classes(): + """FieldMsg with one field per type — per-field and scaling benchmarks.""" + fp = descriptor_pb2.FileDescriptorProto() + fp.name = "pyproto_field_benchmark.proto" + fp.syntax = "proto3" + + fi = fp.message_type.add() + fi.name = "FieldInner" + f = fi.field.add() + f.name = "v"; f.number = 1; f.type = _T.TYPE_UINT64; f.label = _T.LABEL_OPTIONAL + + fm = fp.message_type.add() + fm.name = "FieldMsg" + for name, number, tid, lbl in ( + ("f_uint64", 1, _T.TYPE_UINT64, _T.LABEL_OPTIONAL), + ("f_string", 2, _T.TYPE_STRING, _T.LABEL_OPTIONAL), + ("f_bytes", 3, _T.TYPE_BYTES, _T.LABEL_OPTIONAL), + ("f_double", 4, _T.TYPE_DOUBLE, _T.LABEL_OPTIONAL), + ("f_bool", 5, _T.TYPE_BOOL, _T.LABEL_OPTIONAL), + ("f_fixed64", 6, _T.TYPE_FIXED64, _T.LABEL_OPTIONAL), + ("f_fixed32", 7, _T.TYPE_FIXED32, _T.LABEL_OPTIONAL), + ("f_sint32", 8, _T.TYPE_SINT32, _T.LABEL_OPTIONAL), + ("f_packed_u64", 9, _T.TYPE_UINT64, _T.LABEL_REPEATED), + ("f_packed_dbl", 10, _T.TYPE_DOUBLE, _T.LABEL_REPEATED), + ): + f = fm.field.add() + f.name = name; f.number = number; f.type = tid; f.label = lbl + + mf = fm.field.add() + mf.name = "f_msg"; mf.number = 11; mf.type = _T.TYPE_MESSAGE + mf.label = _T.LABEL_OPTIONAL; mf.type_name = "FieldInner" + + pool = descriptor_pool.DescriptorPool() + pool.Add(fp) + return ( + message_factory.GetMessageClass(pool.FindMessageTypeByName("FieldInner")), + message_factory.GetMessageClass(pool.FindMessageTypeByName("FieldMsg")), + ) + + +def _build_repeated_msg_classes(): + """Item + Container — repeated embedded message benchmark.""" + fp = descriptor_pb2.FileDescriptorProto() + fp.name = "pyproto_repeated_benchmark.proto" + fp.syntax = "proto3" + + item = fp.message_type.add() + item.name = "Item" + for name, number, tid in ( + ("label", 1, _T.TYPE_STRING), + ("count", 2, _T.TYPE_UINT64), + ("value", 3, _T.TYPE_DOUBLE), + ): + f = item.field.add() + f.name = name; f.number = number; f.type = tid; f.label = _T.LABEL_OPTIONAL + + container = fp.message_type.add() + container.name = "Container" + rf = container.field.add() + rf.name = "items"; rf.number = 1; rf.type = _T.TYPE_MESSAGE + rf.label = _T.LABEL_REPEATED; rf.type_name = "Item" + + pool = descriptor_pool.DescriptorPool() + pool.Add(fp) + return ( + message_factory.GetMessageClass(pool.FindMessageTypeByName("Item")), + message_factory.GetMessageClass(pool.FindMessageTypeByName("Container")), + ) + + +_Inner, _Record = _build_full_record_classes() +_FieldInner, _FieldMsg = _build_field_msg_classes() +_Item, _Container = _build_repeated_msg_classes() + + +# ── Shared benchmark data ────────────────────────────────────────────────────── + +_NAME = "benchmark.record.example" +_COUNT = 9_876_543_210 +_VALUE = 3.141592653589793 +_DATA = b"\xde\xad\xbe\xef" * 8 +_TS = 1_782_401_900_556_236_527 +_FLAGS = 0xDEAD +_INNER_LABEL = "inner.label" +_INNER_SEQ = 42 +_BUCKET_COUNTS = [0, 1, 4, 12, 35, 78, 120, 89, 42, 15, 4, 1, 0] +_BOUNDS = [0.0, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0] + +_FIELD_INNER_BYTES = _FieldInner(v=42).SerializeToString() + +# Item data for repeated-message benchmarks: (label, count, value) tuples. +# All values are non-default so every field is serialized. +_REPEATED_SIZES = [1, 5, 20] +_ITEM_DATA = [ + (f"item.label.{i}", (i + 1) * 100, (i + 1) * 0.5) + for i in range(max(_REPEATED_SIZES)) +] + +# Pre-built google.protobuf objects — construction cost excluded from +# serialization-only benchmarks in section 7. +_PRE_BUILT_RECORD = _Record( + name=_NAME, count=_COUNT, value=_VALUE, active=True, + data=_DATA, timestamp_ns=_TS, flags=_FLAGS, + inner=_Inner(label=_INNER_LABEL, seq=_INNER_SEQ), + bucket_counts=_BUCKET_COUNTS, bounds=_BOUNDS, +) + +_PRE_BUILT_FIELD_PB = { + "uint64": _FieldMsg(f_uint64=_COUNT), + "string": _FieldMsg(f_string=_NAME), + "bytes": _FieldMsg(f_bytes=_DATA), + "double": _FieldMsg(f_double=_VALUE), + "bool": _FieldMsg(f_bool=True), + "fixed64": _FieldMsg(f_fixed64=_TS), + "fixed32": _FieldMsg(f_fixed32=_FLAGS), + "sint32": _FieldMsg(f_sint32=-12345), + "packed_uint64": _FieldMsg(f_packed_u64=_BUCKET_COUNTS), + "packed_double": _FieldMsg(f_packed_dbl=_BOUNDS), + "msg": _FieldMsg(f_msg=_FieldInner(v=42)), +} + + +# ══════════════════════════════════════════════════════════════════════════════ +# 1. Full-message benchmark +# ══════════════════════════════════════════════════════════════════════════════ + +def _pyproto_encode() -> bytes: + inner = string(1, _INNER_LABEL) + u64(2, _INNER_SEQ) + return ( + string(1, _NAME) + + u64(2, _COUNT) + + dbl(3, _VALUE) + + bool_field(4, True) + + byt(5, _DATA) + + fix64(6, _TS) + + fix32(7, _FLAGS) + + msg(8, inner) + + packed_uint64(9, _BUCKET_COUNTS) + + packed_double(10, _BOUNDS) + ) + + +def _pb_encode() -> bytes: + return _Record( + name=_NAME, + count=_COUNT, + value=_VALUE, + active=True, + data=_DATA, + timestamp_ns=_TS, + flags=_FLAGS, + inner=_Inner(label=_INNER_LABEL, seq=_INNER_SEQ), + bucket_counts=_BUCKET_COUNTS, + bounds=_BOUNDS, + ).SerializeToString() + + +def test_encode_outputs_identical() -> None: + assert _pyproto_encode() == _pb_encode() + + +def test_encode_pyproto(benchmark) -> None: + result = benchmark(_pyproto_encode) + assert len(result) > 0 + + +def test_encode_protobuf(benchmark) -> None: + result = benchmark(_pb_encode) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 2. Per-field-type benchmarks +# +# Each pyproto field helper is benchmarked against google.protobuf encoding +# the same single field. Both sides include message-construction overhead +# where applicable (there is no separate "build" vs "serialize" step in the +# pyproto API — it is a pure function call). +# ══════════════════════════════════════════════════════════════════════════════ + +_PER_FIELD = [ + ("uint64", lambda: u64(1, _COUNT), + lambda: _FieldMsg(f_uint64=_COUNT).SerializeToString()), + ("string", lambda: string(2, _NAME), + lambda: _FieldMsg(f_string=_NAME).SerializeToString()), + ("bytes", lambda: byt(3, _DATA), + lambda: _FieldMsg(f_bytes=_DATA).SerializeToString()), + ("double", lambda: dbl(4, _VALUE), + lambda: _FieldMsg(f_double=_VALUE).SerializeToString()), + ("bool", lambda: bool_field(5, True), + lambda: _FieldMsg(f_bool=True).SerializeToString()), + ("fixed64", lambda: fix64(6, _TS), + lambda: _FieldMsg(f_fixed64=_TS).SerializeToString()), + ("fixed32", lambda: fix32(7, _FLAGS), + lambda: _FieldMsg(f_fixed32=_FLAGS).SerializeToString()), + ("sint32", lambda: sint32(8, -12345), + lambda: _FieldMsg(f_sint32=-12345).SerializeToString()), + ("packed_uint64",lambda: packed_uint64(9, _BUCKET_COUNTS), + lambda: _FieldMsg(f_packed_u64=_BUCKET_COUNTS).SerializeToString()), + ("packed_double",lambda: packed_double(10, _BOUNDS), + lambda: _FieldMsg(f_packed_dbl=_BOUNDS).SerializeToString()), + ("msg", lambda: msg(11, _FIELD_INNER_BYTES), + lambda: _FieldMsg(f_msg=_FieldInner(v=42)).SerializeToString()), +] + +_FIELD_IDS = [name for name, _, _ in _PER_FIELD] + + +@mark.parametrize("name,pyproto_fn,pb_fn", _PER_FIELD, ids=_FIELD_IDS) +def test_field_outputs_identical(name, pyproto_fn, pb_fn) -> None: + assert pyproto_fn() == pb_fn(), f"encoding mismatch for field type: {name}" + + +@mark.parametrize("fn", [p for _, p, _ in _PER_FIELD], ids=_FIELD_IDS) +def test_field_pyproto(benchmark, fn) -> None: + result = benchmark(fn) + assert len(result) > 0 + + +@mark.parametrize("fn", [pb for _, _, pb in _PER_FIELD], ids=_FIELD_IDS) +def test_field_protobuf(benchmark, fn) -> None: + result = benchmark(fn) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 3. Scaling benchmarks +# +# How encoding time grows with payload size for the three field types whose +# cost is proportional to data length: string, bytes, and packed repeated. +# Data is pre-built outside the benchmark loop; only the encoding is timed. +# ══════════════════════════════════════════════════════════════════════════════ + +_STR_SIZES = [4, 256, 16_384] +_BYTES_SIZES = [4, 256, 16_384] +_PACKED_SIZES = [10, 100, 1_000] + + +@mark.parametrize("n", _STR_SIZES, ids=["4B", "256B", "16KB"]) +def test_scale_string_pyproto(benchmark, n) -> None: + s = "x" * n + result = benchmark(lambda: string(2, s)) + assert len(result) > 0 + + +@mark.parametrize("n", _STR_SIZES, ids=["4B", "256B", "16KB"]) +def test_scale_string_protobuf(benchmark, n) -> None: + s = "x" * n + result = benchmark(lambda: _FieldMsg(f_string=s).SerializeToString()) + assert len(result) > 0 + + +@mark.parametrize("n", _BYTES_SIZES, ids=["4B", "256B", "16KB"]) +def test_scale_bytes_pyproto(benchmark, n) -> None: + data = b"x" * n + result = benchmark(lambda: byt(3, data)) + assert len(result) > 0 + + +@mark.parametrize("n", _BYTES_SIZES, ids=["4B", "256B", "16KB"]) +def test_scale_bytes_protobuf(benchmark, n) -> None: + data = b"x" * n + result = benchmark(lambda: _FieldMsg(f_bytes=data).SerializeToString()) + assert len(result) > 0 + + +@mark.parametrize("n", _PACKED_SIZES, ids=["10", "100", "1000"]) +def test_scale_packed_uint64_pyproto(benchmark, n) -> None: + values = list(range(n)) + result = benchmark(lambda: packed_uint64(9, values)) + assert len(result) > 0 + + +@mark.parametrize("n", _PACKED_SIZES, ids=["10", "100", "1000"]) +def test_scale_packed_uint64_protobuf(benchmark, n) -> None: + values = list(range(n)) + result = benchmark(lambda: _FieldMsg(f_packed_u64=values).SerializeToString()) + assert len(result) > 0 + + +@mark.parametrize("n", _PACKED_SIZES, ids=["10", "100", "1000"]) +def test_scale_packed_double_pyproto(benchmark, n) -> None: + values = [float(i) * 0.1 for i in range(n)] + result = benchmark(lambda: packed_double(10, values)) + assert len(result) > 0 + + +@mark.parametrize("n", _PACKED_SIZES, ids=["10", "100", "1000"]) +def test_scale_packed_double_protobuf(benchmark, n) -> None: + values = [float(i) * 0.1 for i in range(n)] + result = benchmark(lambda: _FieldMsg(f_packed_dbl=values).SerializeToString()) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 4. Varint bit-width benchmarks +# +# encode_varint is the hottest path in _pyprotobuf: every tag and every +# varint-type field value goes through it. These benchmarks measure how +# encoding time scales with the number of continuation bytes (bit width). +# +# The google.protobuf column encodes the same integer as a uint64 field +# (including message-object construction and tag overhead), which is the +# closest available comparison point since protobuf does not expose a +# standalone varint encoder. Use the pyproto column to track raw varint +# speed; use the ratio to see the combined tag+field overhead gap. +# ══════════════════════════════════════════════════════════════════════════════ + +_VARINT_CASES = [ + ("1byte", 63), # fits in 1 byte (0x00–0x7f) + ("2byte", 300), # requires 2 bytes (0x80–0x3fff) + ("3byte", 100_000), # requires 3 bytes (0x4000–0x1fffff) + ("5byte", 2**32 - 1), # requires 5 bytes (max uint32) +] + +_VARINT_IDS = [c[0] for c in _VARINT_CASES] +_VARINT_VALUES = [c[1] for c in _VARINT_CASES] + + +@mark.parametrize("v", _VARINT_VALUES, ids=_VARINT_IDS) +def test_varint_pyproto(benchmark, v) -> None: + result = benchmark(lambda: encode_varint(v)) + assert len(result) > 0 + + +@mark.parametrize("v", _VARINT_VALUES, ids=_VARINT_IDS) +def test_varint_protobuf(benchmark, v) -> None: + result = benchmark(lambda: _FieldMsg(f_uint64=v).SerializeToString()) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 5. Concatenation strategy: `+` vs `b"".join()` +# +# The current SerializeToString() pattern chains field results with `+`, +# creating N-1 intermediate bytes objects (one per addition). b"".join() +# allocates the final buffer once and copies each part exactly once. +# These two tests use the same field data so the only variable is the +# concatenation strategy. +# ══════════════════════════════════════════════════════════════════════════════ + +def _pyproto_encode_join() -> bytes: + inner = b"".join([string(1, _INNER_LABEL), u64(2, _INNER_SEQ)]) + return b"".join([ + string(1, _NAME), + u64(2, _COUNT), + dbl(3, _VALUE), + bool_field(4, True), + byt(5, _DATA), + fix64(6, _TS), + fix32(7, _FLAGS), + msg(8, inner), + packed_uint64(9, _BUCKET_COUNTS), + packed_double(10, _BOUNDS), + ]) + + +def test_concat_strategy_outputs_identical() -> None: + assert _pyproto_encode() == _pyproto_encode_join() + + +def test_encode_concat_pyproto(benchmark) -> None: + result = benchmark(_pyproto_encode) + assert len(result) > 0 + + +def test_encode_join_pyproto(benchmark) -> None: + result = benchmark(_pyproto_encode_join) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 6. All-default fields (fast path) +# +# Proto3 omits fields whose value equals the type default (0, "", b"", False, +# 0.0, []). Each _pyprotobuf helper returns b"" immediately for defaults. +# This benchmark measures the minimum cost of SerializeToString() — calling +# every helper in a message when none produce any output. +# ══════════════════════════════════════════════════════════════════════════════ + +def _pyproto_encode_all_defaults() -> bytes: + # Embedded message field omitted: real SerializeToString() guards with + # `if self.field is not None`. Every helper here returns b"". + return ( + string(1, "") + + u64(2, 0) + + dbl(3, 0.0) + + bool_field(4, False) + + byt(5, b"") + + fix64(6, 0) + + fix32(7, 0) + + packed_uint64(9, []) + + packed_double(10, []) + ) + + +def test_all_defaults_pyproto(benchmark) -> None: + result = benchmark(_pyproto_encode_all_defaults) + assert result == b"" + + +def test_all_defaults_protobuf(benchmark) -> None: + result = benchmark(lambda: _Record().SerializeToString()) + assert result == b"" + + +# ══════════════════════════════════════════════════════════════════════════════ +# 7. google.protobuf: construction vs serialization split +# +# Previous benchmarks bundle message-object construction and serialization +# together for google.protobuf. pyproto has no construction phase — it is a +# pure function call. Separating the two phases shows where google.protobuf's +# time actually goes and gives a fairer encoding-only comparison. +# +# Read these alongside the existing test_encode_pyproto / test_encode_protobuf: +# test_encode_pyproto — pyproto: pure call, no object +# test_encode_protobuf_construct — google.protobuf: construction only +# test_encode_protobuf_serialize — google.protobuf: serialization only +# test_encode_protobuf — google.protobuf: construction + serialization +# ══════════════════════════════════════════════════════════════════════════════ + +def _pb_construct(): + return _Record( + name=_NAME, count=_COUNT, value=_VALUE, active=True, + data=_DATA, timestamp_ns=_TS, flags=_FLAGS, + inner=_Inner(label=_INNER_LABEL, seq=_INNER_SEQ), + bucket_counts=_BUCKET_COUNTS, bounds=_BOUNDS, + ) + + +def test_encode_protobuf_construct(benchmark) -> None: + result = benchmark(_pb_construct) + assert result is not None + + +def test_encode_protobuf_serialize(benchmark) -> None: + result = benchmark(_PRE_BUILT_RECORD.SerializeToString) + assert len(result) > 0 + + +@mark.parametrize("name", list(_PRE_BUILT_FIELD_PB), ids=list(_PRE_BUILT_FIELD_PB)) +def test_field_serialize_only_protobuf(benchmark, name) -> None: + result = benchmark(_PRE_BUILT_FIELD_PB[name].SerializeToString) + assert len(result) > 0 + + +# ══════════════════════════════════════════════════════════════════════════════ +# 8. Repeated embedded messages +# +# Encodes a Container with N Item sub-messages. Each Item has a string, +# uint64, and double field. Unlike packed repeated scalars, repeated messages +# require one SerializeToString-equivalent call per element plus one msg() +# wrapper per element — this tests whether per-element overhead compounds +# or stays flat. +# +# pyproto side: b"".join(msg(1, string(1,l)+u64(2,c)+dbl(3,v)) for ...) +# protobuf side: _Container(items=[_Item(...), ...]).SerializeToString() +# ══════════════════════════════════════════════════════════════════════════════ + +def _pyproto_encode_repeated(n: int) -> bytes: + return b"".join( + msg(1, string(1, label) + u64(2, count) + dbl(3, value)) + for label, count, value in _ITEM_DATA[:n] + ) + + +def _pb_encode_repeated(n: int) -> bytes: + return _Container( + items=[ + _Item(label=label, count=count, value=value) + for label, count, value in _ITEM_DATA[:n] + ] + ).SerializeToString() + + +@mark.parametrize("n", _REPEATED_SIZES, ids=["1", "5", "20"]) +def test_repeated_msg_outputs_identical(n) -> None: + assert _pyproto_encode_repeated(n) == _pb_encode_repeated(n) + + +@mark.parametrize("n", _REPEATED_SIZES, ids=["1", "5", "20"]) +def test_repeated_msg_pyproto(benchmark, n) -> None: + result = benchmark(lambda: _pyproto_encode_repeated(n)) + assert len(result) > 0 + + +@mark.parametrize("n", _REPEATED_SIZES, ids=["1", "5", "20"]) +def test_repeated_msg_protobuf(benchmark, n) -> None: + result = benchmark(lambda: _pb_encode_repeated(n)) + assert len(result) > 0 diff --git a/opentelemetry-proto/tests/performance/test_benchmark_otlp.py b/opentelemetry-proto/tests/performance/test_benchmark_otlp.py new file mode 100644 index 00000000000..6a90aff9b3b --- /dev/null +++ b/opentelemetry-proto/tests/performance/test_benchmark_otlp.py @@ -0,0 +1,408 @@ +# tests/performance/test_benchmark_otlp.py +# +# Benchmark: pure-Python pyproto (opentelemetry._proto) vs the real +# google.protobuf implementation (opentelemetry.proto, upb/C backend) on +# *realistic, full OTLP export payloads* — the actual workload the exporters +# put through these message classes. +# +# test_benchmark.py measures the _pyprotobuf primitives (varint, single +# fields, scaling) against synthetic google.protobuf messages built from +# throw-away descriptors. This file instead exercises the real OTLP message +# trees — ExportTraceServiceRequest / ExportLogsServiceRequest / +# ExportMetricsServiceRequest, nested Resource → Scope → Span/LogRecord/Metric +# with attributes, events, links, status and data points — at several batch +# sizes, so the pyproto-vs-protobuf gap can be read off for payloads that look +# like what a real collector export sends. +# +# Both implementations expose an identical constructor + SerializeToString() +# API (the equivalence suite proves the bytes match), so a single builder, +# parameterized by a bundle of classes, drives both sides. +# +# BUILD + SERIALIZE benchmarks — one full encode of a logical payload from +# source data, i.e. the per-export cost an +# exporter actually pays. +# SERIALIZE-ONLY benchmarks — SerializeToString() on a pre-built message, +# isolating raw encoding from construction. +# +# The real (google.protobuf) classes are only available when the upstream +# ``opentelemetry-proto`` package owns the public ``opentelemetry.proto`` path +# (installed *after* this package so it wins the namespace). When it does not, +# ``opentelemetry.proto`` resolves to this package's pure-Python shim and the +# comparison would be pyproto-vs-pyproto — so the whole module skips, exactly +# like the equivalence suite's conftest guard. +# +# Run (install order matters — real protobuf must win opentelemetry.proto): +# uv pip install . && uv pip install protobuf opentelemetry-proto pytest-benchmark +# uv run pytest tests/performance/test_benchmark_otlp.py \ +# --benchmark-sort=fullname --benchmark-group-by=group + +from types import SimpleNamespace + +from pytest import mark, skip + +from opentelemetry._proto.collector.logs.v1 import logs_service_pb2 as _py_coll_logs +from opentelemetry._proto.collector.metrics.v1 import ( + metrics_service_pb2 as _py_coll_metrics, +) +from opentelemetry._proto.collector.trace.v1 import ( + trace_service_pb2 as _py_coll_trace, +) +from opentelemetry._proto.common.v1 import common_pb2 as _py_common +from opentelemetry._proto.logs.v1 import logs_pb2 as _py_logs +from opentelemetry._proto.metrics.v1 import metrics_pb2 as _py_metrics +from opentelemetry._proto.resource.v1 import resource_pb2 as _py_resource +from opentelemetry._proto.trace.v1 import trace_pb2 as _py_trace + +# Public path: the real opentelemetry-proto when installed, else this package's +# pure-Python shim. Guard that it is the real package before benchmarking. +from opentelemetry.proto.common.v1 import common_pb2 as _pb_common + +if "pyproto" in (_pb_common.__file__ or ""): + skip( + "opentelemetry.proto resolves to the pyproto shim " + f"({_pb_common.__file__}); install the real opentelemetry-proto " + "package (after this one, so it owns opentelemetry.proto) to compare " + "pure-Python pyproto against the real google.protobuf implementation.", + allow_module_level=True, + ) + +from opentelemetry.proto.collector.logs.v1 import logs_service_pb2 as _pb_coll_logs +from opentelemetry.proto.collector.metrics.v1 import ( + metrics_service_pb2 as _pb_coll_metrics, +) +from opentelemetry.proto.collector.trace.v1 import ( + trace_service_pb2 as _pb_coll_trace, +) +from opentelemetry.proto.logs.v1 import logs_pb2 as _pb_logs +from opentelemetry.proto.metrics.v1 import metrics_pb2 as _pb_metrics +from opentelemetry.proto.resource.v1 import resource_pb2 as _pb_resource +from opentelemetry.proto.trace.v1 import trace_pb2 as _pb_trace + + +# ── Class bundles ─────────────────────────────────────────────────────────── +# +# Both implementations share the same class names and constructor kwargs, so a +# builder written against one bundle works verbatim against the other. + +def _bundle(common, resource, trace, logs, metrics, coll_trace, coll_logs, coll_metrics): + return SimpleNamespace( + AnyValue=common.AnyValue, + KeyValue=common.KeyValue, + InstrumentationScope=common.InstrumentationScope, + Resource=resource.Resource, + Span=trace.Span, + Status=trace.Status, + ScopeSpans=trace.ScopeSpans, + ResourceSpans=trace.ResourceSpans, + LogRecord=logs.LogRecord, + ScopeLogs=logs.ScopeLogs, + ResourceLogs=logs.ResourceLogs, + NumberDataPoint=metrics.NumberDataPoint, + Sum=metrics.Sum, + HistogramDataPoint=metrics.HistogramDataPoint, + Histogram=metrics.Histogram, + Metric=metrics.Metric, + ScopeMetrics=metrics.ScopeMetrics, + ResourceMetrics=metrics.ResourceMetrics, + ExportTraceServiceRequest=coll_trace.ExportTraceServiceRequest, + ExportLogsServiceRequest=coll_logs.ExportLogsServiceRequest, + ExportMetricsServiceRequest=coll_metrics.ExportMetricsServiceRequest, + ) + + +PY = _bundle( + _py_common, _py_resource, _py_trace, _py_logs, _py_metrics, + _py_coll_trace, _py_coll_logs, _py_coll_metrics, +) +PB = _bundle( + _pb_common, _pb_resource, _pb_trace, _pb_logs, _pb_metrics, + _pb_coll_trace, _pb_coll_logs, _pb_coll_metrics, +) + + +# ── Deterministic source data ─────────────────────────────────────────────── +# +# No randomness (scripts/tests must be reproducible); ids and values are +# derived from the element index so every payload is stable across runs. + +_SCHEMA_URL = "https://opentelemetry.io/schemas/1.30.0" + + +def _trace_id(i: int) -> bytes: + return bytes(((i + b) % 256) for b in range(16)) + + +def _span_id(i: int) -> bytes: + return bytes(((i * 7 + b) % 256) for b in range(8)) + + +def _resource(M): + """A resource with the attribute set a real service emits.""" + return M.Resource( + attributes=[ + M.KeyValue(key="service.name", value=M.AnyValue(string_value="checkout-service")), + M.KeyValue(key="service.version", value=M.AnyValue(string_value="1.24.0")), + M.KeyValue(key="service.instance.id", value=M.AnyValue(string_value="pod-7f9c-abc123")), + M.KeyValue(key="process.pid", value=M.AnyValue(int_value=42317)), + M.KeyValue(key="host.name", value=M.AnyValue(string_value="ip-10-0-12-34")), + M.KeyValue(key="telemetry.sdk.language", value=M.AnyValue(string_value="python")), + M.KeyValue(key="telemetry.sdk.version", value=M.AnyValue(string_value="1.30.0")), + ] + ) + + +def _span_attributes(M, i: int): + return [ + M.KeyValue(key="http.request.method", value=M.AnyValue(string_value="GET")), + M.KeyValue(key="url.path", value=M.AnyValue(string_value=f"/api/orders/{i}")), + M.KeyValue(key="http.response.status_code", value=M.AnyValue(int_value=200)), + M.KeyValue(key="server.address", value=M.AnyValue(string_value="orders.internal")), + M.KeyValue(key="network.protocol.version", value=M.AnyValue(string_value="1.1")), + M.KeyValue(key="db.system", value=M.AnyValue(string_value="postgresql")), + ] + + +def _span(M, i: int): + base = 1_700_000_000_000_000_000 + i * 1_000_000 + return M.Span( + trace_id=_trace_id(i), + span_id=_span_id(i), + parent_span_id=_span_id(i + 1), + name="GET /api/orders/{id}", + kind=2, # SPAN_KIND_SERVER + start_time_unix_nano=base, + end_time_unix_nano=base + 4_200_000, + attributes=_span_attributes(M, i), + events=[ + M.Span.Event( + time_unix_nano=base + 1_000_000, + name="cache.miss", + attributes=[ + M.KeyValue(key="cache.key", value=M.AnyValue(string_value=f"order:{i}")), + ], + ), + M.Span.Event( + time_unix_nano=base + 2_500_000, + name="db.query", + attributes=[ + M.KeyValue(key="db.rows", value=M.AnyValue(int_value=17)), + ], + ), + ], + links=[ + M.Span.Link( + trace_id=_trace_id(i + 100), + span_id=_span_id(i + 100), + trace_state="vendor=abc", + ), + ], + status=M.Status(code=1, message=""), # STATUS_CODE_OK + flags=1, + ) + + +def _log_record(M, i: int): + base = 1_700_000_000_000_000_000 + i * 1_000_000 + return M.LogRecord( + time_unix_nano=base, + observed_time_unix_nano=base + 1_000, + severity_number=9, # INFO + severity_text="INFO", + body=M.AnyValue(string_value=f"request {i} completed in 4.2ms with status 200"), + attributes=[ + M.KeyValue(key="log.source", value=M.AnyValue(string_value="access")), + M.KeyValue(key="http.route", value=M.AnyValue(string_value="/api/orders/{id}")), + M.KeyValue(key="http.response.status_code", value=M.AnyValue(int_value=200)), + M.KeyValue(key="thread.id", value=M.AnyValue(int_value=140_234_567)), + ], + trace_id=_trace_id(i), + span_id=_span_id(i), + flags=1, + ) + + +def _metric(M, i: int, n_dp: int): + base = 1_700_000_000_000_000_000 + i * 1_000_000 + return M.Metric( + name=f"http.server.request.duration.{i}", + description="Duration of HTTP server requests.", + unit="s", + histogram=M.Histogram( + aggregation_temporality=2, # CUMULATIVE + data_points=[ + M.HistogramDataPoint( + attributes=[ + M.KeyValue(key="http.request.method", value=M.AnyValue(string_value="GET")), + M.KeyValue(key="http.response.status_code", value=M.AnyValue(int_value=200)), + ], + start_time_unix_nano=base, + time_unix_nano=base + 60_000_000_000, + count=1_234, + sum=456.789, + bucket_counts=[0, 12, 145, 402, 388, 210, 61, 14, 2, 0], + explicit_bounds=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5], + min=0.002, + max=3.1, + ) + for _ in range(n_dp) + ], + ), + ) + + +# ── Payload builders (batch of resources × scopes × items) ────────────────── + +def build_trace_request(M, n_res: int, n_scope: int, n_span: int): + return M.ExportTraceServiceRequest( + resource_spans=[ + M.ResourceSpans( + resource=_resource(M), + scope_spans=[ + M.ScopeSpans( + scope=M.InstrumentationScope(name="opentelemetry.instrumentation.flask", version="0.48b0"), + spans=[_span(M, i) for i in range(n_span)], + schema_url=_SCHEMA_URL, + ) + for _ in range(n_scope) + ], + schema_url=_SCHEMA_URL, + ) + for _ in range(n_res) + ] + ) + + +def build_logs_request(M, n_res: int, n_scope: int, n_log: int): + return M.ExportLogsServiceRequest( + resource_logs=[ + M.ResourceLogs( + resource=_resource(M), + scope_logs=[ + M.ScopeLogs( + scope=M.InstrumentationScope(name="opentelemetry.sdk._logs", version="1.30.0"), + log_records=[_log_record(M, i) for i in range(n_log)], + schema_url=_SCHEMA_URL, + ) + for _ in range(n_scope) + ], + schema_url=_SCHEMA_URL, + ) + for _ in range(n_res) + ] + ) + + +def build_metrics_request(M, n_res: int, n_metric: int, n_dp: int): + return M.ExportMetricsServiceRequest( + resource_metrics=[ + M.ResourceMetrics( + resource=_resource(M), + scope_metrics=[ + M.ScopeMetrics( + scope=M.InstrumentationScope(name="opentelemetry.sdk.metrics", version="1.30.0"), + metrics=[_metric(M, i, n_dp) for i in range(n_metric)], + schema_url=_SCHEMA_URL, + ) + ], + schema_url=_SCHEMA_URL, + ) + for _ in range(n_res) + ] + ) + + +# Scale points: (id, dims). Dims meaning is per-builder (see signatures above). +# "single" — one item, the smallest useful export. +# "scope_N" — one resource/scope, N items: isolates per-item encoding cost. +# "batch" — several resources/scopes: a full collector-sized export. +_TRACE_SCALES = [ + ("single", (1, 1, 1)), + ("scope_100", (1, 1, 100)), + ("batch_512", (4, 2, 64)), +] +_LOG_SCALES = [ + ("single", (1, 1, 1)), + ("scope_100", (1, 1, 100)), + ("batch_1000", (2, 1, 500)), +] +_METRIC_SCALES = [ + ("single", (1, 1, 1)), + ("scope_50", (1, 50, 1)), + ("batch_500", (2, 25, 10)), +] + +_SIGNALS = { + "trace": (build_trace_request, _TRACE_SCALES), + "logs": (build_logs_request, _LOG_SCALES), + "metrics": (build_metrics_request, _METRIC_SCALES), +} + + +# ── Equivalence guard: the two sides must encode to identical bytes ────────── +# +# The benchmark only means something if both implementations do the same work. +# Proto3 field-order serialization makes the byte streams comparable directly. + +_ALL_CASES = [ + (signal, label, dims) + for signal, (_builder, scales) in _SIGNALS.items() + for label, dims in scales +] +_ALL_IDS = [f"{signal}-{label}" for signal, label, _ in _ALL_CASES] + + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +def test_otlp_outputs_identical(signal, label, dims) -> None: + builder = _SIGNALS[signal][0] + py_bytes = builder(PY, *dims).SerializeToString() + pb_bytes = builder(PB, *dims).SerializeToString() + assert py_bytes == pb_bytes, ( + f"{signal}/{label}: pyproto and protobuf disagree " + f"({len(py_bytes)} vs {len(pb_bytes)} bytes)" + ) + + +# ── Build + serialize: the real per-export cost ───────────────────────────── +# +# One full encode of a logical payload from source data — construct the message +# tree and serialize it, as an exporter does on every export. + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +@mark.benchmark(group="build_serialize") +def test_build_serialize_pyproto(benchmark, signal, label, dims) -> None: + builder = _SIGNALS[signal][0] + result = benchmark(lambda: builder(PY, *dims).SerializeToString()) + assert len(result) > 0 + + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +@mark.benchmark(group="build_serialize") +def test_build_serialize_protobuf(benchmark, signal, label, dims) -> None: + builder = _SIGNALS[signal][0] + result = benchmark(lambda: builder(PB, *dims).SerializeToString()) + assert len(result) > 0 + + +# ── Serialize only: raw encoding of a pre-built message ───────────────────── +# +# Isolates SerializeToString() from message construction. pyproto builds nothing +# up front (its constructors just stash references), so its serialize-only and +# build+serialize numbers are close; google.protobuf does real work in both +# construction and serialization, so this split shows where its time goes. + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +@mark.benchmark(group="serialize_only") +def test_serialize_only_pyproto(benchmark, signal, label, dims) -> None: + builder = _SIGNALS[signal][0] + message = builder(PY, *dims) + result = benchmark(message.SerializeToString) + assert len(result) > 0 + + +@mark.parametrize("signal,label,dims", _ALL_CASES, ids=_ALL_IDS) +@mark.benchmark(group="serialize_only") +def test_serialize_only_protobuf(benchmark, signal, label, dims) -> None: + builder = _SIGNALS[signal][0] + message = builder(PB, *dims) + result = benchmark(message.SerializeToString) + assert len(result) > 0 diff --git a/opentelemetry-proto/tests/test_proto.py b/opentelemetry-proto/tests/test_proto.py deleted file mode 100644 index 2120c0a06a1..00000000000 --- a/opentelemetry-proto/tests/test_proto.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 -# type: ignore - -from importlib.util import find_spec -from unittest import TestCase - - -class TestInstrumentor(TestCase): - def test_proto(self): - if find_spec("opentelemetry.proto") is None: - self.fail("opentelemetry-proto not installed") diff --git a/opentelemetry-proto/tests/unit/__init__.py b/opentelemetry-proto/tests/unit/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/__init__.py b/opentelemetry-proto/tests/unit/_pyprotobuf/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/test_enum.py b/opentelemetry-proto/tests/unit/_pyprotobuf/test_enum.py new file mode 100644 index 00000000000..ac20d349ba9 --- /dev/null +++ b/opentelemetry-proto/tests/unit/_pyprotobuf/test_enum.py @@ -0,0 +1,96 @@ +# tests/test__enum.py +# +# encode_enum encodes an integer enum value using the same wire format as +# int32: non-negative values as a plain varint, negative values as a 64-bit +# two's-complement varint. These tests verify that contract using hand-computed +# expected byte literals. + +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory +from pytest import mark + +from opentelemetry._proto._pyprotobuf import encode_enum, encode_int, encode_tag + + +def test_zero() -> None: + # The proto3 default enum value is always 0. + assert encode_enum(0) == b"\x00" + + +def test_one() -> None: + # Typical enum constant fits in a single varint byte. + assert encode_enum(1) == b"\x01" + + +def test_two() -> None: + assert encode_enum(2) == b"\x02" + + +def test_two_byte_value() -> None: + # A large enum constant that requires two varint bytes (same as encode_int(300)). + assert encode_enum(300) == b"\xac\x02" + + +def test_negative_value() -> None: + # Negative enum values use 64-bit two's-complement encoding. + # -1 → 10-byte varint for 2^64-1. + assert encode_enum(-1) == b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +def test_matches_encode_int_for_positive_values() -> None: + # encode_enum and encode_int must produce identical bytes for non-negative inputs. + for value in [0, 1, 2, 127, 128, 255, 300, 2**16]: + assert encode_enum(value) == encode_int(value) + + +def test_matches_encode_int_for_negative_values() -> None: + for value in [-1, -2, -(2**31)]: + assert encode_enum(value) == encode_int(value) + + +# ── oracle — byte-for-byte comparison with google.protobuf ──────────────────── +# +# A proto2 message with a Color enum field is used as the oracle. proto2 is +# chosen so the field is serialised even when its value is the default (0). +# +# The Color enum defines RED=0, GREEN=1, BLUE=2. Only those three values are +# used because proto2 rejects assignments of undefined enum constants. +# +# The serialised message is exactly encode_tag(field, 0) + encode_enum(value). + +_FIELD = 1 +_WT = 0 # wire type 0 — varint + + +def _build_enum_message_class(): + file_proto = descriptor_pb2.FileDescriptorProto() + file_proto.name = "opentelemetry_pyproto_test_enum.proto" + file_proto.syntax = "proto2" + + color = file_proto.enum_type.add() + color.name = "Color" + for name, number in [("RED", 0), ("GREEN", 1), ("BLUE", 2)]: + ev = color.value.add() + ev.name = name + ev.number = number + + msg_proto = file_proto.message_type.add() + msg_proto.name = "EnumMessage" + f = msg_proto.field.add() + f.name = "color_field" + f.number = _FIELD + f.type = descriptor_pb2.FieldDescriptorProto.TYPE_ENUM + f.label = descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL + f.type_name = ".Color" + + pool = descriptor_pool.DescriptorPool() + pool.Add(file_proto) + return message_factory.GetMessageClass(pool.FindMessageTypeByName("EnumMessage")) + + +_EnumMessage = _build_enum_message_class() + + +@mark.parametrize("value", [0, 1, 2]) +def test_encode_enum_matches_protobuf(value: int) -> None: + expected = encode_tag(_FIELD, _WT) + encode_enum(value) + assert _EnumMessage(color_field=value).SerializeToString() == expected diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/test_fields.py b/opentelemetry-proto/tests/unit/_pyprotobuf/test_fields.py new file mode 100644 index 00000000000..6327a842766 --- /dev/null +++ b/opentelemetry-proto/tests/unit/_pyprotobuf/test_fields.py @@ -0,0 +1,651 @@ +# tests/_pyprotobuf/test_fields.py +# +# Tests for the proto3 field-level encoding helpers in fields.py. +# +# Every test follows the same pattern: +# 1. Verify default-omission: proto3 defaults (zero, empty, None) produce b"". +# 2. Verify tag: the first byte(s) of the output encode the correct field +# number and wire type using the formula (field_number << 3) | wire_type. +# 3. Verify value: the bytes after the tag match the correct wire encoding. +# +# All expected byte literals are derived by hand from the protobuf wire-format +# specification and cross-checked against encode_tag + the primitive encoders. + +from math import inf, nan +from struct import pack + +from pytest import mark + +from opentelemetry._proto._pyprotobuf import encode_tag, encode_varint +from opentelemetry._proto._pyprotobuf.fields import ( + WT_32BIT, + WT_64BIT, + WT_LEN, + WT_VARINT, + bool_field, + byt, + dbl, + fix32, + fix64, + msg, + opt_dbl, + packed_double, + packed_fix64, + packed_uint64, + sint32, + string, + u64, +) + + +# ── Wire-type constants ──────────────────────────────────────────────────────── + + +def test_wt_varint_is_zero() -> None: + assert WT_VARINT == 0 + + +def test_wt_64bit_is_one() -> None: + assert WT_64BIT == 1 + + +def test_wt_len_is_two() -> None: + assert WT_LEN == 2 + + +def test_wt_32bit_is_five() -> None: + assert WT_32BIT == 5 + + +# ── msg ──────────────────────────────────────────────────────────────────────── +# +# msg always writes (no omission for empty content). The caller decides whether +# to guard on None. Wire layout: tag | varint(len(content)) | content. + + +def test_msg_empty_content() -> None: + # An empty sub-message produces tag + varint(0), not b"". + # tag(1, 2) = (1<<3)|2 = 10 = 0x0A; varint(0) = 0x00 + assert msg(1, b"") == b"\x0a\x00" + + +def test_msg_single_byte_content() -> None: + # tag(1, 2) = 0x0A; varint(1) = 0x01; content = 0xAB + assert msg(1, b"\xab") == b"\x0a\x01\xab" + + +def test_msg_two_byte_content() -> None: + # tag(1, 2) = 0x0A; varint(2) = 0x02; content = 0xDE 0xAD + assert msg(1, b"\xde\xad") == b"\x0a\x02\xde\xad" + + +def test_msg_field_number_2() -> None: + # tag(2, 2) = (2<<3)|2 = 18 = 0x12 + assert msg(2, b"\x01") == b"\x12\x01\x01" + + +def test_msg_tag_uses_wt_len() -> None: + result = msg(3, b"\xff") + assert result[0] == (3 << 3) | WT_LEN + + +def test_msg_length_prefix_correct_for_long_content() -> None: + # 128 bytes of content — length prefix needs two varint bytes (0x80 0x01) + content = b"\x00" * 128 + result = msg(1, content) + assert result[0:1] == b"\x0a" + assert result[1:3] == b"\x80\x01" + assert result[3:] == content + + +def test_msg_matches_formula() -> None: + content = b"\x42" * 5 + field = 4 + expected = encode_tag(field, WT_LEN) + encode_varint(len(content)) + content + assert msg(field, content) == expected + + +# ── string ───────────────────────────────────────────────────────────────────── +# +# Omit when empty. Wire layout: tag (wt=2) | varint(len(utf8)) | utf8 bytes. + + +def test_string_empty_is_omitted() -> None: + assert string(1, "") == b"" + + +def test_string_ascii() -> None: + # tag(1, 2) = 0x0A; varint(2) = 0x02; "hi" = 0x68 0x69 + assert string(1, "hi") == b"\x0a\x02hi" + + +def test_string_single_char() -> None: + assert string(1, "A") == b"\x0a\x01A" + + +def test_string_two_byte_utf8() -> None: + # "é" = U+00E9, UTF-8: C3 A9 (2 bytes) + assert string(1, "é") == b"\x0a\x02\xc3\xa9" + + +def test_string_three_byte_utf8() -> None: + # "中" = U+4E2D, UTF-8: E4 B8 AD (3 bytes) + assert string(1, "中") == b"\x0a\x03\xe4\xb8\xad" + + +def test_string_field_number_affects_tag() -> None: + # tag(3, 2) = (3<<3)|2 = 26 = 0x1A + assert string(3, "x") == b"\x1a\x01x" + + +def test_string_tag_uses_wt_len() -> None: + result = string(2, "hello") + assert result[0] == (2 << 3) | WT_LEN + + +def test_string_length_is_byte_count_not_char_count() -> None: + # "日" = 3 UTF-8 bytes; length prefix must be 3, not 1 + result = string(1, "日") + assert result[1] == 3 + + +@mark.parametrize("value", ["a", "hello", "café", "Ünïcödé", "日本語", "\U0001F600"]) +def test_string_matches_formula(value: str) -> None: + utf8 = value.encode("utf-8") + expected = encode_tag(1, WT_LEN) + encode_varint(len(utf8)) + utf8 + assert string(1, value) == expected + + +# ── byt ──────────────────────────────────────────────────────────────────────── +# +# Omit when empty. Wire layout: tag (wt=2) | varint(len) | raw bytes. +# Identical layout to string but no UTF-8 encoding step. + + +def test_byt_empty_is_omitted() -> None: + assert byt(1, b"") == b"" + + +def test_byt_single_byte() -> None: + # tag(1, 2) = 0x0A; varint(1) = 0x01; payload = 0x42 + assert byt(1, b"\x42") == b"\x0a\x01\x42" + + +def test_byt_two_bytes() -> None: + assert byt(1, b"\xde\xad") == b"\x0a\x02\xde\xad" + + +def test_byt_null_bytes_preserved() -> None: + assert byt(1, b"\x00\x00") == b"\x0a\x02\x00\x00" + + +def test_byt_high_bytes_preserved() -> None: + assert byt(1, b"\xff\x80") == b"\x0a\x02\xff\x80" + + +def test_byt_field_number_affects_tag() -> None: + # tag(7, 2) = (7<<3)|2 = 58 = 0x3A + assert byt(7, b"\x01") == b"\x3a\x01\x01" + + +def test_byt_tag_uses_wt_len() -> None: + result = byt(2, b"\x99") + assert result[0] == (2 << 3) | WT_LEN + + +@mark.parametrize("value", [b"\x00", b"\xff", b"hello", b"\x00\x01\x02", b"\x80\x81\x82"]) +def test_byt_matches_formula(value: bytes) -> None: + expected = encode_tag(1, WT_LEN) + encode_varint(len(value)) + value + assert byt(1, value) == expected + + +# ── u64 ──────────────────────────────────────────────────────────────────────── +# +# Omit when zero. Wire layout: tag (wt=0) | varint(value). + + +def test_u64_zero_is_omitted() -> None: + assert u64(1, 0) == b"" + + +def test_u64_one() -> None: + # tag(1, 0) = 0x08; varint(1) = 0x01 + assert u64(1, 1) == b"\x08\x01" + + +def test_u64_127() -> None: + assert u64(1, 127) == b"\x08\x7f" + + +def test_u64_128() -> None: + # varint(128) = 0x80 0x01 + assert u64(1, 128) == b"\x08\x80\x01" + + +def test_u64_field_number_affects_tag() -> None: + # tag(4, 0) = (4<<3)|0 = 32 = 0x20 + assert u64(4, 1) == b"\x20\x01" + + +def test_u64_tag_uses_wt_varint() -> None: + result = u64(5, 99) + assert result[0] == (5 << 3) | WT_VARINT + + +@mark.parametrize("value", [1, 127, 128, 255, 300, 2**32 - 1, 2**32, 2**64 - 1]) +def test_u64_matches_formula(value: int) -> None: + expected = encode_tag(1, WT_VARINT) + encode_varint(value) + assert u64(1, value) == expected + + +# ── bool_field ───────────────────────────────────────────────────────────────── +# +# Omit when False. True encodes as varint 1. Wire type is WT_VARINT. + + +def test_bool_false_is_omitted() -> None: + assert bool_field(1, False) == b"" + + +def test_bool_true() -> None: + # tag(1, 0) = 0x08; varint(1) = 0x01 + assert bool_field(1, True) == b"\x08\x01" + + +def test_bool_field_number_affects_tag() -> None: + # tag(6, 0) = (6<<3)|0 = 48 = 0x30 + assert bool_field(6, True) == b"\x30\x01" + + +def test_bool_tag_uses_wt_varint() -> None: + result = bool_field(2, True) + assert result[0] == (2 << 3) | WT_VARINT + + +def test_bool_true_always_encodes_as_one() -> None: + # bool True always produces a varint 1 regardless of the Python truthiness integer + assert bool_field(1, True) == encode_tag(1, WT_VARINT) + b"\x01" + + +# ── fix32 ────────────────────────────────────────────────────────────────────── +# +# Omit when zero. Wire layout: tag (wt=5) | 4-byte little-endian uint32. + + +def test_fix32_zero_is_omitted() -> None: + assert fix32(1, 0) == b"" + + +def test_fix32_one() -> None: + # tag(1, 5) = (1<<3)|5 = 13 = 0x0D; pack(" None: + # 2^32-1 → all bytes 0xFF + assert fix32(1, 2**32 - 1) == b"\x0d\xff\xff\xff\xff" + + +def test_fix32_field_number_affects_tag() -> None: + # tag(3, 5) = (3<<3)|5 = 29 = 0x1D + assert fix32(3, 1) == b"\x1d\x01\x00\x00\x00" + + +def test_fix32_tag_uses_wt_32bit() -> None: + result = fix32(2, 7) + assert result[0] == (2 << 3) | WT_32BIT + + +def test_fix32_always_four_value_bytes() -> None: + result = fix32(1, 42) + # 1 tag byte + 4 value bytes + assert len(result) == 5 + + +@mark.parametrize("value", [1, 255, 256, 2**16, 2**24, 2**32 - 1]) +def test_fix32_matches_formula(value: int) -> None: + expected = encode_tag(1, WT_32BIT) + pack(" None: + assert fix64(1, 0) == b"" + + +def test_fix64_one() -> None: + # tag(1, 1) = (1<<3)|1 = 9 = 0x09; pack(" None: + assert fix64(1, 2**64 - 1) == b"\x09\xff\xff\xff\xff\xff\xff\xff\xff" + + +def test_fix64_field_number_affects_tag() -> None: + # tag(2, 1) = (2<<3)|1 = 17 = 0x11 + assert fix64(2, 1) == b"\x11\x01\x00\x00\x00\x00\x00\x00\x00" + + +def test_fix64_tag_uses_wt_64bit() -> None: + result = fix64(3, 99) + assert result[0] == (3 << 3) | WT_64BIT + + +def test_fix64_always_eight_value_bytes() -> None: + result = fix64(1, 42) + # 1 tag byte + 8 value bytes + assert len(result) == 9 + + +@mark.parametrize("value", [1, 255, 2**32 - 1, 2**32, 2**63, 2**64 - 1]) +def test_fix64_matches_formula(value: int) -> None: + expected = encode_tag(1, WT_64BIT) + pack(" None: + assert dbl(1, 0.0) == b"" + + +def test_dbl_negative_zero_is_omitted() -> None: + # -0.0 == 0.0 in Python, so it is treated as the proto3 default and omitted. + assert dbl(1, -0.0) == b"" + + +def test_dbl_one() -> None: + # tag(1, 1) = 0x09; pack(" None: + assert dbl(1, -1.0) == b"\x09\x00\x00\x00\x00\x00\x00\xf0\xbf" + + +def test_dbl_infinity_is_encoded() -> None: + result = dbl(1, inf) + assert result == encode_tag(1, WT_64BIT) + pack(" None: + result = dbl(1, nan) + assert result == encode_tag(1, WT_64BIT) + pack(" None: + # tag(4, 1) = (4<<3)|1 = 33 = 0x21 + result = dbl(4, 1.0) + assert result[0] == (4 << 3) | WT_64BIT + + +def test_dbl_always_eight_value_bytes() -> None: + result = dbl(1, 3.14) + assert len(result) == 9 + + +@mark.parametrize("value", [1.0, -1.0, 0.5, 3.14, 1e100, -1e100, inf, -inf]) +def test_dbl_matches_formula(value: float) -> None: + expected = encode_tag(1, WT_64BIT) + pack(" None: + assert opt_dbl(1, None) == b"" + + +def test_opt_dbl_zero_is_NOT_omitted() -> None: + # This is the key difference from dbl: 0.0 is a valid measurement. + result = opt_dbl(1, 0.0) + assert result != b"" + assert result == encode_tag(1, WT_64BIT) + pack(" None: + result = opt_dbl(1, -0.0) + assert result != b"" + + +def test_opt_dbl_one() -> None: + assert opt_dbl(1, 1.0) == b"\x09\x00\x00\x00\x00\x00\x00\xf0\x3f" + + +def test_opt_dbl_negative_one() -> None: + assert opt_dbl(1, -1.0) == b"\x09\x00\x00\x00\x00\x00\x00\xf0\xbf" + + +def test_opt_dbl_infinity_is_encoded() -> None: + result = opt_dbl(1, inf) + assert result == encode_tag(1, WT_64BIT) + pack(" None: + result = opt_dbl(1, nan) + assert result == encode_tag(1, WT_64BIT) + pack(" None: + result = opt_dbl(5, 1.0) + assert result[0] == (5 << 3) | WT_64BIT + + +@mark.parametrize("value", [0.0, -0.0, 1.0, -1.0, 0.5, 3.14, 1e100, inf, -inf]) +def test_opt_dbl_matches_formula(value: float) -> None: + expected = encode_tag(1, WT_64BIT) + pack(" None: + # dbl omits 0.0; opt_dbl does not. + assert dbl(1, 0.0) == b"" + assert opt_dbl(1, 0.0) != b"" + + +# ── sint32 ───────────────────────────────────────────────────────────────────── +# +# Omit when zero. ZigZag encoding: n>=0 → 2n, n<0 → -2n-1. Wire type WT_VARINT. + + +def test_sint32_zero_is_omitted() -> None: + assert sint32(1, 0) == b"" + + +def test_sint32_positive_one() -> None: + # ZigZag(1) = 2; tag(1,0)=0x08; varint(2)=0x02 + assert sint32(1, 1) == b"\x08\x02" + + +def test_sint32_negative_one() -> None: + # ZigZag(-1) = 1; tag(1,0)=0x08; varint(1)=0x01 + assert sint32(1, -1) == b"\x08\x01" + + +def test_sint32_positive_two() -> None: + # ZigZag(2) = 4 + assert sint32(1, 2) == b"\x08\x04" + + +def test_sint32_negative_two() -> None: + # ZigZag(-2) = 3 + assert sint32(1, -2) == b"\x08\x03" + + +def test_sint32_field_number_affects_tag() -> None: + # tag(7, 0) = (7<<3)|0 = 56 = 0x38 + result = sint32(7, 1) + assert result[0] == (7 << 3) | WT_VARINT + + +def test_sint32_tag_uses_wt_varint() -> None: + result = sint32(3, -5) + assert result[0] == (3 << 3) | WT_VARINT + + +@mark.parametrize("value", [1, -1, 2, -2, 127, -128, 150, -150, 2**31 - 1, -(2**31)]) +def test_sint32_zigzag_formula(value: int) -> None: + zigzag = 2 * value if value >= 0 else -2 * value - 1 + expected = encode_tag(1, WT_VARINT) + encode_varint(zigzag) + assert sint32(1, value) == expected + + +# ── packed_uint64 ────────────────────────────────────────────────────────────── +# +# Omit when empty. Wire layout: tag (wt=2) | varint(payload_len) | varint*... + + +def test_packed_uint64_empty_is_omitted() -> None: + assert packed_uint64(1, []) == b"" + + +def test_packed_uint64_single_element() -> None: + # payload = varint(1) = 0x01; len=1 + # tag(1,2)=0x0A; varint(1)=0x01; payload=0x01 + assert packed_uint64(1, [1]) == b"\x0a\x01\x01" + + +def test_packed_uint64_three_elements() -> None: + # payload = varint(1)+varint(2)+varint(3) = 0x01 0x02 0x03; len=3 + assert packed_uint64(1, [1, 2, 3]) == b"\x0a\x03\x01\x02\x03" + + +def test_packed_uint64_element_zero() -> None: + # Zero element encodes as varint(0) = 0x00 — still written (not omitted within payload) + assert packed_uint64(1, [0]) == b"\x0a\x01\x00" + + +def test_packed_uint64_field_number_affects_tag() -> None: + result = packed_uint64(3, [1]) + assert result[0] == (3 << 3) | WT_LEN + + +def test_packed_uint64_tag_uses_wt_len() -> None: + result = packed_uint64(2, [42]) + assert result[0] == (2 << 3) | WT_LEN + + +def test_packed_uint64_length_prefix_covers_payload() -> None: + values = [1, 128, 300] + payload = b"".join(encode_varint(v) for v in values) + result = packed_uint64(1, values) + # byte at index 1 is varint(len(payload)); for short payloads this is 1 byte + assert result == encode_tag(1, WT_LEN) + encode_varint(len(payload)) + payload + + +@mark.parametrize("values", [[1], [0, 1, 2], [127, 128, 255], [2**32 - 1, 2**64 - 1]]) +def test_packed_uint64_matches_formula(values: list) -> None: + payload = b"".join(encode_varint(v) for v in values) + expected = encode_tag(1, WT_LEN) + encode_varint(len(payload)) + payload + assert packed_uint64(1, values) == expected + + +# ── packed_fix64 ─────────────────────────────────────────────────────────────── +# +# Omit when empty. Wire layout: tag (wt=2) | varint(payload_len) | fixed64*... +# Each element is 8 bytes little-endian regardless of value. + + +def test_packed_fix64_empty_is_omitted() -> None: + assert packed_fix64(1, []) == b"" + + +def test_packed_fix64_single_element() -> None: + # payload = pack(" None: + # payload = 16 bytes; varint(16)=0x10 + result = packed_fix64(1, [1, 2]) + expected = b"\x0a\x10" + pack(" None: + result = packed_fix64(1, [0]) + assert result == b"\x0a\x08" + b"\x00" * 8 + + +def test_packed_fix64_field_number_affects_tag() -> None: + result = packed_fix64(5, [1]) + assert result[0] == (5 << 3) | WT_LEN + + +def test_packed_fix64_payload_length_always_multiple_of_8() -> None: + for n in [1, 2, 3, 5]: + result = packed_fix64(1, list(range(n))) + # payload length is n*8; varint(n*8) occupies 1 byte for n<=15 + payload_len = result[1] + assert payload_len == n * 8 + + +@mark.parametrize("values", [[0], [1, 2], [2**32 - 1, 2**64 - 1], [0, 0, 0]]) +def test_packed_fix64_matches_formula(values: list) -> None: + payload = b"".join(pack(" None: + assert packed_double(1, []) == b"" + + +def test_packed_double_single_element() -> None: + # payload = pack(" None: + result = packed_double(1, [1.0, 2.0]) + expected = b"\x0a\x10" + pack("<2d", 1.0, 2.0) + assert result == expected + + +def test_packed_double_zero_element_is_written() -> None: + # 0.0 is valid inside a packed repeated field (only the field itself is omitted when empty) + result = packed_double(1, [0.0]) + assert result == b"\x0a\x08" + pack(" None: + result = packed_double(7, [1.0]) + assert result[0] == (7 << 3) | WT_LEN + + +def test_packed_double_payload_length_always_multiple_of_8() -> None: + for n in [1, 2, 3]: + result = packed_double(1, [float(i) for i in range(n)]) + payload_len = result[1] + assert payload_len == n * 8 + + +@mark.parametrize( + "values", + [[0.0], [1.0, -1.0], [0.5, 3.14, 2.71], [inf, -inf]], +) +def test_packed_double_matches_formula(values: list) -> None: + payload = pack(f"<{len(values)}d", *values) + expected = encode_tag(1, WT_LEN) + encode_varint(len(payload)) + payload + assert packed_double(1, values) == expected diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/test_scalars.py b/opentelemetry-proto/tests/unit/_pyprotobuf/test_scalars.py new file mode 100644 index 00000000000..442c8e51b9c --- /dev/null +++ b/opentelemetry-proto/tests/unit/_pyprotobuf/test_scalars.py @@ -0,0 +1,684 @@ +# tests/test__scalars.py +# +# Tests for all scalar encoders in _scalars.py. The oracle for fixed-width +# types is Python's struct module (independent standard-library implementation). +# Variable-width types (varint-backed) are verified against hand-computed +# expected byte literals derived from the protobuf wire-format spec. + +from math import e, inf, nan, pi, tau +from struct import pack, unpack + +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory +from pytest import mark + +from opentelemetry._proto._pyprotobuf import ( + encode_bool, + encode_bytes, + encode_double, + encode_fixed32, + encode_fixed64, + encode_float, + encode_int, + encode_sfixed32, + encode_sfixed64, + encode_sint32, + encode_sint64, + encode_string, + encode_tag, + encode_uint32, + encode_uint64, + encode_varint, +) + + +# ── encode_uint32 ────────────────────────────────────────────────────────────── + + +def test_uint32_zero() -> None: + assert encode_uint32(0) == b"\x00" + + +def test_uint32_one() -> None: + assert encode_uint32(1) == b"\x01" + + +def test_uint32_max() -> None: + # 2^32-1 encodes as five varint bytes: all low bits set. + assert encode_uint32(2**32 - 1) == b"\xff\xff\xff\xff\x0f" + + +@mark.parametrize("value", [0, 1, 127, 128, 255, 300, 2**16 - 1, 2**16, 2**32 - 1]) +def test_uint32_matches_uint64_encoding(value: int) -> None: + # uint32 and uint64 share the same varint encoding for values in [0, 2^32-1]. + assert encode_uint32(value) == encode_uint64(value) + + +# ── encode_uint64 ────────────────────────────────────────────────────────────── + + +def test_uint64_zero() -> None: + assert encode_uint64(0) == b"\x00" + + +def test_uint64_max() -> None: + assert encode_uint64(2**64 - 1) == b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +@mark.parametrize( + "value", + [0, 1, 127, 128, 2**32 - 1, 2**32, 2**56, 2**63 - 1, 2**63, 2**64 - 1], +) +def test_uint64_is_varint(value: int) -> None: + assert encode_uint64(value) == encode_varint(value) + + +# ── encode_bool ──────────────────────────────────────────────────────────────── + + +def test_bool_false() -> None: + assert encode_bool(False) == b"\x00" + + +def test_bool_true() -> None: + assert encode_bool(True) == b"\x01" + + +def test_bool_truthy_int() -> None: + # Any truthy value must encode as 1, not as the integer itself. + assert encode_bool(42) == b"\x01" + + +def test_bool_falsy_int() -> None: + assert encode_bool(0) == b"\x00" + + +# ── encode_int ───────────────────────────────────────────────────────────────── +# +# int32 / int64: non-negative values encode like varint; negative values are +# sign-extended to 64 bits (two's complement) then encoded as a varint. + + +def test_int_zero() -> None: + assert encode_int(0) == b"\x00" + + +def test_int_positive_small() -> None: + assert encode_int(1) == b"\x01" + + +def test_int_positive_300() -> None: + assert encode_int(300) == b"\xac\x02" + + +def test_int_negative_one() -> None: + # -1 in 64-bit two's complement is 2^64-1, which requires 10 varint bytes. + assert encode_int(-1) == b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +def test_int_negative_two() -> None: + assert encode_int(-2) == b"\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +def test_int_int32_min() -> None: + # -2^31 in 64-bit two's complement: bits 31-63 all set, bits 0-30 clear. + assert encode_int(-(2**31)) == b"\x80\x80\x80\x80\xf8\xff\xff\xff\xff\x01" + + +def test_int_int64_min() -> None: + # -2^63: only bit 63 set. Nine groups of 0x80, then final byte 0x01. + assert encode_int(-(2**63)) == b"\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01" + + +# ── encode_sint32 ────────────────────────────────────────────────────────────── +# +# ZigZag32: n>=0 → 2n, n<0 → -2n-1. Result fits in 32 bits; encoded as varint. + + +def test_sint32_zero() -> None: + # ZigZag(0) = 0 + assert encode_sint32(0) == b"\x00" + + +def test_sint32_negative_one() -> None: + # ZigZag(-1) = 1 + assert encode_sint32(-1) == b"\x01" + + +def test_sint32_positive_one() -> None: + # ZigZag(1) = 2 + assert encode_sint32(1) == b"\x02" + + +def test_sint32_negative_two() -> None: + # ZigZag(-2) = 3 + assert encode_sint32(-2) == b"\x03" + + +def test_sint32_positive_two() -> None: + # ZigZag(2) = 4 + assert encode_sint32(2) == b"\x04" + + +def test_sint32_max() -> None: + # ZigZag(2^31-1) = 2^32-2 = 0xFFFFFFFE → 5-byte varint + assert encode_sint32(2**31 - 1) == b"\xfe\xff\xff\xff\x0f" + + +def test_sint32_min() -> None: + # ZigZag(-2^31) = 2^32-1 = 0xFFFFFFFF → 5-byte varint + assert encode_sint32(-(2**31)) == b"\xff\xff\xff\xff\x0f" + + +@mark.parametrize("value", [0, 1, -1, 2, -2, 150, -150, 2**31 - 1, -(2**31)]) +def test_sint32_zigzag(value: int) -> None: + # Verify ZigZag correctness using the arithmetic definition as oracle. + zigzag = 2 * value if value >= 0 else -2 * value - 1 + assert encode_sint32(value) == encode_varint(zigzag) + + +# ── encode_sint64 ────────────────────────────────────────────────────────────── +# +# ZigZag64: same interleaving as sint32 but over the 64-bit domain. + + +def test_sint64_zero() -> None: + assert encode_sint64(0) == b"\x00" + + +def test_sint64_negative_one() -> None: + assert encode_sint64(-1) == b"\x01" + + +def test_sint64_positive_one() -> None: + assert encode_sint64(1) == b"\x02" + + +def test_sint64_max() -> None: + # ZigZag64(2^63-1) = 2^64-2 = 0xFFFFFFFFFFFFFFFE → 10-byte varint + assert encode_sint64(2**63 - 1) == b"\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +def test_sint64_min() -> None: + # ZigZag64(-2^63) = 2^64-1 = 0xFFFFFFFFFFFFFFFF → 10-byte varint + assert encode_sint64(-(2**63)) == b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +@mark.parametrize( + "value", + [0, 1, -1, 2, -2, 150, -150, 2**31 - 1, -(2**31), 2**32, -(2**32), 2**63 - 1, -(2**63)], +) +def test_sint64_zigzag(value: int) -> None: + zigzag = 2 * value if value >= 0 else -2 * value - 1 + assert encode_sint64(value) == encode_varint(zigzag) + + +# ── encode_float ─────────────────────────────────────────────────────────────── + + +def test_float_zero() -> None: + assert encode_float(0.0) == b"\x00\x00\x00\x00" + + +def test_float_one() -> None: + # IEEE 754 single 1.0 = 0x3F800000; little-endian: 00 00 80 3F + assert encode_float(1.0) == b"\x00\x00\x80\x3f" + + +def test_float_negative_one() -> None: + # IEEE 754 single -1.0 = 0xBF800000; little-endian: 00 00 80 BF + assert encode_float(-1.0) == b"\x00\x00\x80\xbf" + + +def test_float_always_four_bytes() -> None: + assert len(encode_float(0.0)) == 4 + assert len(encode_float(1.0)) == 4 + assert len(encode_float(inf)) == 4 + + +@mark.parametrize( + "value", + [ + 0.0, 1.0, -1.0, 0.5, -0.5, 0.25, 2.0, -2.0, + unpack(" None: + assert encode_float(value) == pack(" None: + assert encode_float(nan) == pack(" None: + assert encode_double(0.0) == b"\x00\x00\x00\x00\x00\x00\x00\x00" + + +def test_double_one() -> None: + # IEEE 754 double 1.0 = 0x3FF0000000000000; little-endian: 00 00 00 00 00 00 F0 3F + assert encode_double(1.0) == b"\x00\x00\x00\x00\x00\x00\xf0\x3f" + + +def test_double_negative_one() -> None: + assert encode_double(-1.0) == b"\x00\x00\x00\x00\x00\x00\xf0\xbf" + + +def test_double_always_eight_bytes() -> None: + assert len(encode_double(0.0)) == 8 + assert len(encode_double(1.0)) == 8 + assert len(encode_double(inf)) == 8 + + +@mark.parametrize( + "value", + [ + 0.0, 1.0, -1.0, 0.5, -0.5, pi, e, tau, + 1e100, -1e100, 5e-324, 1.7976931348623157e308, inf, -inf, + ], +) +def test_double_matches_struct(value: float) -> None: + assert encode_double(value) == pack(" None: + assert encode_double(nan) == pack(" None: + assert encode_fixed32(0) == b"\x00\x00\x00\x00" + + +def test_fixed32_one() -> None: + assert encode_fixed32(1) == b"\x01\x00\x00\x00" + + +def test_fixed32_max() -> None: + assert encode_fixed32(2**32 - 1) == b"\xff\xff\xff\xff" + + +def test_fixed32_always_four_bytes() -> None: + assert len(encode_fixed32(0)) == 4 + assert len(encode_fixed32(2**32 - 1)) == 4 + + +@mark.parametrize("value", [0, 1, 127, 128, 255, 256, 2**16 - 1, 2**16, 2**24, 2**32 - 1]) +def test_fixed32_matches_struct(value: int) -> None: + assert encode_fixed32(value) == pack(" None: + assert encode_sfixed32(0) == b"\x00\x00\x00\x00" + + +def test_sfixed32_negative_one() -> None: + # -1 in 32-bit two's complement is 0xFFFFFFFF; all bytes 0xFF. + assert encode_sfixed32(-1) == b"\xff\xff\xff\xff" + + +def test_sfixed32_min() -> None: + # -2^31 = 0x80000000 little-endian: 00 00 00 80 + assert encode_sfixed32(-(2**31)) == b"\x00\x00\x00\x80" + + +def test_sfixed32_max() -> None: + # 2^31-1 = 0x7FFFFFFF little-endian: FF FF FF 7F + assert encode_sfixed32(2**31 - 1) == b"\xff\xff\xff\x7f" + + +def test_sfixed32_always_four_bytes() -> None: + assert len(encode_sfixed32(0)) == 4 + assert len(encode_sfixed32(-1)) == 4 + + +@mark.parametrize("value", [0, 1, -1, 127, -128, 2**15 - 1, -(2**15), 2**31 - 1, -(2**31)]) +def test_sfixed32_matches_struct(value: int) -> None: + assert encode_sfixed32(value) == pack(" None: + assert encode_fixed64(0) == b"\x00\x00\x00\x00\x00\x00\x00\x00" + + +def test_fixed64_one() -> None: + assert encode_fixed64(1) == b"\x01\x00\x00\x00\x00\x00\x00\x00" + + +def test_fixed64_max() -> None: + assert encode_fixed64(2**64 - 1) == b"\xff\xff\xff\xff\xff\xff\xff\xff" + + +def test_fixed64_always_eight_bytes() -> None: + assert len(encode_fixed64(0)) == 8 + assert len(encode_fixed64(2**64 - 1)) == 8 + + +@mark.parametrize( + "value", [0, 1, 255, 2**16 - 1, 2**32 - 1, 2**32, 2**48, 2**63, 2**64 - 1] +) +def test_fixed64_matches_struct(value: int) -> None: + assert encode_fixed64(value) == pack(" None: + assert encode_sfixed64(0) == b"\x00\x00\x00\x00\x00\x00\x00\x00" + + +def test_sfixed64_negative_one() -> None: + # -1 in 64-bit two's complement: all bytes 0xFF. + assert encode_sfixed64(-1) == b"\xff\xff\xff\xff\xff\xff\xff\xff" + + +def test_sfixed64_min() -> None: + # -2^63 = 0x8000000000000000 little-endian: 00 00 00 00 00 00 00 80 + assert encode_sfixed64(-(2**63)) == b"\x00\x00\x00\x00\x00\x00\x00\x80" + + +def test_sfixed64_max() -> None: + # 2^63-1 = 0x7FFFFFFFFFFFFFFF little-endian: FF FF FF FF FF FF FF 7F + assert encode_sfixed64(2**63 - 1) == b"\xff\xff\xff\xff\xff\xff\xff\x7f" + + +def test_sfixed64_always_eight_bytes() -> None: + assert len(encode_sfixed64(0)) == 8 + assert len(encode_sfixed64(-1)) == 8 + + +@mark.parametrize( + "value", + [0, 1, -1, 127, -128, 2**31 - 1, -(2**31), 2**32, -(2**32), 2**63 - 1, -(2**63)], +) +def test_sfixed64_matches_struct(value: int) -> None: + assert encode_sfixed64(value) == pack(" None: + assert encode_string("") == b"\x00" + + +def test_string_ascii_single_char() -> None: + assert encode_string("A") == b"\x01A" + + +def test_string_ascii_word() -> None: + assert encode_string("hi") == b"\x02hi" + + +def test_string_two_byte_utf8() -> None: + # "é" = U+00E9, UTF-8: 0xC3 0xA9 (2 bytes, not 1 character) + assert encode_string("é") == b"\x02\xc3\xa9" + + +def test_string_three_byte_utf8() -> None: + # "中" = U+4E2D, UTF-8: 0xE4 0xB8 0xAD (3 bytes) + assert encode_string("中") == b"\x03\xe4\xb8\xad" + + +def test_string_length_counts_bytes_not_chars() -> None: + # "日本語" is 3 characters but 9 UTF-8 bytes. + value = "日本語" + result = encode_string(value) + assert result[0] == 9 # byte count, not char count + assert result[1:] == value.encode("utf-8") + + +def test_string_two_byte_length_prefix() -> None: + # A string of 128 ASCII characters needs a 2-byte varint length prefix. + result = encode_string("a" * 128) + assert result[:2] == b"\x80\x01" + assert result[2:] == b"a" * 128 + + +@mark.parametrize( + "value", + ["", "a", "hello", "café", "Ünïcödé", "日本語", "中文", "\U0001F600", "hello 世界"], +) +def test_string_length_prefix_matches_utf8_bytecount(value: str) -> None: + utf8 = value.encode("utf-8") + result = encode_string(value) + assert result == encode_varint(len(utf8)) + utf8 + + +# ── encode_bytes ─────────────────────────────────────────────────────────────── + + +def test_bytes_empty() -> None: + assert encode_bytes(b"") == b"\x00" + + +def test_bytes_single_byte() -> None: + assert encode_bytes(b"\x42") == b"\x01\x42" + + +def test_bytes_high_bytes() -> None: + # Values >= 0x80 must pass through verbatim. + assert encode_bytes(b"\xff\x80") == b"\x02\xff\x80" + + +def test_bytes_null_bytes() -> None: + assert encode_bytes(b"\x00\x00\x00") == b"\x03\x00\x00\x00" + + +def test_bytes_all_byte_values() -> None: + # 256-byte payload; length prefix is encode_varint(256) = b'\x80\x02' + payload = bytes(range(256)) + result = encode_bytes(payload) + assert result[:2] == b"\x80\x02" + assert result[2:] == payload + + +def test_bytes_two_byte_length_prefix() -> None: + value = b"\xab" * 128 + result = encode_bytes(value) + assert result[:2] == b"\x80\x01" + assert result[2:] == value + + +@mark.parametrize( + "value", + [b"", b"\x00", b"\xff", b"hello", b"\x00\x01\x02\x03", b"\x80\x81\x82", b"\xfe\xff"], +) +def test_bytes_length_prefix_matches_payload_length(value: bytes) -> None: + result = encode_bytes(value) + assert result == encode_varint(len(value)) + value + + +# ── oracle — byte-for-byte comparison with google.protobuf ──────────────────── +# +# A proto2 message with one optional field per scalar type is used as the +# oracle. proto2 is chosen because it serialises every field that has been +# explicitly set, including fields whose value equals the type default (0, +# False, b"", ""), which proto3 would silently omit. +# +# For each function under test the pattern is: +# expected = encode_tag(field_number, wire_type) + encode_X(value) +# assert _ScalarMessage(**{field_name: value}).SerializeToString() == expected +# +# Field number assignments: +_F_UINT32 = 1 # wt 0 +_F_UINT64 = 2 # wt 0 +_F_BOOL = 3 # wt 0 +_F_INT32 = 4 # wt 0 +_F_INT64 = 5 # wt 0 +_F_SINT32 = 6 # wt 0 +_F_SINT64 = 7 # wt 0 +_F_FLOAT = 8 # wt 5 +_F_DOUBLE = 9 # wt 1 +_F_FIXED32 = 10 # wt 5 +_F_SFIXED32 = 11 # wt 5 +_F_FIXED64 = 12 # wt 1 +_F_SFIXED64 = 13 # wt 1 +_F_STRING = 14 # wt 2 +_F_BYTES = 15 # wt 2 + +_WT_VARINT = 0 +_WT_64BIT = 1 +_WT_LEN = 2 +_WT_32BIT = 5 + + +def _build_scalar_message_class(): + file_proto = descriptor_pb2.FileDescriptorProto() + file_proto.name = "opentelemetry_pyproto_test_scalars.proto" + file_proto.syntax = "proto2" + msg_proto = file_proto.message_type.add() + msg_proto.name = "ScalarMessage" + + T = descriptor_pb2.FieldDescriptorProto + + def _add(name, number, type_id): + f = msg_proto.field.add() + f.name = name + f.number = number + f.type = type_id + f.label = T.LABEL_OPTIONAL + + _add("uint32_field", _F_UINT32, T.TYPE_UINT32) + _add("uint64_field", _F_UINT64, T.TYPE_UINT64) + _add("bool_field", _F_BOOL, T.TYPE_BOOL) + _add("int32_field", _F_INT32, T.TYPE_INT32) + _add("int64_field", _F_INT64, T.TYPE_INT64) + _add("sint32_field", _F_SINT32, T.TYPE_SINT32) + _add("sint64_field", _F_SINT64, T.TYPE_SINT64) + _add("float_field", _F_FLOAT, T.TYPE_FLOAT) + _add("double_field", _F_DOUBLE, T.TYPE_DOUBLE) + _add("fixed32_field", _F_FIXED32, T.TYPE_FIXED32) + _add("sfixed32_field", _F_SFIXED32, T.TYPE_SFIXED32) + _add("fixed64_field", _F_FIXED64, T.TYPE_FIXED64) + _add("sfixed64_field", _F_SFIXED64, T.TYPE_SFIXED64) + _add("string_field", _F_STRING, T.TYPE_STRING) + _add("bytes_field", _F_BYTES, T.TYPE_BYTES) + + pool = descriptor_pool.DescriptorPool() + pool.Add(file_proto) + return message_factory.GetMessageClass(pool.FindMessageTypeByName("ScalarMessage")) + + +_ScalarMessage = _build_scalar_message_class() + + +def _s(field_name: str, value) -> bytes: + return _ScalarMessage(**{field_name: value}).SerializeToString() + + +# ── encode_uint32 oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, 127, 128, 255, 300, 2**16, 2**32 - 1]) +def test_encode_uint32_matches_protobuf(value: int) -> None: + assert _s("uint32_field", value) == encode_tag(_F_UINT32, _WT_VARINT) + encode_uint32(value) + + +# ── encode_uint64 oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, 127, 128, 2**32 - 1, 2**32, 2**63, 2**64 - 1]) +def test_encode_uint64_matches_protobuf(value: int) -> None: + assert _s("uint64_field", value) == encode_tag(_F_UINT64, _WT_VARINT) + encode_uint64(value) + + +# ── encode_bool oracle ───────────────────────────────────────────────────────── + +@mark.parametrize("value", [False, True]) +def test_encode_bool_matches_protobuf(value: bool) -> None: + assert _s("bool_field", value) == encode_tag(_F_BOOL, _WT_VARINT) + encode_bool(value) + + +# ── encode_int oracle (int32 / int64) ───────────────────────────────────────── + +@mark.parametrize("value", [0, 1, 127, 128, 2**31 - 1, -1, -2, -128, -(2**31)]) +def test_encode_int_int32_matches_protobuf(value: int) -> None: + assert _s("int32_field", value) == encode_tag(_F_INT32, _WT_VARINT) + encode_int(value) + + +@mark.parametrize("value", [0, 1, 2**63 - 1, -1, -(2**63)]) +def test_encode_int_int64_matches_protobuf(value: int) -> None: + assert _s("int64_field", value) == encode_tag(_F_INT64, _WT_VARINT) + encode_int(value) + + +# ── encode_sint32 oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, -1, 2, -2, 150, -150, 2**31 - 1, -(2**31)]) +def test_encode_sint32_matches_protobuf(value: int) -> None: + assert _s("sint32_field", value) == encode_tag(_F_SINT32, _WT_VARINT) + encode_sint32(value) + + +# ── encode_sint64 oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, -1, 150, -150, 2**63 - 1, -(2**63)]) +def test_encode_sint64_matches_protobuf(value: int) -> None: + assert _s("sint64_field", value) == encode_tag(_F_SINT64, _WT_VARINT) + encode_sint64(value) + + +# ── encode_float oracle ──────────────────────────────────────────────────────── + +@mark.parametrize("value", [0.0, 1.0, -1.0, 0.5, inf, -inf]) +def test_encode_float_matches_protobuf(value: float) -> None: + assert _s("float_field", value) == encode_tag(_F_FLOAT, _WT_32BIT) + encode_float(value) + + +# ── encode_double oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", [0.0, 1.0, -1.0, pi, 1e100, inf, -inf]) +def test_encode_double_matches_protobuf(value: float) -> None: + assert _s("double_field", value) == encode_tag(_F_DOUBLE, _WT_64BIT) + encode_double(value) + + +# ── encode_fixed32 oracle ────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, 255, 2**16, 2**32 - 1]) +def test_encode_fixed32_matches_protobuf(value: int) -> None: + assert _s("fixed32_field", value) == encode_tag(_F_FIXED32, _WT_32BIT) + encode_fixed32(value) + + +# ── encode_sfixed32 oracle ───────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, -1, 2**31 - 1, -(2**31)]) +def test_encode_sfixed32_matches_protobuf(value: int) -> None: + assert _s("sfixed32_field", value) == encode_tag(_F_SFIXED32, _WT_32BIT) + encode_sfixed32(value) + + +# ── encode_fixed64 oracle ────────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, 2**32, 2**64 - 1]) +def test_encode_fixed64_matches_protobuf(value: int) -> None: + assert _s("fixed64_field", value) == encode_tag(_F_FIXED64, _WT_64BIT) + encode_fixed64(value) + + +# ── encode_sfixed64 oracle ───────────────────────────────────────────────────── + +@mark.parametrize("value", [0, 1, -1, 2**63 - 1, -(2**63)]) +def test_encode_sfixed64_matches_protobuf(value: int) -> None: + assert _s("sfixed64_field", value) == encode_tag(_F_SFIXED64, _WT_64BIT) + encode_sfixed64(value) + + +# ── encode_string oracle ─────────────────────────────────────────────────────── + +@mark.parametrize("value", ["", "a", "hello", "café", "日本語", "\U0001F600", "x" * 128]) +def test_encode_string_matches_protobuf(value: str) -> None: + assert _s("string_field", value) == encode_tag(_F_STRING, _WT_LEN) + encode_string(value) + + +# ── encode_bytes oracle ──────────────────────────────────────────────────────── + +@mark.parametrize("value", [b"", b"\x00", b"\xff", b"hello", b"\x80\x81\x82", b"\xab" * 128]) +def test_encode_bytes_matches_protobuf(value: bytes) -> None: + assert _s("bytes_field", value) == encode_tag(_F_BYTES, _WT_LEN) + encode_bytes(value) diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/test_tag.py b/opentelemetry-proto/tests/unit/_pyprotobuf/test_tag.py new file mode 100644 index 00000000000..c4c31a816a3 --- /dev/null +++ b/opentelemetry-proto/tests/unit/_pyprotobuf/test_tag.py @@ -0,0 +1,152 @@ +# tests/test__tag.py +# +# encode_tag(field_number, wire_type) encodes (field_number << 3) | wire_type +# as a varint. These tests verify the formula and the varint encoding of the +# resulting tag integer, using hand-computed expected bytes. +# +# Wire type constants: +# 0 VARINT — int32, int64, uint32, uint64, bool, enum +# 1 64BIT — fixed64, sfixed64, double +# 2 LEN — string, bytes, embedded messages, packed repeated +# 5 32BIT — fixed32, sfixed32, float + +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory +from pytest import mark + +from opentelemetry._proto._pyprotobuf import encode_tag, encode_varint + + +@mark.parametrize( + ("field_number", "wire_type"), + [ + (1, 0), # tag = 8 → 1-byte varint + (1, 1), # tag = 9 → 1-byte varint + (1, 2), # tag = 10 → 1-byte varint + (1, 5), # tag = 13 → 1-byte varint + (15, 0), # tag = 120 → last 1-byte tag for wire type 0 + (16, 0), # tag = 128 → first 2-byte tag for wire type 0 + (150, 0), # tag = 1200 → 2-byte varint + (1024, 0), # tag = 8192 → 2-byte varint + (1048576, 2), # tag = 8388610 → 4-byte varint + ], +) +def test_encode_tag_matches_formula(field_number: int, wire_type: int) -> None: + # encode_tag must produce the same bytes as encoding the tag integer directly. + tag_int = (field_number << 3) | wire_type + assert encode_tag(field_number, wire_type) == encode_varint(tag_int) + + +def test_field_1_wire_type_0() -> None: + # (1 << 3) | 0 = 8 → single byte 0x08 + assert encode_tag(1, 0) == b"\x08" + + +def test_field_1_wire_type_2() -> None: + # (1 << 3) | 2 = 10 → single byte 0x0A + assert encode_tag(1, 2) == b"\x0a" + + +def test_field_15_wire_type_0() -> None: + # (15 << 3) | 0 = 120 → single byte 0x78 (last 1-byte wt-0 tag) + assert encode_tag(15, 0) == b"\x78" + + +def test_field_16_wire_type_0() -> None: + # (16 << 3) | 0 = 128 → two bytes 0x80 0x01 (first 2-byte wt-0 tag) + assert encode_tag(16, 0) == b"\x80\x01" + + +def test_field_2_wire_type_1() -> None: + # (2 << 3) | 1 = 17 → single byte 0x11 + assert encode_tag(2, 1) == b"\x11" + + +def test_field_3_wire_type_5() -> None: + # (3 << 3) | 5 = 29 → single byte 0x1D + assert encode_tag(3, 5) == b"\x1d" + + +# ── oracle — byte-for-byte comparison with google.protobuf ──────────────────── +# +# A proto2 message with fields at distinct positions and wire types is used as +# the oracle. For each (field_number, wire_type) pair we serialise a message +# with only that field set and verify that encode_tag(field_number, wire_type) +# matches the first len(tag) bytes of the serialised output. +# +# Field number / tag varint length: +# field 1, wt 0: tag = 8 → 1-byte (last before multi-byte for wt 0) +# field 10, wt 1: tag = 81 → 1-byte (wire type 1) +# field 11, wt 2: tag = 90 → 1-byte (wire type 2) +# field 12, wt 5: tag = 101 → 1-byte (wire type 5) +# field 15, wt 0: tag = 120 → 1-byte (last 1-byte wt-0 tag) +# field 16, wt 0: tag = 128 → 2-byte (first 2-byte wt-0 tag) +# field 150, wt 0: tag = 1200 → 2-byte +# field 1048576, wt 2: tag = 8388610 → 4-byte + +_WT_VARINT = 0 +_WT_64BIT = 1 +_WT_LEN = 2 +_WT_32BIT = 5 + +_F_UINT64 = 1 +_F_FIXED64 = 10 +_F_STRING = 11 +_F_FIXED32 = 12 +_F_UINT64_15 = 15 +_F_UINT64_16 = 16 +_F_UINT64_150 = 150 +_F_STRING_BIG = 1048576 + + +def _build_tag_message_class(): + file_proto = descriptor_pb2.FileDescriptorProto() + file_proto.name = "opentelemetry_pyproto_test_tag.proto" + file_proto.syntax = "proto2" + msg_proto = file_proto.message_type.add() + msg_proto.name = "TagMessage" + + T = descriptor_pb2.FieldDescriptorProto + + def _add(name, number, type_id): + f = msg_proto.field.add() + f.name = name + f.number = number + f.type = type_id + f.label = T.LABEL_OPTIONAL + + _add("uint64_field", _F_UINT64, T.TYPE_UINT64) + _add("fixed64_field", _F_FIXED64, T.TYPE_FIXED64) + _add("string_field", _F_STRING, T.TYPE_STRING) + _add("fixed32_field", _F_FIXED32, T.TYPE_FIXED32) + _add("uint64_field_15", _F_UINT64_15, T.TYPE_UINT64) + _add("uint64_field_16", _F_UINT64_16, T.TYPE_UINT64) + _add("uint64_field_150", _F_UINT64_150, T.TYPE_UINT64) + _add("string_field_big", _F_STRING_BIG, T.TYPE_STRING) + + pool = descriptor_pool.DescriptorPool() + pool.Add(file_proto) + return message_factory.GetMessageClass(pool.FindMessageTypeByName("TagMessage")) + + +_TagMessage = _build_tag_message_class() + + +@mark.parametrize( + ("field_name", "field_number", "wire_type", "field_value"), + [ + ("uint64_field", _F_UINT64, _WT_VARINT, 1 ), + ("fixed64_field", _F_FIXED64, _WT_64BIT, 1 ), + ("string_field", _F_STRING, _WT_LEN, "x"), + ("fixed32_field", _F_FIXED32, _WT_32BIT, 1 ), + ("uint64_field_15", _F_UINT64_15, _WT_VARINT, 1 ), + ("uint64_field_16", _F_UINT64_16, _WT_VARINT, 1 ), + ("uint64_field_150", _F_UINT64_150, _WT_VARINT, 1 ), + ("string_field_big", _F_STRING_BIG, _WT_LEN, "x"), + ], +) +def test_encode_tag_matches_protobuf( + field_name: str, field_number: int, wire_type: int, field_value +) -> None: + serialized = _TagMessage(**{field_name: field_value}).SerializeToString() + tag = encode_tag(field_number, wire_type) + assert serialized[: len(tag)] == tag diff --git a/opentelemetry-proto/tests/unit/_pyprotobuf/test_varint.py b/opentelemetry-proto/tests/unit/_pyprotobuf/test_varint.py new file mode 100644 index 00000000000..d4f409d9f93 --- /dev/null +++ b/opentelemetry-proto/tests/unit/_pyprotobuf/test_varint.py @@ -0,0 +1,115 @@ +# tests/test__varint.py + +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory +from pytest import mark, raises + +from opentelemetry._proto._pyprotobuf import encode_tag, encode_varint + + +def test_zero() -> None: + assert encode_varint(0) == b"\x00" + + +def test_single_byte_max() -> None: + assert encode_varint(127) == b"\x7f" + + +def test_first_two_byte_value() -> None: + assert encode_varint(128) == b"\x80\x01" + + +def test_150() -> None: + assert encode_varint(150) == b"\x96\x01" + + +def test_300() -> None: + assert encode_varint(300) == b"\xac\x02" + + +def test_uint32_max() -> None: + assert encode_varint(2**32 - 1) == b"\xff\xff\xff\xff\x0f" + + +def test_uint64_max() -> None: + assert encode_varint(2**64 - 1) == b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" + + +def test_rejects_negative_values() -> None: + with raises(ValueError, match="varint values must be non-negative"): + encode_varint(-1) + + +def test_two_byte_boundary_low() -> None: + # 16383 = 0x3FFF — the largest value that fits in two varint bytes. + # 7-bit groups: 0x7F (lower), 0x7F (upper, no continuation bit). + assert encode_varint(16_383) == b"\xff\x7f" + + +def test_three_byte_boundary_low() -> None: + # 16384 = 0x4000 — the first value that requires three varint bytes. + assert encode_varint(16_384) == b"\x80\x80\x01" + + +def test_one() -> None: + # 1 fits in a single byte; no continuation bit needed. + assert encode_varint(1) == b"\x01" + + +# ── oracle — byte-for-byte comparison with google.protobuf ──────────────────── +# +# A proto2 message with a single uint64 field is used as the oracle. proto2 +# is chosen because it serialises the field even when its value is the type +# default (0). uint64 covers the full [0, 2^64-1] varint range. +# +# The serialised message for a single field is exactly: +# tag (varint) + value (varint) +# +# Asserting that encode_tag(field, wt) + encode_varint(value) equals the +# serialised message verifies that our varint encoder produces the exact same +# bytes as google.protobuf for every tested value. + +_FIELD = 1 +_WT = 0 # wire type 0 — varint + + +def _build_varint_message_class(): + file_proto = descriptor_pb2.FileDescriptorProto() + file_proto.name = "opentelemetry_pyproto_test_varint.proto" + file_proto.syntax = "proto2" + msg_proto = file_proto.message_type.add() + msg_proto.name = "VarintMessage" + f = msg_proto.field.add() + f.name = "uint64_field" + f.number = _FIELD + f.type = descriptor_pb2.FieldDescriptorProto.TYPE_UINT64 + f.label = descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL + pool = descriptor_pool.DescriptorPool() + pool.Add(file_proto) + return message_factory.GetMessageClass(pool.FindMessageTypeByName("VarintMessage")) + + +_VarintMessage = _build_varint_message_class() + + +@mark.parametrize( + "value", + [ + 0, + 1, + 127, + 128, + 150, + 300, + 16_383, + 16_384, + 2**16, + 2**28, + 2**32 - 1, + 2**32, + 2**63 - 1, + 2**64 - 1, + ], +) +def test_encode_varint_matches_protobuf(value: int) -> None: + expected = encode_tag(_FIELD, _WT) + encode_varint(value) + assert _VarintMessage(uint64_field=value).SerializeToString() == expected