blob: 42e666fbcc04a24fb6643cba89ebb6dc824bc803 [file] [log] [blame]
/*
* Copyright (C) 2018 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 <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/tracing/ipc/default_socket.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/base/test/test_task_runner.h"
#include "src/profiling/memory/heapprofd_producer.h"
#include "test/gtest_and_gmock.h"
#include "test/test_helper.h"
#include "protos/perfetto/config/profiling/heapprofd_config.pbzero.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <sys/system_properties.h>
#endif
namespace perfetto {
namespace profiling {
namespace {
constexpr useconds_t kMsToUs = 1000;
constexpr auto kTracingDisabledTimeoutMs = 30000;
constexpr auto kWaitForReadDataTimeoutMs = 10000;
using ::testing::AnyOf;
using ::testing::Bool;
using ::testing::Eq;
class HeapprofdDelegate : public ThreadDelegate {
public:
HeapprofdDelegate(const std::string& producer_socket)
: producer_socket_(producer_socket) {}
~HeapprofdDelegate() override = default;
void Initialize(base::TaskRunner* task_runner) override {
producer_.reset(
new HeapprofdProducer(HeapprofdMode::kCentral, task_runner));
producer_->ConnectWithRetries(producer_socket_.c_str());
}
private:
std::string producer_socket_;
std::unique_ptr<HeapprofdProducer> producer_;
};
constexpr const char* kHeapprofdModeProperty = "heapprofd.userdebug.mode";
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
std::string ReadProperty(const std::string& name, std::string def) {
const prop_info* pi = __system_property_find(name.c_str());
if (pi) {
__system_property_read_callback(
pi,
[](void* cookie, const char*, const char* value, uint32_t) {
*reinterpret_cast<std::string*>(cookie) = value;
},
&def);
}
return def;
}
int SetModeProperty(std::string* value) {
if (value) {
__system_property_set(kHeapprofdModeProperty, value->c_str());
delete value;
}
return 0;
}
base::ScopedResource<std::string*, SetModeProperty, nullptr> EnableFork() {
std::string prev_property_value = ReadProperty(kHeapprofdModeProperty, "");
__system_property_set(kHeapprofdModeProperty, "fork");
return base::ScopedResource<std::string*, SetModeProperty, nullptr>(
new std::string(prev_property_value));
}
base::ScopedResource<std::string*, SetModeProperty, nullptr> DisableFork() {
std::string prev_property_value = ReadProperty(kHeapprofdModeProperty, "");
__system_property_set(kHeapprofdModeProperty, "");
return base::ScopedResource<std::string*, SetModeProperty, nullptr>(
new std::string(prev_property_value));
}
#else
std::string ReadProperty(const std::string&, std::string) {
PERFETTO_FATAL("Only works on Android.");
}
int SetModeProperty(std::string*) {
PERFETTO_FATAL("Only works on Android.");
}
base::ScopedResource<std::string*, SetModeProperty, nullptr> EnableFork() {
PERFETTO_FATAL("Only works on Android.");
}
base::ScopedResource<std::string*, SetModeProperty, nullptr> DisableFork() {
PERFETTO_FATAL("Only works on Android.");
}
#endif
constexpr size_t kStartupAllocSize = 10;
void AllocateAndFree(size_t bytes) {
// This volatile is needed to prevent the compiler from trying to be
// helpful and compiling a "useless" malloc + free into a noop.
volatile char* x = static_cast<char*>(malloc(bytes));
if (x) {
x[1] = 'x';
free(const_cast<char*>(x));
}
}
void __attribute__((noreturn)) ContinuousMalloc(size_t bytes) {
for (;;) {
AllocateAndFree(bytes);
usleep(10 * kMsToUs);
}
}
pid_t ForkContinuousMalloc(size_t bytes) {
// Make sure forked process does not get reparented to init.
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0:
ContinuousMalloc(bytes);
default:
break;
}
return pid;
}
void __attribute__((constructor)) RunContinuousMalloc() {
if (getenv("HEAPPROFD_TESTING_RUN_MALLOC") != nullptr)
ContinuousMalloc(kStartupAllocSize);
}
std::unique_ptr<TestHelper> GetHelper(base::TestTaskRunner* task_runner) {
std::unique_ptr<TestHelper> helper(new TestHelper(task_runner));
helper->ConnectConsumer();
helper->WaitForConsumerConnect();
return helper;
}
std::string FormatHistogram(const protos::ProfilePacket_Histogram& hist) {
std::string out;
std::string prev_upper_limit = "-inf";
for (const auto& bucket : hist.buckets()) {
std::string upper_limit;
if (bucket.max_bucket())
upper_limit = "inf";
else
upper_limit = std::to_string(bucket.upper_limit());
out += "[" + prev_upper_limit + ", " + upper_limit +
"]: " + std::to_string(bucket.count()) + "; ";
prev_upper_limit = std::move(upper_limit);
}
return out + "\n";
}
std::string FormatStats(const protos::ProfilePacket_ProcessStats& stats) {
return std::string("unwinding_errors: ") +
std::to_string(stats.unwinding_errors()) + "\n" +
"heap_samples: " + std::to_string(stats.heap_samples()) + "\n" +
"map_reparses: " + std::to_string(stats.map_reparses()) + "\n" +
"unwinding_time_us: " + FormatHistogram(stats.unwinding_time_us());
}
std::string TestSuffix(const ::testing::TestParamInfo<bool>& info) {
return info.param ? "ForkMode" : "CentralMode";
}
class HeapprofdEndToEnd : public ::testing::TestWithParam<bool> {
public:
HeapprofdEndToEnd() {
// This is not needed for correctness, but works around a init behavior that
// makes this test take much longer. If persist.heapprofd.enable is set to 0
// and then set to 1 again too quickly, init decides that the service is
// "restarting" and waits before restarting it.
usleep(50000);
bool should_fork = GetParam();
if (should_fork) {
fork_prop_ = EnableFork();
PERFETTO_CHECK(ReadProperty(kHeapprofdModeProperty, "") == "fork");
} else {
fork_prop_ = DisableFork();
PERFETTO_CHECK(ReadProperty(kHeapprofdModeProperty, "") == "");
}
}
protected:
base::TestTaskRunner task_runner;
base::ScopedResource<std::string*, SetModeProperty, nullptr> fork_prop_{
nullptr};
std::unique_ptr<TestHelper> Trace(const TraceConfig& trace_config) {
auto helper = GetHelper(&task_runner);
helper->StartTracing(trace_config);
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
return helper;
}
void PrintStats(TestHelper* helper) {
const auto& packets = helper->trace();
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
// protobuf uint64 does not like the PRIu64 formatter.
PERFETTO_LOG("Stats for %s: %s", std::to_string(dump.pid()).c_str(),
FormatStats(dump.stats()).c_str());
}
}
}
void ValidateSampleSizes(TestHelper* helper,
uint64_t pid,
uint64_t alloc_size) {
const auto& packets = helper->trace();
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
if (dump.pid() != pid)
continue;
for (const auto& sample : dump.samples()) {
EXPECT_EQ(sample.self_allocated() % alloc_size, 0u);
EXPECT_EQ(sample.self_freed() % alloc_size, 0u);
EXPECT_THAT(sample.self_allocated() - sample.self_freed(),
AnyOf(Eq(0u), Eq(alloc_size)));
}
}
}
}
void ValidateFromStartup(TestHelper* helper,
uint64_t pid,
bool from_startup) {
const auto& packets = helper->trace();
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
if (dump.pid() != pid)
continue;
EXPECT_EQ(dump.from_startup(), from_startup);
}
}
}
void ValidateRejectedConcurrent(TestHelper* helper,
uint64_t pid,
bool rejected_concurrent) {
const auto& packets = helper->trace();
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
if (dump.pid() != pid)
continue;
EXPECT_EQ(dump.rejected_concurrent(), rejected_concurrent);
}
}
}
void ValidateHasSamples(TestHelper* helper, uint64_t pid) {
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t last_allocated = 0;
uint64_t last_freed = 0;
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
if (dump.pid() != pid)
continue;
for (const auto& sample : dump.samples()) {
last_allocated = sample.self_allocated();
last_freed = sample.self_freed();
samples++;
}
profile_packets++;
}
}
EXPECT_GT(profile_packets, 0u);
EXPECT_GT(samples, 0u);
EXPECT_GT(last_allocated, 0u);
EXPECT_GT(last_freed, 0u);
}
void ValidateOnlyPID(TestHelper* helper, uint64_t pid) {
size_t dumps = 0;
const auto& packets = helper->trace();
for (const protos::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
EXPECT_EQ(dump.pid(), pid);
dumps++;
}
}
EXPECT_GT(dumps, 0u);
}
};
TEST_P(HeapprofdEndToEnd, Smoke) {
constexpr size_t kAllocSize = 1024;
pid_t pid = ForkContinuousMalloc(kAllocSize);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(2000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
ds_config->set_target_buffer(0);
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
heapprofd_config->set_all(false);
auto* cont_config = heapprofd_config->set_continuous_dump_config();
cont_config->set_dump_phase_ms(0);
cont_config->set_dump_interval_ms(100);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
auto helper = Trace(trace_config);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
}
TEST_P(HeapprofdEndToEnd, TwoProcesses) {
constexpr size_t kAllocSize = 1024;
constexpr size_t kAllocSize2 = 7;
pid_t pid = ForkContinuousMalloc(kAllocSize);
pid_t pid2 = ForkContinuousMalloc(kAllocSize2);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(2000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
ds_config->set_target_buffer(0);
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
heapprofd_config->add_pid(static_cast<uint64_t>(pid2));
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
auto helper = Trace(trace_config);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid2));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid2), kAllocSize2);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
PERFETTO_CHECK(kill(pid2, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid2, nullptr, 0)) == pid2);
}
TEST_P(HeapprofdEndToEnd, FinalFlush) {
constexpr size_t kAllocSize = 1024;
pid_t pid = ForkContinuousMalloc(kAllocSize);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(2000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
ds_config->set_target_buffer(0);
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
auto helper = Trace(trace_config);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
}
TEST_P(HeapprofdEndToEnd, NativeStartup) {
auto helper = GetHelper(&task_runner);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
helper->StartTracing(trace_config);
// Wait to guarantee that the process forked below is hooked by the profiler
// by virtue of the startup check, and not by virtue of being seen as a
// running process. This sleep is here to prevent that, accidentally, the
// test gets to the fork()+exec() too soon, before the heap profiling daemon
// has received the trace config.
sleep(1);
// Make sure the forked process does not get reparented to init.
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0: {
const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
int null = open("/dev/null", O_RDWR);
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
nullptr, envp) == 0);
break;
}
default:
break;
}
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t total_allocated = 0;
uint64_t total_freed = 0;
for (const protos::TracePacket& packet : packets) {
if (packet.has_profile_packet() &&
packet.profile_packet().process_dumps().size() > 0) {
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1);
const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
total_allocated += sample.self_allocated();
total_freed += sample.self_freed();
}
}
}
EXPECT_EQ(profile_packets, 1u);
EXPECT_GT(samples, 0u);
EXPECT_GT(total_allocated, 0u);
EXPECT_GT(total_freed, 0u);
}
TEST_P(HeapprofdEndToEnd, NativeStartupDenormalizedCmdline) {
auto helper = GetHelper(&task_runner);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
helper->StartTracing(trace_config);
// Wait to guarantee that the process forked below is hooked by the profiler
// by virtue of the startup check, and not by virtue of being seen as a
// running process. This sleep is here to prevent that, accidentally, the
// test gets to the fork()+exec() too soon, before the heap profiling daemon
// has received the trace config.
sleep(1);
// Make sure the forked process does not get reparented to init.
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0: {
const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
int null = open("/dev/null", O_RDWR);
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
nullptr, envp) == 0);
break;
}
default:
break;
}
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t total_allocated = 0;
uint64_t total_freed = 0;
for (const protos::TracePacket& packet : packets) {
if (packet.has_profile_packet() &&
packet.profile_packet().process_dumps().size() > 0) {
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1);
const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
total_allocated += sample.self_allocated();
total_freed += sample.self_freed();
}
}
}
EXPECT_EQ(profile_packets, 1u);
EXPECT_GT(samples, 0u);
EXPECT_GT(total_allocated, 0u);
EXPECT_GT(total_freed, 0u);
}
TEST_P(HeapprofdEndToEnd, DiscoverByName) {
auto helper = GetHelper(&task_runner);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
// Make sure the forked process does not get reparented to init.
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0: {
const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
int null = open("/dev/null", O_RDWR);
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
nullptr, envp) == 0);
break;
}
default:
break;
}
// Wait to make sure process is fully initialized, so we do not accidentally
// match it by the startup logic.
sleep(1);
helper->StartTracing(trace_config);
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t total_allocated = 0;
uint64_t total_freed = 0;
for (const protos::TracePacket& packet : packets) {
if (packet.has_profile_packet() &&
packet.profile_packet().process_dumps().size() > 0) {
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1);
const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
total_allocated += sample.self_allocated();
total_freed += sample.self_freed();
}
}
}
EXPECT_EQ(profile_packets, 1u);
EXPECT_GT(samples, 0u);
EXPECT_GT(total_allocated, 0u);
EXPECT_GT(total_freed, 0u);
}
TEST_P(HeapprofdEndToEnd, DiscoverByNameDenormalizedCmdline) {
auto helper = GetHelper(&task_runner);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
// Make sure the forked process does not get reparented to init.
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0: {
const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
int null = open("/dev/null", O_RDWR);
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
nullptr, envp) == 0);
break;
}
default:
break;
}
// Wait to make sure process is fully initialized, so we do not accidentally
// match it by the startup logic.
sleep(1);
helper->StartTracing(trace_config);
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t total_allocated = 0;
uint64_t total_freed = 0;
for (const protos::TracePacket& packet : packets) {
if (packet.has_profile_packet() &&
packet.profile_packet().process_dumps().size() > 0) {
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1);
const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
total_allocated += sample.self_allocated();
total_freed += sample.self_freed();
}
}
}
EXPECT_EQ(profile_packets, 1u);
EXPECT_GT(samples, 0u);
EXPECT_GT(total_allocated, 0u);
EXPECT_GT(total_freed, 0u);
}
TEST_P(HeapprofdEndToEnd, ReInit) {
constexpr size_t kFirstIterationBytes = 5;
constexpr size_t kSecondIterationBytes = 7;
base::Pipe signal_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
base::Pipe ack_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
setsid();
pid_t pid = fork();
switch (pid) {
case -1:
PERFETTO_FATAL("Failed to fork.");
case 0: {
size_t bytes = kFirstIterationBytes;
signal_pipe.wr.reset();
ack_pipe.rd.reset();
for (;;) {
AllocateAndFree(bytes);
char buf[1];
if (bool(signal_pipe.rd) &&
read(*signal_pipe.rd, buf, sizeof(buf)) == 0) {
// make sure the client has noticed that the session has stopped
AllocateAndFree(bytes);
bytes = kSecondIterationBytes;
signal_pipe.rd.reset();
ack_pipe.wr.reset();
}
usleep(10 * kMsToUs);
}
PERFETTO_FATAL("Should be unreachable");
}
default:
break;
}
signal_pipe.rd.reset();
ack_pipe.wr.reset();
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(2000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
ds_config->set_target_buffer(0);
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
heapprofd_config->set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
auto helper = Trace(trace_config);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
kFirstIterationBytes);
signal_pipe.wr.reset();
char buf[1];
ASSERT_EQ(read(*ack_pipe.rd, buf, sizeof(buf)), 0);
ack_pipe.rd.reset();
// A brief sleep to allow the client to notice that the profiling session is
// to be torn down (as it rejects concurrent sessions).
usleep(500 * kMsToUs);
PERFETTO_LOG("HeapprofdEndToEnd::Reinit: Starting second");
helper = Trace(trace_config);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
kSecondIterationBytes);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
}
TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
constexpr size_t kAllocSize = 1024;
pid_t pid = ForkContinuousMalloc(kAllocSize);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
ds_config->set_target_buffer(0);
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
heapprofd_config->set_all(false);
auto* cont_config = heapprofd_config->set_continuous_dump_config();
cont_config->set_dump_phase_ms(0);
cont_config->set_dump_interval_ms(100);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
auto helper = GetHelper(&task_runner);
helper->StartTracing(trace_config);
sleep(1);
auto helper_concurrent = GetHelper(&task_runner);
helper_concurrent->StartTracing(trace_config);
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PrintStats(helper.get());
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
ValidateRejectedConcurrent(helper_concurrent.get(),
static_cast<uint64_t>(pid), false);
helper_concurrent->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper_concurrent->ReadData();
helper_concurrent->WaitForReadData(0, kWaitForReadDataTimeoutMs);
PrintStats(helper.get());
ValidateOnlyPID(helper_concurrent.get(), static_cast<uint64_t>(pid));
ValidateRejectedConcurrent(helper_concurrent.get(),
static_cast<uint64_t>(pid), true);
PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
}
// TODO(rsavitski): fold exit status assertions into existing tests where
// possible.
TEST_P(HeapprofdEndToEnd, NativeProfilingActiveAtProcessExit) {
constexpr uint64_t kTestAllocSize = 128;
base::Pipe start_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
setsid();
pid_t pid = fork();
if (pid == 0) { // child
start_pipe.rd.reset();
start_pipe.wr.reset();
for (int i = 0; i < 200; i++) {
// malloc and leak, otherwise the free batching will cause us to filter
// out the allocations (as we don't see the interleaved frees).
volatile char* x = static_cast<char*>(malloc(kTestAllocSize));
if (x) {
x[0] = 'x';
}
usleep(10 * kMsToUs);
}
exit(0);
}
ASSERT_NE(pid, -1) << "Failed to fork.";
start_pipe.wr.reset();
// Construct tracing config (without starting profiling).
auto helper = GetHelper(&task_runner);
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
trace_config.set_duration_ms(5000);
trace_config.set_data_source_stop_timeout_ms(10000);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.heapprofd");
protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
heapprofd_config->set_sampling_interval_bytes(1);
heapprofd_config->add_pid(static_cast<uint64_t>(pid));
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
// Wait for child to have been scheduled at least once.
char buf[1] = {};
ASSERT_EQ(PERFETTO_EINTR(read(*start_pipe.rd, buf, sizeof(buf))), 0);
start_pipe.rd.reset();
// Trace until child exits.
helper->StartTracing(trace_config);
siginfo_t siginfo = {};
int wait_ret =
PERFETTO_EINTR(waitid(P_PID, static_cast<id_t>(pid), &siginfo, WEXITED));
ASSERT_FALSE(wait_ret) << "Failed to waitid.";
// Assert that the child exited successfully.
EXPECT_EQ(siginfo.si_code, CLD_EXITED) << "Child did not exit by itself.";
EXPECT_EQ(siginfo.si_status, 0) << "Child's exit status not successful.";
// Assert that we did profile the process.
helper->FlushAndWait(2000);
helper->DisableTracing();
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
size_t profile_packets = 0;
size_t samples = 0;
uint64_t total_allocated = 0;
for (const protos::TracePacket& packet : packets) {
if (packet.has_profile_packet() &&
packet.profile_packet().process_dumps().size() > 0) {
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1);
const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
total_allocated += sample.self_allocated();
}
}
}
EXPECT_EQ(profile_packets, 1u);
EXPECT_GT(samples, 0u);
EXPECT_GT(total_allocated, 0u);
}
// This test only works when run on Android using an Android Q version of
// Bionic.
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
INSTANTIATE_TEST_CASE_P(DISABLED_Run, HeapprofdEndToEnd, Bool(), TestSuffix);
#else
INSTANTIATE_TEST_CASE_P(Run, HeapprofdEndToEnd, Bool(), TestSuffix);
#endif
} // namespace
} // namespace profiling
} // namespace perfetto