lib/sys/src/properties/perf.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const hypothesis = @import("hypothesis");
4 const sys = @import("sys");
5
6 const perf = sys.perf;
7 const native_endian = builtin.target.cpu.arch.endian();
8
9 const property_max_callchain = 4;
10 const property_max_raw = 16;
11 const property_max_record = 192;
12
13 const PropertyGeneratedRecord = struct {
14 config: perf.Config,
15 bytes: [property_max_record]u8,
16 len: usize,
17 ip: ?u64 = null,
18 pid: ?u32 = null,
19 tid: ?u32 = null,
20 time: ?u64 = null,
21 cpu: ?u32 = null,
22 period: ?u64 = null,
23 callchain: [property_max_callchain]u64 = undefined,
24 callchain_len: usize = 0,
25 raw: [property_max_raw]u8 = undefined,
26 raw_len: usize = 0,
27
28 fn slice(self: *const PropertyGeneratedRecord) []const u8 {
29 return self.bytes[0..self.len];
30 }
31 };
32
33 fn sampleBit(field: perf.SampleField) u64 {
34 return @backingInt(field);
35 }
36
37 pub fn settings() hypothesis.Settings {
38 var out = hypothesis.Settings.dev()
39 .withSeed(0x5359_5350_4552_4650)
40 .withDatabase("zig-out/hypothesis-failures/sys");
41 out.max_examples = 128;
42 out.target_examples = 128;
43 return out;
44 }
45
46 fn drawPropertyUsize(data: *hypothesis.ConjectureData, min: usize, max: usize, shrink_towards: usize) !usize {
47 return @intCast(try data.drawInteger(
48 @intCast(min),
49 @intCast(max),
50 @intCast(shrink_towards),
51 ));
52 }
53
54 fn drawPropertyU32(data: *hypothesis.ConjectureData) !u32 {
55 return @intCast(try data.drawInteger(0, std.math.maxInt(u32), 0));
56 }
57
58 fn drawPropertyU8(data: *hypothesis.ConjectureData) !u8 {
59 return @intCast(try data.drawInteger(0, std.math.maxInt(u8), 0));
60 }
61
62 fn drawPropertyU64(data: *hypothesis.ConjectureData) !u64 {
63 if (try data.drawBoolean()) {
64 const edge_values = [_]u64{
65 0,
66 1,
67 2,
68 7,
69 31,
70 std.math.maxInt(u32),
71 @as(u64, @intCast(std.math.maxInt(i64))),
72 @as(u64, @intCast(std.math.maxInt(i64))) + 1,
73 std.math.maxInt(u64),
74 };
75 return edge_values[try drawPropertyUsize(data, 0, edge_values.len - 1, 0)];
76 }
77 return try data.drawInteger(0, std.math.maxInt(u64), 0);
78 }
79
80 fn enablePropertyField(config: *perf.Config, field: perf.SampleField) void {
81 config.sample_type |= sampleBit(field);
82 }
83
84 fn propertyFieldEnabled(field_bits: u8, bit: u3) bool {
85 return field_bits & (@as(u8, 1) << bit) != 0;
86 }
87
88 fn writeAt(comptime T: type, bytes: []u8, offset: *usize, value: T) void {
89 std.mem.writeInt(T, bytes[offset.*..][0..@sizeOf(T)], value, native_endian);
90 offset.* += @sizeOf(T);
91 }
92
93 fn finishSample(bytes: []u8, len: usize) []const u8 {
94 std.mem.writeInt(u32, bytes[0..4], @backingInt(perf.RecordType.sample), native_endian);
95 std.mem.writeInt(u16, bytes[4..6], 0, native_endian);
96 std.mem.writeInt(u16, bytes[6..8], @intCast(len), native_endian);
97 return bytes[0..len];
98 }
99
100 fn writeRing(data: []u8, index: usize, bytes: []const u8) void {
101 for (bytes, 0..) |byte, offset| {
102 data[(index + offset) % data.len] = byte;
103 }
104 }
105
106 fn generatePropertyRecord(data: *hypothesis.ConjectureData, field_bits: u8) !PropertyGeneratedRecord {
107 var generated: PropertyGeneratedRecord = .{
108 .config = .{},
109 .bytes = undefined,
110 .len = perf.header_size,
111 };
112 @memset(&generated.bytes, 0);
113
114 if (propertyFieldEnabled(field_bits, 0)) {
115 enablePropertyField(&generated.config, .ip);
116 generated.ip = try drawPropertyU64(data);
117 writeAt(u64, &generated.bytes, &generated.len, generated.ip.?);
118 }
119 if (propertyFieldEnabled(field_bits, 1)) {
120 enablePropertyField(&generated.config, .pid_tid);
121 generated.pid = try drawPropertyU32(data);
122 generated.tid = try drawPropertyU32(data);
123 writeAt(u32, &generated.bytes, &generated.len, generated.pid.?);
124 writeAt(u32, &generated.bytes, &generated.len, generated.tid.?);
125 }
126 if (propertyFieldEnabled(field_bits, 2)) {
127 enablePropertyField(&generated.config, .time);
128 generated.time = try drawPropertyU64(data);
129 writeAt(u64, &generated.bytes, &generated.len, generated.time.?);
130 }
131 if (propertyFieldEnabled(field_bits, 3)) {
132 enablePropertyField(&generated.config, .cpu);
133 generated.cpu = try drawPropertyU32(data);
134 writeAt(u32, &generated.bytes, &generated.len, generated.cpu.?);
135 writeAt(u32, &generated.bytes, &generated.len, 0);
136 }
137 if (propertyFieldEnabled(field_bits, 4)) {
138 enablePropertyField(&generated.config, .period);
139 generated.period = try drawPropertyU64(data);
140 writeAt(u64, &generated.bytes, &generated.len, generated.period.?);
141 }
142 if (propertyFieldEnabled(field_bits, 5)) {
143 enablePropertyField(&generated.config, .callchain);
144 generated.callchain_len = try drawPropertyUsize(data, 0, property_max_callchain, 0);
145 writeAt(u64, &generated.bytes, &generated.len, @intCast(generated.callchain_len));
146 for (generated.callchain[0..generated.callchain_len]) |*address| {
147 address.* = try drawPropertyU64(data);
148 writeAt(u64, &generated.bytes, &generated.len, address.*);
149 }
150 }
151 if (propertyFieldEnabled(field_bits, 6)) {
152 enablePropertyField(&generated.config, .raw);
153 generated.raw_len = try drawPropertyUsize(data, 0, property_max_raw, 0);
154 writeAt(u32, &generated.bytes, &generated.len, @intCast(generated.raw_len));
155 for (generated.raw[0..generated.raw_len]) |*byte| {
156 byte.* = try drawPropertyU8(data);
157 generated.bytes[generated.len] = byte.*;
158 generated.len += 1;
159 }
160 }
161
162 _ = finishSample(&generated.bytes, generated.len);
163 return generated;
164 }
165
166 fn expectPropertyOptionalU64(expected: ?u64, actual: anyerror!u64) !void {
167 if (expected) |value| {
168 try std.testing.expectEqual(value, try actual);
169 } else {
170 try std.testing.expectError(error.FieldNotSampled, actual);
171 }
172 }
173
174 fn expectPropertyOptionalU32(expected: ?u32, actual: anyerror!u32) !void {
175 if (expected) |value| {
176 try std.testing.expectEqual(value, try actual);
177 } else {
178 try std.testing.expectError(error.FieldNotSampled, actual);
179 }
180 }
181
182 fn expectPropertyRecord(generated: *const PropertyGeneratedRecord, sample: perf.Record) !void {
183 try std.testing.expect(sample.isSample());
184 try std.testing.expectEqual(perf.RecordType.sample, sample.recordType());
185 try std.testing.expectEqual(generated.len, try sample.payloadEndOffset());
186 try expectPropertyOptionalU64(generated.ip, sample.getIp());
187 try expectPropertyOptionalU32(generated.pid, sample.getPid());
188 try expectPropertyOptionalU32(generated.tid, sample.getTid());
189 try expectPropertyOptionalU64(generated.time, sample.getTime());
190 try expectPropertyOptionalU32(generated.cpu, sample.getCpu());
191 try expectPropertyOptionalU64(generated.period, sample.getPeriod());
192
193 if (generated.config.isSampling(.callchain)) {
194 const callchain = try sample.getCallchain();
195 try std.testing.expectEqual(generated.callchain_len, callchain.len());
196 for (generated.callchain[0..generated.callchain_len], 0..) |address, index| {
197 try std.testing.expectEqual(address, callchain.at(index));
198 }
199 } else {
200 try std.testing.expectError(error.FieldNotSampled, sample.getCallchain());
201 }
202
203 if (generated.config.isSampling(.raw)) {
204 const raw = try sample.getRaw();
205 try std.testing.expectEqualSlices(u8, generated.raw[0..generated.raw_len], raw);
206 } else {
207 try std.testing.expectError(error.FieldNotSampled, sample.getRaw());
208 }
209 }
210
211 fn checkPropertyRecordReplayCase(data: *hypothesis.ConjectureData, field_bits: u8, tail: usize) !void {
212 const generated = try generatePropertyRecord(data, field_bits);
213 const sample = try perf.Record.init(generated.config, generated.slice());
214 try expectPropertyRecord(&generated, sample);
215
216 var ring: [property_max_record]u8 = undefined;
217 @memset(&ring, 0);
218 writeRing(&ring, tail, generated.slice());
219
220 var reader = perf.RingReader.init(
221 generated.config,
222 &ring,
223 @intCast(tail),
224 @as(u64, @intCast(tail)) + generated.len,
225 );
226 try std.testing.expect(try reader.hasData());
227
228 var scratch: [property_max_record]u8 = undefined;
229 const parsed = (try reader.next(&scratch)) orelse return error.MissingRingRecord;
230 try expectPropertyRecord(&generated, parsed);
231 try std.testing.expectEqual(@as(u64, @intCast(tail)) + generated.len, reader.index);
232 try std.testing.expectEqual(@as(?perf.Record, null), try reader.next(&scratch));
233 }
234
235 pub const SampleReplayProperty = struct {
236 pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
237 try checkPropertyRecordReplayCase(data, 0, 0);
238 try checkPropertyRecordReplayCase(data, 0, property_max_record - 3);
239
240 const field_bits: u8 = @intCast(try drawPropertyUsize(data, 0, 0x7f, 0));
241 const tail = try drawPropertyUsize(data, 0, property_max_record - 1, 0);
242 try checkPropertyRecordReplayCase(data, field_bits, tail);
243 }
244 };
245
246 test "property: perf sample records survive parser and ring replay" {
247 try hypothesis.checkNamed(SampleReplayProperty, "sys-perf-sample-record-replay", settings());
248 }