1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "perfetto/ext/base/watchdog.h"
18
19 #if PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
20
21 #include <fcntl.h>
22 #include <inttypes.h>
23 #include <signal.h>
24 #include <stdint.h>
25
26 #include <fstream>
27 #include <thread>
28
29 #include "perfetto/base/build_config.h"
30 #include "perfetto/base/logging.h"
31 #include "perfetto/base/thread_utils.h"
32 #include "perfetto/ext/base/scoped_file.h"
33
34 namespace perfetto {
35 namespace base {
36
37 namespace {
38
39 constexpr uint32_t kDefaultPollingInterval = 30 * 1000;
40
IsMultipleOf(uint32_t number,uint32_t divisor)41 bool IsMultipleOf(uint32_t number, uint32_t divisor) {
42 return number >= divisor && number % divisor == 0;
43 }
44
MeanForArray(const uint64_t array[],size_t size)45 double MeanForArray(const uint64_t array[], size_t size) {
46 uint64_t total = 0;
47 for (size_t i = 0; i < size; i++) {
48 total += array[i];
49 }
50 return static_cast<double>(total / size);
51
52 }
53
54 } // namespace
55
Watchdog(uint32_t polling_interval_ms)56 Watchdog::Watchdog(uint32_t polling_interval_ms)
57 : polling_interval_ms_(polling_interval_ms) {}
58
~Watchdog()59 Watchdog::~Watchdog() {
60 if (!thread_.joinable()) {
61 PERFETTO_DCHECK(!enabled_);
62 return;
63 }
64 PERFETTO_DCHECK(enabled_);
65 enabled_ = false;
66 exit_signal_.notify_one();
67 thread_.join();
68 }
69
GetInstance()70 Watchdog* Watchdog::GetInstance() {
71 static Watchdog* watchdog = new Watchdog(kDefaultPollingInterval);
72 return watchdog;
73 }
74
CreateFatalTimer(uint32_t ms)75 Watchdog::Timer Watchdog::CreateFatalTimer(uint32_t ms) {
76 if (!enabled_.load(std::memory_order_relaxed))
77 return Watchdog::Timer(0);
78
79 return Watchdog::Timer(ms);
80 }
81
Start()82 void Watchdog::Start() {
83 std::lock_guard<std::mutex> guard(mutex_);
84 if (thread_.joinable()) {
85 PERFETTO_DCHECK(enabled_);
86 } else {
87 PERFETTO_DCHECK(!enabled_);
88
89 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
90 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
91 // Kick the thread to start running but only on Android or Linux.
92 enabled_ = true;
93 thread_ = std::thread(&Watchdog::ThreadMain, this);
94 #endif
95 }
96 }
97
SetMemoryLimit(uint64_t bytes,uint32_t window_ms)98 void Watchdog::SetMemoryLimit(uint64_t bytes, uint32_t window_ms) {
99 // Update the fields under the lock.
100 std::lock_guard<std::mutex> guard(mutex_);
101
102 PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) || bytes == 0);
103
104 size_t size = bytes == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
105 memory_window_bytes_.Reset(size);
106 memory_limit_bytes_ = bytes;
107 }
108
SetCpuLimit(uint32_t percentage,uint32_t window_ms)109 void Watchdog::SetCpuLimit(uint32_t percentage, uint32_t window_ms) {
110 std::lock_guard<std::mutex> guard(mutex_);
111
112 PERFETTO_CHECK(percentage <= 100);
113 PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) ||
114 percentage == 0);
115
116 size_t size = percentage == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
117 cpu_window_time_ticks_.Reset(size);
118 cpu_limit_percentage_ = percentage;
119 }
120
ThreadMain()121 void Watchdog::ThreadMain() {
122 base::ScopedFile stat_fd(base::OpenFile("/proc/self/stat", O_RDONLY));
123 if (!stat_fd) {
124 PERFETTO_ELOG("Failed to open stat file to enforce resource limits.");
125 return;
126 }
127
128 std::unique_lock<std::mutex> guard(mutex_);
129 for (;;) {
130 exit_signal_.wait_for(guard,
131 std::chrono::milliseconds(polling_interval_ms_));
132 if (!enabled_)
133 return;
134
135 lseek(stat_fd.get(), 0, SEEK_SET);
136
137 char c[512];
138 if (read(stat_fd.get(), c, sizeof(c)) < 0) {
139 PERFETTO_ELOG("Failed to read stat file to enforce resource limits.");
140 return;
141 }
142 c[sizeof(c) - 1] = '\0';
143
144 unsigned long int utime = 0l;
145 unsigned long int stime = 0l;
146 long int rss_pages = -1l;
147 PERFETTO_CHECK(
148 sscanf(c,
149 "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
150 "%lu %*d %*d %*d %*d %*d %*d %*u %*u %ld",
151 &utime, &stime, &rss_pages) == 3);
152
153 uint64_t cpu_time = utime + stime;
154 uint64_t rss_bytes = static_cast<uint64_t>(rss_pages) * base::kPageSize;
155
156 CheckMemory(rss_bytes);
157 CheckCpu(cpu_time);
158 }
159 }
160
CheckMemory(uint64_t rss_bytes)161 void Watchdog::CheckMemory(uint64_t rss_bytes) {
162 if (memory_limit_bytes_ == 0)
163 return;
164
165 // Add the current stat value to the ring buffer and check that the mean
166 // remains under our threshold.
167 if (memory_window_bytes_.Push(rss_bytes)) {
168 if (memory_window_bytes_.Mean() > static_cast<double>(memory_limit_bytes_)) {
169 PERFETTO_ELOG(
170 "Memory watchdog trigger. Memory window of %f bytes is above the "
171 "%" PRIu64 " bytes limit.",
172 memory_window_bytes_.Mean(), memory_limit_bytes_);
173 kill(getpid(), SIGABRT);
174 }
175 }
176 }
177
CheckCpu(uint64_t cpu_time)178 void Watchdog::CheckCpu(uint64_t cpu_time) {
179 if (cpu_limit_percentage_ == 0)
180 return;
181
182 // Add the cpu time to the ring buffer.
183 if (cpu_window_time_ticks_.Push(cpu_time)) {
184 // Compute the percentage over the whole window and check that it remains
185 // under the threshold.
186 uint64_t difference_ticks = cpu_window_time_ticks_.NewestWhenFull() -
187 cpu_window_time_ticks_.OldestWhenFull();
188 double window_interval_ticks =
189 (static_cast<double>(WindowTimeForRingBuffer(cpu_window_time_ticks_)) /
190 1000.0) *
191 static_cast<double>(sysconf(_SC_CLK_TCK));
192 double percentage = static_cast<double>(difference_ticks) /
193 static_cast<double>(window_interval_ticks) * 100;
194 if (percentage > cpu_limit_percentage_) {
195 PERFETTO_ELOG("CPU watchdog trigger. %f%% CPU use is above the %" PRIu32
196 "%% CPU limit.",
197 percentage, cpu_limit_percentage_);
198 kill(getpid(), SIGABRT);
199 }
200 }
201 }
202
WindowTimeForRingBuffer(const WindowedInterval & window)203 uint32_t Watchdog::WindowTimeForRingBuffer(const WindowedInterval& window) {
204 return static_cast<uint32_t>(window.size() - 1) * polling_interval_ms_;
205 }
206
Push(uint64_t sample)207 bool Watchdog::WindowedInterval::Push(uint64_t sample) {
208 // Add the sample to the current position in the ring buffer.
209 buffer_[position_] = sample;
210
211 // Update the position with next one circularily.
212 position_ = (position_ + 1) % size_;
213
214 // Set the filled flag the first time we wrap.
215 filled_ = filled_ || position_ == 0;
216 return filled_;
217 }
218
Mean() const219 double Watchdog::WindowedInterval::Mean() const {
220 return MeanForArray(buffer_.get(), size_);
221 }
222
Clear()223 void Watchdog::WindowedInterval::Clear() {
224 position_ = 0;
225 buffer_.reset(new uint64_t[size_]());
226 }
227
Reset(size_t new_size)228 void Watchdog::WindowedInterval::Reset(size_t new_size) {
229 position_ = 0;
230 size_ = new_size;
231 buffer_.reset(new_size == 0 ? nullptr : new uint64_t[new_size]());
232 }
233
Timer(uint32_t ms)234 Watchdog::Timer::Timer(uint32_t ms) {
235 if (!ms)
236 return; // No-op timer created when the watchdog is disabled.
237
238 struct sigevent sev = {};
239 sev.sigev_notify = SIGEV_THREAD_ID;
240 sev._sigev_un._tid = base::GetThreadId();
241 sev.sigev_signo = SIGABRT;
242 PERFETTO_CHECK(timer_create(CLOCK_MONOTONIC, &sev, &timerid_) != -1);
243 struct itimerspec its = {};
244 its.it_value.tv_sec = ms / 1000;
245 its.it_value.tv_nsec = 1000000L * (ms % 1000);
246 PERFETTO_CHECK(timer_settime(timerid_, 0, &its, nullptr) != -1);
247 }
248
~Timer()249 Watchdog::Timer::~Timer() {
250 if (timerid_ != nullptr) {
251 timer_delete(timerid_);
252 }
253 }
254
Timer(Timer && other)255 Watchdog::Timer::Timer(Timer&& other) noexcept {
256 timerid_ = other.timerid_;
257 other.timerid_ = nullptr;
258 }
259
260 } // namespace base
261 } // namespace perfetto
262
263 #endif // PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
264