blob: c5f7e43ddf33217ce9fd887d048712b69ba7508e [file] [log] [blame]
// 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 "chromiumos-wide-profiling/test_utils.h"
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include "base/logging.h"
#include "chromiumos-wide-profiling/perf_serializer.h"
#include "chromiumos-wide-profiling/quipper_proto.h"
#include "chromiumos-wide-profiling/run_command.h"
#include "chromiumos-wide-profiling/utils.h"
using quipper::PerfDataProto;
using quipper::PerfSerializer;
using quipper::TextFormat;
namespace {
// Newline character.
const char kNewLineDelimiter = '\n';
// Extension of protobuf files in text format.
const char kProtobufTextExtension[] = ".pb_text";
// 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 {
// Splits a character array by |delimiter| into a vector of strings tokens.
void SplitString(const string& str,
char delimiter,
std::vector<string>* tokens) {
std::stringstream ss(str);
std::string token;
while (std::getline(ss, token, delimiter))
// Split a char buffer into separate lines.
void SeparateLines(const std::vector<char>& bytes, std::vector<string>* lines) {
if (!bytes.empty())
SplitString(string(&bytes[0], bytes.size()), kNewLineDelimiter, lines);
bool ReadExistingProtobufText(const string& filename, string* output_string) {
std::vector<char> output_buffer;
if (!quipper::FileToBuffer(filename + kProtobufTextExtension,
&output_buffer)) {
LOG(ERROR) << "Could not open file " << filename + kProtobufTextExtension;
return false;
output_string->assign(&output_buffer[0], output_buffer.size());
return true;
// Given a perf data file, return its protobuf representation (given by
// PerfSerializer) as a text string.
bool GetProtobufTextFormat(const string& filename, string* output_string) {
PerfDataProto perf_data_proto;
PerfSerializer serializer;
if (!serializer.SerializeFromFile(filename, &perf_data_proto)) {
return false;
// Reset the timestamp field since it causes reproducability issues when
// testing.
if (!TextFormat::PrintToString(perf_data_proto, output_string)) {
return false;
return true;
} // namespace
namespace quipper {
const char* kSupportedMetadata[] = {
"os release",
"perf version",
"nrcpus online",
"nrcpus avail",
"total memory",
"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.
string GetTestInputFilePath(const string& filename) {
return "testdata/" + filename;
string GetPerfPath() {
return "/usr/bin/perf";
int64_t GetFileSize(const string& filename) {
FILE* fp = fopen(filename.c_str(), "rb");
if (!fp)
return -1;
int64_t file_size = GetFileSizeFromHandle(fp);
return file_size;
bool CompareFileContents(const string& filename1, const string& filename2) {
std::vector<char> file1_contents;
std::vector<char> 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<string, string>* output) {
// Try reading from a pre-generated report. If it doesn't exist, call perf
// buildid-list.
std::vector<char> buildid_list;
LOG(INFO) << filename + kBuildIDListExtension;
if (!quipper::FileToBuffer(filename + kBuildIDListExtension, &buildid_list)) {
if (!RunCommand({GetPerfPath(), "buildid-list", "--force", "-i", filename},
&buildid_list)) {
LOG(ERROR) << "Failed to run perf buildid-list";
return false;
std::vector<string> lines;
SeparateLines(buildid_list, &lines);
/* The output now looks like the following:
cff4586f322eb113d59f54f6e0312767c6746524 [kernel.kallsyms]
c099914666223ff6403882604c96803f180688f5 /lib64/
7ac2d19f88118a4970adb48a84ed897b963e3fb7 /lib64/
for (string line : lines) {
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.
// TODO(dhsharp): replace this with a command-line flag.
static const bool kWriteNewGoldenFiles = false;
} // namespace
bool CheckPerfDataAgainstBaseline(const string& filename) {
string protobuf_text;
if (!GetProtobufTextFormat(filename, &protobuf_text)) {
return false;
string existing_input_file = GetTestInputFilePath(basename(filename.c_str()));
string baseline;
if (!ReadExistingProtobufText(existing_input_file , &baseline)) {
return false;
bool matches_baseline = (baseline == protobuf_text);
if (kWriteNewGoldenFiles) {
string existing_input_pb_text =
existing_input_file + kProtobufTextExtension + ".new";
if (matches_baseline) {
LOG(INFO) << "NOT writing 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_text);
return true;
return matches_baseline;
bool ComparePerfBuildIDLists(const string& file1, const string& file2) {
std::map<string, string> output1;
std::map<string, string> output2;
// Generate a build id list for each file.
CHECK(GetPerfBuildIDMap(file1, &output1));
CHECK(GetPerfBuildIDMap(file2, &output2));
// Compare the output strings.
return output1 == output2;
PerfParser::Options GetTestOptions() {
PerfParser::Options options;
options.sample_mapping_percentage_threshold = 100.0f;
return options;
} // namespace quipper