Skip to content
Open
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
6,019 changes: 3,243 additions & 2,776 deletions dist/core.c

Large diffs are not rendered by default.

13 changes: 0 additions & 13 deletions src/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const utils = @import("./utils.zig");
pub const panic = if (builtin.is_test) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.no_panic;
const allocator = if (builtin.is_test) std.testing.allocator else std.heap.c_allocator;

pub const PROTOCOL_VERSION: u64 = 1;

pub export fn instrument_hooks_set_feature(feature: u64, enabled: bool) void {
const feature_enum = @as(features.Feature, @enumFromInt(feature));
features.set_feature(feature_enum, enabled);
Expand All @@ -26,17 +24,6 @@ pub export fn instrument_hooks_init() ?*InstrumentHooks {
return null;
};

if (hooks.* == .perf) {
hooks.perf.send_version(PROTOCOL_VERSION) catch {
utils.print("[ERROR] instrument-hooks: failed to communicate with CodSpeed runner\n", .{});
utils.print("[ERROR] instrument-hooks: please update the CodSpeed action to the latest version\n", .{});

hooks.deinit();
allocator.destroy(hooks);
std.posix.exit(1);
};
}

return hooks;
}

Expand Down
6 changes: 6 additions & 0 deletions src/instruments/analysis.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const shared = @import("../shared.zig");
const fifo_instrument = @import("fifo_instrument.zig");

const AnalysisError = error{ModeError};

pub const AnalysisInstrument = fifo_instrument.FifoInstrument(.Analysis, AnalysisError);
58 changes: 58 additions & 0 deletions src/instruments/fifo_instrument.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const std = @import("std");
const runner_fifo = @import("../runner_fifo.zig");
const shared = @import("../shared.zig");

/// Creates a complete FIFO-based instrument struct that validates a specific integration mode
pub fn FifoInstrument(comptime mode: shared.IntegrationMode, comptime error_type: anytype) type {
return struct {
fifo: runner_fifo.RunnerFifo,

const Self = @This();

pub fn init(allocator: std.mem.Allocator) !Self {
var fifo = try runner_fifo.RunnerFifo.init(allocator);

// Get the instrumentation mode from the runner
const detected_mode = fifo.get_integration_mode() catch |err| {
fifo.deinit();
return err;
};

// Only accept if the runner is in the correct mode
if (detected_mode != mode) {
fifo.deinit();
return error_type.ModeError;
}

return Self{ .fifo = fifo };
}

pub fn deinit(self: *Self) void {
self.fifo.deinit();
}

pub fn send_version(self: *Self, protocol_version: u64) !void {
try self.fifo.send_version(protocol_version);
}

pub fn start_benchmark(self: *Self) !void {
try self.fifo.start_benchmark();
}

pub fn stop_benchmark(self: *Self) !void {
try self.fifo.stop_benchmark();
}

pub fn set_executed_benchmark(self: *Self, pid: u32, uri: [*c]const u8) !void {
try self.fifo.set_executed_benchmark(pid, uri);
}

pub fn set_integration(self: *Self, name: [*c]const u8, version: [*c]const u8) !void {
try self.fifo.set_integration(name, version);
}

pub fn add_marker(self: *Self, pid: u32, marker: shared.MarkerType) !void {
try self.fifo.add_marker(pid, marker);
}
};
}
158 changes: 3 additions & 155 deletions src/instruments/perf.zig
Original file line number Diff line number Diff line change
@@ -1,158 +1,6 @@
const std = @import("std");
const fifo = @import("../fifo.zig");
const shared = @import("../shared.zig");
const fifo_instrument = @import("fifo_instrument.zig");

pub const PerfInstrument = struct {
allocator: std.mem.Allocator,
writer: fifo.UnixPipe.Writer,
reader: fifo.UnixPipe.Reader,
const PerfError = error{ModeError};

const Self = @This();

pub fn init(allocator: std.mem.Allocator) !Self {
return .{
.allocator = allocator,
.writer = try fifo.UnixPipe.openWrite(allocator, shared.RUNNER_CTL_FIFO),
.reader = try fifo.UnixPipe.openRead(allocator, shared.RUNNER_ACK_FIFO),
};
}

pub fn deinit(self: *Self) void {
self.writer.deinit();
self.reader.deinit();
}

pub fn send_cmd(self: *Self, cmd: fifo.Command) !void {
try self.writer.sendCmd(cmd);
try self.reader.waitForAck(null);
}

pub fn is_instrumented(self: *Self) bool {
self.send_cmd(fifo.Command.PingPerf) catch {
return false;
};

return true;
}

pub noinline fn start_benchmark(self: *Self) !void {
@branchHint(.cold); // Prevent inline

try self.writer.sendCmd(fifo.Command.StartBenchmark);
try self.reader.waitForAck(null);
}

pub noinline fn stop_benchmark(self: *Self) !void {
@branchHint(.cold); // Prevent inline

try self.writer.sendCmd(fifo.Command.StopBenchmark);
try self.reader.waitForAck(null);
}

pub fn set_executed_benchmark(self: *Self, pid: u32, uri: [*c]const u8) !void {
try self.writer.sendCmd(fifo.Command{ .ExecutedBenchmark = .{
.pid = pid,
.uri = std.mem.span(uri),
} });
try self.reader.waitForAck(null);
}

pub fn set_integration(self: *Self, name: [*c]const u8, version: [*c]const u8) !void {
try self.writer.sendCmd(fifo.Command{ .SetIntegration = .{
.name = std.mem.span(name),
.version = std.mem.span(version),
} });
try self.reader.waitForAck(null);
}

pub fn add_marker(self: *Self, pid: u32, marker: shared.MarkerType) !void {
try self.writer.sendCmd(fifo.Command{ .AddMarker = .{
.pid = pid,
.marker = marker,
} });
try self.reader.waitForAck(null);
}

pub fn send_version(self: *Self, protocol_version: u64) !void {
try self.writer.sendCmd(fifo.Command{ .SetVersion = protocol_version });
try self.reader.waitForAck(null);
}
};

test "perf integration" {
const allocator = std.testing.allocator;

try fifo.UnixPipe.create(shared.RUNNER_ACK_FIFO);
try fifo.UnixPipe.create(shared.RUNNER_CTL_FIFO);

var ctl_fifo = try fifo.UnixPipe.openRead(allocator, shared.RUNNER_CTL_FIFO);
defer ctl_fifo.deinit();

var ack_fifo = try fifo.UnixPipe.openWrite(allocator, shared.RUNNER_ACK_FIFO);
defer ack_fifo.deinit();

const FifoTester = struct {
allocator: std.mem.Allocator,
ctl_pipe: *fifo.UnixPipe.Reader,
ack_pipe: *fifo.UnixPipe.Writer,

received_cmd: ?fifo.Command = null,
error_occurred: bool = false,

pub fn func(ctx: *@This()) void {
const received_cmd = ctx.ctl_pipe.recvCmd() catch |err| {
std.debug.print("Failed to receive command: {}\n", .{err});
ctx.error_occurred = true;
return;
};
ctx.received_cmd = received_cmd;

ctx.ack_pipe.sendCmd(fifo.Command.Ack) catch |err| {
std.debug.print("Failed to send ACK: {}\n", .{err});
ctx.error_occurred = true;
};
}

pub fn send(self: *@This(), comptime f: anytype, args: anytype) !fifo.Command {
// 1. Create the thread which handles the command
// 2. Execute the callback
// 3. Wait for the thread to finish
//
const receiver_thread = try std.Thread.spawn(.{}, @This().func, .{self});
try @call(.auto, f, args);
receiver_thread.join();

if (self.error_occurred) {
return error.IntegrationError;
}
self.error_occurred = false;

return self.received_cmd.?;
}
};

var tester = FifoTester{
.allocator = allocator,
.ctl_pipe = &ctl_fifo,
.ack_pipe = &ack_fifo,
};

var perf = try PerfInstrument.init(allocator);
defer perf.deinit();

const si_result = try tester.send(PerfInstrument.set_integration, .{ &perf, "zig", "0.10.0" });
try std.testing.expect(si_result.equal(fifo.Command{ .SetIntegration = .{ .name = "zig", .version = "0.10.0" } }));
si_result.deinit(allocator);

const cb_result = try tester.send(PerfInstrument.set_executed_benchmark, .{ &perf, 42, "foo" });
try std.testing.expect(cb_result.equal(fifo.Command{ .ExecutedBenchmark = .{ .pid = 42, .uri = "foo" } }));
cb_result.deinit(allocator);

const start_result = try tester.send(PerfInstrument.start_benchmark, .{&perf});
try std.testing.expect(start_result.equal(fifo.Command.StartBenchmark));
start_result.deinit(allocator);

const stop_result = try tester.send(PerfInstrument.stop_benchmark, .{&perf});
try std.testing.expect(stop_result.equal(fifo.Command.StopBenchmark));
stop_result.deinit(allocator);
}
pub const PerfInstrument = fifo_instrument.FifoInstrument(.Perf, PerfError);
39 changes: 25 additions & 14 deletions src/instruments/root.zig
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
const std = @import("std");
const builtin = @import("builtin");
const perf = @import("perf.zig");
const analysis = @import("analysis.zig");
const valgrind = @import("valgrind.zig");
const shared = @import("../shared.zig");
const ValgrindInstrument = valgrind.ValgrindInstrument;
const PerfInstrument = perf.PerfInstrument;
const AnalysisInstrument = analysis.AnalysisInstrument;

pub const InstrumentHooks = union(enum) {
valgrind: ValgrindInstrument,
perf: perf.PerfInstrument,
perf: PerfInstrument,
analysis: AnalysisInstrument,
none: void,

const Self = @This();

pub fn init(allocator: std.mem.Allocator) !Self {
if (ValgrindInstrument.is_instrumented()) {
return Self{ .valgrind = ValgrindInstrument.init(allocator) };
}
if (ValgrindInstrument.init(allocator)) |valgrind_inst| {
return Self{ .valgrind = valgrind_inst };
} else |_| {}

var perf_inst = perf.PerfInstrument.init(allocator) catch {
return Self{ .none = {} };
};
if (perf_inst.is_instrumented()) {
if (AnalysisInstrument.init(allocator)) |analysis_inst| {
return Self{ .analysis = analysis_inst };
} else |_| {}

if (PerfInstrument.init(allocator)) |perf_inst| {
return Self{ .perf = perf_inst };
}
} else |_| {}

return Self{ .none = {} };
}
Expand All @@ -31,17 +35,16 @@ pub const InstrumentHooks = union(enum) {
switch (self.*) {
.valgrind => {},
.perf => self.perf.deinit(),
.analysis => self.analysis.deinit(),
.none => {},
}
}

pub inline fn is_instrumented(self: *Self) bool {
return switch (self.*) {
.valgrind => ValgrindInstrument.is_instrumented(),
.perf => |perf_inst| {
var mutable_perf = perf_inst;
return mutable_perf.is_instrumented();
},
.perf => true,
.analysis => true,
.none => false,
};
}
Expand All @@ -51,6 +54,8 @@ pub const InstrumentHooks = union(enum) {
return self.perf.start_benchmark();
} else if (self.* == .valgrind) {
return ValgrindInstrument.start_benchmark();
} else if (self.* == .analysis) {
return self.analysis.start_benchmark();
}
}

Expand All @@ -59,13 +64,16 @@ pub const InstrumentHooks = union(enum) {
return ValgrindInstrument.stop_benchmark();
} else if (self.* == .perf) {
return self.perf.stop_benchmark();
} else if (self.* == .analysis) {
return self.analysis.stop_benchmark();
}
}

pub inline fn set_executed_benchmark(self: *Self, pid: u32, uri: [*c]const u8) !void {
switch (self.*) {
.valgrind => ValgrindInstrument.set_executed_benchmark(pid, uri),
.perf => try self.perf.set_executed_benchmark(pid, uri),
.analysis => try self.analysis.set_executed_benchmark(pid, uri),
.none => {},
}
}
Expand All @@ -74,13 +82,16 @@ pub const InstrumentHooks = union(enum) {
switch (self.*) {
.valgrind => try self.valgrind.set_integration(name, version),
.perf => try self.perf.set_integration(name, version),
.analysis => try self.analysis.set_integration(name, version),
.none => {},
}
}

pub inline fn add_marker(self: *Self, pid: u32, marker: shared.MarkerType) !void {
if (self.* == .perf) {
return self.perf.add_marker(pid, marker);
} else if (self.* == .analysis) {
return self.analysis.add_marker(pid, marker);
}
}
};
6 changes: 5 additions & 1 deletion src/instruments/valgrind.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ pub const ValgrindInstrument = struct {
allocator: std.mem.Allocator,
const Self = @This();

pub fn init(allocator: std.mem.Allocator) Self {
pub fn init(allocator: std.mem.Allocator) !Self {
if (!ValgrindInstrument.is_instrumented()) {
return error.NotInstrumented;
}

return Self{
.allocator = allocator,
};
Expand Down
Loading