diff options
Diffstat (limited to 'scripts/pybootchartgui/pybootchartgui/process_tree.py')
-rw-r--r-- | scripts/pybootchartgui/pybootchartgui/process_tree.py | 126 |
1 files changed, 74 insertions, 52 deletions
diff --git a/scripts/pybootchartgui/pybootchartgui/process_tree.py b/scripts/pybootchartgui/pybootchartgui/process_tree.py index bde29ebda8..cf88110b1c 100644 --- a/scripts/pybootchartgui/pybootchartgui/process_tree.py +++ b/scripts/pybootchartgui/pybootchartgui/process_tree.py | |||
@@ -1,3 +1,18 @@ | |||
1 | # This file is part of pybootchartgui. | ||
2 | |||
3 | # pybootchartgui is free software: you can redistribute it and/or modify | ||
4 | # it under the terms of the GNU General Public License as published by | ||
5 | # the Free Software Foundation, either version 3 of the License, or | ||
6 | # (at your option) any later version. | ||
7 | |||
8 | # pybootchartgui is distributed in the hope that it will be useful, | ||
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | # GNU General Public License for more details. | ||
12 | |||
13 | # You should have received a copy of the GNU General Public License | ||
14 | # along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>. | ||
15 | |||
1 | class ProcessTree: | 16 | class ProcessTree: |
2 | """ProcessTree encapsulates a process tree. The tree is built from log files | 17 | """ProcessTree encapsulates a process tree. The tree is built from log files |
3 | retrieved during the boot process. When building the process tree, it is | 18 | retrieved during the boot process. When building the process tree, it is |
@@ -19,55 +34,61 @@ class ProcessTree: | |||
19 | are merged together. | 34 | are merged together. |
20 | 35 | ||
21 | """ | 36 | """ |
22 | LOGGER_PROC = 'bootchartd' | 37 | LOGGER_PROC = 'bootchart-colle' |
23 | EXPLODER_PROCESSES = set(['hwup']) | 38 | EXPLODER_PROCESSES = set(['hwup']) |
24 | 39 | ||
25 | def __init__(self, psstats, monitoredApp, prune, for_testing = False): | 40 | def __init__(self, writer, kernel, psstats, sample_period, |
41 | monitoredApp, prune, idle, taskstats, | ||
42 | accurate_parentage, for_testing = False): | ||
43 | self.writer = writer | ||
26 | self.process_tree = [] | 44 | self.process_tree = [] |
27 | self.psstats = psstats | 45 | self.taskstats = taskstats |
28 | self.process_list = sorted(psstats.process_list, key = lambda p: p.pid) | 46 | if psstats is None: |
29 | self.sample_period = psstats.sample_period | 47 | process_list = kernel |
30 | 48 | elif kernel is None: | |
31 | self.build() | 49 | process_list = psstats.process_map.values() |
32 | self.update_ppids_for_daemons(self.process_list) | 50 | else: |
51 | process_list = list(kernel) + list(psstats.process_map.values()) | ||
52 | self.process_list = sorted(process_list, key = lambda p: p.pid) | ||
53 | self.sample_period = sample_period | ||
54 | |||
55 | self.build() | ||
56 | if not accurate_parentage: | ||
57 | self.update_ppids_for_daemons(self.process_list) | ||
33 | 58 | ||
34 | self.start_time = self.get_start_time(self.process_tree) | 59 | self.start_time = self.get_start_time(self.process_tree) |
35 | self.end_time = self.get_end_time(self.process_tree) | 60 | self.end_time = self.get_end_time(self.process_tree) |
36 | self.duration = self.end_time - self.start_time | 61 | self.duration = self.end_time - self.start_time |
62 | self.idle = idle | ||
37 | 63 | ||
38 | if for_testing: | 64 | if for_testing: |
39 | return | 65 | return |
40 | 66 | ||
41 | # print 'proc_tree before prune: num_proc=%i, duration=%i' % (self.num_nodes(self.process_list), self.duration) | 67 | removed = self.merge_logger(self.process_tree, self.LOGGER_PROC, monitoredApp, False) |
42 | 68 | writer.status("merged %i logger processes" % removed) | |
43 | removed = self.merge_logger(self.process_tree, self.LOGGER_PROC, monitoredApp, False) | ||
44 | print "Merged %i logger processes" % removed | ||
45 | 69 | ||
46 | if prune: | 70 | if prune: |
47 | removed = self.prune(self.process_tree, None) | 71 | p_processes = self.prune(self.process_tree, None) |
48 | print "Pruned %i processes" % removed | 72 | p_exploders = self.merge_exploders(self.process_tree, self.EXPLODER_PROCESSES) |
49 | removed = self.merge_exploders(self.process_tree, self.EXPLODER_PROCESSES) | 73 | p_threads = self.merge_siblings(self.process_tree) |
50 | print "Pruned %i exploders" % removed | 74 | p_runs = self.merge_runs(self.process_tree) |
51 | removed = self.merge_siblings(self.process_tree) | 75 | writer.status("pruned %i process, %i exploders, %i threads, and %i runs" % (p_processes, p_exploders, p_threads, p_runs)) |
52 | print "Pruned %i threads" % removed | ||
53 | removed = self.merge_runs(self.process_tree) | ||
54 | print "Pruned %i runs" % removed | ||
55 | 76 | ||
56 | self.sort(self.process_tree) | 77 | self.sort(self.process_tree) |
57 | 78 | ||
58 | self.start_time = self.get_start_time(self.process_tree) | 79 | self.start_time = self.get_start_time(self.process_tree) |
59 | self.end_time = self.get_end_time(self.process_tree) | 80 | self.end_time = self.get_end_time(self.process_tree) |
60 | self.duration = self.end_time - self.start_time | 81 | self.duration = self.end_time - self.start_time |
61 | 82 | ||
62 | self.num_proc = self.num_nodes(self.process_tree) | 83 | self.num_proc = self.num_nodes(self.process_tree) |
63 | 84 | ||
64 | def build(self): | 85 | def build(self): |
65 | """Build the process tree from the list of top samples.""" | 86 | """Build the process tree from the list of top samples.""" |
66 | self.process_tree = [] | 87 | self.process_tree = [] |
67 | for proc in self.process_list: | 88 | for proc in self.process_list: |
68 | if not proc.parent: | 89 | if not proc.parent: |
69 | self.process_tree.append(proc) | 90 | self.process_tree.append(proc) |
70 | else: | 91 | else: |
71 | proc.parent.child_list.append(proc) | 92 | proc.parent.child_list.append(proc) |
72 | 93 | ||
73 | def sort(self, process_subtree): | 94 | def sort(self, process_subtree): |
@@ -85,11 +106,11 @@ class ProcessTree: | |||
85 | 106 | ||
86 | def get_start_time(self, process_subtree): | 107 | def get_start_time(self, process_subtree): |
87 | """Returns the start time of the process subtree. This is the start | 108 | """Returns the start time of the process subtree. This is the start |
88 | time of the earliest process. | 109 | time of the earliest process. |
89 | 110 | ||
90 | """ | 111 | """ |
91 | if not process_subtree: | 112 | if not process_subtree: |
92 | return 100000000; | 113 | return 100000000 |
93 | return min( [min(proc.start_time, self.get_start_time(proc.child_list)) for proc in process_subtree] ) | 114 | return min( [min(proc.start_time, self.get_start_time(proc.child_list)) for proc in process_subtree] ) |
94 | 115 | ||
95 | def get_end_time(self, process_subtree): | 116 | def get_end_time(self, process_subtree): |
@@ -98,13 +119,13 @@ class ProcessTree: | |||
98 | 119 | ||
99 | """ | 120 | """ |
100 | if not process_subtree: | 121 | if not process_subtree: |
101 | return -100000000; | 122 | return -100000000 |
102 | return max( [max(proc.start_time + proc.duration, self.get_end_time(proc.child_list)) for proc in process_subtree] ) | 123 | return max( [max(proc.start_time + proc.duration, self.get_end_time(proc.child_list)) for proc in process_subtree] ) |
103 | 124 | ||
104 | def get_max_pid(self, process_subtree): | 125 | def get_max_pid(self, process_subtree): |
105 | """Returns the max PID found in the process tree.""" | 126 | """Returns the max PID found in the process tree.""" |
106 | if not process_subtree: | 127 | if not process_subtree: |
107 | return -100000000; | 128 | return -100000000 |
108 | return max( [max(proc.pid, self.get_max_pid(proc.child_list)) for proc in process_subtree] ) | 129 | return max( [max(proc.pid, self.get_max_pid(proc.child_list)) for proc in process_subtree] ) |
109 | 130 | ||
110 | def update_ppids_for_daemons(self, process_list): | 131 | def update_ppids_for_daemons(self, process_list): |
@@ -118,29 +139,29 @@ class ProcessTree: | |||
118 | rcendpid = -1 | 139 | rcendpid = -1 |
119 | rcproc = None | 140 | rcproc = None |
120 | for p in process_list: | 141 | for p in process_list: |
121 | if p.cmd == "rc" and p.ppid == 1: | 142 | if p.cmd == "rc" and p.ppid // 1000 == 1: |
122 | rcproc = p | 143 | rcproc = p |
123 | rcstartpid = p.pid | 144 | rcstartpid = p.pid |
124 | rcendpid = self.get_max_pid(p.child_list) | 145 | rcendpid = self.get_max_pid(p.child_list) |
125 | if rcstartpid != -1 and rcendpid != -1: | 146 | if rcstartpid != -1 and rcendpid != -1: |
126 | for p in process_list: | 147 | for p in process_list: |
127 | if p.pid > rcstartpid and p.pid < rcendpid and p.ppid == 1: | 148 | if p.pid > rcstartpid and p.pid < rcendpid and p.ppid // 1000 == 1: |
128 | p.ppid = rcstartpid | 149 | p.ppid = rcstartpid |
129 | p.parent = rcproc | 150 | p.parent = rcproc |
130 | for p in process_list: | 151 | for p in process_list: |
131 | p.child_list = [] | 152 | p.child_list = [] |
132 | self.build() | 153 | self.build() |
133 | 154 | ||
134 | def prune(self, process_subtree, parent): | 155 | def prune(self, process_subtree, parent): |
135 | """Prunes the process tree by removing idle processes and processes | 156 | """Prunes the process tree by removing idle processes and processes |
136 | that only live for the duration of a single top sample. Sibling | 157 | that only live for the duration of a single top sample. Sibling |
137 | processes with the same command line (i.e. threads) are merged | 158 | processes with the same command line (i.e. threads) are merged |
138 | together. This filters out sleepy background processes, short-lived | 159 | together. This filters out sleepy background processes, short-lived |
139 | processes and bootcharts' analysis tools. | 160 | processes and bootcharts' analysis tools. |
140 | """ | 161 | """ |
141 | def is_idle_background_process_without_children(p): | 162 | def is_idle_background_process_without_children(p): |
142 | process_end = p.start_time + p.duration | 163 | process_end = p.start_time + p.duration |
143 | return not p.active and \ | 164 | return not p.active and \ |
144 | process_end >= self.start_time + self.duration and \ | 165 | process_end >= self.start_time + self.duration and \ |
145 | p.start_time > self.start_time and \ | 166 | p.start_time > self.start_time and \ |
146 | p.duration > 0.9 * self.duration and \ | 167 | p.duration > 0.9 * self.duration and \ |
@@ -175,8 +196,8 @@ class ProcessTree: | |||
175 | 196 | ||
176 | def merge_logger(self, process_subtree, logger_proc, monitored_app, app_tree): | 197 | def merge_logger(self, process_subtree, logger_proc, monitored_app, app_tree): |
177 | """Merges the logger's process subtree. The logger will typically | 198 | """Merges the logger's process subtree. The logger will typically |
178 | spawn lots of sleep and cat processes, thus polluting the | 199 | spawn lots of sleep and cat processes, thus polluting the |
179 | process tree. | 200 | process tree. |
180 | 201 | ||
181 | """ | 202 | """ |
182 | num_removed = 0 | 203 | num_removed = 0 |
@@ -185,7 +206,7 @@ class ProcessTree: | |||
185 | if logger_proc == p.cmd and not app_tree: | 206 | if logger_proc == p.cmd and not app_tree: |
186 | is_app_tree = True | 207 | is_app_tree = True |
187 | num_removed += self.merge_logger(p.child_list, logger_proc, monitored_app, is_app_tree) | 208 | num_removed += self.merge_logger(p.child_list, logger_proc, monitored_app, is_app_tree) |
188 | # don't remove the logger itself | 209 | # don't remove the logger itself |
189 | continue | 210 | continue |
190 | 211 | ||
191 | if app_tree and monitored_app != None and monitored_app == p.cmd: | 212 | if app_tree and monitored_app != None and monitored_app == p.cmd: |
@@ -193,7 +214,7 @@ class ProcessTree: | |||
193 | 214 | ||
194 | if is_app_tree: | 215 | if is_app_tree: |
195 | for child in p.child_list: | 216 | for child in p.child_list: |
196 | self.__merge_processes(p, child) | 217 | self.merge_processes(p, child) |
197 | num_removed += 1 | 218 | num_removed += 1 |
198 | p.child_list = [] | 219 | p.child_list = [] |
199 | else: | 220 | else: |
@@ -202,7 +223,7 @@ class ProcessTree: | |||
202 | 223 | ||
203 | def merge_exploders(self, process_subtree, processes): | 224 | def merge_exploders(self, process_subtree, processes): |
204 | """Merges specific process subtrees (used for processes which usually | 225 | """Merges specific process subtrees (used for processes which usually |
205 | spawn huge meaningless process trees). | 226 | spawn huge meaningless process trees). |
206 | 227 | ||
207 | """ | 228 | """ |
208 | num_removed = 0 | 229 | num_removed = 0 |
@@ -210,7 +231,7 @@ class ProcessTree: | |||
210 | if processes in processes and len(p.child_list) > 0: | 231 | if processes in processes and len(p.child_list) > 0: |
211 | subtreemap = self.getProcessMap(p.child_list) | 232 | subtreemap = self.getProcessMap(p.child_list) |
212 | for child in subtreemap.values(): | 233 | for child in subtreemap.values(): |
213 | self.__merge_processes(p, child) | 234 | self.merge_processes(p, child) |
214 | num_removed += len(subtreemap) | 235 | num_removed += len(subtreemap) |
215 | p.child_list = [] | 236 | p.child_list = [] |
216 | p.cmd += " (+)" | 237 | p.cmd += " (+)" |
@@ -218,10 +239,10 @@ class ProcessTree: | |||
218 | num_removed += self.merge_exploders(p.child_list, processes) | 239 | num_removed += self.merge_exploders(p.child_list, processes) |
219 | return num_removed | 240 | return num_removed |
220 | 241 | ||
221 | def merge_siblings(self,process_subtree): | 242 | def merge_siblings(self, process_subtree): |
222 | """Merges thread processes. Sibling processes with the same command | 243 | """Merges thread processes. Sibling processes with the same command |
223 | line are merged together. | 244 | line are merged together. |
224 | 245 | ||
225 | """ | 246 | """ |
226 | num_removed = 0 | 247 | num_removed = 0 |
227 | idx = 0 | 248 | idx = 0 |
@@ -233,7 +254,7 @@ class ProcessTree: | |||
233 | idx -= 1 | 254 | idx -= 1 |
234 | num_removed += 1 | 255 | num_removed += 1 |
235 | p.child_list.extend(nextp.child_list) | 256 | p.child_list.extend(nextp.child_list) |
236 | self.__merge_processes(p, nextp) | 257 | self.merge_processes(p, nextp) |
237 | num_removed += self.merge_siblings(p.child_list) | 258 | num_removed += self.merge_siblings(p.child_list) |
238 | idx += 1 | 259 | idx += 1 |
239 | if len(process_subtree) > 0: | 260 | if len(process_subtree) > 0: |
@@ -243,7 +264,7 @@ class ProcessTree: | |||
243 | 264 | ||
244 | def merge_runs(self, process_subtree): | 265 | def merge_runs(self, process_subtree): |
245 | """Merges process runs. Single child processes which share the same | 266 | """Merges process runs. Single child processes which share the same |
246 | command line with the parent are merged. | 267 | command line with the parent are merged. |
247 | 268 | ||
248 | """ | 269 | """ |
249 | num_removed = 0 | 270 | num_removed = 0 |
@@ -253,16 +274,17 @@ class ProcessTree: | |||
253 | if len(p.child_list) == 1 and p.child_list[0].cmd == p.cmd: | 274 | if len(p.child_list) == 1 and p.child_list[0].cmd == p.cmd: |
254 | child = p.child_list[0] | 275 | child = p.child_list[0] |
255 | p.child_list = list(child.child_list) | 276 | p.child_list = list(child.child_list) |
256 | self.__merge_processes(p, child) | 277 | self.merge_processes(p, child) |
257 | num_removed += 1 | 278 | num_removed += 1 |
258 | continue | 279 | continue |
259 | num_removed += self.merge_runs(p.child_list) | 280 | num_removed += self.merge_runs(p.child_list) |
260 | idx += 1 | 281 | idx += 1 |
261 | return num_removed | 282 | return num_removed |
262 | 283 | ||
263 | def __merge_processes(self, p1, p2): | 284 | def merge_processes(self, p1, p2): |
264 | """Merges two process samples.""" | 285 | """Merges two process' samples.""" |
265 | p1.samples.extend(p2.samples) | 286 | p1.samples.extend(p2.samples) |
287 | p1.samples.sort( key = lambda p: p.time ) | ||
266 | p1time = p1.start_time | 288 | p1time = p1.start_time |
267 | p2time = p2.start_time | 289 | p2time = p2.start_time |
268 | p1.start_time = min(p1time, p2time) | 290 | p1.start_time = min(p1time, p2time) |