blob: 5d9b66aaac37a1dab7a2033d2a3bd63c829612cf [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper program to analyze the time that Chrome's renderers spend in system
// calls. Start Chrome like this:
//
// SECCOMP_SANDBOX_DEBUGGING=1 chrome --enable-seccomp-sandbox 2>&1 | timestats
//
// The program prints CPU time (0-100%) spent within system calls. This gives
// a general idea of where it is worthwhile to spend effort optimizing Chrome.
//
// Caveats:
// - there currently is no way to estimate what the overhead is for running
// inside of the sandbox vs. running without a sandbox.
// - we currently use a very simple heuristic to decide whether a system call
// is blocking or not. Blocking system calls should not be included in the
// computations. But it is quite possible for the numbers to be somewhat
// wrong, because the heuristic failed.
// - in order to collect this data, we have to turn on sandbox debugging.
// There is a measurable performance penalty to doing so. Production numbers
// are strictly better than the numbers reported by this tool.
#include <set>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
static const int kAvgWindowSizeMs = 500;
static const int kPeakWindowSizeMs = 2*1000;
// Class containing information on a single system call. Most notably, it
// contains the time when the system call happened, and the time that it
// took to complete.
class Datum {
friend class Data;
public:
Datum(const char* name, double ms)
: name_(name),
ms_(ms) {
struct timeval tv;
gettimeofday(&tv, NULL);
timestamp_ = tv.tv_sec*1000.0 + tv.tv_usec/1000.0;
}
virtual ~Datum() { }
double operator-(const Datum& b) {
return timestamp_ - b.timestamp_;
}
protected:
const char* name_;
double ms_;
double timestamp_;
};
// Class containing data on the most recent system calls. It maintains
// sliding averages for total CPU time used, and it also maintains a peak
// CPU usage. The peak usage is usually updated slower than the average
// usage, as that makes it easier to inspect visually.
class Data {
public:
Data() { }
virtual ~Data() { }
void addData(const char* name, double ms) {
average_.push_back(Datum(name, ms));
peak_.push_back(Datum(name, ms));
// Prune entries outside of the window
std::vector<Datum>::iterator iter;
for (iter = average_.begin();
*average_.rbegin() - *iter > kAvgWindowSizeMs;
++iter) {
}
average_.erase(average_.begin(), iter);
for (iter = peak_.begin();
*peak_.rbegin() - *iter > kPeakWindowSizeMs;
++iter){
}
peak_.erase(peak_.begin(), iter);
// Add the total usage of all system calls inside of the window
double total = 0;
for (iter = average_.begin(); iter != average_.end(); ++iter) {
total += iter->ms_;
}
// Compute the peak CPU usage during the last window
double peak = 0;
double max = 0;
std::vector<Datum>::iterator tail = peak_.begin();
for (iter = tail; iter != peak_.end(); ++iter) {
while (*iter - *tail > kAvgWindowSizeMs) {
peak -= tail->ms_;
++tail;
}
peak += iter->ms_;
if (peak > max) {
max = peak;
}
}
// Print the average CPU usage in the last window
char buf[80];
total *= 100.0/kAvgWindowSizeMs;
max *= 100.0/kAvgWindowSizeMs;
sprintf(buf, "%6.2f%% (peak=%6.2f%%) ", total, max);
// Animate the actual usage, displaying both average and peak values
int len = strlen(buf);
int space = sizeof(buf) - len - 1;
int mark = (total * space + 50)/100;
int bar = (max * space + 50)/100;
for (int i = 0; i < mark; ++i) {
buf[len++] = '*';
}
if (mark == bar) {
if (bar) {
len--;
}
} else {
for (int i = 0; i < bar - mark - 1; ++i) {
buf[len++] = ' ';
}
}
buf[len++] = '|';
while (len < static_cast<int>(sizeof(buf))) {
buf[len++] = ' ';
}
strcpy(buf + len, "\r");
fwrite(buf, len + 1, 1, stdout);
fflush(stdout);
}
private:
std::vector<Datum> average_;
std::vector<Datum> peak_;
};
static Data data;
int main(int argc, char *argv[]) {
char buf[80];
bool expensive = false;
while (fgets(buf, sizeof(buf), stdin)) {
// Allow longer delays for expensive system calls
if (strstr(buf, "This is an expensive system call")) {
expensive = true;
continue;
}
// Parse the string and extract the elapsed time
const char elapsed[] = "Elapsed time: ";
char* ms_string = strstr(buf, elapsed);
char* endptr;
double ms;
char* colon = strchr(buf, ':');
// If this string doesn't match, then it must be some other type of
// message. Just ignore it.
// It is quite likely that we will regularly encounter debug messages
// that either should be parsed by a completely different tool, or
// messages that were intended for humans to read.
if (!ms_string ||
((ms = strtod(ms_string + sizeof(elapsed) - 1, &endptr)),
endptr == ms_string) ||
!colon) {
continue;
}
// Filter out system calls that were probably just blocking
// TODO(markus): automatically compute the cut-off for blocking calls
if (!expensive && ms > 0.05) {
continue;
}
expensive = false;
// Extract the name of the system call
*colon = '\000';
// Add the data point and update the display
data.addData(buf, ms);
}
puts("");
return 0;
}