/src/perfetto/src/base/periodic_task.cc
Line | Count | Source |
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_BUT_NOT_QNX) || \ |
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 | | |
37 | | uint32_t GetNextDelayMs(const TimeMillis& now_ms, |
38 | 300 | const PeriodicTask::Args& args) { |
39 | 300 | if (args.one_shot) |
40 | 0 | return args.period_ms; |
41 | | |
42 | 300 | return args.period_ms - |
43 | 300 | static_cast<uint32_t>(now_ms.count() % args.period_ms); |
44 | 300 | } |
45 | | |
46 | 0 | ScopedPlatformHandle CreateTimerFd(const PeriodicTask::Args& args) { |
47 | 0 | #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ |
48 | 0 | (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19) |
49 | 0 | ScopedPlatformHandle tfd( |
50 | 0 | timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)); |
51 | 0 | uint32_t phase_ms = GetNextDelayMs(GetBootTimeMs(), args); |
52 | |
|
53 | 0 | 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 | 0 | its.it_value.tv_sec = static_cast<time_t>(phase_ms / 1000u); |
58 | 0 | its.it_value.tv_nsec = 1 + static_cast<long>((phase_ms % 1000u) * 1000000u); |
59 | 0 | if (args.one_shot) { |
60 | 0 | its.it_interval.tv_sec = 0; |
61 | 0 | its.it_interval.tv_nsec = 0; |
62 | 0 | } else { |
63 | 0 | const uint32_t period_ms = args.period_ms; |
64 | 0 | its.it_interval.tv_sec = static_cast<time_t>(period_ms / 1000u); |
65 | 0 | its.it_interval.tv_nsec = static_cast<long>((period_ms % 1000u) * 1000000u); |
66 | 0 | } |
67 | 0 | if (timerfd_settime(*tfd, 0, &its, nullptr) < 0) |
68 | 0 | return ScopedPlatformHandle(); |
69 | 0 | return tfd; |
70 | | #else |
71 | | ignore_result(args); |
72 | | return ScopedPlatformHandle(); |
73 | | #endif |
74 | 0 | } |
75 | | |
76 | | } // namespace |
77 | | |
78 | | PeriodicTask::PeriodicTask(TaskRunner* task_runner) |
79 | 600 | : task_runner_(task_runner), weak_ptr_factory_(this) {} |
80 | | |
81 | 600 | PeriodicTask::~PeriodicTask() { |
82 | 600 | Reset(); |
83 | 600 | } |
84 | | |
85 | 300 | void PeriodicTask::Start(Args args) { |
86 | 300 | PERFETTO_DCHECK_THREAD(thread_checker_); |
87 | 300 | Reset(); |
88 | 300 | if (args.period_ms == 0 || !args.task) { |
89 | 0 | PERFETTO_DCHECK(args.period_ms > 0); |
90 | 0 | PERFETTO_DCHECK(args.task); |
91 | 0 | return; |
92 | 0 | } |
93 | 300 | args_ = std::move(args); |
94 | 300 | if (args_.use_suspend_aware_timer) { |
95 | 0 | timer_fd_ = CreateTimerFd(args_); |
96 | 0 | if (timer_fd_) { |
97 | 0 | auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
98 | 0 | task_runner_->AddFileDescriptorWatch( |
99 | 0 | *timer_fd_, |
100 | 0 | std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_)); |
101 | 0 | } else { |
102 | 0 | PERFETTO_DPLOG("timerfd not supported, falling back on PostDelayedTask"); |
103 | 0 | } |
104 | 0 | } // if (use_suspend_aware_timer). |
105 | | |
106 | 300 | if (!timer_fd_) |
107 | 300 | PostNextTask(); |
108 | | |
109 | 300 | if (args_.start_first_task_immediately) |
110 | 300 | args_.task(); |
111 | 300 | } |
112 | | |
113 | 300 | void PeriodicTask::PostNextTask() { |
114 | 300 | PERFETTO_DCHECK_THREAD(thread_checker_); |
115 | 300 | PERFETTO_DCHECK(args_.period_ms > 0); |
116 | 300 | PERFETTO_DCHECK(!timer_fd_); |
117 | 300 | uint32_t delay_ms = GetNextDelayMs(GetWallTimeMs(), args_); |
118 | 300 | auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
119 | 300 | task_runner_->PostDelayedTask( |
120 | 300 | std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_), |
121 | 300 | delay_ms); |
122 | 300 | } |
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. |
128 | | void PeriodicTask::RunTaskAndPostNext(WeakPtr<PeriodicTask> thiz, |
129 | 0 | uint32_t generation) { |
130 | 0 | if (!thiz || !thiz->args_.task || generation != thiz->generation_) |
131 | 0 | return; // Destroyed or Reset() in the meanwhile. |
132 | 0 | PERFETTO_DCHECK_THREAD(thiz->thread_checker_); |
133 | 0 | 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 | 0 | uint64_t ignored = 0; |
141 | 0 | errno = 0; |
142 | 0 | auto rsize = Read(*thiz->timer_fd_, &ignored, sizeof(&ignored)); |
143 | 0 | if (rsize != sizeof(uint64_t)) { |
144 | 0 | if (errno == EAGAIN) |
145 | 0 | return; // A spurious wakeup. Rare, but can happen, just ignore. |
146 | 0 | PERFETTO_PLOG("read(timerfd) failed, falling back on PostDelayedTask"); |
147 | 0 | thiz->ResetTimerFd(); |
148 | 0 | } |
149 | 0 | #endif |
150 | 0 | } |
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 | 0 | auto task = thiz->args_.task; |
158 | | |
159 | | // The repetition of the if() is to deal with the ResetTimerFd() case above. |
160 | 0 | if (thiz->args_.one_shot) { |
161 | 0 | thiz->Reset(); |
162 | 0 | } else if (!thiz->timer_fd_) { |
163 | 0 | thiz->PostNextTask(); |
164 | 0 | } |
165 | |
|
166 | 0 | task(); |
167 | 0 | } |
168 | | |
169 | 1.20k | void PeriodicTask::Reset() { |
170 | 1.20k | PERFETTO_DCHECK_THREAD(thread_checker_); |
171 | 1.20k | ++generation_; |
172 | 1.20k | args_ = Args(); |
173 | 1.20k | PERFETTO_DCHECK(!args_.task); |
174 | 1.20k | ResetTimerFd(); |
175 | 1.20k | } |
176 | | |
177 | 1.20k | void PeriodicTask::ResetTimerFd() { |
178 | 1.20k | if (!timer_fd_) |
179 | 1.20k | return; |
180 | 0 | task_runner_->RemoveFileDescriptorWatch(*timer_fd_); |
181 | 0 | timer_fd_.reset(); |
182 | 0 | } |
183 | | |
184 | | } // namespace base |
185 | | } // namespace perfetto |