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