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