blob: 19eb66714ca0c3b3c3295af602edaaa9eb468309 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "perfd/cpu/simpleperf.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstring>
#include <memory>
using std::string;
using testing::IsNull;
using testing::Not;
using testing::StartsWith;
using testing::StrEq;
namespace {
const char* const kFakeSimpleperfDir = "/fake/path/";
const char* const kFakeTracePath = "/tmp/fake-trace";
// Checks that |a1| is a legitimate command line argument. It should appear in
// the given input string at least once; and for each occurrence, it should not
// start the string, should follow a space, and should be followed by a space
// unless it ends the string.
MATCHER_P(HasArgument, a1, "") {
string candidate{a1};
if (candidate.length() == 0) return true;
std::string::size_type position = 0;
int occurrence = 0;
while (true) {
position = arg.find(candidate, position);
if (position == std::string::npos) {
break;
} else {
occurrence++;
// Check an argument should not be at the beginning of the string.
if (position == 0) {
*result_listener << "\"" << a1 << "\" should not start the string";
return false;
} else {
// Check an argument should follow a space.
if (arg[position - 1] != ' ') {
*result_listener << "\"" << a1 << "\" should follow a space";
return false;
}
// Update position to points to the first character after the match.
position = position + candidate.length();
if (position < arg.length() && arg[position] != ' ') {
*result_listener
<< "\"" << a1
<< "\" should end the string or be followed by a space";
return false;
}
}
}
}
if (occurrence == 0) {
*result_listener << "\"" << a1 << "\" should appear at least once";
return false;
}
return true;
}
} // namespace
namespace profiler {
// A subclass of Simpleperf to be used in tests. This class essentially
// overrides |GetFeatures| so we can simulate a call to `simpleperf list
// --show-features`, which affects the result of |GetRecordCommand|.
class FakeSimpleperfGetFeatures final : public Simpleperf {
public:
explicit FakeSimpleperfGetFeatures()
: FakeSimpleperfGetFeatures(false, true) {}
explicit FakeSimpleperfGetFeatures(bool is_emulator, bool is_user_build)
: Simpleperf(kFakeSimpleperfDir, is_emulator, is_user_build) {}
// A public wrapper for the homonym protected method, for testing.
string GetRecordCommand(int pid, const string& pkg_name,
const string& abi_arch, const string& trace_path,
int sampling_interval_us) const {
return Simpleperf::GetRecordCommand(pid, pkg_name, abi_arch, trace_path,
sampling_interval_us);
}
// A public wrapper for the homonym protected method, for testing.
void SplitRecordCommand(char* original_cmd, char** split_cmd) const {
return Simpleperf::SplitRecordCommand(original_cmd, split_cmd);
}
string GetFeatures(const string& abi_arch) const { return features_; }
void SetFeatures(string features) { features_ = features; }
private:
string features_;
};
TEST(SimpleperfTest, RecordCommandParams) {
FakeSimpleperfGetFeatures simpleperf;
string record_command = simpleperf.GetRecordCommand(3039, "my.package", "arm",
kFakeTracePath, 100);
// simpleperf binary + "record"
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm record"));
// PID
EXPECT_THAT(record_command, HasArgument("-p 3039"));
// package name
EXPECT_THAT(record_command, HasArgument("--app my.package"));
// trace path
EXPECT_THAT(record_command, HasArgument("-o /tmp/fake-trace"));
// Sampling frequency. Note sampling interval is 100us, so frequency is 10000
// samples per second.
EXPECT_THAT(record_command, HasArgument("-f 10000"));
// --exit-with-parent flag
EXPECT_THAT(record_command, HasArgument("--exit-with-parent"));
}
TEST(SimpleperfTest, NonUserBuildUseSuRoot) {
FakeSimpleperfGetFeatures simpleperf{false /* is_emulator */,
false /* is_user_build */};
string record_command = simpleperf.GetRecordCommand(3039, "my.package", "arm",
kFakeTracePath, 100);
// Record should be run as root
EXPECT_THAT(record_command, StartsWith("su root "));
// PID should be present
EXPECT_THAT(record_command, HasArgument("-p 3039"));
// --app flag shouldn't be present
EXPECT_THAT(record_command, Not(HasArgument("--app")));
}
TEST(SimpleperfTest, NonUserBuildWithStartupUsesRunAs) {
FakeSimpleperfGetFeatures simpleperf{false /* is_emulator */,
false /* is_user_build */};
string record_command = simpleperf.GetRecordCommand(
kStartupProfilingPid, "my.package", "arm", kFakeTracePath, 100);
// Record should not be run as root
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm record"));
// PID should not be present as it's not available
EXPECT_THAT(record_command, Not(HasArgument("-p")));
// package name should be present
EXPECT_THAT(record_command, HasArgument("--app my.package"));
}
TEST(SimpleperfTest, UserBuildAlwaysUsesRunAs) {
FakeSimpleperfGetFeatures simpleperf{false /* is_emulator */,
true /* is_user_build */};
string record_command = simpleperf.GetRecordCommand(
kStartupProfilingPid, "my.package", "arm", kFakeTracePath, 100);
// Record should not be run as root
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm record"));
// PID should not be present as it's not available
EXPECT_THAT(record_command, Not(HasArgument("-p")));
// package name should be present
EXPECT_THAT(record_command, HasArgument("--app my.package"));
record_command = simpleperf.GetRecordCommand(
20 /* any other PID */, "my.package", "arm", kFakeTracePath, 100);
// Record should not be run as root
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm record"));
// PID should not be present as it's not available
EXPECT_THAT(record_command, Not(HasArgument("-p")));
// package name should be present
EXPECT_THAT(record_command, HasArgument("--app my.package"));
}
TEST(SimpleperfTest, StartupProfilingPid) {
FakeSimpleperfGetFeatures simpleperf;
string record_command = simpleperf.GetRecordCommand(
kStartupProfilingPid, "my.package", "arm", kFakeTracePath, 100);
// PID, Startup profiling should not use pid
EXPECT_THAT(record_command, Not(HasArgument("-p")));
// package name
EXPECT_THAT(record_command, HasArgument("--app my.package"));
}
TEST(SimpleperfTest, SimpleperfBinaryName) {
FakeSimpleperfGetFeatures simpleperf;
int pid = 42;
string app = "my.good.app";
int sampling_interval = 100;
string record_command = simpleperf.GetRecordCommand(
pid, app, "arm", kFakeTracePath, sampling_interval);
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm record"));
record_command = simpleperf.GetRecordCommand(
pid, app, "arm64", kFakeTracePath, sampling_interval);
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_arm64 record"));
record_command = simpleperf.GetRecordCommand(pid, app, "x86", kFakeTracePath,
sampling_interval);
EXPECT_THAT(record_command, StartsWith("/fake/path/simpleperf_x86 record"));
record_command = simpleperf.GetRecordCommand(
pid, app, "x86_64", kFakeTracePath, sampling_interval);
EXPECT_THAT(record_command,
StartsWith("/fake/path/simpleperf_x86_64 record"));
}
TEST(SimpleperfTest, EmulatorUsesCpuClockEvents) {
FakeSimpleperfGetFeatures simpleperf_emulator{true /* is_emulator */,
true /* is_user_build */};
string record_command = simpleperf_emulator.GetRecordCommand(
1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, HasArgument("-e cpu-clock"));
FakeSimpleperfGetFeatures simpleperf{false /* is_emulator */,
true /* is_user_build */};
record_command =
simpleperf.GetRecordCommand(1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, Not(HasArgument("-e cpu-clock")));
}
TEST(SimpleperfTest, TraceOffCpuFlag) {
FakeSimpleperfGetFeatures simpleperf;
simpleperf.SetFeatures("trace-offcpu\nother feature");
string record_command =
simpleperf.GetRecordCommand(1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, HasArgument("--trace-offcpu"));
simpleperf.SetFeatures("other feature");
record_command =
simpleperf.GetRecordCommand(1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, Not(HasArgument("--trace-offcpu")));
}
TEST(SimpleperfTest, DwarfVsFpCallGraph) {
FakeSimpleperfGetFeatures simpleperf;
simpleperf.SetFeatures("dwarf-based-call-graph");
string record_command =
simpleperf.GetRecordCommand(1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, HasArgument("--call-graph dwarf"));
simpleperf.SetFeatures("");
record_command =
simpleperf.GetRecordCommand(1, "any.package", "arm", kFakeTracePath, 1);
EXPECT_THAT(record_command, HasArgument("--call-graph fp"));
}
TEST(SimpleperfTest, SplitRecordCommand) {
FakeSimpleperfGetFeatures simpleperf;
char* split_str[5];
std::unique_ptr<char[]> original_str;
string test_string = ""; // Empty string
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], IsNull());
test_string = " "; // Single space
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], IsNull());
test_string = "String with spaces";
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], StrEq("String"));
EXPECT_THAT(split_str[1], StrEq("with"));
EXPECT_THAT(split_str[2], StrEq("spaces"));
EXPECT_THAT(split_str[3], IsNull());
test_string = "Other string with\0null character";
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], StrEq("Other"));
EXPECT_THAT(split_str[1], StrEq("string"));
EXPECT_THAT(split_str[2], StrEq("with"));
EXPECT_THAT(split_str[3], testing::IsNull());
test_string = " leading space";
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], StrEq("leading"));
EXPECT_THAT(split_str[1], StrEq("space"));
EXPECT_THAT(split_str[2], testing::IsNull());
test_string = "trailing space ";
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], StrEq("trailing"));
EXPECT_THAT(split_str[1], StrEq("space"));
EXPECT_THAT(split_str[2], testing::IsNull());
test_string = "double space";
original_str.reset(new char[test_string.length() + 1]);
std::strcpy(original_str.get(), test_string.c_str());
simpleperf.SplitRecordCommand(original_str.get(), split_str);
EXPECT_THAT(split_str[0], StrEq("double"));
EXPECT_THAT(split_str[1], StrEq("space"));
EXPECT_THAT(split_str[2], testing::IsNull());
}
} // namespace profiler