Merge "Documentation: sched: Update frequency guidance explanations"

This commit is contained in:
Linux Build Service Account 2016-11-11 20:05:45 -08:00 committed by Gerrit - the friendly Code Review server
commit 327b9b6314

View file

@ -31,6 +31,7 @@ CONTENTS
6.1 Per-CPU Window-Based Stats
6.2 Per-task Window-Based Stats
6.3 Effect of various task events
6.4 Tying it all together
7. Tunables
8. HMP Scheduler Trace Points
8.1 sched_enq_deq_task
@ -872,11 +873,17 @@ both in what they mean and also how they are derived.
*** 6.1 Per-CPU Window-Based Stats
In addition to the per-task window-based demand, the HMP scheduler
extensions also track the aggregate demand seen on each CPU. This is
done using the same windows that the task demand is tracked with
(which is in turn set by the governor when frequency guidance is in
use). There are four quantities maintained for each CPU by the HMP scheduler:
The scheduler tracks two separate types of quantities on a per CPU basis.
The first type has to deal with the aggregate load on a CPU and the second
type deals with top-tasks on that same CPU. We will first proceed to explain
what is maintained as part of each type of statistics and then provide the
connection between these two types of statistics at the end.
First lets describe the HMP scheduler extensions to track the aggregate load
seen on each CPU. This is done using the same windows that the task demand
is tracked with (which is in turn set by the governor when frequency guidance
is in use). There are four quantities maintained for each CPU by the HMP
scheduler for tracking CPU load:
curr_runnable_sum: aggregate demand from all tasks which executed during
the current (not yet completed) window
@ -903,24 +910,86 @@ A 'new' task is defined as a task whose number of active windows since fork is
less than sysctl_sched_new_task_windows. An active window is defined as a window
where a task was observed to be runnable.
Moving on the second type of statistics; top-tasks, the scheduler tracks a list
of top tasks per CPU. A top-task is defined as the task that runs the most in a
given window on that CPU. This includes task that ran on that CPU through out
the window or were migrated to that CPU prior to window expiration. It does not
include tasks that were migrated away from that CPU prior to window expiration.
To track top tasks, we first realize that there is no strict need to maintain
the task struct itself as long as we know the load exerted by the top task. We
also realize that to maintain top tasks on every CPU we have to track the
execution of every single task that runs during the window. The load associated
with a task needs to be migrated when the task migrates from one CPU to another.
When the top task migrates away, we need to locate the second top task and so
on.
Given the above realizations, we use hashmaps to track top task load both
for the current and the previous window. This hashmap is implemented as an array
of fixed size. The key of the hashmap is given by
task_execution_time_in_a_window / array_size. The size of the array (number of
buckets in the hashmap) dictate the load granularity of each bucket. The value
stored in each bucket is a refcount of all the tasks that executed long enough
to be in that bucket. This approach has a few benefits. Firstly, any top task
stats update now take O(1) time. While task migration is also O(1), it does
still involve going through up to the size of the array to find the second top
task. We optimize this search by using bitmaps. The next set bit in the bitmap
gives the position of the second top task in our hashamp.
Secondly, and more importantly, not having to store the task struct itself
saves a lot of memory usage in that 1) there is no need to retrieve task structs
later causing cache misses and 2) we don't have to unnecessarily hold up task
memory for up to 2 full windows by calling get_task_struct() after a task exits.
Given the motivation above, here are a list of quantities tracked as part of
per CPU task top-tasks management
top_tasks[NUM_TRACKED_WINDOWS] - Hashmap of top-task load for the current and
previous window
BITMAP_ARRAY(top_tasks_bitmap) - Two bitmaps for the current and previous
windows corresponding to the top-task
hashmap.
load_subs[NUM_TRACKED_WINDOWS] - An array of load subtractions to be carried
out form curr/prev_runnable_sums for each CPU
prior to reporting load to the governor. The
purpose for this will be explained later in
the section pertaining to the TASK_MIGRATE
event. The type struct load_subtractions,
stores the value of the subtraction along
with the window start value for the window
for which the subtraction has to take place.
curr_table - Indication of which index of the array points to the current
window.
curr_top - The top task on a CPU at any given moment in the current window
prev_top - The top task on a CPU in the previous window
*** 6.2 Per-task window-based stats
Corresponding to curr_runnable_sum and prev_runnable_sum, two counters are
maintained per-task
curr_window - represents cpu demand of task in its most recently tracked
window
prev_window - represents cpu demand of task in the window prior to the one
being tracked by curr_window
curr_window_cpu - represents task's contribution to cpu busy time on
various CPUs in the current window
The above counters are resued for nt_curr_runnable_sum and
nt_prev_runnable_sum.
prev_window_cpu - represents task's contribution to cpu busy time on
various CPUs in the previous window
curr_window - represents the sum of all entries in curr_window_cpu
prev_window - represents the sum of all entries in prev_window_cpu
"cpu demand" of a task includes its execution time and can also include its
wait time. 'SCHED_FREQ_ACCOUNT_WAIT_TIME' controls whether task's wait
time is included in its 'curr_window' and 'prev_window' counters or not.
time is included in its CPU load counters or not.
Needless to say, curr_runnable_sum counter of a cpu is derived from curr_window
Curr_runnable_sum counter of a cpu is derived from curr_window_cpu[cpu]
counter of various tasks that ran on it in its most recent window.
*** 6.3 Effect of various task events
@ -931,11 +1000,17 @@ PICK_NEXT_TASK
This represents beginning of execution for a task. Provided the task
refers to a non-idle task, a portion of task's wait time that
corresponds to the current window being tracked on a cpu is added to
task's curr_window counter, provided SCHED_FREQ_ACCOUNT_WAIT_TIME is
set. The same quantum is also added to cpu's curr_runnable_sum counter.
The remaining portion, which corresponds to task's wait time in previous
window is added to task's prev_window and cpu's prev_runnable_sum
counters.
task's curr_window_cpu and curr_window counter, provided
SCHED_FREQ_ACCOUNT_WAIT_TIME is set. The same quantum is also added to
cpu's curr_runnable_sum counter. The remaining portion, which
corresponds to task's wait time in previous window is added to task's
prev_window, prev_window_cpu and cpu's prev_runnable_sum counters.
CPUs top_tasks hashmap is updated if needed with the new information.
Any previous entries in the hashmap are deleted and newer entries are
created. The top_tasks_bitmap reflects the updated state of the
hashmap. If the top task for the current and/or previous window has
changed, curr_top and prev_top are updated accordingly.
PUT_PREV_TASK
This represents end of execution of a time-slice for a task, where the
@ -943,9 +1018,16 @@ PUT_PREV_TASK
or (in case of task being idle with cpu having non-zero rq->nr_iowait
count and sched_io_is_busy =1), a portion of task's execution time, that
corresponds to current window being tracked on a cpu is added to task's
curr_window_counter and also to cpu's curr_runnable_sum counter. Portion
of task's execution that corresponds to the previous window is added to
task's prev_window and cpu's prev_runnable_sum counters.
curr_window_cpu and curr_window counter and also to cpu's
curr_runnable_sum counter. Portion of task's execution that corresponds
to the previous window is added to task's prev_window, prev_window_cpu
and cpu's prev_runnable_sum counters.
CPUs top_tasks hashmap is updated if needed with the new information.
Any previous entries in the hashmap are deleted and newer entries are
created. The top_tasks_bitmap reflects the updated state of the
hashmap. If the top task for the current and/or previous window has
changed, curr_top and prev_top are updated accordingly.
TASK_UPDATE
This event is called on a cpu's currently running task and hence
@ -955,34 +1037,128 @@ TASK_UPDATE
TASK_WAKE
This event signifies a task waking from sleep. Since many windows
could have elapsed since the task went to sleep, its curr_window
and prev_window are updated to reflect task's demand in the most
recent and its previous window that is being tracked on a cpu.
could have elapsed since the task went to sleep, its
curr_window_cpu/curr_window and prev_window_cpu/prev_window are
updated to reflect task's demand in the most recent and its previous
window that is being tracked on a cpu. Updated stats will trigger
the same book-keeping for top-tasks as other events.
TASK_MIGRATE
This event signifies task migration across cpus. It is invoked on the
task prior to being moved. Thus at the time of this event, the task
can be considered to be in "waiting" state on src_cpu. In that way
this event reflects actions taken under PICK_NEXT_TASK (i.e its
wait time is added to task's curr/prev_window counters as well
wait time is added to task's curr/prev_window/_cpu counters as well
as src_cpu's curr/prev_runnable_sum counters, provided
SCHED_FREQ_ACCOUNT_WAIT_TIME is non-zero). After that update,
src_cpu's curr_runnable_sum is reduced by task's curr_window value
and dst_cpu's curr_runnable_sum is increased by task's curr_window
value. Similarly, src_cpu's prev_runnable_sum is reduced by task's
prev_window value and dst_cpu's prev_runnable_sum is increased by
task's prev_window value.
SCHED_FREQ_ACCOUNT_WAIT_TIME is non-zero).
After that update, we make a distinction between intra-cluster and
inter-cluster migrations for further book-keeping.
For intra-cluster migrations, we simply remove the entry for the task
in the top_tasks hashmap from the source CPU and add the entry to the
destination CPU. The top_tasks_bitmap, curr_top and prev_top are
updated accordingly. We then find the second top-task top in our
top_tasks hashmap for both the current and previous window and set
curr_top and prev_top to their new values.
For inter-cluster migrations we have a much more complicated scheme.
Firstly we add to the destination CPU's curr/prev_runnable_sum
the tasks curr/prev_window. Note we add the sum and not the
contribution any individual CPU. This is because when a tasks migrates
across clusters, we need the new cluster to ramp up to the appropriate
frequency given the task's total execution summed up across all CPUs
in the previous cluster.
Secondly the src_cpu's curr/prev_runnable_sum are reduced by task's
curr/prev_window_cpu values.
Thirdly, we need to walk all the CPUs in the cluster and subtract from
each CPU's curr/prev_runnable_sum the task's respective
curr/prev_window_cpu values. However, subtracting load from each of
the source CPUs is not trivial, as it would require all runqueue
locks to be held. To get around this we introduce a deferred load
subtraction mechanism whereby subtracting load from each of the source
CPUs is deferred until an opportune moment. This opportune moment is
when the governor comes asking the scheduler for load. At that time, all
necessary runqueue locks are already held.
There are a few cases to consider when doing deferred subtraction. Since
we are not holding all runqueue locks other CPUs in the source cluster
can be in a different window than the source CPU where the task is
migrating from.
Case 1:
Other CPU in the source cluster is in the same window. No special
consideration.
Case 2:
Other CPU in the source cluster is ahead by 1 window. In this
case, we will be doing redundant updates to subtraction load for the
prev window. There is no way to avoid this redundant update though,
without holding the rq lock.
Case 3:
Other CPU in the source cluster is trailing by 1 window In this
case, we might end up overwriting old data for that CPU. But this is not
a problem as when the other CPU calls update_task_ravg() it will move to
the same window. This relies on maintaining synchronized windows between
CPUs, which is true today.
To achieve all the above, we simple add the task's curr/prev_window_cpu
contributions to the per CPU load_subtractions array. These load
subtractions are subtracted from the respective CPU's
curr/prev_runnable_sums before the governor queries CPU load. Once this
is complete, the scheduler sets all curr/prev_window_cpu contributions
of the task to 0 for all CPUs in the source cluster. The destination
CPUs's curr/prev_window_cpu is updated with the tasks curr/prev_window
sums.
Finally, we must deal with frequency aggregation. When frequency
aggregation is in effect, there is little point in dealing with per CPU
footprint since the load of all related tasks have to be reported on a
single CPU. Therefore when a task enters a related group we clear out
all per CPU contributions and add it to the task CPU's cpu_time struct.
From that point onwards we stop managing per CPU contributions upon
inter cluster migrations since that work is redundant. Finally when a
task exits a related group we must walk every CPU in reset all CPU
contributions. We then set the task CPU contribution to the respective
curr/prev sum values and add that sum to the task CPU rq runnable sum.
Top-task management is the same as in the case of intra-cluster
migrations.
IRQ_UPDATE
This event signifies end of execution of an interrupt handler. This
event results in update of cpu's busy time counters, curr_runnable_sum
and prev_runnable_sum, provided cpu was idle.
When sched_io_is_busy = 0, only the interrupt handling time is added
to cpu's curr_runnable_sum and prev_runnable_sum counters. When
sched_io_is_busy = 1, the event mirrors actions taken under
TASK_UPDATED event i.e time since last accounting of idle task's cpu
usage is added to cpu's curr_runnable_sum and prev_runnable_sum
counters.
and prev_runnable_sum, provided cpu was idle. When sched_io_is_busy = 0,
only the interrupt handling time is added to cpu's curr_runnable_sum and
prev_runnable_sum counters. When sched_io_is_busy = 1, the event mirrors
actions taken under TASK_UPDATED event i.e time since last accounting
of idle task's cpu usage is added to cpu's curr_runnable_sum and
prev_runnable_sum counters. No update is needed for top-tasks in this
case.
*** 6.4 Tying it all together
Now the scheduler maintains two independent quantities for load reporing 1) CPU
load as represented by prev_runnable_sum and 2) top-tasks. The reported load
is governed by tunable sched_freq_reporting_policy. The default choice is
FREQ_REPORT_MAX_CPU_LOAD_TOP_TASK. In other words:
max(prev_runnable_sum, top_task load)
Let's explain the rationale behind the choice. CPU load tracks the exact amount
of execution observed on a CPU. This is close to the quantity that the vanilla
governor used to track. It offers the advantages of no load over-reporting that
our earlier load fixup mechanisms had deal with. It then also tackles the part
picture problem by keeping of track of tasks that might be migrating across
CPUs leaving a small footprint on each CPU. Since we maintain one top task per
CPU, we can handle as many top tasks as the number of CPUs in a cluster. We
might miss a few cases where the combined load of the top and non-top tasks on
a CPU are more representative of the true load. However, those cases have been
deemed to rare and have little impact on overall load/frequency behavior.
===========
7. TUNABLES
@ -1238,6 +1414,18 @@ However LPM exit latency associated with an idle CPU outweigh the above
benefits on some targets. When this knob is turned on, the waker CPU is
selected if it has only 1 runnable task.
*** 7.20 sched_freq_reporting_policy
Appears at: /proc/sys/kernel/sched_freq_reporting_policy
Default value: 0
This dictates what the load reporting policy to the governor should be. The
default value is FREQ_REPORT_MAX_CPU_LOAD_TOP_TASK. Other values include
FREQ_REPORT_CPU_LOAD which only reports CPU load to the governor and
FREQ_REPORT_TOP_TASK which only reports the load of the top task on a CPU
to the governor.
=========================
8. HMP SCHEDULER TRACE POINTS
=========================
@ -1318,7 +1506,7 @@ frequency of the CPU for real time task placement).
Logged when window-based stats are updated for a task. The update may happen
for a variety of reasons, see section 2.5, "Task Events."
<idle>-0 [004] d.h4 12700.711513: sched_update_task_ravg: wc 12700711473496 ws 12700691772135 delta 19701361 event TASK_WAKE cpu 4 cur_freq 199200 cur_pid 0 task 13227 (powertop) ms 12640648272532 delta 60063200964 demand 13364423 sum 0 irqtime 0 cs 0 ps 495018 cur_window 0 prev_window 0
rcu_preempt-7 [000] d..3 262857.738888: sched_update_task_ravg: wc 262857521127957 ws 262857490000000 delta 31127957 event PICK_NEXT_TASK cpu 0 cur_freq 291055 cur_pid 7 task 9309 (kworker/u16:0) ms 262857520627280 delta 500677 demand 282196 sum 156201 irqtime 0 pred_demand 267103 rq_cs 478718 rq_ps 0 cur_window 78433 (78433 0 0 0 0 0 0 0 ) prev_window 146430 (0 146430 0 0 0 0 0 0 ) nt_cs 0 nt_ps 0 active_wins 149 grp_cs 0 grp_ps 0, grp_nt_cs 0, grp_nt_ps: 0 curr_top 6 prev_top 2
- wc: wallclock, output of sched_clock(), monotonically increasing time since
boot (will roll over in 585 years) (ns)
@ -1344,9 +1532,27 @@ for a variety of reasons, see section 2.5, "Task Events."
counter.
- ps: prev_runnable_sum of cpu (ns). See section 6.1 for more details of this
counter.
- cur_window: cpu demand of task in its most recently tracked window (ns)
- prev_window: cpu demand of task in the window prior to the one being tracked
by cur_window
- cur_window: cpu demand of task in its most recently tracked window summed up
across all CPUs (ns). This is followed by a list of contributions on each
individual CPU.
- prev_window: cpu demand of task in its previous window summed up across
all CPUs (ns). This is followed by a list of contributions on each individual
CPU.
- nt_cs: curr_runnable_sum of a cpu for new tasks only (ns).
- nt_ps: prev_runnable_sum of a cpu for new tasks only (ns).
- active_wins: No. of active windows since task statistics were initialized
- grp_cs: curr_runnable_sum for colocated tasks. This is independent from
cs described above. The addition of these two fields give the total CPU
load for the most recent window
- grp_ps: prev_runnable_sum for colocated tasks. This is independent from
ps described above. The addition of these two fields give the total CPU
load for the previous window.
- grp_nt_cs: curr_runnable_sum of a cpu for grouped new tasks only (ns).
- grp_nt_ps: prev_runnable_sum for a cpu for grouped new tasks only (ns).
- curr_top: index of the top task in the top_tasks array in the current
window for a CPU.
- prev_top: index of the top task in the top_tasks array in the previous
window for a CPU
*** 8.5 sched_update_history