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 }