Line data Source code
1 : // Licensed under the Apache License, Version 2.0
2 : // Copyright 2025, Michael Bushe, All rights reserved.
3 :
4 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart'
5 : show OTelLog;
6 : import 'package:fixnum/fixnum.dart';
7 :
8 : import '../../../../proto/common/v1/common.pb.dart' as common_proto;
9 : import '../../../../proto/metrics/v1/metrics.pb.dart' as proto;
10 : import '../../../../proto/resource/v1/resource.pb.dart' as resource_proto;
11 : import '../../../resource/resource.dart';
12 : import '../../data/metric.dart';
13 : import '../../data/metric_point.dart';
14 :
15 : /// Utility class for transforming metric data to OTLP protobuf format.
16 : class MetricTransformer {
17 : /// Transforms a Resource to an OTLP Resource proto.
18 2 : static resource_proto.Resource transformResource(Resource resource) {
19 2 : final resourceProto = resource_proto.Resource();
20 2 : final attributes = resource.attributes;
21 :
22 4 : resourceProto.attributes.addAll(
23 : attributes
24 2 : .toMap()
25 2 : .entries
26 12 : .map((entry) => _createKeyValue(entry.key, entry.value.value)),
27 : );
28 :
29 : return resourceProto;
30 : }
31 :
32 : /// Transforms a Metric to an OTLP Metric proto.
33 2 : static proto.Metric transformMetric(Metric metric) {
34 2 : final metricProto = proto.Metric();
35 4 : metricProto.name = metric.name;
36 :
37 2 : if (metric.description != null) {
38 4 : metricProto.description = metric.description!;
39 : }
40 :
41 2 : if (metric.unit != null) {
42 4 : metricProto.unit = metric.unit!;
43 : }
44 :
45 2 : if (OTelLog.isLogMetrics()) {
46 2 : OTelLog.logMetric(
47 6 : 'MetricTransformer: Transforming metric ${metric.name} of type ${metric.type}');
48 : }
49 :
50 : // Set data based on metric type
51 2 : switch (metric.type) {
52 2 : case MetricType.histogram:
53 : // Histogram metric
54 2 : final histogramDataPoints = <proto.HistogramDataPoint>[];
55 4 : for (final point in metric.points) {
56 4 : if (point.value is HistogramValue) {
57 2 : final dataPoint = _createHistogramDataPoint(point);
58 2 : histogramDataPoints.add(dataPoint);
59 : }
60 : }
61 :
62 : // Create a new histogram with the correct temporality and data points
63 2 : final histogram = proto.Histogram(
64 4 : aggregationTemporality: metric.temporality ==
65 : AggregationTemporality.delta
66 : ? proto.AggregationTemporality.AGGREGATION_TEMPORALITY_DELTA
67 : : proto.AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE,
68 : dataPoints: histogramDataPoints,
69 : );
70 :
71 2 : metricProto.histogram = histogram;
72 : break;
73 :
74 2 : case MetricType.sum:
75 : // Sum metric
76 2 : final numberDataPoints = <proto.NumberDataPoint>[];
77 4 : for (final point in metric.points) {
78 2 : final dataPoint = _createNumberDataPoint(point);
79 2 : numberDataPoints.add(dataPoint);
80 : }
81 :
82 : // Create a new sum with the correct temporality and data points
83 2 : final sum = proto.Sum(
84 2 : isMonotonic: metric.isMonotonic ??
85 : true, // Assuming sum metrics are monotonic by default
86 4 : aggregationTemporality: metric.temporality ==
87 : AggregationTemporality.delta
88 : ? proto.AggregationTemporality.AGGREGATION_TEMPORALITY_DELTA
89 : : proto.AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE,
90 : dataPoints: numberDataPoints,
91 : );
92 :
93 2 : metricProto.sum = sum;
94 : break;
95 :
96 2 : case MetricType.gauge:
97 : // Gauge metric
98 2 : final numberDataPoints = <proto.NumberDataPoint>[];
99 4 : for (final point in metric.points) {
100 2 : final dataPoint = _createNumberDataPoint(point);
101 2 : numberDataPoints.add(dataPoint);
102 : }
103 :
104 : // Create a new gauge with the data points
105 2 : final gauge = proto.Gauge(dataPoints: numberDataPoints);
106 2 : metricProto.gauge = gauge;
107 : break;
108 : }
109 :
110 : return metricProto;
111 : }
112 :
113 : /// Creates a histogram data point for the given MetricPoint.
114 2 : static proto.HistogramDataPoint _createHistogramDataPoint(
115 : MetricPoint<dynamic> point) {
116 2 : final histogramValue = point.value as HistogramValue;
117 :
118 : // Prepare attributes
119 4 : final attributes = point.attributes.toMap();
120 2 : final attributeKeyValues = attributes.entries
121 12 : .map((entry) => _createKeyValue(entry.key, entry.value.value))
122 2 : .toList();
123 :
124 : // Prepare exemplars if available
125 2 : final exemplars = <proto.Exemplar>[];
126 2 : if (point.hasExemplars) {
127 0 : for (final exemplar in point.exemplars!) {
128 0 : final exemplarProto = proto.Exemplar(
129 0 : timeUnixNano: Int64(exemplar.timestamp.microsecondsSinceEpoch * 1000),
130 0 : asDouble: exemplar.value.toDouble(),
131 : );
132 0 : exemplars.add(exemplarProto);
133 : }
134 : }
135 :
136 : // Create bucket counts as Int64 list
137 : final bucketCountsInt64 =
138 6 : histogramValue.bucketCounts.map(Int64.new).toList();
139 :
140 : // Create the HistogramDataPoint with all fields set
141 2 : return proto.HistogramDataPoint(
142 : attributes: attributeKeyValues,
143 8 : startTimeUnixNano: Int64(point.startTime.microsecondsSinceEpoch * 1000),
144 8 : timeUnixNano: Int64(point.endTime.microsecondsSinceEpoch * 1000),
145 4 : count: Int64(histogramValue.count),
146 4 : sum: histogramValue.sum.toDouble(),
147 : bucketCounts: bucketCountsInt64,
148 4 : explicitBounds: List<double>.from(histogramValue.boundaries),
149 : exemplars: exemplars,
150 4 : min: histogramValue.min?.toDouble(),
151 4 : max: histogramValue.max?.toDouble(),
152 : );
153 : }
154 :
155 : /// Creates a number data point for the given MetricPoint.
156 2 : static proto.NumberDataPoint _createNumberDataPoint(
157 : MetricPoint<dynamic> point) {
158 : // Prepare attributes
159 4 : final attributes = point.attributes.toMap();
160 2 : final attributeKeyValues = attributes.entries
161 12 : .map((entry) => _createKeyValue(entry.key, entry.value.value))
162 2 : .toList();
163 :
164 : // Prepare exemplars if available
165 2 : final exemplars = <proto.Exemplar>[];
166 2 : if (point.hasExemplars) {
167 0 : for (final exemplar in point.exemplars!) {
168 0 : final exemplarProto = proto.Exemplar(
169 0 : timeUnixNano: Int64(exemplar.timestamp.microsecondsSinceEpoch * 1000),
170 0 : asDouble: exemplar.value.toDouble(),
171 : );
172 0 : exemplars.add(exemplarProto);
173 : }
174 : }
175 :
176 : // Create the NumberDataPoint with all fields set
177 2 : return proto.NumberDataPoint(
178 : attributes: attributeKeyValues,
179 8 : startTimeUnixNano: Int64(point.startTime.microsecondsSinceEpoch * 1000),
180 8 : timeUnixNano: Int64(point.endTime.microsecondsSinceEpoch * 1000),
181 4 : asDouble: (point.value is num)
182 4 : ? (point.value as num).toDouble()
183 0 : : double.tryParse(point.value.toString()) ?? 0.0,
184 : exemplars: exemplars,
185 : );
186 : }
187 :
188 : /// Creates a KeyValue proto from a key and value.
189 2 : static common_proto.KeyValue _createKeyValue(String key, dynamic value) {
190 2 : final keyValue = common_proto.KeyValue();
191 2 : keyValue.key = key;
192 :
193 2 : if (value is String) {
194 4 : keyValue.value = common_proto.AnyValue(stringValue: value);
195 1 : } else if (value is bool) {
196 2 : keyValue.value = common_proto.AnyValue(boolValue: value);
197 1 : } else if (value is int) {
198 3 : keyValue.value = common_proto.AnyValue(intValue: Int64(value));
199 1 : } else if (value is double) {
200 2 : keyValue.value = common_proto.AnyValue(doubleValue: value);
201 0 : } else if (value is List) {
202 0 : final arrayValue = common_proto.ArrayValue();
203 0 : for (final item in value) {
204 0 : final anyValue = common_proto.AnyValue();
205 0 : if (item is String) {
206 0 : anyValue.stringValue = item;
207 0 : } else if (item is bool) {
208 0 : anyValue.boolValue = item;
209 0 : } else if (item is int) {
210 0 : anyValue.intValue = Int64(item);
211 0 : } else if (item is double) {
212 0 : anyValue.doubleValue = item;
213 : }
214 0 : arrayValue.values.add(anyValue);
215 : }
216 0 : keyValue.value = common_proto.AnyValue(arrayValue: arrayValue);
217 : } else {
218 : // Default to string representation for unsupported types
219 0 : keyValue.value = common_proto.AnyValue(stringValue: value.toString());
220 : }
221 :
222 : return keyValue;
223 : }
224 : }
|