xref: /aosp_15_r20/external/perfetto/src/base/periodic_task.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2021 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/periodic_task.h"
18 
19 #include <limits>
20 
21 #include "perfetto/base/build_config.h"
22 #include "perfetto/base/logging.h"
23 #include "perfetto/base/task_runner.h"
24 #include "perfetto/base/time.h"
25 #include "perfetto/ext/base/file_utils.h"
26 
27 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
28     (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
29 #include <sys/timerfd.h>
30 #endif
31 
32 namespace perfetto {
33 namespace base {
34 
35 namespace {
36 
GetNextDelayMs(const TimeMillis & now_ms,const PeriodicTask::Args & args)37 uint32_t GetNextDelayMs(const TimeMillis& now_ms,
38                         const PeriodicTask::Args& args) {
39   if (args.one_shot)
40     return args.period_ms;
41 
42   return args.period_ms -
43          static_cast<uint32_t>(now_ms.count() % args.period_ms);
44 }
45 
CreateTimerFd(const PeriodicTask::Args & args)46 ScopedPlatformHandle CreateTimerFd(const PeriodicTask::Args& args) {
47 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
48     (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
49   ScopedPlatformHandle tfd(
50       timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK));
51   uint32_t phase_ms = GetNextDelayMs(GetBootTimeMs(), args);
52 
53   struct itimerspec its {};
54   // The "1 +" is to make sure that we never pass a zero it_value in the
55   // unlikely case of phase_ms being 0. That would cause the timer to be
56   // considered disarmed by timerfd_settime.
57   its.it_value.tv_sec = static_cast<time_t>(phase_ms / 1000u);
58   its.it_value.tv_nsec = 1 + static_cast<long>((phase_ms % 1000u) * 1000000u);
59   if (args.one_shot) {
60     its.it_interval.tv_sec = 0;
61     its.it_interval.tv_nsec = 0;
62   } else {
63     const uint32_t period_ms = args.period_ms;
64     its.it_interval.tv_sec = static_cast<time_t>(period_ms / 1000u);
65     its.it_interval.tv_nsec = static_cast<long>((period_ms % 1000u) * 1000000u);
66   }
67   if (timerfd_settime(*tfd, 0, &its, nullptr) < 0)
68     return ScopedPlatformHandle();
69   return tfd;
70 #else
71   ignore_result(args);
72   return ScopedPlatformHandle();
73 #endif
74 }
75 
76 }  // namespace
77 
PeriodicTask(TaskRunner * task_runner)78 PeriodicTask::PeriodicTask(TaskRunner* task_runner)
79     : task_runner_(task_runner), weak_ptr_factory_(this) {}
80 
~PeriodicTask()81 PeriodicTask::~PeriodicTask() {
82   Reset();
83 }
84 
Start(Args args)85 void PeriodicTask::Start(Args args) {
86   PERFETTO_DCHECK_THREAD(thread_checker_);
87   Reset();
88   if (args.period_ms == 0 || !args.task) {
89     PERFETTO_DCHECK(args.period_ms > 0);
90     PERFETTO_DCHECK(args.task);
91     return;
92   }
93   args_ = std::move(args);
94   if (args_.use_suspend_aware_timer) {
95     timer_fd_ = CreateTimerFd(args_);
96     if (timer_fd_) {
97       auto weak_this = weak_ptr_factory_.GetWeakPtr();
98       task_runner_->AddFileDescriptorWatch(
99           *timer_fd_,
100           std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_));
101     } else {
102       PERFETTO_DPLOG("timerfd not supported, falling back on PostDelayedTask");
103     }
104   }  // if (use_suspend_aware_timer).
105 
106   if (!timer_fd_)
107     PostNextTask();
108 
109   if (args_.start_first_task_immediately)
110     args_.task();
111 }
112 
PostNextTask()113 void PeriodicTask::PostNextTask() {
114   PERFETTO_DCHECK_THREAD(thread_checker_);
115   PERFETTO_DCHECK(args_.period_ms > 0);
116   PERFETTO_DCHECK(!timer_fd_);
117   uint32_t delay_ms = GetNextDelayMs(GetWallTimeMs(), args_);
118   auto weak_this = weak_ptr_factory_.GetWeakPtr();
119   task_runner_->PostDelayedTask(
120       std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_),
121       delay_ms);
122 }
123 
124 // static
125 // This function can be called in two ways (both from the TaskRunner):
126 // 1. When using a timerfd, this task is registered as a FD watch.
127 // 2. When using PostDelayedTask, this is the task posted on the TaskRunner.
RunTaskAndPostNext(WeakPtr<PeriodicTask> thiz,uint32_t generation)128 void PeriodicTask::RunTaskAndPostNext(WeakPtr<PeriodicTask> thiz,
129                                       uint32_t generation) {
130   if (!thiz || !thiz->args_.task || generation != thiz->generation_)
131     return;  // Destroyed or Reset() in the meanwhile.
132   PERFETTO_DCHECK_THREAD(thiz->thread_checker_);
133   if (thiz->timer_fd_) {
134 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
135     PERFETTO_FATAL("timerfd for periodic tasks unsupported on Windows");
136 #else
137     // If we are using a timerfd there is no need to repeatedly call
138     // PostDelayedTask(). The kernel will wakeup the timer fd periodically. We
139     // just need to read() it.
140     uint64_t ignored = 0;
141     errno = 0;
142     auto rsize = Read(*thiz->timer_fd_, &ignored, sizeof(&ignored));
143     if (rsize != sizeof(uint64_t)) {
144       if (errno == EAGAIN)
145         return;  // A spurious wakeup. Rare, but can happen, just ignore.
146       PERFETTO_PLOG("read(timerfd) failed, falling back on PostDelayedTask");
147       thiz->ResetTimerFd();
148     }
149 #endif
150   }
151 
152   // Create a copy of the task to deal with either:
153   // 1. one_shot causing a Reset().
154   // 2. task() invoking internally Reset().
155   // That would cause a reset of the args_.task itself, which would invalidate
156   // the task bind state while we are invoking it.
157   auto task = thiz->args_.task;
158 
159   // The repetition of the if() is to deal with the ResetTimerFd() case above.
160   if (thiz->args_.one_shot) {
161     thiz->Reset();
162   } else if (!thiz->timer_fd_) {
163     thiz->PostNextTask();
164   }
165 
166   task();
167 }
168 
Reset()169 void PeriodicTask::Reset() {
170   PERFETTO_DCHECK_THREAD(thread_checker_);
171   ++generation_;
172   args_ = Args();
173   PERFETTO_DCHECK(!args_.task);
174   ResetTimerFd();
175 }
176 
ResetTimerFd()177 void PeriodicTask::ResetTimerFd() {
178   if (!timer_fd_)
179     return;
180   task_runner_->RemoveFileDescriptorWatch(*timer_fd_);
181   timer_fd_.reset();
182 }
183 
184 }  // namespace base
185 }  // namespace perfetto
186