prod/native/libphpbridge/code/OtlpExporter/MetricConverter.h (189 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you 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. */ #pragma once #include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" #include "opentelemetry/proto/metrics/v1/metrics.pb.h" #include "opentelemetry/proto/common/v1/common.pb.h" #include "opentelemetry/proto/resource/v1/resource.pb.h" #include "ConverterHelpers.h" #include "AutoZval.h" #include "AttributesConverter.h" #include "CiCharTraits.h" #include <boost/algorithm/hex.hpp> #include <string> #include <string_view> #include <unordered_map> namespace elasticapm::php { using namespace std::string_view_literals; class MetricConverter { public: std::string getStringSerialized(AutoZval const &batch) { return convert(batch).SerializeAsString(); } opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest convert(AutoZval const &metrics) { if (!metrics.isArray()) { throw std::runtime_error("Invalid iterable passed to MetricsConverter"); } opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request; std::unordered_map<std::string, opentelemetry::proto::metrics::v1::ResourceMetrics *> resourceMetricsMap; std::unordered_map<std::string, opentelemetry::proto::metrics::v1::ScopeMetrics *> scopeMetricsMap; for (auto const &metric : metrics) { auto resource = metric.readProperty("resource"); auto scope = metric.readProperty("instrumentationScope"); std::string resourceId = ConverterHelpers::getResourceId(resource); std::string scopeId = ConverterHelpers::getScopeId(scope); auto *resourceMetrics = resourceMetricsMap[resourceId]; if (!resourceMetrics) { resourceMetrics = request.add_resource_metrics(); convertResourceMetrics(resource, resourceMetrics); resourceMetricsMap[resourceId] = resourceMetrics; } std::string key = resourceId + '|' + scopeId; auto *scopeMetrics = scopeMetricsMap[key]; if (!scopeMetrics) { scopeMetrics = resourceMetrics->add_scope_metrics(); convertScopeMetrics(scope, scopeMetrics); scopeMetricsMap[key] = scopeMetrics; } convertMetric(metric, scopeMetrics->add_metrics()); } return request; } private: void convertResourceMetrics(AutoZval &resource, opentelemetry::proto::metrics::v1::ResourceMetrics *out) { auto attributes = resource.callMethod("getAttributes"sv); auto resMetrics = out->mutable_resource(); AttributesConverter::convertAttributes(attributes, resMetrics->mutable_attributes()); resMetrics->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong()); if (auto schemaUrl = resource.callMethod("getSchemaUrl"sv); schemaUrl.isString()) { out->set_schema_url(schemaUrl.getStringView()); // TODO ??? no value at all or empty string? (in php null is casted to empty string) } } void convertScopeMetrics(AutoZval &scopeMetrics, opentelemetry::proto::metrics::v1::ScopeMetrics *out) { auto scope = out->mutable_scope(); scope->set_name(scopeMetrics.callMethod("getName"sv).getStringView()); if (auto version = scopeMetrics.callMethod("getVersion"sv); version.isString()) { scope->set_version(version.getStringView()); } auto attributes = scopeMetrics.callMethod("getAttributes"sv); AttributesConverter::convertAttributes(attributes, scope->mutable_attributes()); scope->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong()); if (auto schemaUrl = scopeMetrics.callMethod("getSchemaUrl"sv); schemaUrl.isString()) { out->set_schema_url(schemaUrl.getStringView()); } } void convertMetric(AutoZval const &metric, opentelemetry::proto::metrics::v1::Metric *out) { out->set_name(metric.readProperty("name").getStringView()); out->set_description(metric.readProperty("description").getOptStringView().value_or(""sv)); out->set_unit(metric.readProperty("unit").getOptStringView().value_or(""sv)); convertMetricData(metric.readProperty("data"), out); } void convertMetricData(AutoZval const &data, opentelemetry::proto::metrics::v1::Metric *out) { using namespace opentelemetry::proto::metrics::v1; if (data.instanceOf("OpenTelemetry\\SDK\\Metrics\\Data\\Gauge"sv)) { convertGauge(data, out->mutable_gauge()); } else if (data.instanceOf("OpenTelemetry\\SDK\\Metrics\\Data\\Sum"sv)) { convertSum(data, out->mutable_sum()); } else if (data.instanceOf("OpenTelemetry\\SDK\\Metrics\\Data\\Histogram"sv)) { convertHistogram(data, out->mutable_histogram()); } else { // ("Unsupported Metric data type"); } } void convertGauge(AutoZval const &gauge, opentelemetry::proto::metrics::v1::Gauge *out) { for (auto const &point : gauge.readProperty("dataPoints")) { convertNumberDataPoint(point, out->add_data_points()); } } void convertSum(AutoZval const &sum, opentelemetry::proto::metrics::v1::Sum *out) { for (auto const &point : sum.readProperty("dataPoints")) { convertNumberDataPoint(point, out->add_data_points()); } out->set_is_monotonic(sum.readProperty("monotonic").getBoolean()); out->set_aggregation_temporality(convertTemporality(sum.readProperty("temporality"))); } void convertHistogram(AutoZval const &hist, opentelemetry::proto::metrics::v1::Histogram *out) { for (auto const &point : hist.readProperty("dataPoints")) { convertHistogramDataPoint(point, out->add_data_points()); } out->set_aggregation_temporality(convertTemporality(hist.readProperty("temporality"))); } void convertNumberDataPoint(AutoZval const &point, opentelemetry::proto::metrics::v1::NumberDataPoint *out) { AttributesConverter::convertAttributes(point.readProperty("attributes"), out->mutable_attributes()); out->set_start_time_unix_nano(point.readProperty("startTimestamp").getLong()); out->set_time_unix_nano(point.readProperty("timestamp").getLong()); auto value = point.readProperty("value"); if (value.isLong()) { out->set_as_int(value.getLong()); } else if (value.isDouble()) { out->set_as_double(value.getDouble()); } for (auto const &exemplar : point.readProperty("exemplars")) { convertExemplar(exemplar, out->add_exemplars()); } } void convertHistogramDataPoint(AutoZval const &point, opentelemetry::proto::metrics::v1::HistogramDataPoint *out) { AttributesConverter::convertAttributes(point.readProperty("attributes"), out->mutable_attributes()); out->set_start_time_unix_nano(point.readProperty("startTimestamp").getLong()); out->set_time_unix_nano(point.readProperty("timestamp").getLong()); out->set_count(point.readProperty("count").getLong()); out->set_sum(point.readProperty("sum").getNumberAsDouble()); for (auto const &val : point.readProperty("bucketCounts")) { out->add_bucket_counts(val.getLong()); } for (auto const &val : point.readProperty("explicitBounds")) { out->add_explicit_bounds(val.getNumberAsDouble()); } for (auto const &exemplar : point.readProperty("exemplars")) { convertExemplar(exemplar, out->add_exemplars()); } } void convertExemplar(AutoZval const &ex, opentelemetry::proto::metrics::v1::Exemplar *out) { AttributesConverter::convertAttributes(ex.readProperty("attributes"), out->mutable_filtered_attributes()); out->set_time_unix_nano(ex.readProperty("timestamp").getLong()); if (auto spanId = ex.readProperty("spanId"); spanId.isString()) { out->set_span_id(hex2bin(spanId.getStringView())); } if (auto traceId = ex.readProperty("traceId"); traceId.isString()) { out->set_trace_id(hex2bin(traceId.getStringView())); } auto val = ex.readProperty("value"); if (val.isLong()) { out->set_as_int(val.getLong()); } else if (val.isDouble()) { out->set_as_double(val.getDouble()); } } inline std::string hex2bin(std::string_view input) { if (input.length() % 2 != 0) { throw std::invalid_argument("hex2bin: input string must have even length"); } std::string output; boost::algorithm::unhex(input.begin(), input.end(), std::back_inserter(output)); return output; } opentelemetry::proto::metrics::v1::AggregationTemporality convertTemporality(AutoZval const &val) { using opentelemetry::proto::metrics::v1::AggregationTemporality; if (val.isLong()) { switch (val.getLong()) { case 1: return AggregationTemporality::AGGREGATION_TEMPORALITY_DELTA; case 2: return AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE; case 0: default: return AggregationTemporality::AGGREGATION_TEMPORALITY_UNSPECIFIED; } } using namespace elasticapm::utils::string_view_literals; if (val.isString()) { auto iStr = elasticapm::utils::traits_cast<elasticapm::utils::CiCharTraits>(val.getStringView()); if (iStr == "Delta"_cisv) { return AggregationTemporality::AGGREGATION_TEMPORALITY_DELTA; } else if (iStr == "Cumulative"_cisv) { return AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE; } else { return AggregationTemporality::AGGREGATION_TEMPORALITY_UNSPECIFIED; } } throw std::runtime_error("Invalid temporality value: expected int or string (DELTA, CUMULATIVE, UNSPECIFIED)"); } }; } // namespace elasticapm::php