Skip to content

Commit f7c2a09

Browse files
author
Brendan Jackman
committed
instrument/schedstats: Add initial instrument for schedstats
1 parent 41072c4 commit f7c2a09

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

devlib/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from devlib.instrument.hwmon import HwmonInstrument
1717
from devlib.instrument.monsoon import MonsoonInstrument
1818
from devlib.instrument.netstats import NetstatsInstrument
19+
from devlib.instrument.schedstats import SchedstatsInstrument
1920

2021
from devlib.trace.ftrace import FtraceCollector
2122

devlib/instrument/schedstats.py

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Copyright 2017 ARM Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
from collections import OrderedDict
17+
import logging
18+
import re
19+
20+
from devlib.instrument import (Instrument, INSTANTANEOUS,
21+
Measurement, MeasurementType)
22+
from devlib.exception import TargetError
23+
24+
# Each entry in schedstats has a space-separated list of fields. DOMAIN_MEASURES
25+
# and CPU_MEASURES are the fields for domain entries and CPU entries
26+
# resepectively.
27+
#
28+
# See kernel/sched/stat.c and Documentation/scheduler/sched-stats.txt
29+
#
30+
# The names used here are based on the identifiers in the scheduler code.
31+
32+
# Some domain fields are repeated for each idle type
33+
DOMAIN_MEASURES = []
34+
for idle_type in ['CPU_IDLE', 'CPU_NOT_IDLE', 'CPU_NEWLY_IDLE']:
35+
for lb_measure in [
36+
'lb_count',
37+
'lb_balanced',
38+
'lb_failed',
39+
'lb_imbalance',
40+
'lb_gained',
41+
'lb_hot_gained',
42+
'lb_nobusyq',
43+
'lb_nobusyg']:
44+
DOMAIN_MEASURES.append('{}:{}'.format(lb_measure, idle_type))
45+
46+
DOMAIN_MEASURES += [
47+
'alb_count',
48+
'alb_failed',
49+
'alb_pushed',
50+
'sbe_count',
51+
'sbe_balanced',
52+
'sbe_pushed',
53+
'sbf_count',
54+
'sbf_balanced',
55+
'sbf_pushed',
56+
'ttwu_wake_remote',
57+
'ttwu_move_affine',
58+
'ttwu_move_balance'
59+
]
60+
61+
CPU_MEASURES = [
62+
'yld_count',
63+
'legacy_always_zero',
64+
'schedule_count',
65+
'sched_goidle',
66+
'ttwu_count',
67+
'ttwu_local',
68+
'rq_cpu_time',
69+
'run_delay',
70+
'pcount'
71+
]
72+
73+
class SchedstatsInstrument(Instrument):
74+
"""
75+
An instrument for parsing Linux's schedstats
76+
77+
Creates a *site* for each CPU and each sched_domain (i.e. for each line of
78+
/proc/schedstat), and a *channel* for each item in the schedstats file. For
79+
example a *site* named "cpu0" will be created for the scheduler stats on
80+
CPU0 and a *site* named "cpu0domain0" will be created for the scheduler
81+
stats on CPU0's first-level scheduling domain.
82+
83+
For example:
84+
85+
- If :method:`reset` is called with ``sites=['cpu0']`` then all
86+
stats will be collected for CPU0's runqueue, with a channel for each
87+
statistic.
88+
89+
- If :method:`reset` is called with ``kinds=['alb_pushed']`` then the count
90+
of migrations successfully triggered by active_load_balance will be
91+
colelcted for each sched domain, with a channel for each domain.
92+
93+
The measurements are named according to corresponding identifiers in the
94+
kernel scheduler code. The names for ``sched_domain.lb_*`` stats, which are
95+
recorded per ``cpu_idle_type`` are suffixed with a ':' followed by the idle
96+
type, for example ``'lb_balanced:CPU_NEWLY_IDLE'``.
97+
98+
Only supports schedstats version 15.
99+
100+
Only supports the CPU and domain data in /proc/schedstat, not the per-task
101+
data under /proc/<pid>/schedstat.
102+
"""
103+
104+
mode = INSTANTANEOUS
105+
106+
sysctl_path = '/proc/sys/kernel/sched_schedstats'
107+
schedstat_path = '/proc/schedstat'
108+
109+
def __init__(self, *args, **kwargs):
110+
super(SchedstatsInstrument, self).__init__(*args, **kwargs)
111+
self.logger = logging.getLogger(self.__class__.__name__)
112+
113+
try:
114+
self.old_sysctl_value = self.target.read_int(self.sysctl_path)
115+
except TargetError:
116+
if not self.target.file_exists(self.sysctl_path):
117+
raise TargetError('schedstats not supported by target. '
118+
'Ensure CONFIG_SCHEDSTATS is enabled.')
119+
120+
self.target.write_value(self.sysctl_path, 1)
121+
122+
# Check version matches
123+
lines = self.target.read_value(self.schedstat_path).splitlines()
124+
match = re.search(r'version ([0-9]+)', lines[0])
125+
if not match or match.group(1) != '15':
126+
raise TargetError(
127+
'Unsupported schedstat version string: "{}"'.format(lines[0]))
128+
129+
# Take a sample of the schedstat file to figure out which channels to
130+
# create.
131+
# We'll create a site for each CPU and a site for each sched_domain.
132+
for site, measures in self._get_sample().iteritems():
133+
if site.startswith('cpu'):
134+
measurement_category = 'schedstat_cpu'
135+
else:
136+
measurement_category = 'schedstat_domain'
137+
138+
for measurement_name in measures.keys():
139+
# TODO: constructing multiple MeasurementTypes with same
140+
# params. Does it matter?
141+
measurement_type = MeasurementType(
142+
measurement_name, '', measurement_category)
143+
self.add_channel(site=site,
144+
name='{}_{}'.format(site, measurement_name),
145+
measure=measurement_type)
146+
147+
def teardown(self):
148+
self.target.write_value(self.sysctl_path, self.old_sysctl_value)
149+
150+
def _get_sample(self):
151+
lines = self.target.read_value(self.schedstat_path).splitlines()
152+
ret = OrderedDict()
153+
154+
# Example /proc/schedstat contents:
155+
#
156+
# version 15
157+
# timestamp <timestamp>
158+
# cpu0 <cpu fields>
159+
# domain0 <domain fields>
160+
# domain1 <domain fields>
161+
# cpu1 <cpu_fields>
162+
# domain0 <domain fields>
163+
# domain1 <domain fields>
164+
165+
curr_cpu = None
166+
for line in lines[2:]:
167+
tokens = line.split()
168+
if tokens[0].startswith('cpu'):
169+
curr_cpu = tokens[0]
170+
site = curr_cpu
171+
measures = CPU_MEASURES
172+
tokens = tokens[1:]
173+
elif tokens[0].startswith('domain'):
174+
if not curr_cpu:
175+
raise TargetError(
176+
'Failed to parse schedstats, found domain before CPU')
177+
# We'll name the site for the domain like "cpu0domain0"
178+
site = curr_cpu + tokens[0]
179+
measures = DOMAIN_MEASURES
180+
tokens = tokens[2:]
181+
else:
182+
self.logger.warning(
183+
'Unrecognised schedstats line: "{}"'.format(line))
184+
continue
185+
186+
values = [int(t) for t in tokens]
187+
if len(values) != len(measures):
188+
raise TargetError(
189+
'Unexpected length for schedstat line "{}"'.format(line))
190+
ret[site] = OrderedDict(zip(measures, values))
191+
192+
return ret
193+
194+
def take_measurement(self):
195+
ret = []
196+
sample = self._get_sample()
197+
198+
for channel in self.active_channels:
199+
value = sample[channel.site][channel.kind]
200+
ret.append(Measurement(value, channel))
201+
202+
return ret
203+
204+

0 commit comments

Comments
 (0)