summaryrefslogtreecommitdiffstats
path: root/scripts/pybootchartgui/pybootchartgui/process_tree.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/pybootchartgui/pybootchartgui/process_tree.py')
-rw-r--r--scripts/pybootchartgui/pybootchartgui/process_tree.py270
1 files changed, 270 insertions, 0 deletions
diff --git a/scripts/pybootchartgui/pybootchartgui/process_tree.py b/scripts/pybootchartgui/pybootchartgui/process_tree.py
new file mode 100644
index 0000000000..bde29ebda8
--- /dev/null
+++ b/scripts/pybootchartgui/pybootchartgui/process_tree.py
@@ -0,0 +1,270 @@
1class ProcessTree:
2 """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
4 pruned and merged in order to be able to visualize it in a comprehensible
5 manner.
6
7 The following pruning techniques are used:
8
9 * idle processes that keep running during the last process sample
10 (which is a heuristic for a background processes) are removed,
11 * short-lived processes (i.e. processes that only live for the
12 duration of two samples or less) are removed,
13 * the processes used by the boot logger are removed,
14 * exploders (i.e. processes that are known to spawn huge meaningless
15 process subtrees) have their subtrees merged together,
16 * siblings (i.e. processes with the same command line living
17 concurrently -- thread heuristic) are merged together,
18 * process runs (unary trees with processes sharing the command line)
19 are merged together.
20
21 """
22 LOGGER_PROC = 'bootchartd'
23 EXPLODER_PROCESSES = set(['hwup'])
24
25 def __init__(self, psstats, monitoredApp, prune, for_testing = False):
26 self.process_tree = []
27 self.psstats = psstats
28 self.process_list = sorted(psstats.process_list, key = lambda p: p.pid)
29 self.sample_period = psstats.sample_period
30
31 self.build()
32 self.update_ppids_for_daemons(self.process_list)
33
34 self.start_time = self.get_start_time(self.process_tree)
35 self.end_time = self.get_end_time(self.process_tree)
36 self.duration = self.end_time - self.start_time
37
38 if for_testing:
39 return
40
41 # print 'proc_tree before prune: num_proc=%i, duration=%i' % (self.num_nodes(self.process_list), self.duration)
42
43 removed = self.merge_logger(self.process_tree, self.LOGGER_PROC, monitoredApp, False)
44 print "Merged %i logger processes" % removed
45
46 if prune:
47 removed = self.prune(self.process_tree, None)
48 print "Pruned %i processes" % removed
49 removed = self.merge_exploders(self.process_tree, self.EXPLODER_PROCESSES)
50 print "Pruned %i exploders" % removed
51 removed = self.merge_siblings(self.process_tree)
52 print "Pruned %i threads" % removed
53 removed = self.merge_runs(self.process_tree)
54 print "Pruned %i runs" % removed
55
56 self.sort(self.process_tree)
57
58 self.start_time = self.get_start_time(self.process_tree)
59 self.end_time = self.get_end_time(self.process_tree)
60 self.duration = self.end_time - self.start_time
61
62 self.num_proc = self.num_nodes(self.process_tree)
63
64 def build(self):
65 """Build the process tree from the list of top samples."""
66 self.process_tree = []
67 for proc in self.process_list:
68 if not proc.parent:
69 self.process_tree.append(proc)
70 else:
71 proc.parent.child_list.append(proc)
72
73 def sort(self, process_subtree):
74 """Sort process tree."""
75 for p in process_subtree:
76 p.child_list.sort(key = lambda p: p.pid)
77 self.sort(p.child_list)
78
79 def num_nodes(self, process_list):
80 "Counts the number of nodes in the specified process tree."""
81 nodes = 0
82 for proc in process_list:
83 nodes = nodes + self.num_nodes(proc.child_list)
84 return nodes + len(process_list)
85
86 def get_start_time(self, process_subtree):
87 """Returns the start time of the process subtree. This is the start
88 time of the earliest process.
89
90 """
91 if not process_subtree:
92 return 100000000;
93 return min( [min(proc.start_time, self.get_start_time(proc.child_list)) for proc in process_subtree] )
94
95 def get_end_time(self, process_subtree):
96 """Returns the end time of the process subtree. This is the end time
97 of the last collected sample.
98
99 """
100 if not process_subtree:
101 return -100000000;
102 return max( [max(proc.start_time + proc.duration, self.get_end_time(proc.child_list)) for proc in process_subtree] )
103
104 def get_max_pid(self, process_subtree):
105 """Returns the max PID found in the process tree."""
106 if not process_subtree:
107 return -100000000;
108 return max( [max(proc.pid, self.get_max_pid(proc.child_list)) for proc in process_subtree] )
109
110 def update_ppids_for_daemons(self, process_list):
111 """Fedora hack: when loading the system services from rc, runuser(1)
112 is used. This sets the PPID of all daemons to 1, skewing
113 the process tree. Try to detect this and set the PPID of
114 these processes the PID of rc.
115
116 """
117 rcstartpid = -1
118 rcendpid = -1
119 rcproc = None
120 for p in process_list:
121 if p.cmd == "rc" and p.ppid == 1:
122 rcproc = p
123 rcstartpid = p.pid
124 rcendpid = self.get_max_pid(p.child_list)
125 if rcstartpid != -1 and rcendpid != -1:
126 for p in process_list:
127 if p.pid > rcstartpid and p.pid < rcendpid and p.ppid == 1:
128 p.ppid = rcstartpid
129 p.parent = rcproc
130 for p in process_list:
131 p.child_list = []
132 self.build()
133
134 def prune(self, process_subtree, parent):
135 """Prunes the process tree by removing idle processes and processes
136 that only live for the duration of a single top sample. Sibling
137 processes with the same command line (i.e. threads) are merged
138 together. This filters out sleepy background processes, short-lived
139 processes and bootcharts' analysis tools.
140 """
141 def is_idle_background_process_without_children(p):
142 process_end = p.start_time + p.duration
143 return not p.active and \
144 process_end >= self.start_time + self.duration and \
145 p.start_time > self.start_time and \
146 p.duration > 0.9 * self.duration and \
147 self.num_nodes(p.child_list) == 0
148
149 num_removed = 0
150 idx = 0
151 while idx < len(process_subtree):
152 p = process_subtree[idx]
153 if parent != None or len(p.child_list) == 0:
154
155 prune = False
156 if is_idle_background_process_without_children(p):
157 prune = True
158 elif p.duration <= 2 * self.sample_period:
159 # short-lived process
160 prune = True
161
162 if prune:
163 process_subtree.pop(idx)
164 for c in p.child_list:
165 process_subtree.insert(idx, c)
166 num_removed += 1
167 continue
168 else:
169 num_removed += self.prune(p.child_list, p)
170 else:
171 num_removed += self.prune(p.child_list, p)
172 idx += 1
173
174 return num_removed
175
176 def merge_logger(self, process_subtree, logger_proc, monitored_app, app_tree):
177 """Merges the logger's process subtree. The logger will typically
178 spawn lots of sleep and cat processes, thus polluting the
179 process tree.
180
181 """
182 num_removed = 0
183 for p in process_subtree:
184 is_app_tree = app_tree
185 if logger_proc == p.cmd and not app_tree:
186 is_app_tree = True
187 num_removed += self.merge_logger(p.child_list, logger_proc, monitored_app, is_app_tree)
188 # don't remove the logger itself
189 continue
190
191 if app_tree and monitored_app != None and monitored_app == p.cmd:
192 is_app_tree = False
193
194 if is_app_tree:
195 for child in p.child_list:
196 self.__merge_processes(p, child)
197 num_removed += 1
198 p.child_list = []
199 else:
200 num_removed += self.merge_logger(p.child_list, logger_proc, monitored_app, is_app_tree)
201 return num_removed
202
203 def merge_exploders(self, process_subtree, processes):
204 """Merges specific process subtrees (used for processes which usually
205 spawn huge meaningless process trees).
206
207 """
208 num_removed = 0
209 for p in process_subtree:
210 if processes in processes and len(p.child_list) > 0:
211 subtreemap = self.getProcessMap(p.child_list)
212 for child in subtreemap.values():
213 self.__merge_processes(p, child)
214 num_removed += len(subtreemap)
215 p.child_list = []
216 p.cmd += " (+)"
217 else:
218 num_removed += self.merge_exploders(p.child_list, processes)
219 return num_removed
220
221 def merge_siblings(self,process_subtree):
222 """Merges thread processes. Sibling processes with the same command
223 line are merged together.
224
225 """
226 num_removed = 0
227 idx = 0
228 while idx < len(process_subtree)-1:
229 p = process_subtree[idx]
230 nextp = process_subtree[idx+1]
231 if nextp.cmd == p.cmd:
232 process_subtree.pop(idx+1)
233 idx -= 1
234 num_removed += 1
235 p.child_list.extend(nextp.child_list)
236 self.__merge_processes(p, nextp)
237 num_removed += self.merge_siblings(p.child_list)
238 idx += 1
239 if len(process_subtree) > 0:
240 p = process_subtree[-1]
241 num_removed += self.merge_siblings(p.child_list)
242 return num_removed
243
244 def merge_runs(self, process_subtree):
245 """Merges process runs. Single child processes which share the same
246 command line with the parent are merged.
247
248 """
249 num_removed = 0
250 idx = 0
251 while idx < len(process_subtree):
252 p = process_subtree[idx]
253 if len(p.child_list) == 1 and p.child_list[0].cmd == p.cmd:
254 child = p.child_list[0]
255 p.child_list = list(child.child_list)
256 self.__merge_processes(p, child)
257 num_removed += 1
258 continue
259 num_removed += self.merge_runs(p.child_list)
260 idx += 1
261 return num_removed
262
263 def __merge_processes(self, p1, p2):
264 """Merges two process samples."""
265 p1.samples.extend(p2.samples)
266 p1time = p1.start_time
267 p2time = p2.start_time
268 p1.start_time = min(p1time, p2time)
269 pendtime = max(p1time + p1.duration, p2time + p2.duration)
270 p1.duration = pendtime - p1.start_time