blob: 3a76a745666cd98f7879b9645ec3e0aaf4f53a18 [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 <lib/syslog/global.h>
#include <lib/syslog/logger.h>
#include <lib/watchdog/operations.h>
#include <lib/watchdog/watchdog.h>
#include <unistd.h>
#include <chrono>
#include <cstddef>
#include <cstdio>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <type_traits>
#include <utility>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
namespace fs_watchdog {
namespace {
// Default sleep argument for the watchdog
constexpr std::chrono::nanoseconds kSleepDuration = std::chrono::milliseconds(500);
// Custom/overloaded operation timeout
constexpr int kOperationTimeoutSeconds = 1;
constexpr std::chrono::nanoseconds kOperationTimeout =
std::chrono::seconds(kOperationTimeoutSeconds);
const Options kDefaultOptions = {kSleepDuration, true, kDefaultLogSeverity};
const Options kDisabledOptions = {kSleepDuration, false, kDefaultLogSeverity};
// Test that we can start the watchdog.
TEST(Watchdog, StartTest) {
auto watchdog = CreateWatchdog(kDefaultOptions);
ASSERT_TRUE(watchdog->Start().is_ok());
}
// Test that we can shutdown the watchdog.
TEST(Watchdog, ShutDownTest) {
auto watchdog = CreateWatchdog(kDefaultOptions);
ASSERT_TRUE(watchdog->Start().is_ok());
ASSERT_TRUE(watchdog->ShutDown().is_ok());
}
// Test that we can shutdown watchdog without the thread waiting for duration of it's sleep.
TEST(Watchdog, ShutDownImmediatelyTest) {
auto options = kDefaultOptions;
options.sleep = std::chrono::hours(1);
auto watchdog = CreateWatchdog(kDefaultOptions);
ASSERT_TRUE(watchdog->Start().is_ok());
std::this_thread::sleep_for(std::chrono::seconds(1));
auto t1 = std::chrono::steady_clock::now();
ASSERT_TRUE(watchdog->ShutDown().is_ok());
auto t2 = std::chrono::steady_clock::now();
ASSERT_LT(t2 - t1, std::chrono::seconds(10));
}
constexpr const char* kTestOperationName1 = "WatchdogTestOperation1";
constexpr const char* kTestOperationName2 = "WatchdogTestOperation2";
constexpr const char* kTestOperationName3 = "WatchdogTestOperation3";
// These are some of the known messages printed by the watchdog.
const std::string_view kLogMessageOperation("Operation:");
const std::string_view kLogMessageExceededTimeout("exceeded timeout");
const std::string_view kLogMessageTimeout("Timeout");
const std::string_view kLogMessageExceededOperation("exceeded operation:");
const std::string_view kLogMessageCompleted("completed(");
class TestOperation : public OperationBase {
public:
explicit TestOperation(const char* operation_name,
std::chrono::nanoseconds timeout = kOperationTimeout)
: operation_name_(operation_name), timeout_(timeout) {}
std::string_view Name() const final { return operation_name_; }
std::chrono::nanoseconds Timeout() const final { return timeout_; }
private:
// Name of the operation.
const char* operation_name_ = nullptr;
// Timeout for this operation.
std::chrono::nanoseconds timeout_;
};
class TestOperationTracker : public FsOperationTracker {
public:
TestOperationTracker(OperationBase* operation, WatchdogInterface* watchdog, bool track = true)
: FsOperationTracker(operation, watchdog, track) {}
void OnTimeOut(FILE* out_stream) const final { handler_called_++; }
bool TimeoutHandlerCalled() const { return handler_called_ > 0; }
int TimeoutHandlerCalledCount() const { return handler_called_; }
private:
// Incremented on each call to TimeoutHandler.
mutable int handler_called_ = 0;
};
std::unique_ptr<std::string> GetData(int fd) {
constexpr size_t kBufferSize = 1024 * 1024;
auto buffer = std::make_unique<char[]>(kBufferSize);
memset(buffer.get(), 0, kBufferSize);
ssize_t read_length;
size_t offset = 0;
while ((read_length = read(fd, &buffer.get()[offset], kBufferSize - offset - 1)) >= 0) {
EXPECT_GE(read_length, 0);
offset += read_length;
if (offset >= kBufferSize - 1 || read_length == 0) {
buffer.get()[kBufferSize - 1] = '\0';
return std::make_unique<std::string>(std::string(buffer.get()));
}
}
EXPECT_GE(read_length, 0);
return nullptr;
}
// Returns true if the number of occurances of string |substr| in string |str|
// matches expected.
bool CheckOccurance(const std::string& str, const std::string_view substr, int expected) {
int count = 0;
std::string::size_type start = 0;
while ((start = str.find(substr, start)) != std::string::npos) {
++count;
start += substr.length();
}
return count == expected;
}
std::pair<fbl::unique_fd, fbl::unique_fd> SetupLog() {
int pipefd[2];
EXPECT_EQ(pipe2(pipefd, O_NONBLOCK), 0);
fbl::unique_fd fd_to_close1(pipefd[0]);
fbl::unique_fd fd_to_close2(pipefd[1]);
fx_logger_activate_fallback(fx_log_get_logger(), pipefd[0]);
return {std::move(fd_to_close1), std::move(fd_to_close2)};
}
TEST(Watchdog, TryToAddDuplicate) {
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
TestOperationTracker tracker(&op, watchdog.get());
auto result = watchdog->Track(&tracker);
ASSERT_FALSE(result.is_ok());
ASSERT_EQ(result.error_value(), ZX_ERR_ALREADY_EXISTS);
}
TEST(Watchdog, TryToAddDuplicateAfterTimeout) {
[[maybe_unused]] auto fd_pair = SetupLog();
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
TestOperationTracker tracker(&op, watchdog.get());
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds + 1));
ASSERT_TRUE(tracker.TimeoutHandlerCalled());
ASSERT_EQ(watchdog->Track(&tracker).error_value(), ZX_ERR_ALREADY_EXISTS);
}
TEST(Watchdog, StartDisabledWatchdog) {
auto watchdog = CreateWatchdog(kDisabledOptions);
ASSERT_EQ(watchdog->Start().error_value(), ZX_ERR_BAD_STATE);
}
TEST(Watchdog, StartRunningWatchdog) {
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_OK(watchdog->Start());
ASSERT_EQ(watchdog->Start().error_value(), ZX_ERR_BAD_STATE);
}
TEST(Watchdog, ShutDownUnstarted) {
auto watchdog = CreateWatchdog(kDefaultOptions);
ASSERT_EQ(watchdog->ShutDown().error_value(), ZX_ERR_BAD_STATE);
}
TEST(Watchdog, ShutDownAgain) {
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_OK(watchdog->Start());
EXPECT_TRUE(watchdog->ShutDown().is_ok());
ASSERT_EQ(watchdog->ShutDown().error_value(), ZX_ERR_BAD_STATE);
}
TEST(Watchdog, TrackWithDisabledWatchdog) {
auto watchdog = CreateWatchdog(kDisabledOptions);
EXPECT_FALSE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
TestOperationTracker tracker(&op, watchdog.get(), false);
ASSERT_EQ(watchdog->Track(&tracker).error_value(), ZX_ERR_BAD_STATE);
}
TEST(Watchdog, RemoveUntrackedOperation) {
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
OperationTrackerId id;
{
TestOperation op(kTestOperationName1, kOperationTimeout);
TestOperationTracker tracker(&op, watchdog.get(), false);
id = tracker.GetId();
}
ASSERT_EQ(watchdog->Untrack(id).error_value(), ZX_ERR_NOT_FOUND);
}
TEST(Watchdog, OperationTimesOut) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
{
TestOperation op(kTestOperationName1, kOperationTimeout);
TestOperationTracker tracker(&op, watchdog.get());
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds + 1));
ASSERT_TRUE(tracker.TimeoutHandlerCalled());
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 1));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 1));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 2));
}
TEST(Watchdog, NoTimeoutsWhenDisabled) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDisabledOptions);
EXPECT_TRUE(watchdog->Start().is_error());
TestOperation op(kTestOperationName1, kOperationTimeout);
{
TestOperationTracker tracker(&op, watchdog.get(), false);
ASSERT_EQ(watchdog->Track(&tracker).error_value(), ZX_ERR_BAD_STATE);
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds + 1));
ASSERT_FALSE(tracker.TimeoutHandlerCalled());
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 0));
}
TEST(Watchdog, NoTimeoutsWhenShutDown) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
EXPECT_TRUE(watchdog->ShutDown().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
{
TestOperationTracker tracker(&op, watchdog.get());
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds + 1));
ASSERT_FALSE(tracker.TimeoutHandlerCalled());
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 0));
}
TEST(Watchdog, OperationDoesNotTimesOut) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout + std::chrono::seconds(10));
{
TestOperationTracker tracker(&op, watchdog.get());
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds));
ASSERT_FALSE(tracker.TimeoutHandlerCalled());
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// We should not find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 0));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 0));
}
TEST(Watchdog, MultipleOperationsTimeout) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
{
TestOperation op1(kTestOperationName1, kOperationTimeout);
TestOperation op2(kTestOperationName2, kOperationTimeout);
TestOperation op3(kTestOperationName3, kOperationTimeout + std::chrono::seconds(10));
TestOperationTracker tracker1(&op1, watchdog.get());
TestOperationTracker tracker3(&op3, watchdog.get());
TestOperationTracker tracker2(&op2, watchdog.get());
std::this_thread::sleep_for(std::chrono::seconds(kOperationTimeoutSeconds + 1));
ASSERT_TRUE(tracker1.TimeoutHandlerCalled());
ASSERT_TRUE(tracker2.TimeoutHandlerCalled());
ASSERT_FALSE(tracker3.TimeoutHandlerCalled());
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 2));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 2));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 2));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName2, 2));
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName3, 0));
}
TEST(Watchdog, LoggedOnlyOnce) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
{
TestOperationTracker tracker(&op, watchdog.get());
// Sleep as long as it takes to scan in-flight operation twice.
std::this_thread::sleep_for(std::chrono::seconds((2 * kOperationTimeoutSeconds) + 1));
ASSERT_TRUE(tracker.TimeoutHandlerCalled());
ASSERT_EQ(tracker.TimeoutHandlerCalledCount(), 1);
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageOperation, 1));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededTimeout, 1));
// Operation name gets printed twice - once when timesout and once when it
// completes
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 2));
}
TEST(Watchdog, DelayedCompletionLogging) {
auto fd_pair = SetupLog();
{
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
TestOperation op(kTestOperationName1, kOperationTimeout);
{
TestOperationTracker tracker(&op, watchdog.get());
// Sleep as long as it takes to scan in-flight operation twice.
std::this_thread::sleep_for(std::chrono::seconds((2 * kOperationTimeoutSeconds) + 1));
ASSERT_TRUE(tracker.TimeoutHandlerCalled());
ASSERT_EQ(tracker.TimeoutHandlerCalledCount(), 1);
}
}
fd_pair.first.reset();
auto str = GetData(fd_pair.second.get());
// Find known strings.
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageTimeout, 1));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageExceededOperation, 1));
ASSERT_TRUE(CheckOccurance(*str.get(), kLogMessageCompleted, 1));
// Operation name gets printed twice - once when timesout and once when it
// completes
ASSERT_TRUE(CheckOccurance(*str.get(), kTestOperationName1, 2));
}
// Define a an operation(of Append type) with new timeout and track it.
TEST(Watchdog, TrackFsOperationType) {
static const FsOperationType kMyfsAppendOperation(FsOperationType::CommonFsOperation::Append,
std::chrono::nanoseconds(1000000000));
auto watchdog = CreateWatchdog(kDefaultOptions);
EXPECT_TRUE(watchdog->Start().is_ok());
FsOperationTracker tracker(&kMyfsAppendOperation, watchdog.get());
}
} // namespace
} // namespace fs_watchdog