// Copyright (c) 2013 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "test_utils.h" #include #include #include #include #include "base/logging.h" #include "compat/proto.h" #include "file_reader.h" #include "file_utils.h" #include "perf_protobuf_io.h" #include "run_command.h" #include "string_utils.h" using quipper::PerfDataProto; using quipper::SplitString; using quipper::TextFormat; namespace { // Newline character. const char kNewLineDelimiter = '\n'; // Extension of protobuf files in text format. const char kProtobufTextExtension[] = ".pb_text"; // Extension of protobuf files in serialized format. const char kProtobufDataExtension[] = ".pb_data"; // Extension of build ID lists. const char kBuildIDListExtension[] = ".buildids"; enum PerfDataType { kPerfDataNormal, // Perf data is in normal format. kPerfDataPiped, // Perf data is in piped format. }; // The piped commands above produce comma-separated lines with the following // fields: enum { PERF_REPORT_OVERHEAD, PERF_REPORT_SAMPLES, PERF_REPORT_COMMAND, PERF_REPORT_SHARED_OBJECT, NUM_PERF_REPORT_FIELDS, }; // Split a char buffer into separate lines. void SeparateLines(const std::vector& bytes, std::vector* lines) { if (!bytes.empty()) SplitString(string(&bytes[0], bytes.size()), kNewLineDelimiter, lines); } bool ReadExistingProtobufText(const string& filename, string* output_string) { std::vector output_buffer; if (!quipper::FileToBuffer(filename, &output_buffer)) { LOG(ERROR) << "Could not open file " << filename; return false; } output_string->assign(&output_buffer[0], output_buffer.size()); return true; } // Given a perf data file, return its protobuf representation as a text string // and/or a serialized data stream. bool PerfDataToProtoRepresentation(const string& filename, string* output_text, string* output_data) { PerfDataProto perf_data_proto; if (!SerializeFromFile(filename, &perf_data_proto)) { return false; } // Reset the timestamp field since it causes reproducability issues when // testing. perf_data_proto.set_timestamp_sec(0); if (output_text && !TextFormat::PrintToString(perf_data_proto, output_text)) { return false; } if (output_data && !perf_data_proto.SerializeToString(output_data)) return false; return true; } } // namespace namespace quipper { const char* kSupportedMetadata[] = { "hostname", "os release", "perf version", "arch", "nrcpus online", "nrcpus avail", "cpudesc", "cpuid", "total memory", "cmdline", "event", "sibling cores", // CPU topology. "sibling threads", // CPU topology. "node0 meminfo", // NUMA topology. "node0 cpu list", // NUMA topology. "node1 meminfo", // NUMA topology. "node1 cpu list", // NUMA topology. NULL, }; string GetTestInputFilePath(const string& filename) { return "testdata/" + filename; } string GetPerfPath() { return "/usr/bin/perf"; } int64_t GetFileSize(const string& filename) { FileReader reader(filename); if (!reader.IsOpen()) return -1; return reader.size(); } bool CompareFileContents(const string& filename1, const string& filename2) { std::vector file1_contents; std::vector file2_contents; if (!FileToBuffer(filename1, &file1_contents) || !FileToBuffer(filename2, &file2_contents)) { return false; } return file1_contents == file2_contents; } bool GetPerfBuildIDMap(const string& filename, std::map* output) { // Try reading from a pre-generated report. If it doesn't exist, call perf // buildid-list. std::vector buildid_list; LOG(INFO) << filename + kBuildIDListExtension; if (!quipper::FileToBuffer(filename + kBuildIDListExtension, &buildid_list)) { buildid_list.clear(); if (RunCommand({GetPerfPath(), "buildid-list", "--force", "-i", filename}, &buildid_list) != 0) { LOG(ERROR) << "Failed to run perf buildid-list"; return false; } } std::vector lines; SeparateLines(buildid_list, &lines); /* The output now looks like the following: cff4586f322eb113d59f54f6e0312767c6746524 [kernel.kallsyms] c099914666223ff6403882604c96803f180688f5 /lib64/libc-2.15.so 7ac2d19f88118a4970adb48a84ed897b963e3fb7 /lib64/libpthread-2.15.so */ output->clear(); for (string line : lines) { TrimWhitespace(&line); size_t separator = line.find(' '); string build_id = line.substr(0, separator); string filename = line.substr(separator + 1); (*output)[filename] = build_id; } return true; } namespace { // Running tests while this is true will blindly make tests pass. So, remember // to look at the diffs and explain them before submitting. static const bool kWriteNewGoldenFiles = false; // This flag enables comparisons of protobufs in serialized format as a faster // alternative to comparing their human-readable text representations. Set this // flag to false to compare text representations instead. It's also useful for // diffing against the old golden files when writing new golden files. const bool UseProtobufDataFormat = true; } // namespace bool CheckPerfDataAgainstBaseline(const string& filename) { string extension = UseProtobufDataFormat ? kProtobufDataExtension : kProtobufTextExtension; string protobuf_representation; if (UseProtobufDataFormat) { if (!PerfDataToProtoRepresentation(filename, nullptr, &protobuf_representation)) { return false; } } else { if (!PerfDataToProtoRepresentation(filename, &protobuf_representation, nullptr)) { return false; } } string existing_input_file = GetTestInputFilePath(basename(filename.c_str())) + extension; string baseline; if (!ReadExistingProtobufText(existing_input_file, &baseline)) { return false; } bool matches_baseline = (baseline == protobuf_representation); if (kWriteNewGoldenFiles) { string existing_input_pb_text = existing_input_file + ".new"; if (matches_baseline) { LOG(ERROR) << "Not writing non-identical golden file: " << existing_input_pb_text; return true; } LOG(INFO) << "Writing new golden file: " << existing_input_pb_text; BufferToFile(existing_input_pb_text, protobuf_representation); return true; } return matches_baseline; } bool ComparePerfBuildIDLists(const string& file1, const string& file2) { std::map output1; std::map output2; // Generate a build id list for each file. CHECK(GetPerfBuildIDMap(file1, &output1)); CHECK(GetPerfBuildIDMap(file2, &output2)); // Compare the output strings. return output1 == output2; } PerfParserOptions GetTestOptions() { PerfParserOptions options; options.sample_mapping_percentage_threshold = 100.0f; return options; } } // namespace quipper