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