blob: 9979a9fb274ff076b5e733fa7f6f015b28cd6d2e [file] [log] [blame]
//===-cPerf.cpp - Linux perf.data parsing ---------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is a python module that reads perf.data files generated by Linux Perf.
// Writing efficiently-executing Python that reads and manipulates binary files
// is difficult and results in code that looks very un-Python-like.
//
// Also, parsing profiles is on the critical path for result submission and
// often has to run on target devices that may not be as fast as a desktop
// machine (an example is a pandaboard - people still test on Cortex-A9).
//
// This algorithm aims to be faster than 'perf report' or 'perf annotate'.
// In experiments this module is around 6x faster than 'perf annotate' - more
// when we reduce the amount of data imported (see later).
//
// Algorithm
// ---------
//
// We start by mmapping the perf.data file and reading the header, event
// attribute entries and the sample data stream. Every sample is aggregated into
// a set of counters counting every event for every PC location (indexed by mmap
// ID - a data stream can contain samples for the same PC in different binaries
// e.g. if exec() is called). Total counters are also kept for the entire stream
// and per-mmap.
//
// The source for the perf.data format is here: https://lwn.net/Articles/644919/
// and the perf_events manpage.
//
// Once the aggregation is done, we do a single pass through the event data:
//
// for each mmap:
// if the mmap's event total is < 1% of the total in all counters,
// then discard the mmap [1].
//
// load symbol data by calling "objdump -t" and parsing the result.
// for all PCs we have events for, in sorted order:
// find the symbol this PC is part of -> Sym
// look up all PCs between Sym.Start and Sym.End and emit data
//
// The output of this module (cPerf.importPerf) is a dictionary in (almost)
// ProfileV1 form (see profile.py and profilev1impl.py). The only difference is
// that all counters are absolute.
//
// [1]: Perf will start sampling from the moment the perf wrapper tool is
// invoked, and its samples will continue until the perf wrapper tool exits.
// This means that it will often take one or two samples in intermediate
// binaries like "perf", "bash", or libaries such as libdl. We don't care about
// these, and these binaries and libraries are very large to objdump.
//
// So we have a threshold - if a binary contains < 1% of all samples, don't
// bother importing it.
//
//===----------------------------------------------------------------------===//
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#define strtok_r strtok_s
#include <windows.h>
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#define PROT_EXEC 4
#endif
#include <Python.h>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <exception>
#include <fcntl.h>
#include <iostream>
#include <map>
#include <sstream>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef _WIN32
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <vector>
//===----------------------------------------------------------------------===//
// Helpers
//===----------------------------------------------------------------------===//
#ifndef STANDALONE
#ifdef assert
#undef assert
#endif
// Redefine assert so we can use it without destroying the Python interpreter!
#define assert(expr) Assert((expr), #expr, __FILE__, __LINE__)
#endif
// Remove a uint32_t from a byte stream
uint32_t TakeU32(unsigned char *&Buf) {
uint32_t X = * (uint32_t*) Buf;
Buf += sizeof(uint32_t);
return X;
}
// Remove a uint64_t from a byte stream
uint64_t TakeU64(unsigned char *&Buf) {
uint64_t X = * (uint64_t*) Buf;
Buf += sizeof(uint64_t);
return X;
}
// Forks, execs Cmd under a shell and returns
// a file descriptor reading the command's stdout.
FILE *ForkAndExec(std::string Cmd) {
#ifdef _WIN32
Cmd = "cmd.exe /c " + Cmd;
FILE *Stream = _popen(Cmd.c_str(), "rt");
#else
int P[2];
pipe(P);
FILE *Stream = fdopen(P[0], "r");
if (fork() == 0) {
dup2(P[1], 1);
close(P[0]);
execl("/bin/sh", "sh", "-c", Cmd.c_str(), (char *)NULL);
} else {
close(P[1]);
}
#endif
return Stream;
}
#ifdef _WIN32
ssize_t getline(char** lineptr, size_t* n, FILE* stream) {
if (lineptr == nullptr || stream == nullptr || n == nullptr) return -1;
int c = fgetc(stream);
if (c == EOF) return -1;
char* bufptr = *lineptr;
size_t size = *n;
if (bufptr == nullptr) {
size = 128;
bufptr = (char*)malloc(size);
if (bufptr == nullptr) return -1;
*lineptr = bufptr;
}
char* p = bufptr;
while (c != EOF) {
size_t offset = (size_t)(p - bufptr);
if ((offset + 2) > size) {
size = size + 128;
bufptr = (char*)realloc(bufptr, size);
if (bufptr == nullptr) return -1;
*lineptr = bufptr;
p = bufptr + offset;
}
*p++ = c;
if (c == '\n') break;
c = fgetc(stream);
}
*p = '\0';
*n = size;
return p - bufptr;
}
#endif
void Assert(bool Expr, const char *ExprStr, const char *File, int Line) {
if (Expr)
return;
char Str[512];
sprintf(Str, "%s (%s:%d)", ExprStr, File, Line);
throw std::logic_error(Str);
}
//===----------------------------------------------------------------------===//
// Perf structures. Taken from https://lwn.net/Articles/644919/
//===----------------------------------------------------------------------===//
#define PERF_RECORD_MMAP 1
#define PERF_RECORD_SAMPLE 9
#define PERF_RECORD_MMAP2 10
#define PERF_SAMPLE_IP (1U << 0)
#define PERF_SAMPLE_TID (1U << 1)
#define PERF_SAMPLE_TIME (1U << 2)
#define PERF_SAMPLE_ADDR (1U << 3)
#define PERF_SAMPLE_ID (1U << 6)
#define PERF_SAMPLE_CPU (1U << 7)
#define PERF_SAMPLE_PERIOD (1U << 8)
#define PERF_SAMPLE_STREAM_ID (1U << 9)
#define PERF_SAMPLE_IDENTIFIER (1U << 16)
struct perf_file_section {
uint64_t offset; /* offset from start of file */
uint64_t size; /* size of the section */
};
struct perf_header {
char magic[8]; /* PERFILE2 */
uint64_t size; /* size of the header */
uint64_t attr_size; /* size of an attribute in attrs */
struct perf_file_section attrs;
struct perf_file_section data;
struct perf_file_section event_types;
uint64_t flags;
uint64_t flags1[3];
};
struct perf_event_attr {
uint32_t type;
uint32_t size;
uint64_t config;
uint64_t sample_period;
uint64_t sample_type;
uint64_t read_format;
uint64_t flags;
uint32_t wakeup_events;
uint32_t bp_type;
uint64_t bp_addr;
uint64_t bp_len;
uint64_t branch_sample_type;
};
struct perf_event_header {
uint32_t type;
uint16_t misc;
uint16_t size;
};
struct perf_event_sample {
struct perf_event_header header;
uint64_t sample_id;
uint64_t ip;
uint32_t pid, tid;
uint64_t time;
uint64_t id;
uint64_t period;
};
struct perf_event_mmap_common {
struct perf_event_header header;
uint32_t pid, tid;
uint64_t start, extent, pgoff;
};
struct perf_event_mmap {
struct perf_event_mmap_common mmap_common;
char filename[1];
};
struct perf_event_mmap2 {
struct perf_event_mmap_common mmap_common;
uint32_t maj, min;
uint64_t ino, ino_generation;
uint32_t prot, flags;
char filename[1];
};
struct perf_trace_event_type {
uint64_t event_id;
char str[64];
};
enum perf_type_id {
PERF_TYPE_HARDWARE = 0,
PERF_TYPE_SOFTWARE = 1,
PERF_TYPE_TRACEPOINT = 2,
PERF_TYPE_HW_CACHE = 3,
PERF_TYPE_RAW = 4,
PERF_TYPE_BREAKPOINT = 5,
PERF_TYPE_MAX
};
enum perf_hw_id {
PERF_COUNT_HW_CPU_CYCLES = 0,
PERF_COUNT_HW_INSTRUCTIONS = 1,
PERF_COUNT_HW_CACHE_REFERENCES = 2,
PERF_COUNT_HW_CACHE_MISSES = 3,
PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4,
PERF_COUNT_HW_BRANCH_MISSES = 5,
PERF_COUNT_HW_BUS_CYCLES = 6,
PERF_COUNT_HW_STALLED_CYCLES_FRONTEND = 7,
PERF_COUNT_HW_STALLED_CYCLES_BACKEND = 8,
PERF_COUNT_HW_REF_CPU_CYCLES = 9,
PERF_COUNT_HW_MAX
};
static const char* hw_event_names[PERF_COUNT_HW_MAX] = {
"cycles",
"instructions",
"cache-references",
"cache-misses",
"branch-instructions",
"branch-misses",
"bus-cycles",
"stalled-cycles-frontend",
"stalled-cycles-backend",
"ref-cpu-cycles"
};
enum perf_sw_ids {
PERF_COUNT_SW_CPU_CLOCK = 0,
PERF_COUNT_SW_TASK_CLOCK = 1,
PERF_COUNT_SW_PAGE_FAULTS = 2,
PERF_COUNT_SW_CONTEXT_SWITCHES = 3,
PERF_COUNT_SW_CPU_MIGRATIONS = 4,
PERF_COUNT_SW_PAGE_FAULTS_MIN = 5,
PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6,
PERF_COUNT_SW_ALIGNMENT_FAULTS = 7,
PERF_COUNT_SW_EMULATION_FAULTS = 8,
PERF_COUNT_SW_MAX
};
static const char* sw_event_names[PERF_COUNT_SW_MAX] = {
"cpu-clock",
"task-clock",
"page-faults",
"context-switches",
"cpu-migrations",
"minor-faults",
"major-faults",
"alignment-faults",
"emulation-faults"
};
//===----------------------------------------------------------------------===//
// Readers for objdump output
//===----------------------------------------------------------------------===//
struct Map {
Map(uint64_t Start, uint64_t End, const char *Filename)
: Start(Start), End(End), Filename(Filename) {}
uint64_t Start, End;
const char *Filename;
// Mapping-related adjustments. Here FileOffset(func) is the offset of func
// in the ELF file, VAddr(func) is the virtual address associated with this
// symbol (in case of executable and shared object ELF files, st_value field
// of a symbol table's entry is symbol's virtual address) and &func is the
// actual memory address after relocations took place in the address space of
// the process being profiled.
uint64_t FileToPCOffset; // FileOffset(func) + FileToPCOffset == &func
uint64_t VAddrToFileOffset; // VAddr(func) + VAddrToFileOffset == FileOffset(func)
};
struct EventDesc {
uint64_t Start;
uint64_t End;
size_t MapId;
};
struct Symbol {
uint64_t Start;
uint64_t End;
std::string Name;
bool operator<(const Symbol &Other) const { return Start < Other.Start; }
bool operator==(const Symbol &Other) const {
return Start == Other.Start && End == Other.End && Name == Other.Name;
}
};
class SymTabOutput : public std::vector<Symbol> {
public:
std::string Objdump, BinaryCacheRoot;
SymTabOutput(std::string Objdump, std::string BinaryCacheRoot)
: Objdump(Objdump), BinaryCacheRoot(BinaryCacheRoot) {}
void fetchExecSegment(Map *M, uint64_t *FileOffset, uint64_t *VAddr) {
std::string Cmd = Objdump + " -p -C " +
BinaryCacheRoot + std::string(M->Filename) +
#ifdef _WIN32
" 2> NUL";
#else
" 2>/dev/null";
#endif
auto Stream = ForkAndExec(Cmd);
char *Line = nullptr, *PrevLine = nullptr;
size_t LineLen = 0;
*FileOffset = *VAddr = 0;
while (true) {
if (PrevLine)
free (PrevLine);
if (Line)
PrevLine = strdup (Line);
ssize_t Len = getline(&Line, &LineLen, Stream);
if (Len == -1)
break;
if (!strstr(Line, "flags r-x") && !strstr(Line, "flags rwx"))
continue;
/* Format is weird.. but we did find the section so punt. */
const char *OFFSET_LABEL = "off ";
const char *VADDR_LABEL = "vaddr ";
char *pos_offset = strstr(PrevLine, OFFSET_LABEL);
char *pos_vaddr = strstr(PrevLine, VADDR_LABEL);
if (!pos_offset || !pos_vaddr)
break;
pos_offset += strlen(OFFSET_LABEL);
pos_vaddr += strlen(VADDR_LABEL);
*FileOffset = strtoull(pos_offset, NULL, 16);
*VAddr = strtoull(pos_vaddr, NULL, 16);
break;
}
if (Line)
free(Line);
if (PrevLine)
free(PrevLine);
#ifdef _WIN32
_pclose(Stream);
#else
fclose(Stream);
wait(NULL);
#endif
}
void fetchSymbols(Map *M) {
std::string Cmd = Objdump + " -t -T -C " +
BinaryCacheRoot + std::string(M->Filename) +
#ifdef _WIN32
" 2> NUL";
#else
" 2>/dev/null";
#endif
auto Stream = ForkAndExec(Cmd);
char *Line = nullptr;
size_t LineLen = 0;
while (true) {
ssize_t Len = getline(&Line, &LineLen, Stream);
if (Len == -1)
break;
std::stringstream SS(Line);
std::string Start;
if (!std::getline(SS, Start, ' '))
continue;
char* EndPtr = NULL;
uint64_t NStart = strtoull(Start.c_str(), &EndPtr, 16);
if (EndPtr == Start.c_str())
continue;
char GlobLoc; // Local -> 'l', Global -> 'g', Neither -> ' '
if (!SS.get(GlobLoc))
continue;
char Weak; // Weak?
if (!SS.get(Weak))
continue;
char Space;
if (!SS.get(Space)) // Constructor. Not supported yet.
continue;
if (!SS.get(Space)) // Warning. Not supported yet.
continue;
char IFunc; // Indirect reference to another symbol.
if (!SS.get(IFunc))
continue;
char Debug; // Debugging (d) or dynamic (D) symbol.
if (!SS.get(Debug))
continue;
char FileFunc; // Name of function (F), file (f) or object (O).
if (!SS.get(FileFunc))
continue;
if (FileFunc != 'F')
continue;
if (!SS.get(Space))
continue;
std::string Section;
if (!std::getline(SS, Section, '\t'))
continue;
if (Section != ".text")
continue;
std::string Extent;
if (!std::getline(SS, Extent, ' '))
continue;
uint64_t NExtent = strtoull(Extent.c_str(), &EndPtr, 16);
if (EndPtr == Extent.c_str())
continue;
std::string Func;
while (std::getline(SS, Func, ' ') && Func.empty()); // Skip spaces.
if (Func.empty())
continue;
// Note Func includes the symbol table visibility if any.
std::string FuncRest;
if (std::getline(SS, FuncRest)) // Read the rest line if any.
Func += std::string(" ") + FuncRest;
if (Func.back() == '\n')
Func.pop_back();
push_back({NStart, NStart + NExtent, Func});
}
if (Line)
free(Line);
#ifdef _WIN32
_pclose(Stream);
#else
fclose(Stream);
wait(NULL);
#endif
}
void reset(Map *M) {
clear();
// Take possible difference between "offset" and "virtual address" of
// the executable segment into account.
uint64_t FileOffset, VAddr;
fetchExecSegment(M, &FileOffset, &VAddr);
M->VAddrToFileOffset = FileOffset - VAddr;
// Fetch both dynamic and static symbols, sort and unique them.
fetchSymbols(M);
std::sort(begin(), end());
auto NewEnd = std::unique(begin(), end());
erase(NewEnd, end());
}
protected:
int splitLine(const std::string& line, std::vector<std::string>& output, char delim = ' ') {
std::stringstream ss(line);
std::string token;
while (std::getline(ss, token, delim)) {
output.push_back(token);
}
return (int)output.size();
}
};
class ObjdumpOutput {
public:
std::string Objdump, BinaryCacheRoot;
FILE *Stream;
const char *ThisText;
uint64_t ThisAddress;
uint64_t EndAddress;
char *Line;
size_t LineLen;
ObjdumpOutput(std::string Objdump, std::string BinaryCacheRoot)
: Objdump(Objdump), BinaryCacheRoot(BinaryCacheRoot), Stream(nullptr),
Line(NULL), LineLen(0) {}
~ObjdumpOutput() {
if (Stream) {
#ifdef _WIN32
_pclose(Stream);
#else
fclose(Stream);
wait(NULL);
#endif
}
if (Line)
free(Line);
}
void reset(Map *M, uint64_t Start, uint64_t Stop) {
ThisAddress = 0;
ThisText = "";
if (Stream) {
#ifdef _WIN32
_pclose(Stream);
#else
fclose(Stream);
wait(NULL);
#endif
}
char buf1[32], buf2[32];
sprintf(buf1, "%#" PRIx64, Start);
sprintf(buf2, "%#" PRIx64, Stop + 4);
std::string Cmd = Objdump + " -d --no-show-raw-insn --start-address=" +
std::string(buf1) + " --stop-address=" +
std::string(buf2) + " " +
BinaryCacheRoot + std::string(M->Filename) +
#ifdef _WIN32
" 2> NUL";
#else
" 2>/dev/null";
#endif
Stream = ForkAndExec(Cmd);
EndAddress = Stop;
};
std::string getText() { return ThisText; }
uint64_t next() {
getLine();
return ThisAddress;
}
void getLine() {
while (true) {
ssize_t Len = getline(&Line, &LineLen, Stream);
if (Len == -1) {
ThisAddress = EndAddress;
ThisText = "";
return;
}
char *TokBuf;
char *One = strtok_r(Line, ":", &TokBuf);
char *Two = strtok_r(NULL, "\n", &TokBuf);
if (!One || !Two)
continue;
char *EndPtr = NULL;
uint64_t Address = strtoull(One, &EndPtr, 16);
if (EndPtr != &One[strlen(One)])
continue;
ThisAddress = Address;
ThisText = Two;
break;
}
}
};
//===----------------------------------------------------------------------===//
// PerfReader
//===----------------------------------------------------------------------===//
class PerfReader {
public:
PerfReader(const std::string &Filename, std::string Objdump,
std::string BinaryCacheRoot);
~PerfReader();
void readHeader();
void readAttrs();
void readEventDesc();
void readDataStream();
void registerNewMapping(unsigned char *Buf, const char *FileName);
unsigned char *readEvent(unsigned char *);
perf_event_sample parseEvent(unsigned char *Buf, uint64_t Layout);
void emitLine(uint64_t PC, std::map<const char *, uint64_t> *Counters,
const std::string &Text);
void emitFunctionStart(std::string &Name);
void emitFunctionEnd(std::string &Name,
std::map<const char *, uint64_t> &Counters);
void emitTopLevelCounters();
void emitMaps();
void emitSymbol(
Symbol &Sym, Map &M,
std::map<uint64_t, std::map<const char *, uint64_t>>::iterator Event,
std::map<const char *, uint64_t> &SymEvents);
PyObject *complete();
private:
unsigned char *Buffer;
#ifdef _WIN32
HANDLE hFile;
HANDLE hMapFile;
#else
size_t BufferLen;
#endif
perf_header *Header;
std::map<uint64_t, const char *> EventIDs;
std::map<uint64_t, uint64_t> EventLayouts;
std::map<size_t, std::map<uint64_t, std::map<const char *, uint64_t>>> Events;
std::map<const char *, uint64_t> TotalEvents;
std::map<uint64_t, std::map<const char *, uint64_t>> TotalEventsPerMap;
std::vector<Map> Maps;
std::map<uint64_t, std::map<uint64_t, EventDesc>> CurrentMaps;
PyObject *Functions, *TopLevelCounters;
std::vector<PyObject*> Lines;
std::string Objdump, BinaryCacheRoot;
};
PerfReader::PerfReader(const std::string &Filename, std::string Objdump,
std::string BinaryCacheRoot)
: Objdump(Objdump), BinaryCacheRoot(BinaryCacheRoot) {
TopLevelCounters = PyDict_New();
Functions = PyDict_New();
#ifdef _WIN32
Buffer = nullptr;
hMapFile = nullptr;
hFile = ::CreateFileA(Filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
assert(hFile != INVALID_HANDLE_VALUE);
LARGE_INTEGER size;
size.QuadPart = 0;
assert(::GetFileSizeEx(hFile, &size) != FALSE);
hMapFile = ::CreateFileMapping(hFile, NULL, PAGE_READONLY, size.HighPart, size.LowPart, NULL);
assert(hMapFile != nullptr);
Buffer = (unsigned char*)::MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
assert(Buffer != nullptr);
#else
int fd = open(Filename.c_str(), O_RDONLY);
assert(fd > 0);
struct stat sb;
assert(fstat(fd, &sb) == 0);
BufferLen = (size_t)sb.st_size;
Buffer = (unsigned char *)mmap(NULL, BufferLen, PROT_READ, MAP_SHARED, fd, 0);
assert(Buffer != MAP_FAILED);
#endif
}
PerfReader::~PerfReader() {
#ifdef _WIN32
if (hMapFile != NULL) {
if (Buffer != NULL) ::UnmapViewOfFile(Buffer);
::CloseHandle(hMapFile);
}
if (hFile != INVALID_HANDLE_VALUE) ::CloseHandle(hFile);
#else
munmap(Buffer, BufferLen);
#endif
}
void PerfReader::readHeader() {
Header = (perf_header *)&Buffer[0];
assert(!strncmp(Header->magic, "PERFILE2", 8));
}
void PerfReader::readDataStream() {
unsigned char *Buf = &Buffer[Header->data.offset];
unsigned char *End = Buf + Header->data.size;
while (Buf < End)
Buf = readEvent(Buf);
}
#define HEADER_EVENT_DESC 12
void PerfReader::readAttrs() {
if (Header->flags & (1U << HEADER_EVENT_DESC)) {
readEventDesc();
} else {
uint64_t NumEvents = Header->attrs.size / Header->attr_size;
for (unsigned I = 0; I < NumEvents; ++I) {
const perf_event_attr* attr = (const perf_event_attr*)&Buffer[Header->attrs.offset + I * Header->attr_size];
const perf_file_section* ids = (const perf_file_section*)((unsigned char *)attr + attr->size);
unsigned char* Buf = &Buffer[ids->offset];
uint64_t NumIDs = ids->size / sizeof(uint64_t);
const char* Str = "unknown";
switch (attr->type) {
case PERF_TYPE_HARDWARE:
if (attr->config < PERF_COUNT_HW_MAX) Str = hw_event_names[attr->config];
break;
case PERF_TYPE_SOFTWARE:
if (attr->config < PERF_COUNT_SW_MAX) Str = sw_event_names[attr->config];
break;
}
// Weirdness of perf: if there is only one event descriptor, that
// event descriptor can be referred to by ANY id!
if (NumEvents == 1) {
EventIDs[0] = Str;
EventLayouts[0] = attr->sample_type;
}
for (unsigned J = 0; J < NumIDs; ++J) {
auto id = TakeU64(Buf);
EventIDs[id] = Str;
EventLayouts[id] = attr->sample_type;
}
}
}
}
void PerfReader::readEventDesc() {
perf_file_section *P =
(perf_file_section *)&Buffer[Header->data.offset + Header->data.size];
for (int I = 0; I < HEADER_EVENT_DESC; ++I)
if (Header->flags & (1ULL << I))
++P;
unsigned char *Buf = &Buffer[P->offset];
uint32_t NumEvents = TakeU32(Buf);
uint32_t AttrSize = TakeU32(Buf);
for (unsigned I = 0; I < NumEvents; ++I) {
// Parse out the "config" bitmask for the struct layout.
auto *Buf2 = Buf;
(void)TakeU32(Buf2); (void)TakeU32(Buf2); (void)TakeU64(Buf2); (void)TakeU64(Buf2);
auto Layout = TakeU64(Buf2);
Buf += AttrSize;
uint32_t NumIDs = TakeU32(Buf);
uint32_t StrLen = TakeU32(Buf);
const char *Str = (const char *)Buf;
Buf += StrLen;
// Weirdness of perf: if there is only one event descriptor, that
// event descriptor can be referred to by ANY id!
if (NumEvents == 1) {
EventIDs[0] = Str;
EventLayouts[0] = Layout;
}
for (unsigned J = 0; J < NumIDs; ++J) {
auto L = TakeU64(Buf);
EventIDs[L] = Str;
EventLayouts[L] = Layout;
}
}
}
static uint64_t getTimeFromSampleId(unsigned char *EndOfStruct,
uint64_t Layout) {
uint64_t *Ptr = (uint64_t *)EndOfStruct;
// Each of the PERF_SAMPLE_* bits tested below adds an 8-byte field.
if (Layout & PERF_SAMPLE_IDENTIFIER)
--Ptr;
if (Layout & PERF_SAMPLE_CPU)
--Ptr;
if (Layout & PERF_SAMPLE_STREAM_ID)
--Ptr;
if (Layout & PERF_SAMPLE_ID)
--Ptr;
assert(Layout & PERF_SAMPLE_TIME);
--Ptr;
return *Ptr;
}
void PerfReader::registerNewMapping(unsigned char *Buf, const char *Filename) {
perf_event_mmap_common *E = (perf_event_mmap_common *)Buf;
auto MapID = Maps.size();
uint64_t End = E->start + E->extent;
Map NewMapping(E->start, End, Filename);
NewMapping.FileToPCOffset = E->start - E->pgoff;
Maps.push_back(NewMapping);
unsigned char *EndOfEvent = Buf + E->header.size;
// FIXME: The first EventID is used for every event.
// FIXME: The code assumes perf_event_attr.sample_id_all is set.
uint64_t Time = getTimeFromSampleId(EndOfEvent, EventLayouts.begin()->second);
auto &CurrentMap = CurrentMaps[Time];
CurrentMap.insert({E->start, { E->start, End, MapID}});
}
unsigned char *PerfReader::readEvent(unsigned char *Buf) {
perf_event_header *E = (perf_event_header *)Buf;
switch (E->type) {
case PERF_RECORD_MMAP:
{
perf_event_mmap *E = (perf_event_mmap *)Buf;
registerNewMapping(Buf, E->filename);
}
break;
case PERF_RECORD_MMAP2:
{
perf_event_mmap2 *E = (perf_event_mmap2 *)Buf;
if (!(E->prot & PROT_EXEC))
break;
registerNewMapping(Buf, E->filename);
}
break;
case PERF_RECORD_SAMPLE:
{
perf_event_sample* E = (perf_event_sample*)Buf;
auto NewE = parseEvent(((unsigned char*)E) + sizeof(perf_event_header),
EventLayouts.begin()->second);
auto EventID = NewE.id;
auto PC = NewE.ip;
// Search for the map corresponding to this sample. Search backwards through
// time, discarding any maps created after our timestamp.
uint64_t MapID = ~0ULL;
for (auto I = CurrentMaps.rbegin(), E = CurrentMaps.rend();
I != E; ++I) {
if (I->first > NewE.time)
continue;
auto NewI = I->second.upper_bound(PC);
if (NewI == I->second.begin())
continue;
--NewI;
if (NewI->second.Start > PC || NewI->second.End < PC)
continue;
MapID = NewI->second.MapId;
break;
}
if (MapID != ~0ULL) {
assert(EventIDs.count(EventID));
Events[MapID][PC][EventIDs[EventID]] += NewE.period;
TotalEvents[EventIDs[EventID]] += NewE.period;
TotalEventsPerMap[MapID][EventIDs[EventID]] += NewE.period;
}
}
break;
}
return &Buf[E->size];
}
perf_event_sample PerfReader::parseEvent(unsigned char *Buf, uint64_t Layout) {
perf_event_sample E;
memset((char*)&E, 0, sizeof(E));
assert(Layout & PERF_SAMPLE_IP);
assert(Layout & PERF_SAMPLE_PERIOD);
if (Layout & PERF_SAMPLE_IDENTIFIER)
E.id = TakeU64(Buf);
if (Layout & PERF_SAMPLE_IP)
E.ip = TakeU64(Buf);
if (Layout & PERF_SAMPLE_TID) {
E.pid = TakeU32(Buf);
E.tid = TakeU32(Buf);
}
if (Layout & PERF_SAMPLE_TIME)
E.time = TakeU64(Buf);
if (Layout & PERF_SAMPLE_ADDR)
(void) TakeU64(Buf);
if (Layout & PERF_SAMPLE_ID)
E.id = TakeU64(Buf);
if (Layout & PERF_SAMPLE_STREAM_ID)
(void) TakeU64(Buf);
if (Layout & PERF_SAMPLE_CPU)
(void) TakeU64(Buf);
if (Layout & PERF_SAMPLE_PERIOD)
E.period = TakeU64(Buf);
return E;
}
void PerfReader::emitFunctionStart(std::string &Name) {
Lines.clear();
}
void PerfReader::emitFunctionEnd(std::string &Name,
std::map<const char *, uint64_t> &Counters) {
auto *CounterDict = PyDict_New();
for (auto &KV : Counters)
PyDict_SetItemString(CounterDict, KV.first,
PyLong_FromUnsignedLongLong((unsigned long long)KV.second));
auto *LinesList = PyList_New(Lines.size());
unsigned Idx = 0;
for (auto *I : Lines)
PyList_SetItem(LinesList, Idx++, I);
auto *FnDict = PyDict_New();
PyDict_SetItemString(FnDict, "counters", CounterDict);
PyDict_SetItemString(FnDict, "data", LinesList);
PyDict_SetItemString(Functions, Name.c_str(), FnDict);
}
void PerfReader::emitLine(uint64_t PC,
std::map<const char *, uint64_t> *Counters,
const std::string &Text) {
auto *CounterDict = PyDict_New();
if (Counters)
for (auto &KV : *Counters)
PyDict_SetItemString(CounterDict, KV.first,
PyLong_FromUnsignedLongLong((unsigned long long)KV.second));
auto *Line = Py_BuildValue("[NKs]",
CounterDict,
(unsigned long long) PC,
(char*) Text.c_str());
Lines.push_back(Line);
}
void PerfReader::emitTopLevelCounters() {
for (auto &KV : TotalEvents)
PyDict_SetItemString(TopLevelCounters, KV.first,
PyLong_FromUnsignedLongLong((unsigned long long)KV.second));
}
void PerfReader::emitMaps() {
for (auto &KV : Events) {
auto MapID = KV.first;
auto &MapEvents = KV.second;
if (MapEvents.empty())
continue;
if (MapID > Maps.size()) {
// Something went badly wrong. Try and recover.
continue;
}
// Are there enough events here to bother with?
bool AllUnderThreshold = true;
for (auto &I : TotalEventsPerMap[MapID]) {
auto Total = TotalEvents[I.first];
// If a map contains more than 1% of some event, bother with it.
if ((float)I.second / (float)Total > 0.01f) {
AllUnderThreshold = false;
break;
}
}
if (AllUnderThreshold)
continue;
Map &M = Maps[MapID];
SymTabOutput Syms(Objdump, BinaryCacheRoot);
Syms.reset(&M);
uint64_t VAddrToPCOffset = M.VAddrToFileOffset + M.FileToPCOffset;
// Accumulate the event totals for each symbol
auto Sym = Syms.begin();
auto Event = MapEvents.begin();
std::map<uint64_t, std::map<const char*, uint64_t>> SymToEventTotals;
while (Event != MapEvents.end() && Sym != Syms.end()) {
// Skip events until we find one after the start of Sym
auto VAddr = Event->first - VAddrToPCOffset;
if (VAddr < Sym->Start) {
++Event;
continue;
}
// Skip symbols until the event is before the end of Sym
if (VAddr >= Sym->End) {
++Sym;
continue;
}
// We now know that Event lies within Sym, so add it to the totals
for (auto &KV : Event->second)
SymToEventTotals[Sym->Start][KV.first] += KV.second;
++Event;
}
// Emit only symbols that took up > 0.5% of any counter
for (auto &Sym : Syms) {
bool Keep = false;
for (auto &KV : SymToEventTotals[Sym.Start]) {
if ((double)KV.second / (double)TotalEvents[KV.first] > 0.005) {
Keep = true;
break;
}
}
if (Keep)
emitSymbol(Sym, M, MapEvents.lower_bound(Sym.Start + VAddrToPCOffset),
SymToEventTotals[Sym.Start]);
}
}
}
void PerfReader::emitSymbol(
Symbol &Sym, Map &M,
std::map<uint64_t, std::map<const char *, uint64_t>>::iterator Event,
std::map<const char *, uint64_t> &SymEvents) {
uint64_t VAddrToPCOffset = M.VAddrToFileOffset + M.FileToPCOffset;
ObjdumpOutput Dump(Objdump, BinaryCacheRoot);
Dump.reset(&M, Sym.Start, Sym.End);
emitFunctionStart(Sym.Name);
assert(Sym.Start <= Event->first - VAddrToPCOffset &&
Event->first - VAddrToPCOffset < Sym.End);
for (uint64_t I = Dump.next(); I < Sym.End; I = Dump.next()) {
auto VAddr = Event->first - VAddrToPCOffset;
auto Text = Dump.getText();
if (VAddr == I) {
emitLine(I, &Event->second, Text);
++Event;
} else {
emitLine(I, nullptr, Text);
}
}
emitFunctionEnd(Sym.Name, SymEvents);
}
PyObject *PerfReader::complete() {
auto *Obj = PyDict_New();
PyDict_SetItemString(Obj, "counters", TopLevelCounters);
PyDict_SetItemString(Obj, "functions", Functions);
return Obj;
}
#ifndef STANDALONE
static PyObject *cPerf_importPerf(PyObject *self, PyObject *args) {
const char *Fname;
const char *Objdump = "objdump";
const char *BinaryCacheRoot = "";
if (!PyArg_ParseTuple(args, "s|ss", &Fname, &Objdump, &BinaryCacheRoot))
return NULL;
try {
PerfReader P(Fname, Objdump, BinaryCacheRoot);
P.readHeader();
P.readAttrs();
P.readDataStream();
P.emitTopLevelCounters();
P.emitMaps();
return P.complete();
} catch (std::logic_error &E) {
PyErr_SetString(PyExc_AssertionError, E.what());
return NULL;
} catch (std::runtime_error &E) {
PyErr_SetString(PyExc_RuntimeError, E.what());
return NULL;
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown error");
return NULL;
}
}
static PyMethodDef cPerfMethods[] = {{"importPerf", cPerf_importPerf,
METH_VARARGS,
"Import perf.data from a filename"},
{NULL, NULL, 0, NULL}};
static PyModuleDef cPerfModuleDef = {PyModuleDef_HEAD_INIT,
"cPerf",
nullptr,
-1,
cPerfMethods,
nullptr,
nullptr,
nullptr,
nullptr};
PyMODINIT_FUNC PyInit_cPerf(void) {
return PyModule_Create(&cPerfModuleDef);
}
#else // STANDALONE
// You can build the standalone version for a debug purpose using
// clang++ cPerf.cpp -o /tmp/cPerf -DSTANDALONE $(pkg-config --cflags --libs python3-embed)
// getenv() is permitted to overwrite its return value on subsequent calls,
// so copy it to std::string early.
static std::string getEnvVar(const char *VarName, const char *DefaultValue) {
const char *Value = getenv(VarName);
if (!Value)
Value = DefaultValue;
return Value;
}
int main(int argc, char **argv) {
Py_Initialize();
if (argc < 2) return -1;
std::string BinaryCacheRoot = getEnvVar("LNT_BINARY_CACHE_ROOT", "");
std::string Objdump = getEnvVar("CMAKE_OBJDUMP", "objdump");
PerfReader P(argv[1], Objdump, BinaryCacheRoot);
P.readHeader();
P.readAttrs();
P.readDataStream();
P.emitTopLevelCounters();
P.emitMaps();
PyObject_Print(P.complete(), stdout, Py_PRINT_RAW);
fputs("\n", stdout); // Usually expected by UNIX shells
Py_FinalizeEx();
return 0;
}
#endif