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