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