blob: 45aea6406332ae10137dda177aa93367dfc2b8f8 [file] [log] [blame]
// Copyright 2020 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/cobalt/bin/system-metrics/log_stats_fetcher_impl.h"
#include <lib/fidl/cpp/binding_set.h>
#include <lib/gtest/test_loop_fixture.h>
#include "src/cobalt/bin/system-metrics/testing/fake_archive.h"
namespace cobalt {
namespace {
const std::unordered_map<std::string, ComponentEventCode> kComponentCodeMap = {
{"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm", ComponentEventCode::Appmgr},
{"fuchsia-pkg://fuchsia.com/sysmgr#meta/sysmgr.cmx", ComponentEventCode::Sysmgr}};
const char kBaselineArchive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 8,
"kernel_logs": 5,
"by_component": {
"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm": {
"error_logs": 4
}
},
"granular_stats": {
"0": {
"overflowed": false
}
}
}
}
}
)";
class LogStatsFetcherImplTest : public gtest::TestLoopFixture {
public:
LogStatsFetcherImplTest()
: fake_archive_(kBaselineArchive),
fetcher_(
dispatcher(), [this] { return BindArchiveAccessor(); }, kComponentCodeMap,
&fake_clock_) {
RunLoopUntilIdle();
}
bool FetchMetricsSync(LogStatsFetcher::Metrics* result) {
bool fetched = false;
fetcher_.FetchMetrics([&fetched, result](const LogStatsFetcher::Metrics& metrics) {
*result = metrics;
fetched = true;
});
RunLoopUntilIdle();
return fetched;
}
void ReplaceArchive(const char* archive) { fake_archive_ = FakeArchive(archive); }
protected:
fuchsia::diagnostics::ArchiveAccessorPtr BindArchiveAccessor() {
fuchsia::diagnostics::ArchiveAccessorPtr ret;
bindings_.AddBinding(&fake_archive_, ret.NewRequest());
return ret;
}
fidl::BindingSet<fuchsia::diagnostics::ArchiveAccessor> bindings_;
FakeArchive fake_archive_;
abs_clock::FakeClock fake_clock_;
LogStatsFetcherImpl fetcher_;
};
TEST_F(LogStatsFetcherImplTest, Simple) {
const char archive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 11,
"kernel_logs": 6,
"by_component": {
"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm": {
"error_logs": 6
}
},
"granular_stats": {
"0": {
"overflowed": false
}
}
}
}
}
)";
ReplaceArchive(archive);
LogStatsFetcher::Metrics metrics;
ASSERT_TRUE(FetchMetricsSync(&metrics));
// All counts are relative to the baseline, e.g. error count = 11-8 = 3.
EXPECT_EQ(3u, metrics.error_count);
EXPECT_EQ(1u, metrics.klog_count);
EXPECT_EQ(2u, metrics.per_component_error_count.size());
EXPECT_EQ(2u, metrics.per_component_error_count[ComponentEventCode::Appmgr]);
// There's been 3 new errors, 2 of them coming from Appmgr, so 1 should be reported as "Other".
EXPECT_EQ(1u, metrics.per_component_error_count[ComponentEventCode::Other]);
}
TEST_F(LogStatsFetcherImplTest, CountDrops) {
const char archive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 4,
"kernel_logs": 2,
"by_component": {
"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm": {
"error_logs": 1
}
},
"granular_stats": {
"0": {
"overflowed": false
}
}
}
}
}
)";
ReplaceArchive(archive);
LogStatsFetcher::Metrics metrics;
ASSERT_TRUE(FetchMetricsSync(&metrics));
// We should not subtract the baseline if the counts drop (it is assumed that the components have
// restarted).
EXPECT_EQ(4u, metrics.error_count);
EXPECT_EQ(2u, metrics.klog_count);
EXPECT_EQ(2u, metrics.per_component_error_count.size());
EXPECT_EQ(1u, metrics.per_component_error_count[ComponentEventCode::Appmgr]);
EXPECT_EQ(3u, metrics.per_component_error_count[ComponentEventCode::Other]);
}
TEST_F(LogStatsFetcherImplTest, MultipleComponents) {
const char archive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 12,
"kernel_logs": 6,
"by_component": {
"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm": {
"error_logs": 6
},
"fuchsia-pkg://fuchsia.com/sysmgr#meta/sysmgr.cmx": {
"error_logs": 1
}
},
"granular_stats": {
"0": {
"overflowed": false
}
}
}
}
}
)";
ReplaceArchive(archive);
LogStatsFetcher::Metrics metrics;
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(4u, metrics.error_count);
EXPECT_EQ(1u, metrics.klog_count);
EXPECT_EQ(3u, metrics.per_component_error_count.size());
EXPECT_EQ(2u, metrics.per_component_error_count[ComponentEventCode::Appmgr]);
EXPECT_EQ(1u, metrics.per_component_error_count[ComponentEventCode::Sysmgr]);
EXPECT_EQ(1u, metrics.per_component_error_count[ComponentEventCode::Other]);
}
TEST_F(LogStatsFetcherImplTest, NotInAllowlist) {
const char archive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 11,
"kernel_logs": 6,
"by_component": {
"fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm": {
"error_logs": 6
},
"fuchsia-pkg://fuchsia.com/foo#meta/bar.cmx": {
"error_logs": 1
}
},
"granular_stats": {
"0": {
"overflowed": false
}
}
}
}
}
)";
ReplaceArchive(archive);
LogStatsFetcher::Metrics metrics;
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(3u, metrics.error_count);
EXPECT_EQ(1u, metrics.klog_count);
// Since foo is not in the allowlist, it must be reported as "Other".
EXPECT_EQ(2u, metrics.per_component_error_count.size());
EXPECT_EQ(2u, metrics.per_component_error_count[ComponentEventCode::Appmgr]);
EXPECT_EQ(1u, metrics.per_component_error_count[ComponentEventCode::Other]);
}
TEST_F(LogStatsFetcherImplTest, GranularStats) {
const char archive[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 8,
"kernel_logs": 5,
"by_component": {},
"granular_stats": {
"0": {
"overflowed": false,
"0": {
"file_path": "path/to/file.cc",
"line_no": 123,
"count": 3
}
}
}
}
}
}
)";
ReplaceArchive(archive);
// We should not report bucket 0 until at least 15 minutes has passed.
fake_clock_.AdvanceTime(zx::min(10));
LogStatsFetcher::Metrics metrics;
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(0u, metrics.granular_stats.size());
// Now bucket 0 should be reported.
fake_clock_.AdvanceTime(zx::min(10));
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(1u, metrics.granular_stats.size());
EXPECT_EQ(LogStatsFetcher::GranularStatsRecord("path/to/file.cc", 123, 3),
metrics.granular_stats[0]);
// Bucket 0 should not be reported twice.
fake_clock_.AdvanceTime(zx::min(1));
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(0u, metrics.granular_stats.size());
// Bucket 1 is created. It should not be reported until at least 30 minutes has passed.
const char archive2[] = R"(
{
"moniker": "core/log-stats",
"payload": {
"root": {
"error_logs": 8,
"kernel_logs": 5,
"by_component": {},
"granular_stats": {
"0": {
"overflowed": false,
"0": {
"file_path": "path/to/file.cc",
"line_no": 123,
"count": 3
}
},
"1": {
"overflowed": false,
"0": {
"file_path": "another_file.cc",
"line_no": 15,
"count": 1
},
"1": {
"file_path": "src/lib/test.cpp",
"line_no": 150,
"count": 5
}
}
}
}
}
}
)";
ReplaceArchive(archive2);
fake_clock_.AdvanceTime(zx::min(1));
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(0u, metrics.granular_stats.size());
// Advance time. Now more than 30 minutes has passed and bucket 1 should be reported.
fake_clock_.AdvanceTime(zx::min(10));
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(2u, metrics.granular_stats.size());
EXPECT_EQ(LogStatsFetcher::GranularStatsRecord("another_file.cc", 15, 1),
metrics.granular_stats[0]);
EXPECT_EQ(LogStatsFetcher::GranularStatsRecord("src/lib/test.cpp", 150, 5),
metrics.granular_stats[1]);
// Bucket 1 should not be reported twice.
fake_clock_.AdvanceTime(zx::min(1));
ASSERT_TRUE(FetchMetricsSync(&metrics));
EXPECT_EQ(0u, metrics.granular_stats.size());
}
} // namespace
} // namespace cobalt