Home | History | Annotate | Download | only in quipper
      1 // Copyright (c) 2013 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 "test_utils.h"
      6 
      7 #include <string.h>
      8 
      9 #include <algorithm>
     10 #include <cstdlib>
     11 #include <sstream>
     12 
     13 #include "base/logging.h"
     14 
     15 #include "compat/proto.h"
     16 #include "file_reader.h"
     17 #include "file_utils.h"
     18 #include "perf_protobuf_io.h"
     19 #include "run_command.h"
     20 #include "string_utils.h"
     21 
     22 using quipper::PerfDataProto;
     23 using quipper::SplitString;
     24 using quipper::TextFormat;
     25 
     26 namespace {
     27 
     28 // Newline character.
     29 const char kNewLineDelimiter = '\n';
     30 
     31 // Extension of protobuf files in text format.
     32 const char kProtobufTextExtension[] = ".pb_text";
     33 
     34 // Extension of protobuf files in serialized format.
     35 const char kProtobufDataExtension[] = ".pb_data";
     36 
     37 // Extension of build ID lists.
     38 const char kBuildIDListExtension[] = ".buildids";
     39 
     40 enum PerfDataType {
     41   kPerfDataNormal,  // Perf data is in normal format.
     42   kPerfDataPiped,   // Perf data is in piped format.
     43 };
     44 
     45 // The piped commands above produce comma-separated lines with the following
     46 // fields:
     47 enum {
     48   PERF_REPORT_OVERHEAD,
     49   PERF_REPORT_SAMPLES,
     50   PERF_REPORT_COMMAND,
     51   PERF_REPORT_SHARED_OBJECT,
     52   NUM_PERF_REPORT_FIELDS,
     53 };
     54 
     55 // Split a char buffer into separate lines.
     56 void SeparateLines(const std::vector<char>& bytes, std::vector<string>* lines) {
     57   if (!bytes.empty())
     58     SplitString(string(&bytes[0], bytes.size()), kNewLineDelimiter, lines);
     59 }
     60 
     61 bool ReadExistingProtobufText(const string& filename, string* output_string) {
     62   std::vector<char> output_buffer;
     63   if (!quipper::FileToBuffer(filename, &output_buffer)) {
     64     LOG(ERROR) << "Could not open file " << filename;
     65     return false;
     66   }
     67   output_string->assign(&output_buffer[0], output_buffer.size());
     68   return true;
     69 }
     70 
     71 // Given a perf data file, return its protobuf representation as a text string
     72 // and/or a serialized data stream.
     73 bool PerfDataToProtoRepresentation(const string& filename, string* output_text,
     74                                    string* output_data) {
     75   PerfDataProto perf_data_proto;
     76   if (!SerializeFromFile(filename, &perf_data_proto)) {
     77     return false;
     78   }
     79   // Reset the timestamp field since it causes reproducability issues when
     80   // testing.
     81   perf_data_proto.set_timestamp_sec(0);
     82 
     83   if (output_text && !TextFormat::PrintToString(perf_data_proto, output_text)) {
     84     return false;
     85   }
     86   if (output_data && !perf_data_proto.SerializeToString(output_data))
     87     return false;
     88 
     89   return true;
     90 }
     91 
     92 }  // namespace
     93 
     94 namespace quipper {
     95 
     96 const char* kSupportedMetadata[] = {
     97     "hostname",
     98     "os release",
     99     "perf version",
    100     "arch",
    101     "nrcpus online",
    102     "nrcpus avail",
    103     "cpudesc",
    104     "cpuid",
    105     "total memory",
    106     "cmdline",
    107     "event",
    108     "sibling cores",    // CPU topology.
    109     "sibling threads",  // CPU topology.
    110     "node0 meminfo",    // NUMA topology.
    111     "node0 cpu list",   // NUMA topology.
    112     "node1 meminfo",    // NUMA topology.
    113     "node1 cpu list",   // NUMA topology.
    114     NULL,
    115 };
    116 string GetTestInputFilePath(const string& filename) {
    117   return "testdata/" + filename;
    118 }
    119 
    120 string GetPerfPath() {
    121   return "/usr/bin/perf";
    122 }
    123 
    124 int64_t GetFileSize(const string& filename) {
    125   FileReader reader(filename);
    126   if (!reader.IsOpen()) return -1;
    127   return reader.size();
    128 }
    129 
    130 bool CompareFileContents(const string& filename1, const string& filename2) {
    131   std::vector<char> file1_contents;
    132   std::vector<char> file2_contents;
    133   if (!FileToBuffer(filename1, &file1_contents) ||
    134       !FileToBuffer(filename2, &file2_contents)) {
    135     return false;
    136   }
    137 
    138   return file1_contents == file2_contents;
    139 }
    140 
    141 bool GetPerfBuildIDMap(const string& filename,
    142                        std::map<string, string>* output) {
    143   // Try reading from a pre-generated report.  If it doesn't exist, call perf
    144   // buildid-list.
    145   std::vector<char> buildid_list;
    146   LOG(INFO) << filename + kBuildIDListExtension;
    147   if (!quipper::FileToBuffer(filename + kBuildIDListExtension, &buildid_list)) {
    148     buildid_list.clear();
    149     if (RunCommand({GetPerfPath(), "buildid-list", "--force", "-i", filename},
    150                    &buildid_list) != 0) {
    151       LOG(ERROR) << "Failed to run perf buildid-list";
    152       return false;
    153     }
    154   }
    155   std::vector<string> lines;
    156   SeparateLines(buildid_list, &lines);
    157 
    158   /* The output now looks like the following:
    159      cff4586f322eb113d59f54f6e0312767c6746524 [kernel.kallsyms]
    160      c099914666223ff6403882604c96803f180688f5 /lib64/libc-2.15.so
    161      7ac2d19f88118a4970adb48a84ed897b963e3fb7 /lib64/libpthread-2.15.so
    162   */
    163   output->clear();
    164   for (string line : lines) {
    165     TrimWhitespace(&line);
    166     size_t separator = line.find(' ');
    167     string build_id = line.substr(0, separator);
    168     string filename = line.substr(separator + 1);
    169     (*output)[filename] = build_id;
    170   }
    171 
    172   return true;
    173 }
    174 
    175 namespace {
    176 // Running tests while this is true will blindly make tests pass. So, remember
    177 // to look at the diffs and explain them before submitting.
    178 static const bool kWriteNewGoldenFiles = false;
    179 
    180 // This flag enables comparisons of protobufs in serialized format as a faster
    181 // alternative to comparing their human-readable text representations. Set this
    182 // flag to false to compare text representations instead. It's also useful for
    183 // diffing against the old golden files when writing new golden files.
    184 const bool UseProtobufDataFormat = true;
    185 }  // namespace
    186 
    187 bool CheckPerfDataAgainstBaseline(const string& filename) {
    188   string extension =
    189       UseProtobufDataFormat ? kProtobufDataExtension : kProtobufTextExtension;
    190   string protobuf_representation;
    191   if (UseProtobufDataFormat) {
    192     if (!PerfDataToProtoRepresentation(filename, nullptr,
    193                                        &protobuf_representation)) {
    194       return false;
    195     }
    196   } else {
    197     if (!PerfDataToProtoRepresentation(filename, &protobuf_representation,
    198                                        nullptr)) {
    199       return false;
    200     }
    201   }
    202 
    203   string existing_input_file =
    204       GetTestInputFilePath(basename(filename.c_str())) + extension;
    205   string baseline;
    206   if (!ReadExistingProtobufText(existing_input_file, &baseline)) {
    207     return false;
    208   }
    209   bool matches_baseline = (baseline == protobuf_representation);
    210   if (kWriteNewGoldenFiles) {
    211     string existing_input_pb_text = existing_input_file + ".new";
    212     if (matches_baseline) {
    213       LOG(ERROR) << "Not writing non-identical golden file: "
    214                  << existing_input_pb_text;
    215       return true;
    216     }
    217     LOG(INFO) << "Writing new golden file: " << existing_input_pb_text;
    218     BufferToFile(existing_input_pb_text, protobuf_representation);
    219 
    220     return true;
    221   }
    222   return matches_baseline;
    223 }
    224 
    225 bool ComparePerfBuildIDLists(const string& file1, const string& file2) {
    226   std::map<string, string> output1;
    227   std::map<string, string> output2;
    228 
    229   // Generate a build id list for each file.
    230   CHECK(GetPerfBuildIDMap(file1, &output1));
    231   CHECK(GetPerfBuildIDMap(file2, &output2));
    232 
    233   // Compare the output strings.
    234   return output1 == output2;
    235 }
    236 
    237 PerfParserOptions GetTestOptions() {
    238   PerfParserOptions options;
    239   options.sample_mapping_percentage_threshold = 100.0f;
    240   return options;
    241 }
    242 
    243 }  // namespace quipper
    244