/*
* 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 "perfetto/base/build_config.h"
// Watchdog is currently not supported on Mac. This ifdef-based exclusion is
// here only for the Mac build in AOSP. The standalone and chromium builds
// exclude this file at the GN level. However, propagating the per-os exclusion
// through our GN -> BP build file translator is not worth the effort for a
// one-off case.
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
#include "perfetto/base/watchdog_posix.h"
#include <fcntl.h>
#include <inttypes.h>
#include <signal.h>
#include <stdint.h>
#include <fstream>
#include <thread>
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/scoped_file.h"
#include "perfetto/base/thread_utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_EMBEDDER_BUILD)
#error perfetto::base::Watchdog should not be used in Chromium or embedders
#endif
namespace perfetto {
namespace base {
namespace {
constexpr uint32_t kDefaultPollingInterval = 30 * 1000;
bool IsMultipleOf(uint32_t number, uint32_t divisor) {
return number >= divisor && number % divisor == 0;
}
double MeanForArray(const uint64_t array[], size_t size) {
uint64_t total = 0;
for (size_t i = 0; i < size; i++) {
total += array[i];
}
return total / size;
}
} // namespace
Watchdog::Watchdog(uint32_t polling_interval_ms)
: polling_interval_ms_(polling_interval_ms) {}
Watchdog::~Watchdog() {
if (!thread_.joinable()) {
PERFETTO_DCHECK(!enabled_);
return;
}
PERFETTO_DCHECK(enabled_);
enabled_ = false;
exit_signal_.notify_one();
thread_.join();
}
Watchdog* Watchdog::GetInstance() {
static Watchdog* watchdog = new Watchdog(kDefaultPollingInterval);
return watchdog;
}
Watchdog::Timer Watchdog::CreateFatalTimer(uint32_t ms) {
if (!enabled_.load(std::memory_order_relaxed))
return Watchdog::Timer(0);
return Watchdog::Timer(ms);
}
void Watchdog::Start() {
std::lock_guard<std::mutex> guard(mutex_);
if (thread_.joinable()) {
PERFETTO_DCHECK(enabled_);
} else {
PERFETTO_DCHECK(!enabled_);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
// Kick the thread to start running but only on Android or Linux.
enabled_ = true;
thread_ = std::thread(&Watchdog::ThreadMain, this);
#endif
}
}
void Watchdog::SetMemoryLimit(uint64_t bytes, uint32_t window_ms) {
// Update the fields under the lock.
std::lock_guard<std::mutex> guard(mutex_);
PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) || bytes == 0);
size_t size = bytes == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
memory_window_bytes_.Reset(size);
memory_limit_bytes_ = bytes;
}
void Watchdog::SetCpuLimit(uint32_t percentage, uint32_t window_ms) {
std::lock_guard<std::mutex> guard(mutex_);
PERFETTO_CHECK(percentage <= 100);
PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) ||
percentage == 0);
size_t size = percentage == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
cpu_window_time_ticks_.Reset(size);
cpu_limit_percentage_ = percentage;
}
void Watchdog::ThreadMain() {
base::ScopedFile stat_fd(base::OpenFile("/proc/self/stat", O_RDONLY));
if (!stat_fd) {
PERFETTO_ELOG("Failed to open stat file to enforce resource limits.");
return;
}
std::unique_lock<std::mutex> guard(mutex_);
for (;;) {
exit_signal_.wait_for(guard,
std::chrono::milliseconds(polling_interval_ms_));
if (!enabled_)
return;
lseek(stat_fd.get(), 0, SEEK_SET);
char c[512];
if (read(stat_fd.get(), c, sizeof(c)) < 0) {
PERFETTO_ELOG("Failed to read stat file to enforce resource limits.");
return;
}
c[sizeof(c) - 1] = '\0';
unsigned long int utime = 0l;
unsigned long int stime = 0l;
long int rss_pages = -1l;
PERFETTO_CHECK(
sscanf(c,
"%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
"%lu %*d %*d %*d %*d %*d %*d %*u %*u %ld",
&utime, &stime, &rss_pages) == 3);
uint64_t cpu_time = utime + stime;
uint64_t rss_bytes = static_cast<uint64_t>(rss_pages) * base::kPageSize;
CheckMemory(rss_bytes);
CheckCpu(cpu_time);
}
}
void Watchdog::CheckMemory(uint64_t rss_bytes) {
if (memory_limit_bytes_ == 0)
return;
// Add the current stat value to the ring buffer and check that the mean
// remains under our threshold.
if (memory_window_bytes_.Push(rss_bytes)) {
if (memory_window_bytes_.Mean() > memory_limit_bytes_) {
PERFETTO_ELOG(
"Memory watchdog trigger. Memory window of %f bytes is above the "
"%" PRIu64 " bytes limit.",
memory_window_bytes_.Mean(), memory_limit_bytes_);
kill(getpid(), SIGABRT);
}
}
}
void Watchdog::CheckCpu(uint64_t cpu_time) {
if (cpu_limit_percentage_ == 0)
return;
// Add the cpu time to the ring buffer.
if (cpu_window_time_ticks_.Push(cpu_time)) {
// Compute the percentage over the whole window and check that it remains
// under the threshold.
uint64_t difference_ticks = cpu_window_time_ticks_.NewestWhenFull() -
cpu_window_time_ticks_.OldestWhenFull();
double window_interval_ticks =
(static_cast<double>(WindowTimeForRingBuffer(cpu_window_time_ticks_)) /
1000.0) *
sysconf(_SC_CLK_TCK);
double percentage = static_cast<double>(difference_ticks) /
static_cast<double>(window_interval_ticks) * 100;
if (percentage > cpu_limit_percentage_) {
PERFETTO_ELOG("CPU watchdog trigger. %f%% CPU use is above the %" PRIu32
"%% CPU limit.",
percentage, cpu_limit_percentage_);
kill(getpid(), SIGABRT);
}
}
}
uint32_t Watchdog::WindowTimeForRingBuffer(const WindowedInterval& window) {
return static_cast<uint32_t>(window.size() - 1) * polling_interval_ms_;
}
bool Watchdog::WindowedInterval::Push(uint64_t sample) {
// Add the sample to the current position in the ring buffer.
buffer_[position_] = sample;
// Update the position with next one circularily.
position_ = (position_ + 1) % size_;
// Set the filled flag the first time we wrap.
filled_ = filled_ || position_ == 0;
return filled_;
}
double Watchdog::WindowedInterval::Mean() const {
return MeanForArray(buffer_.get(), size_);
}
void Watchdog::WindowedInterval::Clear() {
position_ = 0;
buffer_.reset(new uint64_t[size_]());
}
void Watchdog::WindowedInterval::Reset(size_t new_size) {
position_ = 0;
size_ = new_size;
buffer_.reset(new_size == 0 ? nullptr : new uint64_t[new_size]());
}
Watchdog::Timer::Timer(uint32_t ms) {
if (!ms)
return; // No-op timer created when the watchdog is disabled.
struct sigevent sev = {};
sev.sigev_notify = SIGEV_THREAD_ID;
sev._sigev_un._tid = base::GetThreadId();
sev.sigev_signo = SIGABRT;
PERFETTO_CHECK(timer_create(CLOCK_MONOTONIC, &sev, &timerid_) != -1);
struct itimerspec its = {};
its.it_value.tv_sec = ms / 1000;
its.it_value.tv_nsec = 1000000L * (ms % 1000);
PERFETTO_CHECK(timer_settime(timerid_, 0, &its, nullptr) != -1);
}
Watchdog::Timer::~Timer() {
if (timerid_ != nullptr) {
timer_delete(timerid_);
}
}
Watchdog::Timer::Timer(Timer&& other) noexcept {
timerid_ = other.timerid_;
other.timerid_ = nullptr;
}
} // namespace base
} // namespace perfetto
#endif // PERFETTO_OS_MACOSX