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