blob: 3e63d18dd3ba4bc133d0087b7b4cfc1957130fd4 [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/developer/memory/monitor/high_water.h"
#include <fcntl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/namespace.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/memfs/memfs.h>
#include <lib/syslog/cpp/macros.h>
#include <gtest/gtest.h>
#include <src/lib/files/file.h>
#include <src/lib/files/path.h>
#include "src/developer/memory/metrics/capture.h"
#include "src/developer/memory/metrics/tests/test_utils.h"
using namespace memory;
namespace monitor {
namespace {
const char kMemfsDir[] = "/data";
// Mounts a memfs filesystem at a given path and unmounts it when this object
// goes out of scope.
class ScopedMemfs {
public:
// Creates a new memfs filesystem at the given path.
static zx_status_t InstallAt(const char* path, async_dispatcher_t* dispatcher,
std::unique_ptr<ScopedMemfs>* out) {
fdio_ns_t* ns;
zx_status_t status = fdio_ns_get_installed(&ns);
if (status != ZX_OK) {
return status;
}
memfs_filesystem_t* fs;
zx_handle_t root;
status = memfs_create_filesystem(dispatcher, &fs, &root);
if (status != ZX_OK) {
return status;
}
status = fdio_ns_bind(ns, path, root);
if (status != ZX_OK) {
memfs_free_filesystem(fs, nullptr);
return status;
}
*out = std::make_unique<ScopedMemfs>(fs, path);
return ZX_OK;
}
ScopedMemfs(memfs_filesystem_t* fs, const char* path) : fs_(fs), path_(path) {}
~ScopedMemfs() {
fdio_ns_t* ns;
sync_completion_t completion;
memfs_free_filesystem(fs_, &completion);
FX_CHECK(sync_completion_wait(&completion, ZX_TIME_INFINITE) == ZX_OK)
<< "Failed to unmount memfs";
FX_CHECK(fdio_ns_get_installed(&ns) == ZX_OK) << "Failed to read namespaces";
FX_CHECK(fdio_ns_unbind(ns, path_) == ZX_OK) << "Failed to unbind memfs filesystem";
}
private:
memfs_filesystem_t* fs_;
const char* path_;
};
class HighWaterUnitTest : public gtest::RealLoopFixture {
protected:
int memfs_dir_;
private:
void SetUp() override {
RealLoopFixture::SetUp();
// Install memfs on a different async loop thread to resolve some deadlock
// when doing blocking file operations on our test loop.
FX_CHECK(ScopedMemfs::InstallAt(kMemfsDir, memfs_loop_.dispatcher(), &data_) == ZX_OK);
memfs_loop_.StartThread();
memfs_dir_ = open(kMemfsDir, O_RDONLY | O_DIRECTORY);
ASSERT_LT(0, memfs_dir_);
}
void TearDown() override {
RealLoopFixture::TearDown();
data_.reset();
close(memfs_dir_);
memfs_loop_.Shutdown();
}
// private:
async::Loop memfs_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
std::unique_ptr<ScopedMemfs> data_;
};
TEST_F(HighWaterUnitTest, Basic) {
CaptureSupplier cs({{
.kmem = {.free_bytes = 100},
},
{.kmem = {.free_bytes = 100},
.vmos =
{
{.koid = 1, .name = "v1", .committed_bytes = 101},
},
.processes = {
{.koid = 2, .name = "p1", .vmos = {1}},
}}});
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "latest.txt"));
HighWater hw(kMemfsDir, zx::msec(10), 100, dispatcher(),
[&cs](Capture* c, CaptureLevel l) { return cs.GetCapture(c, l); });
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "latest.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "previous.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "latest_digest.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "previous_digest.txt"));
RunLoopUntil([&cs] { return cs.empty(); });
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest.txt"));
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest_digest.txt"));
EXPECT_FALSE(hw.GetHighWater().empty());
EXPECT_FALSE(hw.GetHighWaterDigest().empty());
}
TEST_F(HighWaterUnitTest, RunTwice) {
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "previous.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "latest.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "previous_digest.txt"));
ASSERT_FALSE(files::IsFileAt(memfs_dir_, "latest_digest.txt"));
{
CaptureSupplier cs({{
.kmem = {.free_bytes = 100},
},
{.kmem = {.free_bytes = 100},
.vmos =
{
{.koid = 1, .name = "v1", .committed_bytes = 101},
},
.processes = {
{.koid = 2, .name = "p1", .vmos = {1}},
}}});
HighWater hw(kMemfsDir, zx::msec(10), 100, dispatcher(),
[&cs](Capture* c, CaptureLevel l) { return cs.GetCapture(c, l); });
RunLoopUntil([&cs] { return cs.empty(); });
EXPECT_FALSE(hw.GetHighWater().empty());
}
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest.txt"));
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest_digest.txt"));
EXPECT_FALSE(files::IsFileAt(memfs_dir_, "previous.txt"));
EXPECT_FALSE(files::IsFileAt(memfs_dir_, "previous_digest.txt"));
{
CaptureSupplier cs({{
.kmem = {.free_bytes = 100},
},
{.kmem = {.free_bytes = 100},
.vmos =
{
{.koid = 1, .name = "v1", .committed_bytes = 101},
},
.processes = {
{.koid = 2, .name = "p1", .vmos = {1}},
}}});
HighWater hw(kMemfsDir, zx::msec(10), 100, dispatcher(),
[&cs](Capture* c, CaptureLevel l) { return cs.GetCapture(c, l); });
RunLoopUntil([&cs] { return cs.empty(); });
EXPECT_FALSE(hw.GetHighWater().empty());
EXPECT_FALSE(hw.GetPreviousHighWater().empty());
EXPECT_FALSE(hw.GetHighWaterDigest().empty());
EXPECT_FALSE(hw.GetPreviousHighWaterDigest().empty());
}
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest.txt"));
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "latest_digest.txt"));
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "previous.txt"));
EXPECT_TRUE(files::IsFileAt(memfs_dir_, "previous_digest.txt"));
}
} // namespace
} // namespace monitor