Skip to content

Commit 61d5f6f

Browse files
committed
chore: backfill tests for metric labels.
Factor metric labels into a helper function and add tests.
1 parent a99a35a commit 61d5f6f

2 files changed

Lines changed: 129 additions & 26 deletions

File tree

scraper.go

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,6 @@ func (s *oxideScraper) Scrape(ctx context.Context) (pmetric.Metrics, error) {
159159
s.scrapeDuration.Record(ctx, elapsed.Seconds())
160160
s.scrapeCount.Add(ctx, 1, metric.WithAttributes(attribute.String("status", "success")))
161161

162-
// Cargo-culted from https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/googlecloudmonitoringreceiver.
163-
// TODO(jmcarp): Can we skip this map?
164-
resourceMetricsMap := make(map[string]pmetric.ResourceMetrics)
165162
for _, result := range results {
166163
for _, table := range result.Tables {
167164
for _, series := range table.Timeseries {
@@ -173,38 +170,23 @@ func (s *oxideScraper) Scrape(ctx context.Context) (pmetric.Metrics, error) {
173170
sort.Strings(labels)
174171
key = fmt.Sprintf("%s_%s", key, strings.Join(labels, "::"))
175172

176-
rm, exists := resourceMetricsMap[key]
177-
if !exists {
178-
rm = metrics.ResourceMetrics().AppendEmpty()
179-
resource := rm.Resource()
180-
for key, value := range series.Fields {
181-
switch value.Type {
182-
case oxide.FieldValueTypeString:
183-
resource.Attributes().PutStr(key, value.Value.(string))
184-
case oxide.FieldValueTypeI8, oxide.FieldValueTypeI16, oxide.FieldValueTypeI32, oxide.FieldValueTypeI64,
185-
oxide.FieldValueTypeU8, oxide.FieldValueTypeU16, oxide.FieldValueTypeU32, oxide.FieldValueTypeU64:
186-
intValue, ok := value.Value.(float64)
187-
if !ok {
188-
s.logger.Warn("couldn't cast label to float", zap.String("metric", table.Name), zap.String("field", key), zap.Any("value", value))
189-
}
190-
resource.Attributes().PutInt(key, int64(intValue))
191-
default:
192-
resource.Attributes().PutStr(key, fmt.Sprintf("%v", value.Value))
193-
}
194-
}
195-
resourceMetricsMap[key] = rm
173+
rm := metrics.ResourceMetrics().AppendEmpty()
174+
resource := rm.Resource()
175+
176+
if err := addLabels(series, table, resource); err != nil {
177+
s.logger.Warn("failed to parse field labels", zap.String("metric", table.Name), zap.Error(err))
178+
s.metricParseErrors.Add(ctx, 1, metric.WithAttributes(
179+
attribute.String("metric_name", table.Name),
180+
))
196181
}
197182

198-
// Ensure we have a ScopeMetrics to append the metric to
199183
var sm pmetric.ScopeMetrics
200184
if rm.ScopeMetrics().Len() == 0 {
201185
sm = rm.ScopeMetrics().AppendEmpty()
202186
} else {
203-
// For simplicity, let's assume all metrics will share the same ScopeMetrics
204187
sm = rm.ScopeMetrics().At(0)
205188
}
206189

207-
// Create a new Metric
208190
m := sm.Metrics().AppendEmpty()
209191

210192
m.SetName(table.Name)
@@ -287,6 +269,29 @@ func (s *oxideScraper) Scrape(ctx context.Context) (pmetric.Metrics, error) {
287269
return metrics, nil
288270
}
289271

272+
func addLabels(series oxide.Timeseries, table oxide.OxqlTable, resource pcommon.Resource) error {
273+
for key, value := range series.Fields {
274+
switch value.Type {
275+
case oxide.FieldValueTypeString:
276+
strValue, ok := value.Value.(string)
277+
if !ok {
278+
return fmt.Errorf("couldn't cast label %+v for metric %s to string; got unexpected type %T", value.Value, table.Name, value.Value)
279+
}
280+
resource.Attributes().PutStr(key, strValue)
281+
case oxide.FieldValueTypeI8, oxide.FieldValueTypeI16, oxide.FieldValueTypeI32, oxide.FieldValueTypeI64,
282+
oxide.FieldValueTypeU8, oxide.FieldValueTypeU16, oxide.FieldValueTypeU32, oxide.FieldValueTypeU64:
283+
intValue, ok := value.Value.(float64)
284+
if !ok {
285+
return fmt.Errorf("couldn't cast label%+v for metric %s to float64; got unexpected type %T", value.Value, table.Name, value.Value)
286+
}
287+
resource.Attributes().PutInt(key, int64(intValue))
288+
default:
289+
resource.Attributes().PutStr(key, fmt.Sprintf("%v", value.Value))
290+
}
291+
}
292+
return nil
293+
}
294+
290295
func addHistogram(dataPoints pmetric.HistogramDataPointSlice, quantileGauge pmetric.Gauge, table oxide.OxqlTable, series oxide.Timeseries) error {
291296
timestamps := series.Points.Timestamps
292297
for idx, point := range series.Points.Values {

scraper_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,104 @@ import (
1111
"go.opentelemetry.io/collector/pdata/pmetric"
1212
)
1313

14+
func TestAddLabels(t *testing.T) {
15+
table := oxide.OxqlTable{Name: "test_metric"}
16+
17+
for _, tc := range []struct {
18+
name string
19+
series oxide.Timeseries
20+
wantResource pcommon.Resource
21+
wantErr string
22+
}{
23+
{
24+
name: "string: success",
25+
series: oxide.Timeseries{
26+
Fields: map[string]oxide.FieldValue{
27+
"hostname": {
28+
Type: oxide.FieldValueTypeString,
29+
Value: "server-01",
30+
},
31+
},
32+
},
33+
wantResource: func() pcommon.Resource {
34+
r := pcommon.NewResource()
35+
r.Attributes().PutStr("hostname", "server-01")
36+
return r
37+
}(),
38+
},
39+
{
40+
name: "int: success",
41+
series: oxide.Timeseries{
42+
Fields: map[string]oxide.FieldValue{
43+
"port": {
44+
Type: oxide.FieldValueTypeI64,
45+
Value: float64(8080),
46+
},
47+
},
48+
},
49+
wantResource: func() pcommon.Resource {
50+
r := pcommon.NewResource()
51+
r.Attributes().PutInt("port", 8080)
52+
return r
53+
}(),
54+
},
55+
{
56+
name: "uuid: success",
57+
series: oxide.Timeseries{
58+
Fields: map[string]oxide.FieldValue{
59+
"instance_id": {
60+
Type: oxide.FieldValueTypeUuid,
61+
Value: "550e8400-e29b-41d4-a716-446655440000",
62+
},
63+
},
64+
},
65+
wantResource: func() pcommon.Resource {
66+
r := pcommon.NewResource()
67+
r.Attributes().PutStr("instance_id", "550e8400-e29b-41d4-a716-446655440000")
68+
return r
69+
}(),
70+
},
71+
{
72+
name: "string: type assertion error",
73+
series: oxide.Timeseries{
74+
Fields: map[string]oxide.FieldValue{
75+
"hostname": {
76+
Type: oxide.FieldValueTypeString,
77+
Value: 123,
78+
},
79+
},
80+
},
81+
wantErr: "couldn't cast label",
82+
},
83+
{
84+
name: "int field: type assertion error",
85+
series: oxide.Timeseries{
86+
Fields: map[string]oxide.FieldValue{
87+
"port": {
88+
Type: oxide.FieldValueTypeI64,
89+
Value: "not a number",
90+
},
91+
},
92+
},
93+
wantErr: "couldn't cast label",
94+
},
95+
} {
96+
t.Run(tc.name, func(t *testing.T) {
97+
resource := pcommon.NewResource()
98+
99+
err := addLabels(tc.series, table, resource)
100+
101+
if tc.wantErr != "" {
102+
require.ErrorContains(t, err, tc.wantErr)
103+
return
104+
}
105+
require.NoError(t, err)
106+
107+
require.Equal(t, tc.wantResource.Attributes().AsRaw(), resource.Attributes().AsRaw())
108+
})
109+
}
110+
}
111+
14112
func TestAddPoint(t *testing.T) {
15113
now := time.Now()
16114
table := oxide.OxqlTable{Name: "test_metric"}

0 commit comments

Comments
 (0)