blob: c31e1837ad8504f385cac119dd76b4a7f6e53eee [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/sys/appmgr/cpu_watcher.h"
#include <lib/gtest/real_loop_fixture.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/inspect/cpp/reader.h>
#include <gtest/gtest.h>
namespace component {
namespace {
TEST(CpuWatcher, EmptyTasks) {
inspect::Inspector inspector;
CpuWatcher watcher(inspector.GetRoot().CreateChild("test"), zx::job());
watcher.Measure();
// Ensure that we do not record any measurements for an invalid job handle.
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
const auto* node = hierarchy.GetByPath({"test", "measurements", "root", "@samples"});
ASSERT_NE(nullptr, node);
EXPECT_TRUE(node->children().empty());
EXPECT_TRUE(node->node().properties().empty());
}
TEST(CpuWatcher, BadTask) {
zx::job self;
zx_info_handle_basic_t basic;
ASSERT_EQ(ZX_OK, zx::job::default_job()->get_info(ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic),
nullptr, nullptr));
ASSERT_EQ(ZX_OK, zx::job::default_job()->duplicate(basic.rights & ~ZX_RIGHT_INSPECT, &self));
inspect::Inspector inspector;
CpuWatcher watcher(inspector.GetRoot().CreateChild("test"), zx::job());
watcher.AddTask({"test_invalid"}, std::move(self));
watcher.Measure();
// Ensure that we do not record any measurements for a task that cannot be read.
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
const auto* node =
hierarchy.GetByPath({"test", "measurements", "root", "test_invalid", "@samples"});
ASSERT_NE(nullptr, node);
EXPECT_TRUE(node->children().empty());
EXPECT_TRUE(node->node().properties().empty());
}
// Returns the number of valid samples under the given hierarchy, or -1 if any sample is invalid.
int64_t GetValidSampleCount(const inspect::Hierarchy* hierarchy) {
if (!hierarchy) {
printf("hierarchy is null!\n");
return -1;
}
hierarchy = hierarchy->GetByPath({"@samples"});
if (!hierarchy) {
printf("hierarchy does not have @samples!\n");
return -1;
}
size_t ret = 0;
for (const auto& child : hierarchy->children()) {
if (!std::all_of(child.name().begin(), child.name().end(),
[](char c) { return std::isdigit(c); })) {
printf("name '%s' is not entirely numeric!\n", child.name().c_str());
return -1;
}
auto check_int_nonzero = [&](const std::string& name) -> bool {
const auto* prop = child.node().get_property<inspect::IntPropertyValue>(name);
if (!prop) {
printf("missing %s\n", name.c_str());
return false;
}
if (prop->value() == 0) {
printf("property %s is 0\n", name.c_str());
return false;
}
return true;
};
if (!check_int_nonzero("timestamp") || !check_int_nonzero("cpu_time") ||
!check_int_nonzero("queue_time")) {
printf("invalid entry found at %s\n", child.name().c_str());
return -1;
}
ret++;
}
return ret;
}
TEST(CpuWatcher, SampleSingle) {
zx::job self;
ASSERT_EQ(ZX_OK, zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &self));
inspect::Inspector inspector;
CpuWatcher watcher(inspector.GetRoot().CreateChild("test"), zx::job(), 3 /* max_samples */);
watcher.AddTask({"test_valid"}, std::move(self));
// Ensure that we record measurements up to the limit.
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
1, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
2, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
3, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
// One measurement rolled out.
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
3, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
// Remove the task, the value is still there for now.
watcher.RemoveTask({"test_valid"});
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
3, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
// Measurements roll out now.
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
2, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(
1, GetValidSampleCount(hierarchy.GetByPath({"test", "measurements", "root", "test_valid"})));
// After the last measurement rolls out, the node is deleted.
watcher.Measure();
hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
EXPECT_EQ(nullptr, hierarchy.GetByPath({"test", "measurements", "root", "test_valid"}));
}
TEST(CpuWatcher, SampleMultiple) {
zx::job self;
ASSERT_EQ(ZX_OK, zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &self));
inspect::Inspector inspector;
CpuWatcher watcher(inspector.GetRoot().CreateChild("test"), zx::job(), 3 /* max_samples */);
watcher.AddTask({"test_valid"}, std::move(self));
ASSERT_EQ(ZX_OK, zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &self));
watcher.AddTask({"test_valid", "nested"}, std::move(self));
ASSERT_EQ(ZX_OK, zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &self));
watcher.RemoveTask({"test_valid"});
watcher.Measure();
watcher.AddTask({"separate", "nested"}, std::move(self));
watcher.Measure();
watcher.Measure();
// Expected hierarchy:
// root:
// test_valid: 0 samples
// nested: 3 samples
// separate: 0 samples
// nested: 3 samples
auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()).take_value();
inspect::Hierarchy *test_valid = nullptr, *test_valid_nested = nullptr,
*separate_nested = nullptr, *separate = nullptr;
hierarchy.Visit([&](const std::vector<std::string>& path, inspect::Hierarchy* hierarchy) {
if (path == std::vector<std::string>({"root", "test", "measurements", "root", "test_valid"})) {
test_valid = hierarchy;
} else if (path == std::vector<std::string>(
{"root", "test", "measurements", "root", "test_valid", "nested"})) {
test_valid_nested = hierarchy;
} else if (path ==
std::vector<std::string>({"root", "test", "measurements", "root", "separate"})) {
separate = hierarchy;
} else if (path == std::vector<std::string>(
{"root", "test", "measurements", "root", "separate", "nested"})) {
separate_nested = hierarchy;
}
return true;
});
EXPECT_EQ(0, GetValidSampleCount(test_valid));
EXPECT_EQ(3, GetValidSampleCount(test_valid_nested));
EXPECT_EQ(0, GetValidSampleCount(separate));
EXPECT_EQ(3, GetValidSampleCount(separate_nested));
}
} // namespace
} // namespace component