blob: 16ca3a05ee052d1700558850c077d299bae970a3 [file] [log] [blame]
// Copyright (c) 2012 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 <string>
#include <vector>
#include "chromiumos-wide-profiling/compat/string.h"
#include "chromiumos-wide-profiling/compat/test.h"
#include "chromiumos-wide-profiling/perf_protobuf_io.h"
#include "chromiumos-wide-profiling/perf_reader.h"
#include "chromiumos-wide-profiling/perf_recorder.h"
#include "chromiumos-wide-profiling/perf_serializer.h"
#include "chromiumos-wide-profiling/run_command.h"
#include "chromiumos-wide-profiling/test_utils.h"
namespace quipper {
// Runs "perf record" to see if the command is available on the current system.
// This should also cover the availability of "perf stat", which is a simpler
// way to get information from the counters.
bool IsPerfRecordAvailable() {
return RunCommand(
{"perf", "record", "-a", "-o", "-", "--", "sleep", "0.01"}, NULL) == 0;
}
// Runs "perf mem record" to see if the command is available on the current
// system.
bool IsPerfMemRecordAvailable() {
return RunCommand(
{"perf", "mem", "record", "-a", "-e", "cycles", "--", "sleep", "0.01"},
NULL) == 0;
}
class PerfRecorderTest : public ::testing::Test {
public:
PerfRecorderTest() : perf_recorder_({"sudo", GetPerfPath()}) {}
protected:
PerfRecorder perf_recorder_;
};
TEST_F(PerfRecorderTest, RecordToProtobuf) {
// Read perf data using the PerfReader class.
// Dump it to a string and convert to a protobuf.
// Read the protobuf, and reconstruct the perf data.
string output_string;
EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "record"}, 0.2, &output_string));
quipper::PerfDataProto perf_data_proto;
EXPECT_TRUE(perf_data_proto.ParseFromString(output_string));
const auto& string_meta = perf_data_proto.string_metadata();
const auto& command = string_meta.perf_command_line_token();
EXPECT_EQ(GetPerfPath(), command.Get(0).value());
EXPECT_EQ("record", command.Get(1).value());
EXPECT_EQ("-o", command.Get(2).value());
// Unpredictable: EXPECT_EQ("/tmp/quipper.XXXXXX", command.Get(3).value());
// Instead, check the file path length and prefix.
EXPECT_EQ(strlen("/tmp/quipper.XXXXXX"), command.Get(3).value().size());
EXPECT_EQ("/tmp/quipper",
command.Get(3).value().substr(0, strlen("/tmp/quipper")));
EXPECT_EQ("--", command.Get(4).value());
EXPECT_EQ("sleep", command.Get(5).value());
EXPECT_EQ("0.2", command.Get(6).value());
}
TEST_F(PerfRecorderTest, StatToProtobuf) {
// Run perf stat and verify output.
string output_string;
EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "stat"}, 0.2, &output_string));
EXPECT_GT(output_string.size(), 0);
quipper::PerfStatProto stat;
ASSERT_TRUE(stat.ParseFromString(output_string));
EXPECT_GT(stat.line_size(), 0);
}
TEST_F(PerfRecorderTest, MemRecordToProtobuf) {
if (!IsPerfMemRecordAvailable())
return;
// Run perf mem record and verify output.
string output_string;
EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "mem", "record"}, 0.2, &output_string));
EXPECT_GT(output_string.size(), 0);
quipper::PerfDataProto perf_data_proto;
ASSERT_TRUE(perf_data_proto.ParseFromString(output_string));
}
TEST_F(PerfRecorderTest, StatSingleEvent) {
string output_string;
ASSERT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "stat", "-a", "-e", "cycles"},
0.2, &output_string));
EXPECT_GT(output_string.size(), 0);
quipper::PerfStatProto stat;
ASSERT_TRUE(stat.ParseFromString(output_string));
// Replace the placeholder "perf" with the actual perf path.
string expected_command_line =
string("sudo ") + GetPerfPath() + " stat -a -e cycles -v -- sleep 0.2";
EXPECT_EQ(expected_command_line, stat.command_line());
// Make sure the event counter was read.
ASSERT_EQ(1, stat.line_size());
EXPECT_TRUE(stat.line(0).has_time_ms());
EXPECT_TRUE(stat.line(0).has_count());
EXPECT_TRUE(stat.line(0).has_event_name());
// Running for at least one second.
EXPECT_GE(stat.line(0).time_ms(), 200);
EXPECT_EQ("cycles", stat.line(0).event_name());
}
TEST_F(PerfRecorderTest, StatMultipleEvents) {
string output_string;
ASSERT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
{ "perf", "stat", "-a",
"-e", "cycles",
"-e", "instructions",
"-e", "branches",
"-e", "branch-misses" },
0.2, &output_string));
EXPECT_GT(output_string.size(), 0);
quipper::PerfStatProto stat;
ASSERT_TRUE(stat.ParseFromString(output_string));
// Replace the placeholder "perf" with the actual perf path.
string command_line =
string("sudo ") + GetPerfPath() + " stat -a "
"-e cycles "
"-e instructions "
"-e branches "
"-e branch-misses "
"-v "
"-- sleep 0.2";
EXPECT_TRUE(stat.has_command_line());
EXPECT_EQ(command_line, stat.command_line());
// Make sure all event counters were read.
// Check:
// - Number of events.
// - Running for at least two seconds.
// - Event names recorded properly.
ASSERT_EQ(4, stat.line_size());
EXPECT_TRUE(stat.line(0).has_time_ms());
EXPECT_TRUE(stat.line(0).has_count());
EXPECT_TRUE(stat.line(0).has_event_name());
EXPECT_GE(stat.line(0).time_ms(), 200);
EXPECT_EQ("cycles", stat.line(0).event_name());
EXPECT_TRUE(stat.line(1).has_time_ms());
EXPECT_TRUE(stat.line(1).has_count());
EXPECT_TRUE(stat.line(1).has_event_name());
EXPECT_GE(stat.line(1).time_ms(), 200);
EXPECT_EQ("instructions", stat.line(1).event_name());
EXPECT_TRUE(stat.line(2).has_time_ms());
EXPECT_TRUE(stat.line(2).has_count());
EXPECT_TRUE(stat.line(2).has_event_name());
EXPECT_GE(stat.line(2).time_ms(), 200);
EXPECT_EQ("branches", stat.line(2).event_name());
EXPECT_TRUE(stat.line(3).has_time_ms());
EXPECT_TRUE(stat.line(3).has_count());
EXPECT_TRUE(stat.line(3).has_event_name());
EXPECT_GE(stat.line(3).time_ms(), 200);
EXPECT_EQ("branch-misses", stat.line(3).event_name());
}
TEST_F(PerfRecorderTest, DontAllowCommands) {
string output_string;
EXPECT_FALSE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "record", "--", "sh", "-c", "echo 'malicious'"},
0.2, &output_string));
EXPECT_FALSE(perf_recorder_.RunCommandAndGetSerializedOutput(
{"perf", "stat", "--", "sh", "-c", "echo 'malicious'"},
0.2, &output_string));
}
TEST(PerfRecorderNoPerfTest, FailsIfPerfDoesntExist) {
string output_string;
PerfRecorder perf_recorder({"sudo", "/doesnt-exist/usr/not-bin/not-perf"});
EXPECT_FALSE(perf_recorder.RunCommandAndGetSerializedOutput(
{"perf", "record"}, 0.2, &output_string));
}
} // namespace quipper
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
if (!quipper::IsPerfRecordAvailable())
return 0;
return RUN_ALL_TESTS();
}