blob: c5c0c5c54976b6a0f2c591cf816b7fbaeeb06933 [file] [log] [blame] [edit]
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// This library file contains data structures and helper methods for parsing
/// `perf.data` files produced by `perf` tool on Linux.
///
/// Format of this file is documented in:
///
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/Documentation/perf.data-file-format.txt
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h
///
library;
import 'dart:ffi';
import 'dart:io';
import 'dart:math' as math;
import 'dart:typed_data';
/// `struct perf_header`: header of the `perf.data` file.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L64
final class Header extends Struct {
@Array(8)
external Array<Uint8> magic;
@Uint64()
external int size;
@Uint64()
external int attrSize;
external FileSection attrs;
external FileSection data;
external FileSection eventTypes;
@Uint64()
external int flags;
@Array(3)
external Array<Uint64> flags1;
}
/// `struct perf_file_section`: section inside `perf.data` file.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L59
final class FileSection extends Struct {
@Uint64()
external int offset;
@Uint64()
external int size;
@override
String toString() {
return 'PerfFileSection{offset=$offset,size=$size}';
}
}
/// Optional sections inside `perf.data` file.
///
/// The section is present iff corresponding bit in [PerfHeader.flags] is set.
///
/// `PerfFileSection` descriptors for present sections will follow in sequence
/// immediately after the data section (i.e. the first `PerfFileSection` will
/// be located at `header.data.offset + header.data.size` offset).
///
/// See [PerfData.readOptionalSectionHeaders].
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L15
enum OptionalSection {
reserved,
tracingData,
buildId,
hostname,
osRelease,
version,
arch,
nrCpus,
cpuDesc,
cpuId,
totalMem,
cmdLine,
eventDesc,
cpuTopology,
numaTopology,
branchStack,
groupDesc,
auxTrace,
stat,
cache,
sampleTime,
sampleTopology,
clockId,
dirFormat,
bpfProgInfo,
bpfBtf,
compressed,
cpuPmuCaps,
clockData,
hybridTopology,
pmuCaps
}
/// `perf_event_header`: common header of all event entries.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L815
final class EventHeader extends Struct {
@Uint32()
external int type;
@Uint16()
external int misc;
@Uint16()
external int size;
@override
String toString() => 'PerfEventHeader{type=$type,misc=$misc,size=$size}';
}
/// `perf_event_attr`: configuration of the event monitored by `perf`.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L389
final class EventAttr extends Struct {
/// Major type: hardware/software/tracepoint/etc.
///
/// See [EventType].
@Uint32()
external int type;
@Uint32()
external int size;
/// Type specific configuration information.
@Uint64()
external int config;
@Uint64()
external int samplePeriodOrFreq;
@Uint64()
external int sampleType;
@Uint64()
external int readFormat;
/// Various bit fields which we currently don't care about.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L414
@Uint64()
external int flags;
@Uint32()
external int wakeupEventOrWatermark;
@Uint32()
external int bpType;
/// Union of `bp_addr`/`kprobe_func`/`uprobe_path`/`config1`
@Uint64()
external int config1;
/// Union of `bp_len`/`kprobe_addr`/`probe_offset`/`config2`
@Uint64()
external int config2;
/// One of `enum perf_branch_sample_type`
@Uint64()
external int branchSampleType;
/// Defines set of user regs to dump on samples.
/// See asm/perf_regs.h for details.
@Uint64()
external int sampleRegsUser;
/// Defines size of the user stack to dump on samples.
@Uint32()
external int sampleStackUser;
@Int32()
external int clockid;
/// Defines set of regs to dump for each sample
/// state captured on:
/// - precise = 0: PMU interrupt
/// - precise > 0: sampled instruction
///
/// See asm/perf_regs.h for details.
@Uint64()
external int sampleRegsIntr;
/// Wakeup watermark for AUX area
@Uint32()
external int auxWatermark;
@Uint16()
external int sampleMaxStack;
@Uint16()
external int reserved2;
@Uint32()
external int auxSampleSize;
@Uint32()
external int reserved3;
/// User provided data if sigtrap=1, passed back to user via
/// siginfo_t::si_perf_data, e.g. to permit user to identify the event.
/// Note, siginfo_t::si_perf_data is long-sized, and sig_data will be
/// truncated accordingly on 32 bit architectures.
@Uint64()
external int sigData;
/// Extension of config2
@Uint64()
external int config3;
}
/// `enum perf_event_type`: type of the recorded event.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L838
extension type const EventType(int _) implements int {
/// `PERF_RECORD_MMAP`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L879
static const mmap = EventType(1);
/// `PERF_RECORD_SAMPLE`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L947
static const sample = EventType(9);
/// `PERF_RECORD_MMAP2`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L1035
static const mmap2 = EventType(10);
}
/// `enum perf_type_id`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L29
extension type const TypeId(int index) implements int {
static const hardware = TypeId(0);
static const software = TypeId(1);
static const tracepoint = TypeId(2);
static const hwCache = TypeId(3);
static const raw = TypeId(4);
static const breakpoint = TypeId(5);
}
/// `enum perf_event_sample_format`: additional information recorded for sample.
///
/// Bits that can be set in [PerfEventAttr.sampleType] to request information
/// in the overflow packets.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L139
extension type const SampleFormat(int bit) implements int {
/// `PERF_SAMPLE_IP`
static const ip = SampleFormat(1 << 0);
/// `PERF_SAMPLE_TID`
static const tid = SampleFormat(1 << 1);
/// `PERF_SAMPLE_TIME`
static const time = SampleFormat(1 << 2);
/// `PERF_SAMPLE_ADDR`
static const addr = SampleFormat(1 << 3);
/// `PERF_SAMPLE_READ`
static const read = SampleFormat(1 << 4);
/// `PERF_SAMPLE_CALLCHAIN`
static const callchain = SampleFormat(1 << 5);
/// `PERF_SAMPLE_ID`
static const id = SampleFormat(1 << 6);
/// `PERF_SAMPLE_CPU`
static const cpu = SampleFormat(1 << 7);
/// `PERF_SAMPLE_PERIOD`
static const period = SampleFormat(1 << 8);
/// `PERF_SAMPLE_STREAM_ID`
static const streamId = SampleFormat(1 << 9);
/// `PERF_SAMPLE_RAW`
static const raw = SampleFormat(1 << 10);
/// `PERF_SAMPLE_BRANCH_STACK`
static const branchStack = SampleFormat(1 << 11);
/// `PERF_SAMPLE_REGS_USER`
static const regsUser = SampleFormat(1 << 12);
/// `PERF_SAMPLE_STACK_USER`
static const stackUser = SampleFormat(1 << 13);
/// `PERF_SAMPLE_WEIGHT`
static const weight = SampleFormat(1 << 14);
/// `PERF_SAMPLE_DATA_SRC`
static const dataSrc = SampleFormat(1 << 15);
/// `PERF_SAMPLE_IDENTIFIER`
static const identifier = SampleFormat(1 << 16);
/// `PERF_SAMPLE_TRANSACTION`
static const transaction = SampleFormat(1 << 17);
/// `PERF_SAMPLE_REGS_INTR`
static const regsIntr = SampleFormat(1 << 18);
/// `PERF_SAMPLE_PHYS_ADDR`
static const physAddr = SampleFormat(1 << 19);
/// `PERF_SAMPLE_AUX`
static const aux = SampleFormat(1 << 20);
/// `PERF_SAMPLE_CGROUP`
static const cgroup = SampleFormat(1 << 21);
/// `PERF_SAMPLE_DATA_PAGE_SIZE`
static const dataPageSize = SampleFormat(1 << 22);
/// `PERF_SAMPLE_CODE_PAGE_SIZE`
static const codePageSize = SampleFormat(1 << 23);
/// `PERF_SAMPLE_WEIGHT_STRUCT`
static const weightStruct = SampleFormat(1 << 24);
static const bitNames = {
ip: "ip",
tid: "tid",
time: "time",
addr: "addr",
read: "read",
callchain: "callchain",
id: "id",
cpu: "cpu",
period: "period",
streamId: "streamId",
raw: "raw",
branchStack: "branchStack",
regsUser: "regsUser",
stackUser: "stackUser",
weight: "weight",
dataSrc: "dataSrc",
identifier: "identifier",
transaction: "transaction",
regsIntr: "regsIntr",
physAddr: "physAddr",
aux: "aux",
cgroup: "cgroup",
dataPageSize: "dataPageSize",
codePageSize: "codePageSize",
weightStruct: "weightStruct",
};
static String format(int mask) {
return SampleFormat.bitNames.entries
.where((e) => (mask & e.key) != 0)
.map((e) => e.value)
.join('|');
}
}
/// `PERF_RECORD_MMAP`
///
/// The `MMAP` events record the `PROT_EXEC` mappings so that we can
/// correlate userspace `IP`s to code.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L879
final class MmapEvent extends Struct {
external EventHeader header;
@Uint32()
external int pid;
@Uint32()
external int tid;
@Uint64()
external int addr;
@Uint64()
external int len;
@Uint64()
external int pgoffs;
@Array.variable()
external Array<Uint8> filename;
@override
String toString() => 'MmapEvent{addr=${addr.formatAsAddress()},'
'len=$len,pgoffs=$pgoffs,'
'filename=${filename.toStringFromZeroTerminated()}}';
}
final class BuildId extends Struct {
@Uint8()
external int size;
@Uint8()
external int reserved1;
@Uint16()
external int reserved2;
@Array(20)
external Array<Uint8> buildId;
}
final class Ino extends Struct {
@Uint32()
external int maj;
@Uint32()
external int min;
@Uint64()
external int ino;
@Uint64()
external int inoGeneration;
}
final class BuildIdOrIno extends Union {
external BuildId buildId;
external Ino ino;
}
/// `PERF_RECORD_MMAP2`
///
/// The `MMAP2` records are an augmented version of `MMAP` (see [MapEvent]),
/// they add `maj`, `min`, `ino` numbers to be used to uniquely identify each
/// mapping.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L1035
final class Mmap2Event extends Struct {
external EventHeader header;
@Uint32()
external int pid;
@Uint32()
external int tid;
@Uint64()
external int addr;
@Uint64()
external int len;
@Uint64()
external int pgoffs;
external BuildIdOrIno buildIdOrIno;
@Uint32()
external int prot;
@Uint32()
external int flags;
@Array.variable()
external Array<Uint8> filename;
@override
String toString() => 'Mmap2Event{addr=${addr.formatAsAddress()},'
'len=$len,pgoffs=$pgoffs,'
'filename=${filename.toStringFromZeroTerminated()}}';
}
extension ArrayToString on Array<Uint8> {
String toStringFromFixedLength(int length) =>
String.fromCharCodes([for (var i = 0; i < length; i++) this[i]]);
String toStringFromZeroTerminated() {
final sb = StringBuffer();
for (var i = 0; this[i] != 0; i++) {
sb.writeCharCode(this[i]);
}
return sb.toString();
}
}
extension FormatAsAddress on int {
String formatAsAddress() => toRadixString(16);
}
const int kb = 1024;
const int mb = 1024 * kb;
final class StreamingSectionReader {
final RandomAccessFile f;
final FileSection section;
final chunk = Uint8List(256 * mb);
/// Number of bytes available in the chunk.
int chunkBytes = 0;
/// Offset from the start of the section to the start of the chunk.
int chunkOffset = 0;
/// Position within the chunk.
int pos = 0;
StreamingSectionReader(this.f, this.section) {
f.setPositionSync(section.offset);
refill();
}
bool ensure(int bytes) {
if (chunkBytes < (pos + bytes)) {
refill();
}
return chunkBytes >= (pos + bytes);
}
void refill() {
final int leftOverBytes = chunkBytes - pos;
for (int i = 0; i < leftOverBytes; i++) {
chunk[i] = chunk[i + pos];
}
chunkOffset += pos;
pos = 0;
// Are there any more bytes left to read?
if (chunkOffset >= section.size) {
chunkBytes = 0;
return;
}
print(
"processed $chunkOffset bytes of ${section.size} total (${(chunkOffset / section.size * 100).floor()} %)");
final bytesAlreadyRead = chunkOffset + leftOverBytes;
final bytesToRead =
math.min(section.size - bytesAlreadyRead, chunk.length - leftOverBytes);
final bytesRead =
f.readIntoSync(chunk, leftOverBytes, leftOverBytes + bytesToRead);
chunkBytes = bytesRead + leftOverBytes;
}
}
final class PerfData {
final RandomAccessFile f;
final Header header;
PerfData(this.f)
: header = Struct.create<Header>(f.readSync(sizeOf<Header>())) {
final magic = header.magic.toStringFromFixedLength(8);
if (magic != 'PERFILE2') {
reportError('Incorrect magic in ${f.path} - $magic');
}
}
List<EventAttr> readAttrs() {
f.setPositionSync(header.attrs.offset);
final attrs = f.readSync(header.attrs.size);
final result = <EventAttr>[];
int pos = 0;
while (pos + sizeOf<EventAttr>() < attrs.length) {
final attr = Struct.create<EventAttr>(attrs, pos);
result.add(attr);
pos += attr.size;
}
return result;
}
Map<OptionalSection, FileSection> readOptionalSectionHeaders() {
f.setPositionSync(header.data.offset + header.data.size);
final optionalHeaders =
f.readSync(sizeOf<FileSection>() * OptionalSection.values.length);
int headerIndex = 0;
return {
for (final flag in OptionalSection.values)
if (header.flags & (1 << flag.index) != 0)
flag: Struct.create<FileSection>(
optionalHeaders, sizeOf<FileSection>() * headerIndex++),
};
}
late final _dataReader = StreamingSectionReader(f, header.data);
@pragma('vm:prefer-inline')
void readEvents(bool Function(int type, Uint8List chunk, int pos) callback) {
final reader = _dataReader;
while (true) {
if (!reader.ensure(sizeOf<EventHeader>())) {
// No more events.
return;
}
// Note: `reader.ensure` might refill the chunk and invalidate
// created struct so extract values eagerly.
final EventHeader(:type, :size) =
Struct.create<EventHeader>(reader.chunk, reader.pos);
if (!reader.ensure(size)) {
return;
}
// At this point we are guaranteed to have the whole event in the chunk
// starting at `reader.pos`.
if (!callback(type, reader.chunk, reader.pos)) {
reader.pos += size;
return;
}
reader.pos += size;
}
}
Never reportError(String message) => throw ParseError(f.path, message);
}
final class ParseError extends Error {
final String file;
final String message;
ParseError(this.file, this.message);
@override
String toString() => 'Failed to parse $file: $message';
}