/*
* 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 "src/traced/probes/process_stats_data_source.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "perfetto/trace/trace_packet.pb.h"
#include "perfetto/trace/trace_packet.pbzero.h"
#include "src/tracing/core/trace_writer_for_testing.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::ElementsAreArray;
namespace perfetto {
namespace {
class TestProcessStatsDataSource : public ProcessStatsDataSource {
public:
TestProcessStatsDataSource(TracingSessionID id,
std::unique_ptr<TraceWriter> writer,
const DataSourceConfig& config)
: ProcessStatsDataSource(id, std::move(writer), config) {}
MOCK_METHOD2(ReadProcPidFile, std::string(int32_t pid, const std::string&));
};
class ProcessStatsDataSourceTest : public ::testing::Test {
protected:
ProcessStatsDataSourceTest() {}
TraceWriterForTesting* writer_raw_;
std::unique_ptr<TestProcessStatsDataSource> GetProcessStatsDataSource(
const DataSourceConfig& cfg) {
auto writer =
std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
writer_raw_ = writer.get();
return std::unique_ptr<TestProcessStatsDataSource>(
new TestProcessStatsDataSource(0, std::move(writer), cfg));
}
};
TEST_F(ProcessStatsDataSourceTest, WriteOnceProcess) {
auto data_source = GetProcessStatsDataSource(DataSourceConfig());
EXPECT_CALL(*data_source, ReadProcPidFile(42, "status"))
.WillOnce(Return("Name: foo\nTgid:\t42\nPid: 42\nPPid: 17\n"));
EXPECT_CALL(*data_source, ReadProcPidFile(42, "cmdline"))
.WillOnce(Return(std::string("foo\0bar\0baz\0", 12)));
data_source->OnPids({42});
std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
ASSERT_TRUE(packet->has_process_tree());
ASSERT_EQ(packet->process_tree().processes_size(), 1);
auto first_process = packet->process_tree().processes(0);
ASSERT_EQ(first_process.pid(), 42);
ASSERT_EQ(first_process.ppid(), 17);
EXPECT_THAT(first_process.cmdline(), ElementsAreArray({"foo", "bar", "baz"}));
}
TEST_F(ProcessStatsDataSourceTest, DontRescanCachedPIDsAndTIDs) {
DataSourceConfig config;
config.mutable_process_stats_config()->set_record_thread_names(true);
auto data_source = GetProcessStatsDataSource(config);
for (int p : {10, 11, 12, 20, 21, 22, 30, 31, 32}) {
EXPECT_CALL(*data_source, ReadProcPidFile(p, "status"))
.WillOnce(Invoke([](int32_t pid, const std::string&) {
int32_t tgid = (pid / 10) * 10;
return "Name: \tthread_" + std::to_string(pid) +
"\nTgid: " + std::to_string(tgid) +
"\nPid: " + std::to_string(pid) + "\nPPid: 1\n";
}));
if (p % 10 == 0) {
std::string proc_name = "proc_" + std::to_string(p);
proc_name.resize(proc_name.size() + 1); // Add a trailing \0.
EXPECT_CALL(*data_source, ReadProcPidFile(p, "cmdline"))
.WillOnce(Return(proc_name));
}
}
data_source->OnPids({10, 11, 12, 20, 21, 22, 10, 20, 11, 21});
data_source->OnPids({30});
data_source->OnPids({10, 30, 10, 31, 32});
std::unique_ptr<protos::TracePacket> packet = writer_raw_->ParseProto();
ASSERT_TRUE(packet->has_process_tree());
const auto& proceses = packet->process_tree().processes();
const auto& threads = packet->process_tree().threads();
ASSERT_EQ(proceses.size(), 3);
int tid_idx = 0;
for (int pid_idx = 0; pid_idx < 3; pid_idx++) {
int pid = (pid_idx + 1) * 10;
std::string proc_name = "proc_" + std::to_string(pid);
ASSERT_EQ(proceses.Get(pid_idx).pid(), pid);
ASSERT_EQ(proceses.Get(pid_idx).cmdline().Get(0), proc_name);
for (int tid = pid + 1; tid < pid + 3; tid++, tid_idx++) {
ASSERT_EQ(threads.Get(tid_idx).tid(), tid);
ASSERT_EQ(threads.Get(tid_idx).tgid(), pid);
ASSERT_EQ(threads.Get(tid_idx).name(), "thread_" + std::to_string(tid));
}
}
}
} // namespace
} // namespace perfetto