blob: 20c01e7040907933ae99d9939b673cea67da6127 [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/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/tracing/ipc/default_socket.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"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <sys/system_properties.h>
#endif
#include "protos/perfetto/config/profiling/heapprofd_config.gen.h"
#include "protos/perfetto/trace/profiling/profile_common.gen.h"
#include "protos/perfetto/trace/profiling/profile_packet.gen.h"
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;
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);
}
}
base::Subprocess ForkContinuousMalloc(size_t bytes) {
base::Subprocess proc;
proc.args.entrypoint_for_testing = [bytes] { ContinuousMalloc(bytes); };
proc.Start();
return proc;
}
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::gen::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::gen::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::gen::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::gen::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::gen::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::gen::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::gen::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::gen::TracePacket& packet : packets) {
for (const auto& dump : packet.profile_packet().process_dumps()) {
EXPECT_EQ(dump.pid(), pid);
dumps++;
}
}
EXPECT_GT(dumps, 0u);
}
};
// This checks that the child is still running (to ensure it didn't crash
// unxpectedly) and then kills it.
void KillAssertRunning(base::Subprocess* child) {
ASSERT_EQ(child->Poll(), base::Subprocess::kRunning);
child->KillAndWaitForTermination();
}
TEST_P(HeapprofdEndToEnd, Smoke) {
constexpr size_t kAllocSize = 1024;
base::Subprocess child = ForkContinuousMalloc(kAllocSize);
const auto pid = child.pid();
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);
protos::gen::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.mutable_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);
KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, TwoProcesses) {
constexpr size_t kAllocSize = 1024;
constexpr size_t kAllocSize2 = 7;
base::Subprocess child = ForkContinuousMalloc(kAllocSize);
base::Subprocess child2 = ForkContinuousMalloc(kAllocSize2);
const auto pid = child.pid();
const auto pid2 = child2.pid();
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);
protos::gen::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);
KillAssertRunning(&child);
KillAssertRunning(&child2);
}
TEST_P(HeapprofdEndToEnd, FinalFlush) {
constexpr size_t kAllocSize = 1024;
base::Subprocess child = ForkContinuousMalloc(kAllocSize);
const auto pid = child.pid();
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);
protos::gen::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);
KillAssertRunning(&child);
}
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");
protos::gen::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);
base::Subprocess child({"/proc/self/exe"});
child.args.argv0_override = "heapprofd_continuous_malloc";
child.args.stdout_mode = base::Subprocess::kDevNull;
child.args.stderr_mode = base::Subprocess::kDevNull;
child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
child.Start();
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
KillAssertRunning(&child);
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::gen::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(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.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");
protos::gen::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);
base::Subprocess child({"/proc/self/exe"});
child.args.argv0_override = "heapprofd_continuous_malloc";
child.args.stdout_mode = base::Subprocess::kDevNull;
child.args.stderr_mode = base::Subprocess::kDevNull;
child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
child.Start();
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
KillAssertRunning(&child);
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::gen::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(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.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");
protos::gen::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());
base::Subprocess child({"/proc/self/exe"});
child.args.argv0_override = "heapprofd_continuous_malloc";
child.args.stdout_mode = base::Subprocess::kDevNull;
child.args.stderr_mode = base::Subprocess::kDevNull;
child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
child.Start();
// 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);
KillAssertRunning(&child);
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::gen::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(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.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");
protos::gen::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.
base::Subprocess child({"/proc/self/exe"});
child.args.argv0_override = "heapprofd_continuous_malloc";
child.args.stdout_mode = base::Subprocess::kDevNull;
child.args.stderr_mode = base::Subprocess::kDevNull;
child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
child.Start();
// 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);
KillAssertRunning(&child);
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::gen::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(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.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);
base::Subprocess child;
int signal_pipe_rd = *signal_pipe.rd;
int ack_pipe_wr = *ack_pipe.wr;
child.args.preserve_fds.push_back(signal_pipe_rd);
child.args.preserve_fds.push_back(ack_pipe_wr);
child.args.entrypoint_for_testing = [signal_pipe_rd, ack_pipe_wr] {
// The Subprocess harness takes care of closing all the unused pipe ends.
size_t bytes = kFirstIterationBytes;
bool signalled = false;
for (;;) {
AllocateAndFree(bytes);
char buf[1];
if (!signalled && read(signal_pipe_rd, buf, sizeof(buf)) == 1) {
signalled = true;
close(signal_pipe_rd);
// make sure the client has noticed that the session has stopped
AllocateAndFree(bytes);
bytes = kSecondIterationBytes;
PERFETTO_CHECK(PERFETTO_EINTR(write(ack_pipe_wr, "1", 1)) == 1);
close(ack_pipe_wr);
}
usleep(10 * kMsToUs);
}
PERFETTO_FATAL("Should be unreachable");
};
child.Start();
auto pid = child.pid();
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);
protos::gen::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);
PERFETTO_CHECK(PERFETTO_EINTR(write(*signal_pipe.wr, "1", 1)) == 1);
signal_pipe.wr.reset();
char buf[1];
ASSERT_EQ(PERFETTO_EINTR(read(*ack_pipe.rd, buf, sizeof(buf))), 1);
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);
KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
constexpr size_t kAllocSize = 1024;
base::Subprocess child = ForkContinuousMalloc(kAllocSize);
const auto pid = child.pid();
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);
protos::gen::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.mutable_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);
KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, NativeProfilingActiveAtProcessExit) {
constexpr uint64_t kTestAllocSize = 128;
base::Pipe start_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
base::Subprocess child;
int start_pipe_wr = *start_pipe.wr;
child.args.preserve_fds.push_back(start_pipe_wr);
child.args.entrypoint_for_testing = [start_pipe_wr] {
PERFETTO_CHECK(PERFETTO_EINTR(write(start_pipe_wr, "1", 1)) == 1);
PERFETTO_CHECK(close(start_pipe_wr) == 0 || errno == EINTR);
// The subprocess harness will take care of closing
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);
}
};
child.Start();
auto pid = child.pid();
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");
protos::gen::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))), 1);
start_pipe.rd.reset();
// Trace until child exits.
helper->StartTracing(trace_config);
// Wait for the child and assert that it exited successfully.
EXPECT_TRUE(child.Wait(30000));
EXPECT_EQ(child.status(), base::Subprocess::kExited);
EXPECT_EQ(child.returncode(), 0);
// 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::gen::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(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[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