Home | History | Annotate | Download | only in quipper
      1 // Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "perf_recorder.h"
      6 
      7 #include <algorithm>
      8 #include <cstdio>
      9 #include <cstring>
     10 #include <sstream>
     11 #include <vector>
     12 
     13 #include "binary_data_utils.h"
     14 #include "compat/proto.h"
     15 #include "compat/string.h"
     16 #include "perf_option_parser.h"
     17 #include "perf_parser.h"
     18 #include "perf_protobuf_io.h"
     19 #include "perf_stat_parser.h"
     20 #include "run_command.h"
     21 #include "scoped_temp_path.h"
     22 
     23 namespace quipper {
     24 
     25 namespace {
     26 
     27 // Supported perf subcommands.
     28 const char kPerfRecordCommand[] = "record";
     29 const char kPerfStatCommand[] = "stat";
     30 const char kPerfMemCommand[] = "mem";
     31 
     32 // Reads a perf data file and converts it to a PerfDataProto, which is stored as
     33 // a serialized string in |output_string|. Returns true on success.
     34 bool ParsePerfDataFileToString(const string& filename, string* output_string) {
     35   // Now convert it into a protobuf.
     36 
     37   PerfParserOptions options;
     38   // Make sure to remap address for security reasons.
     39   options.do_remap = true;
     40   // Discard unused perf events to reduce the protobuf size.
     41   options.discard_unused_events = true;
     42   // Read buildids from the filesystem ourself.
     43   options.read_missing_buildids = true;
     44   // Resolve split huge pages mappings.
     45   options.deduce_huge_page_mappings = true;
     46 
     47   PerfDataProto perf_data;
     48   return SerializeFromFileWithOptions(filename, options, &perf_data) &&
     49          perf_data.SerializeToString(output_string);
     50 }
     51 
     52 // Reads a perf data file and converts it to a PerfStatProto, which is stored as
     53 // a serialized string in |output_string|. Returns true on success.
     54 bool ParsePerfStatFileToString(const string& filename,
     55                                const std::vector<string>& command_line_args,
     56                                string* output_string) {
     57   PerfStatProto perf_stat;
     58   if (!ParsePerfStatFileToProto(filename, &perf_stat)) {
     59     LOG(ERROR) << "Failed to parse PerfStatProto from " << filename;
     60     return false;
     61   }
     62 
     63   // Fill in the command line field of the protobuf.
     64   string command_line;
     65   for (size_t i = 0; i < command_line_args.size(); ++i) {
     66     const string& arg = command_line_args[i];
     67     // Strip the output file argument from the command line.
     68     if (arg == "-o") {
     69       ++i;
     70       continue;
     71     }
     72     command_line.append(arg + " ");
     73   }
     74   command_line.resize(command_line.size() - 1);
     75   perf_stat.mutable_command_line()->assign(command_line);
     76 
     77   return perf_stat.SerializeToString(output_string);
     78 }
     79 
     80 }  // namespace
     81 
     82 PerfRecorder::PerfRecorder() : PerfRecorder({"/usr/bin/perf"}) {}
     83 
     84 PerfRecorder::PerfRecorder(const std::vector<string>& perf_binary_command)
     85     : perf_binary_command_(perf_binary_command) {}
     86 
     87 bool PerfRecorder::RunCommandAndGetSerializedOutput(
     88     const std::vector<string>& perf_args, const double time_sec,
     89     string* output_string) {
     90   if (!ValidatePerfCommandLine(perf_args)) {
     91     LOG(ERROR) << "Perf arguments are not safe to run";
     92     return false;
     93   }
     94 
     95   // ValidatePerfCommandLine should have checked perf_args[0] == "perf", and
     96   // that perf_args[1] is a supported sub-command (e.g. "record" or "stat").
     97 
     98   const string& perf_type = perf_args[1];
     99 
    100   if (perf_type != kPerfRecordCommand && perf_type != kPerfStatCommand &&
    101       perf_type != kPerfMemCommand) {
    102     LOG(ERROR) << "Unsupported perf subcommand: " << perf_type;
    103     return false;
    104   }
    105 
    106   ScopedTempFile output_file;
    107 
    108   // Assemble the full command line:
    109   // - Replace "perf" in |perf_args[0]| with |perf_binary_command_| to
    110   //   guarantee we're running a binary we believe we can trust.
    111   // - Add our own paramters.
    112 
    113   std::vector<string> full_perf_args(perf_binary_command_);
    114   full_perf_args.insert(full_perf_args.end(),
    115                         perf_args.begin() + 1,  // skip "perf"
    116                         perf_args.end());
    117   full_perf_args.insert(full_perf_args.end(), {"-o", output_file.path()});
    118 
    119   // The perf stat output parser requires raw data from verbose output.
    120   if (perf_type == kPerfStatCommand) full_perf_args.emplace_back("-v");
    121 
    122   // Append the sleep command to run perf for |time_sec| seconds.
    123   std::stringstream time_string;
    124   time_string << time_sec;
    125   full_perf_args.insert(full_perf_args.end(),
    126                         {"--", "sleep", time_string.str()});
    127 
    128   // The perf command writes the output to a file, so ignore stdout.
    129   int status = RunCommand(full_perf_args, nullptr);
    130   if (status != 0) {
    131     PLOG(ERROR) << "perf command failed with status: " << status << ", Error";
    132     return false;
    133   }
    134 
    135   if (perf_type == kPerfRecordCommand || perf_type == kPerfMemCommand)
    136     return ParsePerfDataFileToString(output_file.path(), output_string);
    137 
    138   // Otherwise, parse as perf stat output.
    139   return ParsePerfStatFileToString(output_file.path(), full_perf_args,
    140                                    output_string);
    141 }
    142 
    143 }  // namespace quipper
    144