调用ProfilerStart和ProfilerStop就可生成perf文件,然后用pprof解析就行
.h
extern void StartProfiler(const char* fname);
extern void StopProfiler();
extern void ProfilerSignalHandler(int sig, siginfo_t* sinfo, void* ucontext);
class ProfileData
{
public:
static const int kMaxStackDepth = 64; // Max stack depth stored in profile
ProfileData();
~ProfileData();
// If data collection is not already enabled start to collect data
// into fname. Parameters related to this profiling run are specified
// by 'options'.
//
// Returns true if data collection could be started, otherwise (if an
// error occurred or if data collection was already enabled) returns
// false.
bool Start(const char *fname);
// If data collection is enabled, stop data collection and write the
// data to disk.
void Stop();
// Stop data collection without writing anything else to disk, and
// discard any collected data.
void Reset();
// If data collection is enabled, record a sample with 'depth'
// entries from 'stack'. (depth must be > 0.) At most
// kMaxStackDepth stack entries will be recorded, starting with
// stack[0].
//
// This function is safe to call from asynchronous signals (but is
// not re-entrant).
void Add(int depth, const void* const* stack);
// Is data collection currently enabled?
bool enabled() const { return out_ >= 0; }
private:
static const int kAssociativity = 4; // For hashtable
static const int kBuckets = 1 << 10; // For hashtable
static const int kBufferLength = 1 << 18; // For eviction buffer
// Type of slots: each slot can be either a count, or a PC value
typedef uintptr_t Slot;
// Hash-table/eviction-buffer entry (a.k.a. a sample)
struct Entry
{
Slot count; // Number of hits
Slot depth; // Stack depth
Slot stack[kMaxStackDepth]; // Stack contents
};
// Hash table bucket
struct Bucket
{
Entry entry[kAssociativity];
};
Bucket* hash_; // hash table
Slot* evict_; // evicted entries
int num_evicted_; // how many evicted entries?
int out_; // fd for output file.
int count_; // How many samples recorded
int evictions_; // How many evictions
size_t total_bytes_; // How much output
char* fname_; // Profile file name
time_t start_time_; // Start time, or 0
// Move 'entry' to the eviction buffer.
void Evict(const Entry& entry);
// Write contents of eviction buffer to disk.
void FlushEvicted();
};
class ProcMapsIterator
{
public:
struct Buffer
{
static const size_t kBufSize = PATH_MAX + 1024;
char buf_[kBufSize];
};
// Create an iterator with specified storage (for use in signal
// handler). "buffer" should point to a ProcMapsIterator::Buffer
// buffer can be NULL in which case a bufer will be allocated.
ProcMapsIterator(pid_t pid, Buffer *buffer);
// Writes the "canonical" form of the /proc/xxx/maps info for a single
// line to the passed-in buffer. Returns the number of bytes written,
// or 0 if it was not able to write the complete line. (To guarantee
// success, buffer should have size at least Buffer::kBufSize.)
// Takes as arguments values set via a call to Next(). The
// "canonical" form of the line (taken from linux's /proc/xxx/maps):
// <start_addr(hex)>-<end_addr(hex)> <perms(rwxp)> <offset(hex)> +
// <major_dev(hex)>:<minor_dev(hex)> <inode> <filename> Note: the
// eg
// 08048000-0804c000 r-xp 00000000 03:01 3793678 /bin/cat
// If you don't have the dev_t (dev), feel free to pass in 0.
// (Next() doesn't return a dev_t, though NextExt does.)
//
// Note: if filename and flags were obtained via a call to Next(),
// then the output of this function is only valid if Next() returned
// true, and only until the iterator is destroyed or Next() is
// called again. (Since filename, at least, points into CurrentLine.)
static int FormatLine(char* buffer, int bufsize,
uint64_t start, uint64_t end, const char *flags,
uint64_t offset, int64 inode, const char *filename,
dev_t dev);
// Find the next entry in /proc/maps; return true if found or false
// if at the end of the file.
//
// Any of the result pointers can be NULL if you're not interested
// in those values.
//
// If "flags" and "filename" are passed, they end up pointing to
// storage within the ProcMapsIterator that is valid only until the
// iterator is destroyed or Next() is called again. The caller may
// modify the contents of these strings (up as far as the first NUL,
// and only until the subsequent call to Next()) if desired.
// The offsets are all uint64_t in order to handle the case of a
// 32-bit process running on a 64-bit kernel
//
// IMPORTANT NOTE: see top-of-class notes for details about what
// mapped regions Next() iterates over, depending on O/S.
// TODO(csilvers): make flags and filename const.
bool Next(uint64_t *start, uint64_t *end, char **flags,
uint64_t *offset, int64 *inode, char **filename);
bool NextExt(uint64_t *start, uint64_t *end, char **flags,
uint64_t *offset, int64 *inode, char **filename,
uint64_t *file_mapping, uint64_t *file_pages,
uint64_t *anon_mapping, uint64_t *anon_pages,
dev_t *dev);
~ProcMapsIterator();
private:
void Init(pid_t pid, Buffer *buffer, bool use_maps_backing);
char *ibuf_; // input buffer
char *stext_; // start of text
char *etext_; // end of text
char *nextline_; // start of next line
char *ebuf_; // end of buffer (1 char for a nul)
int fd_; // filehandle on /proc/*/maps
pid_t pid_;
char flags_[10];
Buffer* dynamic_buffer_; // dynamically-allocated Buffer
bool using_maps_backing_; // true if we are looking at maps_backing instead of maps.
};
.cpp
ProfileData g_ProfileData;
void StartProfiler(const char* fname)
{
g_ProfileData.Start(fname);
// 定时器
struct itimerval timer;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 1000000 / 100; // 0.01秒
timer.it_value = timer.it_interval;
setitimer(ITIMER_PROF, &timer, 0);
// 回调
struct sigaction sa;
sa.sa_sigaction = ProfilerSignalHandler;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&sa.sa_mask);
const int signal_number = SIGPROF;
sigaction(signal_number, &sa, NULL);
}
void StopProfiler()
{
// 定时器
struct itimerval timer;
memset(&timer, 0, sizeof timer);
setitimer(ITIMER_PROF, &timer, 0);
// 回调
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
const int signal_number = SIGPROF;
sigaction(signal_number, &sa, NULL);
g_ProfileData.Stop();
}
void ProfileData::Add(int depth, const void* const* stack)
{
if (!enabled())
{
return;
}
if (depth > kMaxStackDepth)
{
depth = kMaxStackDepth;
}
// Make hash-value
Slot h = 0;
for (int i = 0; i < depth; i++)
{
Slot slot = reinterpret_cast<Slot>(stack[i]);
h = (h << 8) | (h >> (8*(sizeof(h)-1)));
h += (slot * 31) + (slot * 7) + (slot * 3);
}
count_++;
// See if table already has an entry for this trace
bool done = false;
Bucket* bucket = &hash_[h % kBuckets];
for (int a = 0; a < kAssociativity; a++)
{
Entry* e = &bucket->entry[a];
if ((int)e->depth == depth)
{
bool match = true;
for (int i = 0; i < depth; i++)
{
if (e->stack[i] != reinterpret_cast<Slot>(stack[i]))
{
match = false;
break;
}
}
if (match)
{
e->count++;
done = true;
break;
}
}
}
if (!done)
{
// Evict entry with smallest count
Entry* e = &bucket->entry[0];
for (int a = 1; a < kAssociativity; a++)
{
if (bucket->entry[a].count < e->count)
{
e = &bucket->entry[a];
}
}
if (e->count > 0)
{
evictions_++;
Evict(*e);
}
// Use the newly evicted entry
e->depth = depth;
e->count = 1;
for (int i = 0; i < depth; i++)
{
e->stack[i] = reinterpret_cast<Slot>(stack[i]);
}
}
}
void ProfileData::Evict(const Entry& entry)
{
const int d = entry.depth;
const int nslots = d + 2; // Number of slots needed in eviction buffer
if (num_evicted_ + nslots > kBufferLength)
{
FlushEvicted();
assert(num_evicted_ == 0);
assert(nslots <= kBufferLength);
}
evict_[num_evicted_++] = entry.count;
evict_[num_evicted_++] = d;
memcpy(&evict_[num_evicted_], entry.stack, d * sizeof(Slot));
num_evicted_ += d;
}
static void FDWrite(int fd, const char* buf, size_t len)
{
while (len > 0)
{
ssize_t r;
r = write(fd, buf, len);
if (r == -1)
{
return;
}
buf += r;
len -= r;
}
}
static void DumpProcSelfMaps(int fd)
{
ProcMapsIterator::Buffer iterbuf;
ProcMapsIterator it(0, &iterbuf); // 0 means "current pid"
uint64_t start, end, offset;
int64 inode;
char *flags, *filename;
ProcMapsIterator::Buffer linebuf;
while (it.Next(&start, &end, &flags, &offset, &inode, &filename))
{
int written = it.FormatLine(linebuf.buf_, sizeof(linebuf.buf_),
start, end, flags, offset, inode, filename,
0);
FDWrite(fd, linebuf.buf_, written);
}
}
void ProfileData::FlushEvicted()
{
if (num_evicted_ > 0)
{
const char* buf = reinterpret_cast<char*>(evict_);
size_t bytes = sizeof(evict_[0]) * num_evicted_;
total_bytes_ += bytes;
FDWrite(out_, buf, bytes);
}
num_evicted_ = 0;
}
void ProfileData::Stop()
{
if (!enabled())
{
return;
}
// Move data from hash table to eviction buffer
for (int b = 0; b < kBuckets; b++)
{
Bucket* bucket = &hash_[b];
for (int a = 0; a < kAssociativity; a++)
{
if (bucket->entry[a].count > 0)
{
Evict(bucket->entry[a]);
}
}
}
if (num_evicted_ + 3 > kBufferLength)
{
// Ensure there is enough room for end of data marker
FlushEvicted();
}
// Write end of data marker
evict_[num_evicted_++] = 0; // count
evict_[num_evicted_++] = 1; // depth
evict_[num_evicted_++] = 0; // end of data marker
FlushEvicted();
// Dump "/proc/self/maps" so we get list of mapped shared libraries
DumpProcSelfMaps(out_);
Reset();
fprintf(stderr, "PROFILE: interrupts/evictions/bytes = %d/%d/%zu\n",
count_, evictions_, total_bytes_);
}
void ProfileData::Reset()
{
if (!enabled())
{
return;
}
// Don't reset count_, evictions_, or total_bytes_ here. They're used
// by Stop to print information about the profile after reset, and are
// cleared by Start when starting a new profile.
close(out_);
delete[] hash_;
hash_ = 0;
delete[] evict_;
evict_ = 0;
num_evicted_ = 0;
free(fname_);
fname_ = 0;
start_time_ = 0;
out_ = -1;
}
ProfileData::ProfileData()
: hash_(0),
evict_(0),
num_evicted_(0),
out_(-1),
count_(0),
evictions_(0),
total_bytes_(0),
fname_(0),
start_time_(0)
{
}
ProfileData::~ProfileData()
{
Stop();
}
bool ProfileData::Start(const char* fname)
{
if (enabled())
{
return false;
}
// Open output file and initialize various data structures
int fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0)
{
// Can't open outfile for write
return false;
}
start_time_ = time(NULL);
fname_ = strdup(fname);
// Reset counters
num_evicted_ = 0;
count_ = 0;
evictions_ = 0;
total_bytes_ = 0;
hash_ = new Bucket[kBuckets];
evict_ = new Slot[kBufferLength];
memset(hash_, 0, sizeof(hash_[0]) * kBuckets);
// Record special entries
evict_[num_evicted_++] = 0; // count for header
evict_[num_evicted_++] = 3; // depth for header
evict_[num_evicted_++] = 0; // Version number
int period = 1000000 / 100;
evict_[num_evicted_++] = period; // Period (microseconds)
evict_[num_evicted_++] = 0; // Padding
out_ = fd;
return true;
}
void ProfilerSignalHandler(int sig, siginfo_t* sinfo, void* ucontext)
{
const int maxLevel = 200;
void* buffer[maxLevel];
int level = backtrace(buffer, maxLevel);
g_ProfileData.Add(level - 2, buffer + 2);
}
static void ConstructFilename(const char* spec, pid_t pid, char* buf, int buf_size)
{
snprintf(buf, buf_size, spec, static_cast<int>(pid ? pid : getpid()));
}
ProcMapsIterator::ProcMapsIterator(pid_t pid, Buffer *buffer)
{
Init(pid, buffer, false);
}
void ProcMapsIterator::Init(pid_t pid, Buffer *buffer, bool use_maps_backing)
{
pid_ = pid;
using_maps_backing_ = use_maps_backing;
dynamic_buffer_ = NULL;
if (!buffer)
{
// If the user didn't pass in any buffer storage, allocate it
// now. This is the normal case; the signal handler passes in a
// static buffer.
buffer = dynamic_buffer_ = new Buffer;
}
else
{
dynamic_buffer_ = NULL;
}
ibuf_ = buffer->buf_;
stext_ = etext_ = nextline_ = ibuf_;
ebuf_ = ibuf_ + Buffer::kBufSize - 1;
nextline_ = ibuf_;
if (use_maps_backing)
{ // don't bother with clever "self" stuff in this case
ConstructFilename("/proc/%d/maps_backing", pid, ibuf_, Buffer::kBufSize);
}
else if (pid == 0)
{
// We have to kludge a bit to deal with the args ConstructFilename
// expects. The 1 is never used -- it's only impt. that it's not 0.
ConstructFilename("/proc/self/maps", 1, ibuf_, Buffer::kBufSize);
}
else
{
ConstructFilename("/proc/%d/maps", pid, ibuf_, Buffer::kBufSize);
}
// No error logging since this can be called from the crash dump
// handler at awkward moments. Users should call Valid() before
// using.
fd_ = open(ibuf_, O_RDONLY);
}
ProcMapsIterator::~ProcMapsIterator()
{
if (fd_ >= 0)
{
close(fd_);
}
delete dynamic_buffer_;
}
bool ProcMapsIterator::Next(uint64_t *start, uint64_t *end, char **flags, uint64_t *offset, int64 *inode, char **filename)
{
return NextExt(start, end, flags, offset, inode, filename, NULL, NULL, NULL, NULL, NULL);
}
// Finds |c| in |text|, and assign '\0' at the found position.
// The original character at the modified position should be |c|.
// A pointer to the modified position is stored in |endptr|.
// |endptr| should not be NULL.
static bool ExtractUntilChar(char *text, int c, char **endptr)
{
char *found;
found = strchr(text, c);
if (found == NULL)
{
*endptr = NULL;
return false;
}
*endptr = found;
*found = '\0';
return true;
}
template<class T>
static T StringToInteger(char *text, char **endptr, int base)
{
assert(false);
return T();
}
template<>
int StringToInteger<int>(char *text, char **endptr, int base)
{
return strtol(text, endptr, base);
}
template<>
int64 StringToInteger<int64>(char *text, char **endptr, int base)
{
return strtoll(text, endptr, base);
}
template<>
uint64_t StringToInteger<uint64_t>(char *text, char **endptr, int base)
{
return strtoull(text, endptr, base);
}
// Increments |*text_pointer| while it points a whitespace character.
// It is to follow sscanf's whilespace handling.
static void SkipWhileWhitespace(char **text_pointer, int c)
{
if (isspace(c))
{
while (isspace(**text_pointer) && isspace(*((*text_pointer) + 1)))
{
++(*text_pointer);
}
}
}
template<typename T>
static T StringToIntegerUntilChar(char *text, int base, int c, char **endptr_result)
{
*endptr_result = NULL;
char *endptr_extract;
if (!ExtractUntilChar(text, c, &endptr_extract))
return 0;
T result;
char *endptr_strto;
result = StringToInteger<T>(text, &endptr_strto, base);
*endptr_extract = c;
if (endptr_extract != endptr_strto)
return 0;
*endptr_result = endptr_extract;
SkipWhileWhitespace(endptr_result, c);
return result;
}
template<typename T>
static bool StringToIntegerUntilCharWithCheck(T *outptr, char *text, int base, int c, char **endptr)
{
*outptr = StringToIntegerUntilChar<T>(*endptr, base, c, endptr);
if (*endptr == NULL || **endptr == '\0') return false;
++(*endptr);
return true;
}
static char *CopyStringUntilChar(char *text, unsigned out_len, int c, char *out)
{
char *endptr;
if (!ExtractUntilChar(text, c, &endptr))
return NULL;
strncpy(out, text, out_len);
out[out_len-1] = '\0';
*endptr = c;
SkipWhileWhitespace(&endptr, c);
return endptr;
}
static bool ParseProcMapsLine(char *text, uint64_t *start, uint64_t *end,
char *flags, uint64_t *offset,
int *major, int *minor, int64 *inode,
unsigned *filename_offset)
{
/*
* It's similar to:
* sscanf(text, "%"SCNx64"-%"SCNx64" %4s %"SCNx64" %x:%x %"SCNd64" %n",
* start, end, flags, offset, major, minor, inode, filename_offset)
*/
char *endptr = text;
if (endptr == NULL || *endptr == '\0') return false;
if (!StringToIntegerUntilCharWithCheck(start, endptr, 16, '-', &endptr))
return false;
if (!StringToIntegerUntilCharWithCheck(end, endptr, 16, ' ', &endptr))
return false;
endptr = CopyStringUntilChar(endptr, 5, ' ', flags);
if (endptr == NULL || *endptr == '\0') return false;
++endptr;
if (!StringToIntegerUntilCharWithCheck(offset, endptr, 16, ' ', &endptr))
return false;
if (!StringToIntegerUntilCharWithCheck(major, endptr, 16, ':', &endptr))
return false;
if (!StringToIntegerUntilCharWithCheck(minor, endptr, 16, ' ', &endptr))
return false;
if (!StringToIntegerUntilCharWithCheck(inode, endptr, 10, ' ', &endptr))
return false;
*filename_offset = (endptr - text);
return true;
}
// This has too many arguments. It should really be building
// a map object and returning it. The problem is that this is called
// when the memory allocator state is undefined, hence the arguments.
bool ProcMapsIterator::NextExt(uint64_t *start, uint64_t *end, char **flags,
uint64_t *offset, int64 *inode, char **filename,
uint64_t *file_mapping, uint64_t *file_pages,
uint64_t *anon_mapping, uint64_t *anon_pages,
dev_t *dev)
{
do
{
// Advance to the start of the next line
stext_ = nextline_;
// See if we have a complete line in the buffer already
nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ - stext_));
if (!nextline_)
{
// Shift/fill the buffer so we do have a line
int count = etext_ - stext_;
// Move the current text to the start of the buffer
memmove(ibuf_, stext_, count);
stext_ = ibuf_;
etext_ = ibuf_ + count;
int nread = 0; // fill up buffer with text
while (etext_ < ebuf_)
{
nread = read(fd_, etext_, ebuf_ - etext_);
if (nread > 0)
etext_ += nread;
else
break;
}
// Zero out remaining characters in buffer at EOF to avoid returning
// garbage from subsequent calls.
if (etext_ != ebuf_ && nread == 0)
{
memset(etext_, 0, ebuf_ - etext_);
}
*etext_ = '\n'; // sentinel; safe because ibuf extends 1 char beyond ebuf
nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ + 1 - stext_));
}
*nextline_ = 0; // turn newline into nul
nextline_ += ((nextline_ < etext_)? 1 : 0); // skip nul if not end of text
// stext_ now points at a nul-terminated line
uint64_t tmpstart, tmpend, tmpoffset;
int64 tmpinode;
int major, minor;
unsigned filename_offset = 0;
// for now, assume all linuxes have the same format
if (!ParseProcMapsLine(
stext_,
start ? start : &tmpstart,
end ? end : &tmpend,
flags_,
offset ? offset : &tmpoffset,
&major, &minor,
inode ? inode : &tmpinode, &filename_offset)) continue;
// Depending on the Linux kernel being used, there may or may not be a space
// after the inode if there is no filename. sscanf will in such situations
// nondeterministically either fill in filename_offset or not (the results
// differ on multiple calls in the same run even with identical arguments).
// We don't want to wander off somewhere beyond the end of the string.
size_t stext_length = strlen(stext_);
if (filename_offset == 0 || filename_offset > stext_length)
filename_offset = stext_length;
// We found an entry
if (flags) *flags = flags_;
if (filename) *filename = stext_ + filename_offset;
if (dev) *dev = minor | (major << 8);
if (using_maps_backing_)
{
// Extract and parse physical page backing info.
char *backing_ptr = stext_ + filename_offset + strlen(stext_+filename_offset);
// find the second '('
int paren_count = 0;
while (--backing_ptr > stext_)
{
if (*backing_ptr == '(')
{
++paren_count;
if (paren_count >= 2)
{
uint64_t tmp_file_mapping;
uint64_t tmp_file_pages;
uint64_t tmp_anon_mapping;
uint64_t tmp_anon_pages;
sscanf(backing_ptr+1, "F %" "llx" " %" "lld" ") (A %" "llx" " %" "lld" ")",
(long long unsigned int*)(file_mapping ? file_mapping : &tmp_file_mapping),
(long long int*)(file_pages ? file_pages : &tmp_file_pages),
(long long unsigned int*)(anon_mapping ? anon_mapping : &tmp_anon_mapping),
(long long int*)(anon_pages ? anon_pages : &tmp_anon_pages));
// null terminate the file name (there is a space
// before the first (.
backing_ptr[-1] = 0;
break;
}
}
}
}
return true;
} while (etext_ > ibuf_);
// We didn't find anything
return false;
}
int ProcMapsIterator::FormatLine(char* buffer, int bufsize,
uint64_t start, uint64_t end, const char *flags,
uint64_t offset, int64 inode,
const char *filename, dev_t dev)
{
// We assume 'flags' looks like 'rwxp' or 'rwx'.
char r = (flags && flags[0] == 'r') ? 'r' : '-';
char w = (flags && flags[0] && flags[1] == 'w') ? 'w' : '-';
char x = (flags && flags[0] && flags[1] && flags[2] == 'x') ? 'x' : '-';
// p always seems set on linux, so we set the default to 'p', not '-'
char p = (flags && flags[0] && flags[1] && flags[2] && flags[3] != 'p')
? '-' : 'p';
const int rc = snprintf(buffer, bufsize,
"%08" "llx" "-%08" "llx" " %c%c%c%c %08" "llx" " %02x:%02x %-11" "lld" " %s\n",
(long long int)start, (long long int)end, r,w,x,p, (long long int)offset,
static_cast<int>(dev/256), static_cast<int>(dev%256),
(long long int)inode, filename);
return (rc < 0 || rc >= bufsize) ? 0 : rc;
}