1 // Copyright (c) 2012 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 <string>
6 #include <vector>
7
8 #include "compat/string.h"
9 #include "compat/test.h"
10 #include "perf_protobuf_io.h"
11 #include "perf_reader.h"
12 #include "perf_recorder.h"
13 #include "perf_serializer.h"
14 #include "run_command.h"
15 #include "test_utils.h"
16
17 namespace quipper {
18
19 // Runs "perf record" to see if the command is available on the current system.
20 // This should also cover the availability of "perf stat", which is a simpler
21 // way to get information from the counters.
IsPerfRecordAvailable()22 bool IsPerfRecordAvailable() {
23 return RunCommand({"perf", "record", "-a", "-o", "-", "--", "sleep", "0.01"},
24 NULL) == 0;
25 }
26
27 // Runs "perf mem record" to see if the command is available on the current
28 // system.
IsPerfMemRecordAvailable()29 bool IsPerfMemRecordAvailable() {
30 return RunCommand({"perf", "mem", "record", "-a", "-e", "cycles", "--",
31 "sleep", "0.01"},
32 NULL) == 0;
33 }
34
35 class PerfRecorderTest : public ::testing::Test {
36 public:
PerfRecorderTest()37 PerfRecorderTest() : perf_recorder_({"sudo", GetPerfPath()}) {}
38
39 protected:
40 PerfRecorder perf_recorder_;
41 };
42
TEST_F(PerfRecorderTest,RecordToProtobuf)43 TEST_F(PerfRecorderTest, RecordToProtobuf) {
44 // Read perf data using the PerfReader class.
45 // Dump it to a string and convert to a protobuf.
46 // Read the protobuf, and reconstruct the perf data.
47 string output_string;
48 EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
49 {"perf", "record"}, 0.2, &output_string));
50
51 quipper::PerfDataProto perf_data_proto;
52 EXPECT_TRUE(perf_data_proto.ParseFromString(output_string));
53
54 const auto& string_meta = perf_data_proto.string_metadata();
55 const auto& command = string_meta.perf_command_line_token();
56 EXPECT_EQ(GetPerfPath(), command.Get(0).value());
57 EXPECT_EQ("record", command.Get(1).value());
58 EXPECT_EQ("-o", command.Get(2).value());
59
60 // Unpredictable: EXPECT_EQ("/tmp/quipper.XXXXXX", command.Get(3).value());
61 // Instead, check the file path length and prefix.
62 EXPECT_EQ(strlen("/tmp/quipper.XXXXXX"), command.Get(3).value().size());
63 EXPECT_EQ("/tmp/quipper",
64 command.Get(3).value().substr(0, strlen("/tmp/quipper")));
65
66 EXPECT_EQ("--", command.Get(4).value());
67 EXPECT_EQ("sleep", command.Get(5).value());
68 EXPECT_EQ("0.2", command.Get(6).value());
69 }
70
TEST_F(PerfRecorderTest,StatToProtobuf)71 TEST_F(PerfRecorderTest, StatToProtobuf) {
72 // Run perf stat and verify output.
73 string output_string;
74 EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
75 {"perf", "stat"}, 0.2, &output_string));
76
77 EXPECT_GT(output_string.size(), 0);
78 quipper::PerfStatProto stat;
79 ASSERT_TRUE(stat.ParseFromString(output_string));
80 EXPECT_GT(stat.line_size(), 0);
81 }
82
TEST_F(PerfRecorderTest,MemRecordToProtobuf)83 TEST_F(PerfRecorderTest, MemRecordToProtobuf) {
84 if (!IsPerfMemRecordAvailable()) return;
85
86 // Run perf mem record and verify output.
87 string output_string;
88 EXPECT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
89 {"perf", "mem", "record"}, 0.2, &output_string));
90
91 EXPECT_GT(output_string.size(), 0);
92 quipper::PerfDataProto perf_data_proto;
93 ASSERT_TRUE(perf_data_proto.ParseFromString(output_string));
94 }
95
TEST_F(PerfRecorderTest,StatSingleEvent)96 TEST_F(PerfRecorderTest, StatSingleEvent) {
97 string output_string;
98 ASSERT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
99 {"perf", "stat", "-a", "-e", "cycles"}, 0.2, &output_string));
100
101 EXPECT_GT(output_string.size(), 0);
102
103 quipper::PerfStatProto stat;
104 ASSERT_TRUE(stat.ParseFromString(output_string));
105 // Replace the placeholder "perf" with the actual perf path.
106 string expected_command_line =
107 string("sudo ") + GetPerfPath() + " stat -a -e cycles -v -- sleep 0.2";
108 EXPECT_EQ(expected_command_line, stat.command_line());
109
110 // Make sure the event counter was read.
111 ASSERT_EQ(1, stat.line_size());
112 EXPECT_TRUE(stat.line(0).has_time_ms());
113 EXPECT_TRUE(stat.line(0).has_count());
114 EXPECT_TRUE(stat.line(0).has_event_name());
115 // Running for at least one second.
116 EXPECT_GE(stat.line(0).time_ms(), 200);
117 EXPECT_EQ("cycles", stat.line(0).event_name());
118 }
119
TEST_F(PerfRecorderTest,StatMultipleEvents)120 TEST_F(PerfRecorderTest, StatMultipleEvents) {
121 string output_string;
122 ASSERT_TRUE(perf_recorder_.RunCommandAndGetSerializedOutput(
123 {"perf", "stat", "-a", "-e", "cycles", "-e", "instructions", "-e",
124 "branches", "-e", "branch-misses"},
125 0.2, &output_string));
126
127 EXPECT_GT(output_string.size(), 0);
128
129 quipper::PerfStatProto stat;
130 ASSERT_TRUE(stat.ParseFromString(output_string));
131 // Replace the placeholder "perf" with the actual perf path.
132 string command_line = string("sudo ") + GetPerfPath() +
133 " stat -a "
134 "-e cycles "
135 "-e instructions "
136 "-e branches "
137 "-e branch-misses "
138 "-v "
139 "-- sleep 0.2";
140 EXPECT_TRUE(stat.has_command_line());
141 EXPECT_EQ(command_line, stat.command_line());
142
143 // Make sure all event counters were read.
144 // Check:
145 // - Number of events.
146 // - Running for at least two seconds.
147 // - Event names recorded properly.
148 ASSERT_EQ(4, stat.line_size());
149
150 EXPECT_TRUE(stat.line(0).has_time_ms());
151 EXPECT_TRUE(stat.line(0).has_count());
152 EXPECT_TRUE(stat.line(0).has_event_name());
153 EXPECT_GE(stat.line(0).time_ms(), 200);
154 EXPECT_EQ("cycles", stat.line(0).event_name());
155
156 EXPECT_TRUE(stat.line(1).has_time_ms());
157 EXPECT_TRUE(stat.line(1).has_count());
158 EXPECT_TRUE(stat.line(1).has_event_name());
159 EXPECT_GE(stat.line(1).time_ms(), 200);
160 EXPECT_EQ("instructions", stat.line(1).event_name());
161
162 EXPECT_TRUE(stat.line(2).has_time_ms());
163 EXPECT_TRUE(stat.line(2).has_count());
164 EXPECT_TRUE(stat.line(2).has_event_name());
165 EXPECT_GE(stat.line(2).time_ms(), 200);
166 EXPECT_EQ("branches", stat.line(2).event_name());
167
168 EXPECT_TRUE(stat.line(3).has_time_ms());
169 EXPECT_TRUE(stat.line(3).has_count());
170 EXPECT_TRUE(stat.line(3).has_event_name());
171 EXPECT_GE(stat.line(3).time_ms(), 200);
172 EXPECT_EQ("branch-misses", stat.line(3).event_name());
173 }
174
TEST_F(PerfRecorderTest,DontAllowCommands)175 TEST_F(PerfRecorderTest, DontAllowCommands) {
176 string output_string;
177 EXPECT_FALSE(perf_recorder_.RunCommandAndGetSerializedOutput(
178 {"perf", "record", "--", "sh", "-c", "echo 'malicious'"}, 0.2,
179 &output_string));
180 EXPECT_FALSE(perf_recorder_.RunCommandAndGetSerializedOutput(
181 {"perf", "stat", "--", "sh", "-c", "echo 'malicious'"}, 0.2,
182 &output_string));
183 }
184
TEST(PerfRecorderNoPerfTest,FailsIfPerfDoesntExist)185 TEST(PerfRecorderNoPerfTest, FailsIfPerfDoesntExist) {
186 string output_string;
187 PerfRecorder perf_recorder({"sudo", "/doesnt-exist/usr/not-bin/not-perf"});
188 EXPECT_FALSE(perf_recorder.RunCommandAndGetSerializedOutput(
189 {"perf", "record"}, 0.2, &output_string));
190 }
191
192 } // namespace quipper
193
main(int argc,char * argv[])194 int main(int argc, char* argv[]) {
195 ::testing::InitGoogleTest(&argc, argv);
196 if (!quipper::IsPerfRecordAvailable()) return 0;
197 return RUN_ALL_TESTS();
198 }
199