Skip to content

Commit 74e84e7

Browse files
authored
feat: optionally add human-readable labels. (#5)
OxQL provides uuid labels for silos, projects, etc., but these aren't very useful for metrics queries: the user has to look up the uuid for each silo/project of interest. This patch adds the option to fetch silo and project metadata from the api and enrich labels where appropriate. We should support this in the api instead eventually, but it's quicker to add here for now.
1 parent 58b23af commit 74e84e7

3 files changed

Lines changed: 101 additions & 0 deletions

File tree

config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ type Config struct {
1414
Token string `mapstructure:"token"`
1515
MetricPatterns []string `mapstructure:"metric_patterns"`
1616
ScrapeConcurrency int `mapstructure:"scrape_concurrency"`
17+
18+
// AddLabels configures the receiver to add human-readable labels to metrics using the Oxide API.
19+
AddLabels bool `mapstructure:"add_labels"`
1720
}
1821

1922
func (cfg *Config) Validate() error {

scraper.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,37 @@ 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+
// Cache mappings from resource UUIDs to human-readable names. Note: we
163+
// can also add mappings for higher-cardinality resources like
164+
// instances and disks, but this would add more latency to the 0th
165+
// query on the page.
166+
//
167+
// TODO: add human-readable labels to metrics in oximeter so that we
168+
// don't have to enrich them here. Tracked in
169+
// https://github.com/oxidecomputer/omicron/issues/9119.
170+
siloToName := map[string]string{}
171+
projectToName := map[string]string{}
172+
if s.cfg.AddLabels {
173+
silos, err := s.client.SiloListAllPages(ctx, oxide.SiloListParams{})
174+
if err != nil {
175+
return metrics, fmt.Errorf("listing silos: %w", err)
176+
}
177+
for _, silo := range silos {
178+
siloToName[silo.Id] = string(silo.Name)
179+
}
180+
// Note: this only lists projects in the silo corresponding to
181+
// the client's authentication token. In the future, we can
182+
// either add a system endpoint listing all projects for the
183+
// rack, or enrich metrics with project labels in nexus.
184+
projects, err := s.client.ProjectListAllPages(ctx, oxide.ProjectListParams{})
185+
if err != nil {
186+
return metrics, fmt.Errorf("listing projects: %w", err)
187+
}
188+
for _, project := range projects {
189+
projectToName[project.Id] = string(project.Name)
190+
}
191+
}
192+
162193
for _, result := range results {
163194
for _, table := range result.Tables {
164195
for _, series := range table.Timeseries {
@@ -180,6 +211,8 @@ func (s *oxideScraper) Scrape(ctx context.Context) (pmetric.Metrics, error) {
180211
))
181212
}
182213

214+
enrichLabels(resource, siloToName, projectToName)
215+
183216
var sm pmetric.ScopeMetrics
184217
if rm.ScopeMetrics().Len() == 0 {
185218
sm = rm.ScopeMetrics().AppendEmpty()
@@ -292,6 +325,19 @@ func addLabels(series oxide.Timeseries, table oxide.OxqlTable, resource pcommon.
292325
return nil
293326
}
294327

328+
func enrichLabels(resource pcommon.Resource, silos map[string]string, projects map[string]string) {
329+
if siloID, ok := resource.Attributes().Get("silo_id"); ok {
330+
if siloName, ok := silos[siloID.Str()]; ok {
331+
resource.Attributes().PutStr("silo_name", siloName)
332+
}
333+
}
334+
if projectID, ok := resource.Attributes().Get("project_id"); ok {
335+
if projectName, ok := projects[projectID.Str()]; ok {
336+
resource.Attributes().PutStr("project_name", projectName)
337+
}
338+
}
339+
}
340+
295341
func addHistogram(dataPoints pmetric.HistogramDataPointSlice, quantileGauge pmetric.Gauge, table oxide.OxqlTable, series oxide.Timeseries) error {
296342
timestamps := series.Points.Timestamps
297343
for idx, point := range series.Points.Values {

scraper_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,58 @@ func TestAddLabels(t *testing.T) {
109109
}
110110
}
111111

112+
func TestEnrichLabels(t *testing.T) {
113+
for _, tc := range []struct {
114+
name string
115+
resource pcommon.Resource
116+
silos map[string]string
117+
projects map[string]string
118+
wantResource pcommon.Resource
119+
}{
120+
{
121+
name: "silo",
122+
resource: func() pcommon.Resource {
123+
r := pcommon.NewResource()
124+
r.Attributes().PutStr("silo_id", "123e4567-e89b-12d3-a456-426614174000")
125+
return r
126+
}(),
127+
silos: map[string]string{
128+
"123e4567-e89b-12d3-a456-426614174000": "default",
129+
},
130+
projects: map[string]string{},
131+
wantResource: func() pcommon.Resource {
132+
r := pcommon.NewResource()
133+
r.Attributes().PutStr("silo_id", "123e4567-e89b-12d3-a456-426614174000")
134+
r.Attributes().PutStr("silo_name", "default")
135+
return r
136+
}(),
137+
},
138+
{
139+
name: "project",
140+
resource: func() pcommon.Resource {
141+
r := pcommon.NewResource()
142+
r.Attributes().PutStr("project_id", "987fcdeb-51a2-43f7-b890-123456789abc")
143+
return r
144+
}(),
145+
silos: map[string]string{},
146+
projects: map[string]string{
147+
"987fcdeb-51a2-43f7-b890-123456789abc": "my-project",
148+
},
149+
wantResource: func() pcommon.Resource {
150+
r := pcommon.NewResource()
151+
r.Attributes().PutStr("project_id", "987fcdeb-51a2-43f7-b890-123456789abc")
152+
r.Attributes().PutStr("project_name", "my-project")
153+
return r
154+
}(),
155+
},
156+
} {
157+
t.Run(tc.name, func(t *testing.T) {
158+
enrichLabels(tc.resource, tc.silos, tc.projects)
159+
require.Equal(t, tc.wantResource.Attributes().AsRaw(), tc.resource.Attributes().AsRaw())
160+
})
161+
}
162+
}
163+
112164
func TestAddPoint(t *testing.T) {
113165
now := time.Now()
114166
table := oxide.OxqlTable{Name: "test_metric"}

0 commit comments

Comments
 (0)