Skip to content

Commit bbb6553

Browse files
Merge branch 'zig-o11y:main' into main
2 parents 15460c7 + 1ab1b0a commit bbb6553

File tree

4 files changed

+33
-120
lines changed

4 files changed

+33
-120
lines changed

src/api/metrics/instrument.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ test "instrument in meter and instrument in data are the same" {
678678
const counter_value = instrument.data.Counter_u64.data_points.pop() orelse unreachable;
679679
try std.testing.expectEqual(100, counter_value.value);
680680
} else {
681-
std.debug.panic("Counter {s} not found in meter {s} after creation", .{ name, meter.name });
681+
std.debug.panic("Counter {s} not found in meter {s} after creation", .{ name, meter.scope.name });
682682
}
683683
}
684684

@@ -711,7 +711,7 @@ test "instrument fetches measurements from inner" {
711711
std.debug.assert(measurements.int.len == 1);
712712
try std.testing.expectEqual(@as(i64, 100), measurements.int[0].value);
713713
} else {
714-
std.debug.panic("Counter {s} not found in meter {s} after creation", .{ name, meter.name });
714+
std.debug.panic("Counter {s} not found in meter {s} after creation", .{ name, meter.scope.name });
715715
}
716716
}
717717

src/api/metrics/meter.zig

Lines changed: 28 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const spec = @import("spec.zig");
44
const builtin = @import("builtin");
55
const Attribute = @import("../../attributes.zig").Attribute;
66
const Attributes = @import("../../attributes.zig").Attributes;
7+
const InstrumentationScope = @import("../../scope.zig").InstrumentationScope;
8+
79
const DataPoint = @import("measurement.zig").DataPoint;
810
const HistogramDataPoint = @import("measurement.zig").HistogramDataPoint;
911

@@ -18,14 +20,18 @@ const Histogram = @import("instrument.zig").Histogram;
1820
const Gauge = @import("instrument.zig").Gauge;
1921
const MetricReader = @import("../../sdk/metrics/reader.zig").MetricReader;
2022

21-
const defaultMeterVersion = "0.1.0";
2223
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
2324

2425
/// MeterProvider is responsble for creating and managing meters.
2526
/// See https://opentelemetry.io/docs/specs/otel/metrics/api/#meterprovider
2627
pub const MeterProvider = struct {
2728
allocator: std.mem.Allocator,
28-
meters: std.AutoHashMapUnmanaged(u64, Meter),
29+
meters: std.HashMapUnmanaged(
30+
InstrumentationScope,
31+
Meter,
32+
InstrumentationScope.HashContext,
33+
std.hash_map.default_max_load_percentage,
34+
),
2935
readers: std.ArrayListUnmanaged(*MetricReader),
3036

3137
const Self = @This();
@@ -72,39 +78,22 @@ pub const MeterProvider = struct {
7278
}
7379

7480
/// Get a new meter by specifying its name.
75-
/// Options can be passed to specify a version, schemaURL, and attributes.
81+
/// Scope can be passed to specify a version, schemaURL, and attributes.
7682
/// SchemaURL and attributes are default to null.
7783
/// If a meter with the same name already exists, it will be returned.
7884
/// See https://opentelemetry.io/docs/specs/otel/metrics/api/#get-a-meter
79-
pub fn getMeter(self: *Self, options: MeterOptions) !*Meter {
85+
pub fn getMeter(self: *Self, scope: InstrumentationScope) !*Meter {
8086
const i = Meter{
81-
.name = options.name,
82-
.version = options.version,
83-
.attributes = options.attributes,
84-
.schema_url = options.schema_url,
87+
.scope = scope,
8588
.instruments = .empty,
8689
.allocator = self.allocator,
8790
};
88-
// A Meter is identified uniquely by its name, version and schema_url.
89-
// We use a hash of these values to identify the meter.
90-
const meterId = spec.meterIdentifier(options);
9191

92-
// Raise an error if a meter with the same name/version/schema_url is asked to be fetched with different attributes.
93-
if (self.meterExistsWithDifferentAttributes(meterId, options.attributes)) {
94-
return spec.ResourceError.MeterExistsWithDifferentAttributes;
95-
}
96-
const meter = try self.meters.getOrPutValue(self.allocator, meterId, i);
92+
const meter = try self.meters.getOrPutValue(self.allocator, scope, i);
9793

9894
return meter.value_ptr;
9995
}
10096

101-
fn meterExistsWithDifferentAttributes(self: *Self, identifier: u64, attributes: ?[]Attribute) bool {
102-
if (self.meters.get(identifier)) |m| {
103-
return !std.mem.eql(u8, &std.mem.toBytes(m.attributes), &std.mem.toBytes(attributes));
104-
}
105-
return false;
106-
}
107-
10897
pub fn addReader(self: *Self, m: *MetricReader) !void {
10998
if (m.meterProvider != null) {
11099
return spec.ResourceError.MetricReaderAlreadyAttached;
@@ -114,20 +103,10 @@ pub const MeterProvider = struct {
114103
}
115104
};
116105

117-
pub const MeterOptions = struct {
118-
name: []const u8,
119-
version: []const u8 = defaultMeterVersion,
120-
schema_url: ?[]const u8 = null,
121-
attributes: ?[]Attribute = null,
122-
};
123-
124106
/// Meter is a named instance that is used to record measurements.
125107
/// See https://opentelemetry.io/docs/specs/otel/metrics/api/#meter
126108
const Meter = struct {
127-
name: []const u8,
128-
version: []const u8,
129-
schema_url: ?[]const u8,
130-
attributes: ?[]Attribute = null,
109+
scope: InstrumentationScope,
131110
instruments: std.StringHashMapUnmanaged(*Instrument),
132111
allocator: std.mem.Allocator,
133112

@@ -198,7 +177,7 @@ const Meter = struct {
198177
if (self.instruments.contains(id)) {
199178
std.debug.print(
200179
"Instrument with identifying name {s} already exists in meter {s}\n",
201-
.{ id, self.name },
180+
.{ id, self.scope.name },
202181
);
203182
return spec.ResourceError.InstrumentExistsWithSameNameAndIdentifyingFields;
204183
}
@@ -216,7 +195,7 @@ const Meter = struct {
216195
// Cleanup Meters' Instruments values.
217196
self.instruments.deinit(self.allocator);
218197
// Cleanup the meter attributes.
219-
if (self.attributes) |attrs| self.allocator.free(attrs);
198+
if (self.scope.attributes) |attrs| self.allocator.free(attrs);
220199
}
221200
};
222201

@@ -242,10 +221,10 @@ test "meter can be created from custom provider" {
242221

243222
const meter = try mp.getMeter(.{ .name = meter_name, .version = meter_version });
244223

245-
std.debug.assert(std.mem.eql(u8, meter.name, meter_name));
246-
std.debug.assert(std.mem.eql(u8, meter.version, meter_version));
247-
std.debug.assert(meter.schema_url == null);
248-
std.debug.assert(meter.attributes == null);
224+
std.debug.assert(std.mem.eql(u8, meter.scope.name, meter_name));
225+
std.debug.assert(std.mem.eql(u8, meter.scope.version.?, meter_version));
226+
std.debug.assert(meter.scope.schema_url == null);
227+
std.debug.assert(meter.scope.attributes == null);
249228
}
250229

251230
test "meter can be created from default provider with schema url and attributes" {
@@ -259,41 +238,10 @@ test "meter can be created from default provider with schema url and attributes"
259238
const attributes = try Attributes.from(mp.allocator, .{ "key", val });
260239

261240
const meter = try mp.getMeter(.{ .name = meter_name, .version = meter_version, .schema_url = "http://foo.bar", .attributes = attributes });
262-
try std.testing.expectEqual(meter.name, meter_name);
263-
try std.testing.expectEqualStrings(meter.version, meter_version);
264-
try std.testing.expectEqualStrings(meter.schema_url.?, "http://foo.bar");
265-
std.debug.assert(std.mem.eql(u8, std.mem.sliceAsBytes(meter.attributes.?), std.mem.sliceAsBytes(attributes.?)));
266-
}
267-
268-
test "meter has default version when creted with no options" {
269-
const mp = try MeterProvider.default();
270-
defer mp.shutdown();
271-
272-
const meter = try mp.getMeter(.{ .name = "ameter" });
273-
std.debug.assert(std.mem.eql(u8, meter.version, defaultMeterVersion));
274-
}
275-
276-
test "getting same meter with different attributes returns an error" {
277-
const name = "my-meter";
278-
const version = "v1.2.3";
279-
const schema_url = "http://foo.bar";
280-
281-
const mp = try MeterProvider.default();
282-
defer mp.shutdown();
283-
284-
const val1: []const u8 = "value1";
285-
const val2: []const u8 = "value2";
286-
const attributes = try Attributes.from(mp.allocator, .{ "key1", val1 });
287-
288-
_ = try mp.getMeter(.{ .name = name, .version = version, .schema_url = schema_url, .attributes = attributes });
289-
290-
// modify the attributes adding one/
291-
// these attributes are not allocated with the same allocator as the meter.
292-
const attributesUpdated = try Attributes.from(std.testing.allocator, .{ "key1", val1, "key2", val2 });
293-
defer std.testing.allocator.free(attributesUpdated.?);
294-
295-
const r = mp.getMeter(.{ .name = name, .version = version, .schema_url = schema_url, .attributes = attributesUpdated });
296-
try std.testing.expectError(spec.ResourceError.MeterExistsWithDifferentAttributes, r);
241+
try std.testing.expectEqual(meter.scope.name, meter_name);
242+
try std.testing.expectEqualStrings(meter.scope.version.?, meter_version);
243+
try std.testing.expectEqualStrings(meter.scope.schema_url.?, "http://foo.bar");
244+
std.debug.assert(std.mem.eql(u8, std.mem.sliceAsBytes(meter.scope.attributes.?), std.mem.sliceAsBytes(attributes.?)));
297245
}
298246

299247
test "meter register instrument twice with same name fails" {
@@ -555,9 +503,9 @@ pub const AggregatedMetrics = struct {
555503
// only if there are data points.
556504
if (aggregated_data) |agg| {
557505
try results.append(Measurements{
558-
.meterName = meter.name,
559-
.meterSchemaUrl = meter.schema_url,
560-
.meterAttributes = meter.attributes,
506+
.meterName = meter.scope.name,
507+
.meterSchemaUrl = meter.scope.schema_url,
508+
.meterAttributes = meter.scope.attributes,
561509
.instrumentKind = instr.*.kind,
562510
.instrumentOptions = instr.*.opts,
563511
.data = agg,
@@ -647,8 +595,8 @@ test "aggregated metrics fetch to owned slice" {
647595
}
648596

649597
try std.testing.expectEqual(1, result.len);
650-
try std.testing.expectEqualStrings(meter.name, result[0].meterName);
651-
try std.testing.expectEqualStrings(meter.schema_url.?, result[0].meterSchemaUrl.?);
598+
try std.testing.expectEqualStrings(meter.scope.name, result[0].meterName);
599+
try std.testing.expectEqualStrings(meter.scope.schema_url.?, result[0].meterSchemaUrl.?);
652600
try std.testing.expectEqualStrings("test-counter", result[0].instrumentOptions.name);
653601
try std.testing.expectEqual(4, result[0].data.int[0].value);
654602
}

src/api/metrics/spec.zig

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ const std = @import("std");
22
const pbcommon = @import("../../opentelemetry/proto/common/v1.pb.zig");
33
const pbutils = @import("../../pbutils.zig");
44

5+
const InstrumentationScope = @import("../../scope.zig").InstrumentationScope;
6+
57
const MeterProvider = @import("meter.zig").MeterProvider;
6-
const MeterOptions = @import("meter.zig").MeterOptions;
78
const InstrumentOptions = @import("instrument.zig").InstrumentOptions;
89

910
/// FormatError is an error type that is used to represent errors in the format of the data.
@@ -140,46 +141,10 @@ test "validate description" {
140141

141142
/// ResourceError indicates that there is a problem in the access of the resoruce.
142143
pub const ResourceError = error{
143-
MeterExistsWithDifferentAttributes,
144144
InstrumentExistsWithSameNameAndIdentifyingFields,
145145
MetricReaderAlreadyAttached,
146146
};
147147

148-
/// Generate an identifier for a meter: an existing meter with same
149-
/// name, version and schemUrl cannot be created again with different attributes.
150-
pub fn meterIdentifier(options: MeterOptions) u64 {
151-
var hash: [2048]u8 = std.mem.zeroes([2048]u8);
152-
var nextInsertIdx: usize = 0;
153-
const keys = [_][]const u8{ options.name, options.version, options.schema_url orelse "" };
154-
for (keys) |k| {
155-
for (k) |b| {
156-
hash[nextInsertIdx] = b;
157-
}
158-
nextInsertIdx += k.len;
159-
}
160-
return std.hash.XxHash3.hash(0, &hash);
161-
}
162-
163-
test "meter identifier" {
164-
const name = "my-meter";
165-
const version = "v1.2.3";
166-
const schema_url = "http://foo.bar";
167-
168-
const id = meterIdentifier(.{ .name = name, .version = version, .schema_url = schema_url });
169-
std.debug.assert(id == 0xf5938ee137020d5e);
170-
}
171-
172-
test "meter identifier changes with different schema url" {
173-
const name = "my-meter";
174-
const version = "v1.2.3";
175-
const schema_url = "http://foo.bar";
176-
const schema_url2 = "http://foo.baz";
177-
178-
const id = meterIdentifier(.{ .name = name, .version = version, .schema_url = schema_url });
179-
const id2 = meterIdentifier(.{ .name = name, .version = version, .schema_url = schema_url2 });
180-
std.debug.assert(id != id2);
181-
}
182-
183148
/// Identify an instrument in a meter by its name, kind, unit and description.
184149
/// Used to recognize duplicate instrument registration as defined in
185150
/// https://opentelemetry.io/docs/specs/otel/metrics/sdk/#duplicate-instrument-registration.

src/sdk/metrics/reader.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub const MetricReader = struct {
8787
var meters = mp.meters.valueIterator();
8888
while (meters.next()) |meter| {
8989
const measurements: []Measurements = AggregatedMetrics.fetch(self.allocator, meter, self.aggregation) catch |err| {
90-
std.debug.print("MetricReader: error aggregating data points from meter {s}: {?}", .{ meter.name, err });
90+
std.debug.print("MetricReader: error aggregating data points from meter {s}: {?}", .{ meter.scope.name, err });
9191
continue;
9292
};
9393
// this makes a copy of the measurements to the array list

0 commit comments

Comments
 (0)