xref: /aosp_15_r20/external/cronet/base/threading/platform_thread_cros.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 // Description: ChromeOS specific Linux code layered on top of
5 // base/threading/platform_thread_linux{,_base}.cc.
6 
7 #include "base/base_switches.h"
8 #include "base/command_line.h"
9 #include "base/feature_list.h"
10 #include "base/files/file_util.h"
11 #include "base/metrics/field_trial_params.h"
12 #include "base/no_destructor.h"
13 #include "base/process/internal_linux.h"
14 #include "base/process/process.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/threading/cross_process_platform_thread_delegate.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/threading/platform_thread_internal_posix.h"
19 
20 #include <sys/resource.h>
21 
22 namespace base {
23 
24 BASE_FEATURE(kSchedUtilHints,
25              "SchedUtilHints",
26              base::FEATURE_ENABLED_BY_DEFAULT);
27 
28 BASE_FEATURE(kSetThreadBgForBgProcess,
29              "SetThreadBgForBgProcess",
30              FEATURE_DISABLED_BY_DEFAULT);
31 
32 BASE_FEATURE(kSetRtForDisplayThreads,
33              "SetRtForDisplayThreads",
34              FEATURE_DISABLED_BY_DEFAULT);
35 namespace {
36 
37 CrossProcessPlatformThreadDelegate* g_cross_process_platform_thread_delegate =
38     nullptr;
39 
40 std::atomic<bool> g_use_sched_util(true);
41 std::atomic<bool> g_scheduler_hints_adjusted(false);
42 std::atomic<bool> g_threads_bg_enabled(false);
43 std::atomic<bool> g_display_threads_rt(false);
44 
45 // When a device doesn't specify uclamp values via chrome switches,
46 // default boosting for urgent tasks is hardcoded here as 20%.
47 // Higher values can lead to higher power consumption thus this value
48 // is chosen conservatively where it does not show noticeable
49 // power usage increased from several perf/power tests.
50 const int kSchedulerBoostDef = 20;
51 const int kSchedulerLimitDef = 100;
52 const bool kSchedulerUseLatencyTuneDef = true;
53 
54 int g_scheduler_boost_adj;
55 int g_scheduler_limit_adj;
56 bool g_scheduler_use_latency_tune_adj;
57 
58 // Defined by linux uclamp ABI of sched_setattr().
59 constexpr uint32_t kSchedulerUclampMin = 0;
60 constexpr uint32_t kSchedulerUclampMax = 1024;
61 
62 // sched_attr is used to set scheduler attributes for Linux. It is not a POSIX
63 // struct and glibc does not expose it.
64 struct sched_attr {
65   uint32_t size;
66 
67   uint32_t sched_policy;
68   uint64_t sched_flags;
69 
70   /* SCHED_NORMAL, SCHED_BATCH */
71   int32_t sched_nice;
72 
73   /* SCHED_FIFO, SCHED_RR */
74   uint32_t sched_priority;
75 
76   /* SCHED_DEADLINE */
77   uint64_t sched_runtime;
78   uint64_t sched_deadline;
79   uint64_t sched_period;
80 
81   /* Utilization hints */
82   uint32_t sched_util_min;
83   uint32_t sched_util_max;
84 };
85 
86 #if !defined(__NR_sched_setattr)
87 #if defined(__x86_64__)
88 #define __NR_sched_setattr 314
89 #define __NR_sched_getattr 315
90 #elif defined(__i386__)
91 #define __NR_sched_setattr 351
92 #define __NR_sched_getattr 352
93 #elif defined(__arm__)
94 #define __NR_sched_setattr 380
95 #define __NR_sched_getattr 381
96 #elif defined(__aarch64__)
97 #define __NR_sched_setattr 274
98 #define __NR_sched_getattr 275
99 #else
100 #error "We don't have an __NR_sched_setattr for this architecture."
101 #endif
102 #endif
103 
104 #if !defined(SCHED_FLAG_UTIL_CLAMP_MIN)
105 #define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
106 #endif
107 
108 #if !defined(SCHED_FLAG_UTIL_CLAMP_MAX)
109 #define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
110 #endif
111 
sched_getattr(pid_t pid,const struct sched_attr * attr,unsigned int size,unsigned int flags)112 long sched_getattr(pid_t pid,
113                    const struct sched_attr* attr,
114                    unsigned int size,
115                    unsigned int flags) {
116   return syscall(__NR_sched_getattr, pid, attr, size, flags);
117 }
118 
sched_setattr(pid_t pid,const struct sched_attr * attr,unsigned int flags)119 long sched_setattr(pid_t pid,
120                    const struct sched_attr* attr,
121                    unsigned int flags) {
122   return syscall(__NR_sched_setattr, pid, attr, flags);
123 }
124 
125 // Setup whether a thread is latency sensitive. The thread_id should
126 // always be the value in the root PID namespace (see FindThreadID).
SetThreadLatencySensitivity(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)127 void SetThreadLatencySensitivity(ProcessId process_id,
128                                  PlatformThreadId thread_id,
129                                  ThreadType thread_type) {
130   struct sched_attr attr;
131   bool is_urgent = false;
132   int boost_percent, limit_percent;
133   int latency_sensitive_urgent;
134 
135   // Scheduler boost defaults to true unless disabled.
136   if (!g_use_sched_util.load())
137     return;
138 
139   // FieldTrial API can be called only once features were parsed.
140   if (g_scheduler_hints_adjusted.load()) {
141     boost_percent = g_scheduler_boost_adj;
142     limit_percent = g_scheduler_limit_adj;
143     latency_sensitive_urgent = g_scheduler_use_latency_tune_adj;
144   } else {
145     boost_percent = kSchedulerBoostDef;
146     limit_percent = kSchedulerLimitDef;
147     latency_sensitive_urgent = kSchedulerUseLatencyTuneDef;
148   }
149 
150   // The thread_id passed in here is either 0 (in which case we ste for current
151   // thread), or is a tid that is not the NS tid but the global one. The
152   // conversion from NS tid to global tid is done by the callers using
153   // FindThreadID().
154   FilePath thread_dir;
155   if (thread_id && thread_id != PlatformThread::CurrentId())
156     thread_dir = FilePath(StringPrintf("/proc/%d/task/%d/", process_id, thread_id));
157   else
158     thread_dir = FilePath("/proc/thread-self/");
159 
160   FilePath latency_sensitive_file = thread_dir.Append("latency_sensitive");
161 
162   if (!PathExists(latency_sensitive_file))
163     return;
164 
165   // Silently ignore if getattr fails due to sandboxing.
166   if (sched_getattr(thread_id, &attr, sizeof(attr), 0) == -1 ||
167       attr.size != sizeof(attr))
168     return;
169 
170   switch (thread_type) {
171     case ThreadType::kBackground:
172     case ThreadType::kUtility:
173     case ThreadType::kResourceEfficient:
174     case ThreadType::kDefault:
175       break;
176     case ThreadType::kCompositing:
177     case ThreadType::kDisplayCritical:
178       // Compositing and display critical threads need a boost for consistent 60
179       // fps.
180       [[fallthrough]];
181     case ThreadType::kRealtimeAudio:
182       is_urgent = true;
183       break;
184   }
185 
186   PLOG_IF(ERROR,
187           !WriteFile(latency_sensitive_file,
188                      (is_urgent && latency_sensitive_urgent) ? "1" : "0", 1))
189       << "Failed to write latency file.";
190 
191   attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
192   attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
193 
194   if (is_urgent) {
195     attr.sched_util_min =
196         (saturated_cast<uint32_t>(boost_percent) * kSchedulerUclampMax + 50) /
197         100;
198     attr.sched_util_max = kSchedulerUclampMax;
199   } else {
200     attr.sched_util_min = kSchedulerUclampMin;
201     attr.sched_util_max =
202         (saturated_cast<uint32_t>(limit_percent) * kSchedulerUclampMax + 50) /
203         100;
204   }
205 
206   DCHECK_GE(attr.sched_util_min, kSchedulerUclampMin);
207   DCHECK_LE(attr.sched_util_max, kSchedulerUclampMax);
208 
209   attr.size = sizeof(struct sched_attr);
210   if (sched_setattr(thread_id, &attr, 0) == -1) {
211     // We log it as an error because, if the PathExists above succeeded, we
212     // expect this syscall to also work since the kernel is new'ish.
213     PLOG_IF(ERROR, errno != E2BIG)
214         << "Failed to set sched_util_min, performance may be effected.";
215   }
216 }
217 
218 // Get the type by reading through kThreadTypeToNiceValueMap
GetThreadTypeForNiceValue(int nice_value)219 std::optional<ThreadType> GetThreadTypeForNiceValue(int nice_value) {
220   for (auto i : internal::kThreadTypeToNiceValueMap) {
221     if (nice_value == i.nice_value) {
222       return i.thread_type;
223     }
224   }
225   return std::nullopt;
226 }
227 
GetNiceValueForThreadId(PlatformThreadId thread_id)228 std::optional<int> GetNiceValueForThreadId(PlatformThreadId thread_id) {
229   // Get the current nice value of the thread_id
230   errno = 0;
231   int nice_value = getpriority(PRIO_PROCESS, static_cast<id_t>(thread_id));
232   if (nice_value == -1 && errno != 0) {
233     // The thread may disappear for any reason so ignore ESRCH.
234     DVPLOG_IF(1, errno != ESRCH)
235         << "Failed to call getpriority for thread id " << thread_id
236         << ", performance may be effected.";
237     return std::nullopt;
238   }
239   return nice_value;
240 }
241 
242 } // namespace
243 
SetThreadTypeOtherAttrs(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)244 void SetThreadTypeOtherAttrs(ProcessId process_id,
245                              PlatformThreadId thread_id,
246                              ThreadType thread_type) {
247   // For cpuset and legacy schedtune interface
248   PlatformThreadLinux::SetThreadCgroupsForThreadType(thread_id, thread_type);
249 
250   // For upstream uclamp interface. We try both legacy (schedtune, as done
251   // earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
252   SetThreadLatencySensitivity(process_id, thread_id, thread_type);
253 }
254 
255 // Set or reset the RT priority of a thread based on its type
256 // and whether the process it is in is backgrounded.
257 // Setting an RT task to CFS retains the task's nice value.
SetThreadRTPrioFromType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,bool proc_bg)258 void SetThreadRTPrioFromType(ProcessId process_id,
259                              PlatformThreadId thread_id,
260                              ThreadType thread_type,
261                              bool proc_bg) {
262   struct sched_param prio;
263   int policy;
264 
265   switch (thread_type) {
266     case ThreadType::kRealtimeAudio:
267       prio = PlatformThreadChromeOS::kRealTimeAudioPrio;
268       policy = SCHED_RR;
269       break;
270     case ThreadType::kCompositing:
271       [[fallthrough]];
272     case ThreadType::kDisplayCritical:
273       if (!PlatformThreadChromeOS::IsDisplayThreadsRtFeatureEnabled()) {
274         return;
275       }
276       if (proc_bg) {
277         // Per manpage, must be 0. Otherwise could have passed nice value here.
278         // Note that even though the prio.sched_priority passed to the
279         // sched_setscheduler() syscall is 0, the old nice value (which holds the
280         // ThreadType of the thread) is retained.
281         prio.sched_priority = 0;
282         policy = SCHED_OTHER;
283       } else {
284         prio = PlatformThreadChromeOS::kRealTimeDisplayPrio;
285         policy = SCHED_RR;
286       }
287       break;
288     default:
289       return;
290   }
291 
292   PlatformThreadId syscall_tid = thread_id == PlatformThread::CurrentId() ? 0 : thread_id;
293   if (sched_setscheduler(syscall_tid, policy, &prio) != 0) {
294     DVPLOG(1) << "Failed to set policy/priority for thread " << thread_id;
295   }
296 }
297 
SetThreadNiceFromType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)298 void SetThreadNiceFromType(ProcessId process_id,
299                            PlatformThreadId thread_id,
300                            ThreadType thread_type) {
301   PlatformThreadId syscall_tid = thread_id == PlatformThread::CurrentId() ? 0 : thread_id;
302   const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
303   if (setpriority(PRIO_PROCESS, static_cast<id_t>(syscall_tid), nice_setting)) {
304     DVPLOG(1) << "Failed to set nice value of thread " << thread_id << " to "
305               << nice_setting;
306   }
307 }
308 
InitializeFeatures()309 void PlatformThreadChromeOS::InitializeFeatures() {
310   DCHECK(FeatureList::GetInstance());
311   g_threads_bg_enabled.store(FeatureList::IsEnabled(kSetThreadBgForBgProcess));
312   g_display_threads_rt.store(FeatureList::IsEnabled(kSetRtForDisplayThreads));
313   if (!FeatureList::IsEnabled(kSchedUtilHints)) {
314     g_use_sched_util.store(false);
315     return;
316   }
317 
318   int boost_def = kSchedulerBoostDef;
319 
320   if (CommandLine::ForCurrentProcess()->HasSwitch(
321           switches::kSchedulerBoostUrgent)) {
322     std::string boost_switch_str =
323         CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
324             switches::kSchedulerBoostUrgent);
325 
326     int boost_switch_val;
327     if (!StringToInt(boost_switch_str, &boost_switch_val) ||
328         boost_switch_val < 0 || boost_switch_val > 100) {
329       DVLOG(1) << "Invalid input for " << switches::kSchedulerBoostUrgent;
330     } else {
331       boost_def = boost_switch_val;
332     }
333   }
334 
335   g_scheduler_boost_adj = GetFieldTrialParamByFeatureAsInt(
336       kSchedUtilHints, "BoostUrgent", boost_def);
337   g_scheduler_limit_adj = GetFieldTrialParamByFeatureAsInt(
338       kSchedUtilHints, "LimitNonUrgent", kSchedulerLimitDef);
339   g_scheduler_use_latency_tune_adj = GetFieldTrialParamByFeatureAsBool(
340       kSchedUtilHints, "LatencyTune", kSchedulerUseLatencyTuneDef);
341 
342   g_scheduler_hints_adjusted.store(true);
343 }
344 
345 // static
SetCrossProcessPlatformThreadDelegate(CrossProcessPlatformThreadDelegate * delegate)346 void PlatformThreadChromeOS::SetCrossProcessPlatformThreadDelegate(
347     CrossProcessPlatformThreadDelegate* delegate) {
348   // A component cannot override a delegate set by another component, thus
349   // disallow setting a delegate when one already exists.
350   DCHECK_NE(!!g_cross_process_platform_thread_delegate, !!delegate);
351 
352   g_cross_process_platform_thread_delegate = delegate;
353 }
354 
355 // static
IsThreadsBgFeatureEnabled()356 bool PlatformThreadChromeOS::IsThreadsBgFeatureEnabled() {
357   return g_threads_bg_enabled.load();
358 }
359 
360 // static
IsDisplayThreadsRtFeatureEnabled()361 bool PlatformThreadChromeOS::IsDisplayThreadsRtFeatureEnabled() {
362   return g_display_threads_rt.load();
363 }
364 
365 // static
GetThreadTypeFromThreadId(ProcessId process_id,PlatformThreadId thread_id)366 std::optional<ThreadType> PlatformThreadChromeOS::GetThreadTypeFromThreadId(
367     ProcessId process_id,
368     PlatformThreadId thread_id) {
369   // Get the current nice_value of the thread_id
370   std::optional<int> nice_value = GetNiceValueForThreadId(thread_id);
371   if (!nice_value.has_value()) {
372     return std::nullopt;
373   }
374   return GetThreadTypeForNiceValue(nice_value.value());
375 }
376 
377 // static
SetThreadType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)378 void PlatformThreadChromeOS::SetThreadType(ProcessId process_id,
379                                            PlatformThreadId thread_id,
380                                            ThreadType thread_type,
381                                            IsViaIPC via_ipc) {
382   if (g_cross_process_platform_thread_delegate &&
383       g_cross_process_platform_thread_delegate->HandleThreadTypeChange(
384           process_id, thread_id, thread_type)) {
385     return;
386   }
387   SetThreadTypeInternal(process_id, thread_id, thread_type, via_ipc);
388 }
389 
390 // static
SetThreadTypeInternal(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)391 void PlatformThreadChromeOS::SetThreadTypeInternal(ProcessId process_id,
392                                                    PlatformThreadId thread_id,
393                                                    ThreadType thread_type,
394                                                    IsViaIPC via_ipc) {
395   // TODO(b/262267726): Re-use common code with PlatformThreadLinux::SetThreadType
396   // Should not be called concurrently with other functions
397   // like SetThreadBackgrounded.
398   if (via_ipc) {
399     DCHECK_CALLED_ON_VALID_SEQUENCE(
400         PlatformThread::GetCrossProcessThreadPrioritySequenceChecker());
401   }
402 
403   auto proc = Process::Open(process_id);
404   bool backgrounded = false;
405   if (IsThreadsBgFeatureEnabled() &&
406       thread_type != ThreadType::kRealtimeAudio && proc.IsValid() &&
407       proc.GetPriority() == base::Process::Priority::kBestEffort) {
408     backgrounded = true;
409   }
410 
411   SetThreadTypeOtherAttrs(process_id, thread_id,
412                           backgrounded ? ThreadType::kBackground : thread_type);
413 
414   SetThreadRTPrioFromType(process_id, thread_id, thread_type, backgrounded);
415   SetThreadNiceFromType(process_id, thread_id, thread_type);
416 }
417 
SetThreadBackgrounded(ProcessId process_id,PlatformThreadId thread_id,bool backgrounded)418 void PlatformThreadChromeOS::SetThreadBackgrounded(ProcessId process_id,
419                                                    PlatformThreadId thread_id,
420                                                    bool backgrounded) {
421   // Get the current nice value of the thread_id
422   std::optional<int> nice_value = GetNiceValueForThreadId(thread_id);
423   if (!nice_value.has_value()) {
424     return;
425   }
426 
427   std::optional<ThreadType> type =
428       GetThreadTypeForNiceValue(nice_value.value());
429   if (!type.has_value()) {
430     return;
431   }
432 
433   // kRealtimeAudio threads are not backgrounded or foregrounded.
434   if (type == ThreadType::kRealtimeAudio) {
435     return;
436   }
437 
438   SetThreadTypeOtherAttrs(
439       process_id, thread_id,
440       backgrounded ? ThreadType::kBackground : type.value());
441   SetThreadRTPrioFromType(process_id, thread_id, type.value(), backgrounded);
442 }
443 
444 SequenceCheckerImpl&
GetCrossProcessThreadPrioritySequenceChecker()445 PlatformThreadChromeOS::GetCrossProcessThreadPrioritySequenceChecker() {
446   // In order to use a NoDestructor instance, use SequenceCheckerImpl instead of
447   // SequenceCheckerDoNothing because SequenceCheckerImpl is trivially
448   // destructible but SequenceCheckerDoNothing isn't.
449   static NoDestructor<SequenceCheckerImpl> instance;
450   return *instance;
451 }
452 
453 }  // namespace base
454