• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
SeparateLines(const std::vector<char> & bytes,std::vector<string> * 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 
ReadExistingProtobufText(const string & filename,string * output_string)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.
PerfDataToProtoRepresentation(const string & filename,string * output_text,string * output_data)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 };
GetTestInputFilePath(const string & filename)116 string GetTestInputFilePath(const string& filename) {
117   return "testdata/" + filename;
118 }
119 
GetPerfPath()120 string GetPerfPath() {
121   return "/usr/bin/perf";
122 }
123 
GetFileSize(const string & filename)124 int64_t GetFileSize(const string& filename) {
125   FileReader reader(filename);
126   if (!reader.IsOpen()) return -1;
127   return reader.size();
128 }
129 
CompareFileContents(const string & filename1,const string & filename2)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 
GetPerfBuildIDMap(const string & filename,std::map<string,string> * output)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 
CheckPerfDataAgainstBaseline(const string & filename)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 
ComparePerfBuildIDLists(const string & file1,const string & file2)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 
GetTestOptions()237 PerfParserOptions GetTestOptions() {
238   PerfParserOptions options;
239   options.sample_mapping_percentage_threshold = 100.0f;
240   return options;
241 }
242 
243 }  // namespace quipper
244