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