// 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 #include #include #include #include #include #include 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::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::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(sizeof(buf))) { buf[len++] = ' '; } strcpy(buf + len, "\r"); fwrite(buf, len + 1, 1, stdout); fflush(stdout); } private: std::vector average_; std::vector 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; }