Skip to content

Commit 0429170

Browse files
authored
fix(bigtable): handle client region and client host name based on ote… (#13752)
…l conventions
1 parent 229f288 commit 0429170

File tree

2 files changed

+133
-2
lines changed

2 files changed

+133
-2
lines changed

bigtable/otel_metrics.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,41 @@ func newBigtableClientMonitoredResource(ctx context.Context, project, appProfile
100100

101101
smr.clientProject = getAttribute(s, "cloud.account.id", unKnownAtttr)
102102
smr.cloudPlatform = getAttribute(s, "cloud.platform", unKnownAtttr)
103-
smr.hostName = getAttribute(s, "host.name", unKnownAtttr)
103+
104+
smr.hostName = getAttribute(s, "host.name", "")
105+
// See https://opentelemetry.io/docs/specs/semconv/resource/k8s/#pod
106+
if smr.hostName == "" {
107+
smr.hostName = getAttribute(s, "k8s.pod.name", "")
108+
}
109+
// Fallback https://opentelemetry.io/docs/specs/semconv/resource/k8s/#pod
110+
if smr.hostName == "" {
111+
// Fallback to Node name if pod name is missing, add unknown
112+
smr.hostName = getAttribute(s, "k8s.node.name", unKnownAtttr)
113+
}
114+
104115
smr.hostID = getAttribute(s, "host.id", unKnownAtttr)
105116
if smr.hostID == "unknown" {
106117
// cloud run / cloud functions have faas.id instead of host.id
107118

108119
smr.hostID = getAttribute(s, "faas.id", unKnownAtttr)
109120
}
110-
smr.region = getAttribute(s, "cloud.region", "global")
121+
122+
// See https://opentelemetry.io/docs/specs/semconv/resource/cloud/
123+
smr.region = getAttribute(s, "cloud.region", "")
124+
if smr.region == "" {
125+
zone := getAttribute(s, "cloud.availability_zone", "")
126+
if zone != "" && zone != unKnownAtttr {
127+
// handle for only gce zones.
128+
if lastDash := strings.LastIndex(zone, "-"); lastDash > 0 {
129+
smr.region = zone[:lastDash]
130+
}
131+
}
132+
}
133+
134+
if smr.region == "" {
135+
smr.region = "global"
136+
}
137+
111138
smr.resource, err = resource.New(ctx, resource.WithAttributes([]attribute.KeyValue{
112139
{Key: "gcp.resource_type", Value: attribute.StringValue(bigtableClientMonitoredResourceName)},
113140
{Key: "project_id", Value: attribute.StringValue(project)},

bigtable/otel_metrics_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,107 @@ func TestOtelMetricsContext(t *testing.T) {
112112
}
113113
}
114114
}
115+
116+
func TestOtelMetricsSchema(t *testing.T) {
117+
tests := []struct {
118+
name string
119+
inputAttrs []attribute.KeyValue
120+
wantRegion string
121+
wantHostName string
122+
}{
123+
{
124+
name: "zone",
125+
inputAttrs: []attribute.KeyValue{
126+
{Key: "cloud.availability_zone", Value: attribute.StringValue("us-central1-a")},
127+
{Key: "cloud.platform", Value: attribute.StringValue("gcp_compute_engine")},
128+
{Key: "host.name", Value: attribute.StringValue("explicit-host")},
129+
},
130+
wantRegion: "us-central1",
131+
wantHostName: "explicit-host",
132+
},
133+
{
134+
name: "Pod Name",
135+
inputAttrs: []attribute.KeyValue{
136+
{Key: "k8s.pod.name", Value: attribute.StringValue("pod-123")},
137+
{Key: "cloud.region", Value: attribute.StringValue("us-west1")},
138+
},
139+
wantRegion: "us-west1",
140+
wantHostName: "pod-123",
141+
},
142+
{
143+
name: "K8s Node Name",
144+
inputAttrs: []attribute.KeyValue{
145+
{Key: "k8s.node.name", Value: attribute.StringValue("node-abc")},
146+
{Key: "cloud.region", Value: attribute.StringValue("us-west1")},
147+
},
148+
wantRegion: "us-west1",
149+
wantHostName: "node-abc",
150+
},
151+
{
152+
name: "aws zone",
153+
inputAttrs: []attribute.KeyValue{
154+
{Key: "k8s.node.name", Value: attribute.StringValue("node-abc")},
155+
{Key: "cloud.availability_zone", Value: attribute.StringValue("us-west-1a")},
156+
},
157+
wantRegion: "us-west",
158+
wantHostName: "node-abc",
159+
},
160+
{
161+
name: "Global Default",
162+
inputAttrs: []attribute.KeyValue{
163+
{Key: "cloud.platform", Value: attribute.StringValue("unknown")},
164+
},
165+
wantRegion: "global",
166+
wantHostName: "unknown",
167+
},
168+
}
169+
170+
for _, tc := range tests {
171+
t.Run(tc.name, func(t *testing.T) {
172+
ctx := context.Background()
173+
mr := metric.NewManualReader()
174+
175+
// Basic required attributes for the test setup
176+
baseAttrs := []attribute.KeyValue{
177+
{Key: "cloud.account.id", Value: attribute.StringValue("test-account")},
178+
{Key: "host.id", Value: attribute.StringValue("test-host-id")},
179+
}
180+
allAttrs := append(baseAttrs, tc.inputAttrs...)
181+
182+
cfg := metricsConfig{
183+
project: "test-project",
184+
instance: "test-instance",
185+
appProfile: "default",
186+
clientName: "test-client",
187+
clientUID: "test-uid",
188+
manualReader: mr,
189+
disableExporter: true,
190+
resourceOpts: []resource.Option{resource.WithAttributes(allAttrs...)},
191+
}
192+
193+
mc, err := newOtelMetricsContext(ctx, cfg)
194+
if err != nil {
195+
t.Fatalf("newOtelMetricsContext failed: %v", err)
196+
}
197+
defer mc.close()
198+
199+
rm := metricdata.ResourceMetrics{}
200+
if err := mr.Collect(ctx, &rm); err != nil {
201+
t.Fatalf("Collect failed: %v", err)
202+
}
203+
204+
// Extract attributes into a map for easy lookup
205+
gotAttrs := make(map[string]string)
206+
for _, attr := range rm.Resource.Attributes() {
207+
gotAttrs[string(attr.Key)] = attr.Value.AsString()
208+
}
209+
210+
if gotAttrs["region"] != tc.wantRegion {
211+
t.Errorf("region: got %q, want %q", gotAttrs["region"], tc.wantRegion)
212+
}
213+
if gotAttrs["host_name"] != tc.wantHostName {
214+
t.Errorf("host_name: got %q, want %q", gotAttrs["host_name"], tc.wantHostName)
215+
}
216+
})
217+
}
218+
}

0 commit comments

Comments
 (0)