Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | # The MIT License (MIT) |
2 | # | |
a3fa57c0 | 3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
b4156aa2 | 4 | # 2015 - Antoine Busque <abusque@efficios.com> |
4ed24f86 JD |
5 | # |
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | # of this software and associated documentation files (the "Software"), to deal | |
8 | # in the Software without restriction, including without limitation the rights | |
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | # copies of the Software, and to permit persons to whom the Software is | |
11 | # furnished to do so, subject to the following conditions: | |
12 | # | |
13 | # The above copyright notice and this permission notice shall be included in | |
14 | # all copies or substantial portions of the Software. | |
15 | # | |
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
22 | # SOFTWARE. | |
23 | ||
ff833baa | 24 | from . import stats |
bd3cd7c5 JD |
25 | from .analysis import Analysis |
26 | ||
27 | ||
28 | class Cputop(Analysis): | |
b6d9132b | 29 | def __init__(self, state, conf): |
b4156aa2 AB |
30 | notification_cbs = { |
31 | 'sched_migrate_task': self._process_sched_migrate_task, | |
32 | 'sched_switch_per_cpu': self._process_sched_switch_per_cpu, | |
ff833baa AB |
33 | 'sched_switch_per_tid': self._process_sched_switch_per_tid, |
34 | 'prio_changed': self._process_prio_changed, | |
b4156aa2 AB |
35 | } |
36 | ||
b6d9132b | 37 | super().__init__(state, conf) |
b4156aa2 | 38 | self._state.register_notification_cbs(notification_cbs) |
b6d9132b | 39 | |
bd3cd7c5 | 40 | self._ev_count = 0 |
b4156aa2 AB |
41 | self.cpus = {} |
42 | self.tids = {} | |
bd3cd7c5 JD |
43 | |
44 | def process_event(self, ev): | |
b6d9132b | 45 | super().process_event(ev) |
bd3cd7c5 JD |
46 | self._ev_count += 1 |
47 | ||
b6d9132b | 48 | def reset(self): |
ff833baa AB |
49 | for cpu_stats in self.cpus.values(): |
50 | cpu_stats.reset() | |
a234193d | 51 | if cpu_stats.current_task_start_ts is not None: |
ff833baa | 52 | cpu_stats.current_task_start_ts = self._last_event_ts |
b4156aa2 | 53 | |
ff833baa AB |
54 | for proc_stats in self.tids.values(): |
55 | proc_stats.reset() | |
56 | if proc_stats.last_sched_ts is not None: | |
57 | proc_stats.last_sched_ts = self._last_event_ts | |
b6d9132b AB |
58 | |
59 | def _end_period_cb(self): | |
60 | self._compute_stats() | |
b4156aa2 | 61 | |
b6d9132b | 62 | def _compute_stats(self): |
b4156aa2 AB |
63 | """Compute usage stats relative to a certain time range |
64 | ||
65 | For each CPU and process tracked by the analysis, we set its | |
66 | usage_percent attribute, which represents the percentage of | |
67 | usage time for the given CPU or process relative to the full | |
68 | duration of the time range. Do note that we need to know the | |
69 | timestamps and not just the duration, because if a CPU or a | |
70 | process is currently busy, we use the end timestamp to add | |
71 | the partial results of the currently running task to the usage | |
72 | stats. | |
b4156aa2 | 73 | """ |
b6d9132b | 74 | duration = self._last_event_ts - self._period_start_ts |
b4156aa2 AB |
75 | |
76 | for cpu_id in self.cpus: | |
77 | cpu = self.cpus[cpu_id] | |
78 | if cpu.current_task_start_ts is not None: | |
b6d9132b AB |
79 | cpu.total_usage_time += self._last_event_ts - \ |
80 | cpu.current_task_start_ts | |
b4156aa2 AB |
81 | |
82 | cpu.compute_stats(duration) | |
83 | ||
84 | for tid in self.tids: | |
85 | proc = self.tids[tid] | |
86 | if proc.last_sched_ts is not None: | |
b6d9132b AB |
87 | proc.total_cpu_time += self._last_event_ts - \ |
88 | proc.last_sched_ts | |
b4156aa2 AB |
89 | |
90 | proc.compute_stats(duration) | |
91 | ||
92 | def _process_sched_switch_per_cpu(self, **kwargs): | |
93 | timestamp = kwargs['timestamp'] | |
94 | cpu_id = kwargs['cpu_id'] | |
a621ba35 | 95 | wakee_proc = kwargs['wakee_proc'] |
b4156aa2 | 96 | |
a621ba35 AB |
97 | if not self._filter_cpu(cpu_id): |
98 | return | |
99 | ||
b4156aa2 AB |
100 | if cpu_id not in self.cpus: |
101 | self.cpus[cpu_id] = CpuUsageStats(cpu_id) | |
102 | ||
103 | cpu = self.cpus[cpu_id] | |
104 | if cpu.current_task_start_ts is not None: | |
105 | cpu.total_usage_time += timestamp - cpu.current_task_start_ts | |
106 | ||
a621ba35 | 107 | if not self._filter_process(wakee_proc): |
b4156aa2 AB |
108 | cpu.current_task_start_ts = None |
109 | else: | |
110 | cpu.current_task_start_ts = timestamp | |
111 | ||
112 | def _process_sched_switch_per_tid(self, **kwargs): | |
a621ba35 AB |
113 | cpu_id = kwargs['cpu_id'] |
114 | wakee_proc = kwargs['wakee_proc'] | |
b4156aa2 AB |
115 | timestamp = kwargs['timestamp'] |
116 | prev_tid = kwargs['prev_tid'] | |
117 | next_tid = kwargs['next_tid'] | |
118 | next_comm = kwargs['next_comm'] | |
119 | ||
a621ba35 AB |
120 | if not self._filter_cpu(cpu_id): |
121 | return | |
122 | ||
b4156aa2 AB |
123 | if prev_tid in self.tids: |
124 | prev_proc = self.tids[prev_tid] | |
125 | if prev_proc.last_sched_ts is not None: | |
126 | prev_proc.total_cpu_time += timestamp - prev_proc.last_sched_ts | |
127 | prev_proc.last_sched_ts = None | |
128 | ||
a621ba35 AB |
129 | # Only filter on wakee_proc after finalizing the prev_proc |
130 | # accounting | |
131 | if not self._filter_process(wakee_proc): | |
b4156aa2 AB |
132 | return |
133 | ||
134 | if next_tid not in self.tids: | |
ff833baa AB |
135 | self.tids[next_tid] = ProcessCpuStats(None, next_tid, next_comm) |
136 | self.tids[next_tid].update_prio(timestamp, wakee_proc.prio) | |
b4156aa2 AB |
137 | |
138 | next_proc = self.tids[next_tid] | |
139 | next_proc.last_sched_ts = timestamp | |
ff833baa | 140 | |
b4156aa2 AB |
141 | |
142 | def _process_sched_migrate_task(self, **kwargs): | |
a621ba35 | 143 | cpu_id = kwargs['cpu_id'] |
b4156aa2 AB |
144 | proc = kwargs['proc'] |
145 | tid = proc.tid | |
a621ba35 AB |
146 | |
147 | if not self._filter_process(proc): | |
148 | return | |
149 | if not self._filter_cpu(cpu_id): | |
150 | return | |
151 | ||
b4156aa2 AB |
152 | if tid not in self.tids: |
153 | self.tids[tid] = ProcessCpuStats.new_from_process(proc) | |
154 | ||
155 | self.tids[tid].migrate_count += 1 | |
b5db5706 | 156 | |
ff833baa AB |
157 | def _process_prio_changed(self, **kwargs): |
158 | timestamp = kwargs['timestamp'] | |
159 | prio = kwargs['prio'] | |
160 | tid = kwargs['tid'] | |
161 | ||
162 | if tid not in self.tids: | |
163 | return | |
164 | ||
165 | self.tids[tid].update_prio(timestamp, prio) | |
166 | ||
43b66dd6 AB |
167 | def _filter_process(self, proc): |
168 | # Exclude swapper | |
169 | if proc.tid == 0: | |
170 | return False | |
171 | ||
172 | return super()._filter_process(proc) | |
173 | ||
bd3cd7c5 JD |
174 | @property |
175 | def event_count(self): | |
176 | return self._ev_count | |
b4156aa2 AB |
177 | |
178 | ||
179 | class CpuUsageStats(): | |
180 | def __init__(self, cpu_id): | |
181 | self.cpu_id = cpu_id | |
182 | # Usage time and start timestamp are in nanoseconds (ns) | |
183 | self.total_usage_time = 0 | |
184 | self.current_task_start_ts = None | |
185 | self.usage_percent = None | |
186 | ||
187 | def compute_stats(self, duration): | |
88e6acc6 AB |
188 | if duration != 0: |
189 | self.usage_percent = self.total_usage_time * 100 / duration | |
190 | else: | |
191 | self.usage_percent = 0 | |
b4156aa2 | 192 | |
ff833baa | 193 | def reset(self): |
b4156aa2 AB |
194 | self.total_usage_time = 0 |
195 | self.usage_percent = None | |
b4156aa2 AB |
196 | |
197 | ||
ff833baa AB |
198 | class ProcessCpuStats(stats.Process): |
199 | def __init__(self, pid, tid, comm): | |
200 | super().__init__(pid, tid, comm) | |
201 | ||
b4156aa2 AB |
202 | # CPU Time and timestamp in nanoseconds (ns) |
203 | self.total_cpu_time = 0 | |
204 | self.last_sched_ts = None | |
205 | self.migrate_count = 0 | |
206 | self.usage_percent = None | |
207 | ||
b4156aa2 | 208 | def compute_stats(self, duration): |
88e6acc6 AB |
209 | if duration != 0: |
210 | self.usage_percent = self.total_cpu_time * 100 / duration | |
211 | else: | |
212 | self.usage_percent = 0 | |
b4156aa2 | 213 | |
ff833baa AB |
214 | def reset(self): |
215 | super().reset() | |
b4156aa2 AB |
216 | self.total_cpu_time = 0 |
217 | self.migrate_count = 0 | |
218 | self.usage_percent = None |