Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions opentelemetry-sdk/src/api/metrics/measurement.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ pub const Measurements = struct {

data: MeasurementsData,

resource: ?[]const Attribute = null,

pub fn deinit(self: *Measurements, allocator: std.mem.Allocator) void {
switch (self.data) {
inline else => |list| {
Expand Down
1 change: 1 addition & 0 deletions opentelemetry-sdk/src/api/trace/span.zig
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub const Span = struct {
is_recording: bool,
allocator: std.mem.Allocator,
scope: InstrumentationScope,
resource: ?[]const attribute.Attribute = null,

const Self = @This();

Expand Down
53 changes: 52 additions & 1 deletion opentelemetry-sdk/src/sdk/metrics/exporters/otlp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const view = @import("../view.zig");

const protobuf = @import("protobuf");
const pbcommon = @import("opentelemetry-proto").common_v1;
const pbresource = @import("opentelemetry-proto").resource_v1;
const pbmetrics = @import("opentelemetry-proto").metrics_v1;
const pbcollector_metrics = @import("opentelemetry-proto").collector_metrics_v1;

Expand Down Expand Up @@ -107,8 +108,17 @@ pub const OTLPExporter = struct {
.metrics = metrics,
};
}
const resource_attributes = try resourceToProtobuf(
self.allocator,
if (data.len > 0) data[0].resource else null,
);

resource_metrics[0] = pbmetrics.ResourceMetrics{
.resource = null, //FIXME support resource attributes
.resource = pbresource.Resource{
.attributes = resource_attributes,
.dropped_attributes_count = 0,
.entity_refs = std.ArrayList(pbcommon.EntityRef).empty,
},
.scope_metrics = std.ArrayList(pbmetrics.ScopeMetrics).fromOwnedSlice(scope_metrics),
.schema_url = "",
};
Expand Down Expand Up @@ -204,6 +214,17 @@ fn attributeToProtobuf(allocator: std.mem.Allocator, attribute: Attribute) !pbco
};
}

fn resourceToProtobuf(allocator: std.mem.Allocator, resource: ?[]const Attribute) !std.ArrayList(pbcommon.KeyValue) {
var kvs = std.ArrayList(pbcommon.KeyValue).empty;
if (resource) |attrs| {
try kvs.ensureTotalCapacityPrecise(allocator, attrs.len);
for (attrs) |attr| {
kvs.appendAssumeCapacity(try attributeToProtobuf(allocator, attr));
}
}
return kvs;
Comment on lines +219 to +225

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍🏼

}

pub fn attributesToProtobufKeyValueList(allocator: std.mem.Allocator, attributes: ?[]Attribute) !pbcommon.KeyValueList {
if (attributes) |attrs| {
var kvs: pbcommon.KeyValueList = .{ .values = try .initCapacity(allocator, attrs.len) };
Expand Down Expand Up @@ -484,3 +505,33 @@ test "exporters/otlp init/deinit" {
var exporter = try OTLPExporter.init(allocator, io, view.DefaultTemporality, config);
defer exporter.deinit();
}

test "exporters/otlp resource conversion" {
const allocator = std.testing.allocator;

const resource = [_]Attribute{
.{ .key = "service.name", .value = .{ .string = "test-service" } },
.{ .key = "service.version", .value = .{ .string = "1.2.3" } },
};

var kvs = try resourceToProtobuf(allocator, &resource);
defer {
for (kvs.items) |*kv| kv.deinit(allocator);
kvs.deinit(allocator);
}

try std.testing.expectEqual(@as(usize, 2), kvs.items.len);
try std.testing.expectEqualStrings("service.name", kvs.items[0].key);
try std.testing.expectEqualStrings("test-service", kvs.items[0].value.?.value.?.string_value);
try std.testing.expectEqualStrings("service.version", kvs.items[1].key);
try std.testing.expectEqualStrings("1.2.3", kvs.items[1].value.?.value.?.string_value);
}

test "exporters/otlp resource conversion without a resource" {
const allocator = std.testing.allocator;

var kvs = try resourceToProtobuf(allocator, null);
defer kvs.deinit(allocator);

try std.testing.expectEqual(@as(usize, 0), kvs.items.len);
}
1 change: 1 addition & 0 deletions opentelemetry-sdk/src/sdk/metrics/reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub const MetricReader = struct {

for (measurements) |*m| {
try self.temporal_aggregation.process(m, self.temporality);
m.resource = mp.resource;
}

// The exporter takes ownership of the data points, which are deinitialized
Expand Down
88 changes: 87 additions & 1 deletion opentelemetry-sdk/src/sdk/trace/exporters/otlp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,19 @@ pub const OTLPExporter = struct {
try scope_spans_list.append(self.allocator, scope_span);
}

var resource_attributes = std.ArrayList(pbcommon.KeyValue).empty;
if (spans.len > 0) {
if (spans[0].resource) |attrs| {
try resource_attributes.ensureTotalCapacityPrecise(self.allocator, attrs.len);
for (attrs) |attr| {
resource_attributes.appendAssumeCapacity(try attributeToOTLP(attr.key, attr.value));
}
}
}

const resource_span = pbtrace.ResourceSpans{
.resource = pbresource.Resource{
.attributes = std.ArrayList(pbcommon.KeyValue).empty,
.attributes = resource_attributes,
.dropped_attributes_count = 0,
.entity_refs = std.ArrayList(pbcommon.EntityRef).empty,
},
Expand Down Expand Up @@ -462,3 +472,79 @@ test "OTLPExporter basic functionality" {
// We expect a connection error since there's no OTLP server running
try std.testing.expectError(error.ConnectionRefused, result);
}

test "span resource is exported" {
const allocator = std.testing.allocator;
const io = std.testing.io;

var env_map = std.process.Environ.Map.init(allocator);
defer env_map.deinit();

var config = try otlp.ConfigOptions.init(allocator, &env_map);
defer config.deinit();

var exporter = try OTLPExporter.init(allocator, io, config);
defer exporter.deinit();

const resource = [_]attribute.Attribute{
.{ .key = "service.name", .value = .{ .string = "test-service" } },
.{ .key = "service.version", .value = .{ .string = "1.2.3" } },
};

const trace_id = trace.TraceID.init([16]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 });
const span_id = trace.SpanID.init([8]u8{ 1, 2, 3, 4, 5, 6, 7, 8 });
var trace_state = trace.TraceState.init(allocator);
defer trace_state.deinit();

const span_context = trace.SpanContext.init(trace_id, span_id, trace.TraceFlags.default(), trace_state, false);
const scope = InstrumentationScope{ .name = "test-lib", .version = "1.0.0" };
var test_span = trace.Span.init(allocator, span_context, "test-span", .Internal, scope);
test_span.resource = &resource;
defer test_span.deinit();

var spans = [_]trace.Span{test_span};

var request = try exporter.spansToOTLPRequest(spans[0..]);
defer exporter.cleanupRequest(&request);

try std.testing.expectEqual(@as(usize, 1), request.resource_spans.items.len);

const exported = request.resource_spans.items[0].resource.?;
try std.testing.expectEqual(@as(usize, 2), exported.attributes.items.len);
try std.testing.expectEqualStrings("service.name", exported.attributes.items[0].key);
try std.testing.expectEqualStrings("test-service", exported.attributes.items[0].value.?.value.?.string_value);
try std.testing.expectEqualStrings("service.version", exported.attributes.items[1].key);
try std.testing.expectEqualStrings("1.2.3", exported.attributes.items[1].value.?.value.?.string_value);
}

test "spans without a resource export no resource attributes" {
const allocator = std.testing.allocator;
const io = std.testing.io;

var env_map = std.process.Environ.Map.init(allocator);
defer env_map.deinit();

var config = try otlp.ConfigOptions.init(allocator, &env_map);
defer config.deinit();

var exporter = try OTLPExporter.init(allocator, io, config);
defer exporter.deinit();

const trace_id = trace.TraceID.init([16]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 });
const span_id = trace.SpanID.init([8]u8{ 1, 2, 3, 4, 5, 6, 7, 8 });
var trace_state = trace.TraceState.init(allocator);
defer trace_state.deinit();

const span_context = trace.SpanContext.init(trace_id, span_id, trace.TraceFlags.default(), trace_state, false);
const scope = InstrumentationScope{ .name = "test-lib", .version = "1.0.0" };
var test_span = trace.Span.init(allocator, span_context, "test-span", .Internal, scope);
defer test_span.deinit();

var spans = [_]trace.Span{test_span};

var request = try exporter.spansToOTLPRequest(spans[0..]);
defer exporter.cleanupRequest(&request);

const exported = request.resource_spans.items[0].resource.?;
try std.testing.expectEqual(@as(usize, 0), exported.attributes.items.len);
}
1 change: 1 addition & 0 deletions opentelemetry-sdk/src/sdk/trace/provider.zig
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ pub const Tracer = struct {
// Create the span with instrumentation scope
var span = trace_api.Span.init(allocator, span_context, span_name, options.kind, self.scope);
span.is_recording = true; // SDK spans are recording by default
span.resource = self.provider.resource;

// Set attributes if provided
if (options.attributes) |attrs| {
Expand Down
Loading