Refactor in a single package with subpackages
[deliverable/lttng-analyses.git] / lttnganalyses / core / cputop.py
1 #!/usr/bin/env python3
2 #
3 # The MIT License (MIT)
4 #
5 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
6 # 2015 - Antoine Busque <abusque@efficios.com>
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
26 from .analysis import Analysis
27
28
29 class Cputop(Analysis):
30 def __init__(self, state):
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
37 self._state = state
38 self._state.register_notification_cbs(notification_cbs)
39 self._ev_count = 0
40 self.cpus = {}
41 self.tids = {}
42
43 def process_event(self, ev):
44 self._ev_count += 1
45
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
132
133 @property
134 def event_count(self):
135 return self._ev_count
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
This page took 0.037499 seconds and 5 git commands to generate.