diff options
-rw-r--r-- | tools/power/pm-graph/Makefile | 28 | ||||
-rwxr-xr-x | tools/power/pm-graph/analyze_boot.py | 824 | ||||
-rwxr-xr-x | tools/power/pm-graph/analyze_suspend.py (renamed from scripts/analyze_suspend.py) | 916 | ||||
-rw-r--r-- | tools/power/pm-graph/bootgraph.8 | 132 | ||||
-rw-r--r-- | tools/power/pm-graph/sleepgraph.8 | 243 | ||||
-rwxr-xr-x | tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py | 17 |
6 files changed, 1739 insertions, 421 deletions
diff --git a/tools/power/pm-graph/Makefile b/tools/power/pm-graph/Makefile new file mode 100644 index 000000000000..4d0ccc89e6c6 --- /dev/null +++ b/tools/power/pm-graph/Makefile | |||
@@ -0,0 +1,28 @@ | |||
1 | PREFIX ?= /usr | ||
2 | DESTDIR ?= | ||
3 | |||
4 | all: | ||
5 | @echo "Nothing to build" | ||
6 | |||
7 | install : | ||
8 | install -d $(DESTDIR)$(PREFIX)/lib/pm-graph | ||
9 | install analyze_suspend.py $(DESTDIR)$(PREFIX)/lib/pm-graph | ||
10 | install analyze_boot.py $(DESTDIR)$(PREFIX)/lib/pm-graph | ||
11 | |||
12 | ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_boot.py $(DESTDIR)$(PREFIX)/bin/bootgraph | ||
13 | ln -s $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_suspend.py $(DESTDIR)$(PREFIX)/bin/sleepgraph | ||
14 | |||
15 | install -d $(DESTDIR)$(PREFIX)/share/man/man8 | ||
16 | install bootgraph.8 $(DESTDIR)$(PREFIX)/share/man/man8 | ||
17 | install sleepgraph.8 $(DESTDIR)$(PREFIX)/share/man/man8 | ||
18 | |||
19 | uninstall : | ||
20 | rm $(DESTDIR)$(PREFIX)/share/man/man8/bootgraph.8 | ||
21 | rm $(DESTDIR)$(PREFIX)/share/man/man8/sleepgraph.8 | ||
22 | |||
23 | rm $(DESTDIR)$(PREFIX)/bin/bootgraph | ||
24 | rm $(DESTDIR)$(PREFIX)/bin/sleepgraph | ||
25 | |||
26 | rm $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_boot.py | ||
27 | rm $(DESTDIR)$(PREFIX)/lib/pm-graph/analyze_suspend.py | ||
28 | rmdir $(DESTDIR)$(PREFIX)/lib/pm-graph | ||
diff --git a/tools/power/pm-graph/analyze_boot.py b/tools/power/pm-graph/analyze_boot.py new file mode 100755 index 000000000000..3e1dcbbf1adc --- /dev/null +++ b/tools/power/pm-graph/analyze_boot.py | |||
@@ -0,0 +1,824 @@ | |||
1 | #!/usr/bin/python | ||
2 | # | ||
3 | # Tool for analyzing boot timing | ||
4 | # Copyright (c) 2013, Intel Corporation. | ||
5 | # | ||
6 | # This program is free software; you can redistribute it and/or modify it | ||
7 | # under the terms and conditions of the GNU General Public License, | ||
8 | # version 2, as published by the Free Software Foundation. | ||
9 | # | ||
10 | # This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | # more details. | ||
14 | # | ||
15 | # Authors: | ||
16 | # Todd Brandt <todd.e.brandt@linux.intel.com> | ||
17 | # | ||
18 | # Description: | ||
19 | # This tool is designed to assist kernel and OS developers in optimizing | ||
20 | # their linux stack's boot time. It creates an html representation of | ||
21 | # the kernel boot timeline up to the start of the init process. | ||
22 | # | ||
23 | |||
24 | # ----------------- LIBRARIES -------------------- | ||
25 | |||
26 | import sys | ||
27 | import time | ||
28 | import os | ||
29 | import string | ||
30 | import re | ||
31 | import platform | ||
32 | import shutil | ||
33 | from datetime import datetime, timedelta | ||
34 | from subprocess import call, Popen, PIPE | ||
35 | import analyze_suspend as aslib | ||
36 | |||
37 | # ----------------- CLASSES -------------------- | ||
38 | |||
39 | # Class: SystemValues | ||
40 | # Description: | ||
41 | # A global, single-instance container used to | ||
42 | # store system values and test parameters | ||
43 | class SystemValues(aslib.SystemValues): | ||
44 | title = 'BootGraph' | ||
45 | version = 2.0 | ||
46 | hostname = 'localhost' | ||
47 | testtime = '' | ||
48 | kernel = '' | ||
49 | dmesgfile = '' | ||
50 | ftracefile = '' | ||
51 | htmlfile = 'bootgraph.html' | ||
52 | outfile = '' | ||
53 | phoronix = False | ||
54 | addlogs = False | ||
55 | useftrace = False | ||
56 | usedevsrc = True | ||
57 | suspendmode = 'boot' | ||
58 | max_graph_depth = 2 | ||
59 | graph_filter = 'do_one_initcall' | ||
60 | reboot = False | ||
61 | manual = False | ||
62 | iscronjob = False | ||
63 | timeformat = '%.6f' | ||
64 | def __init__(self): | ||
65 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): | ||
66 | self.phoronix = True | ||
67 | self.addlogs = True | ||
68 | self.outfile = os.environ['LOG_FILE'] | ||
69 | self.htmlfile = os.environ['LOG_FILE'] | ||
70 | self.hostname = platform.node() | ||
71 | self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') | ||
72 | if os.path.exists('/proc/version'): | ||
73 | fp = open('/proc/version', 'r') | ||
74 | val = fp.read().strip() | ||
75 | fp.close() | ||
76 | self.kernel = self.kernelVersion(val) | ||
77 | else: | ||
78 | self.kernel = 'unknown' | ||
79 | def kernelVersion(self, msg): | ||
80 | return msg.split()[2] | ||
81 | def kernelParams(self): | ||
82 | cmdline = 'initcall_debug log_buf_len=32M' | ||
83 | if self.useftrace: | ||
84 | cmdline += ' trace_buf_size=128M trace_clock=global '\ | ||
85 | 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ | ||
86 | 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ | ||
87 | 'nofuncgraph-overhead,context-info,graph-time '\ | ||
88 | 'ftrace=function_graph '\ | ||
89 | 'ftrace_graph_max_depth=%d '\ | ||
90 | 'ftrace_graph_filter=%s' % \ | ||
91 | (self.max_graph_depth, self.graph_filter) | ||
92 | return cmdline | ||
93 | def setGraphFilter(self, val): | ||
94 | fp = open(self.tpath+'available_filter_functions') | ||
95 | master = fp.read().split('\n') | ||
96 | fp.close() | ||
97 | for i in val.split(','): | ||
98 | func = i.strip() | ||
99 | if func not in master: | ||
100 | doError('function "%s" not available for ftrace' % func) | ||
101 | self.graph_filter = val | ||
102 | def cronjobCmdString(self): | ||
103 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) | ||
104 | args = iter(sys.argv[1:]) | ||
105 | for arg in args: | ||
106 | if arg in ['-h', '-v', '-cronjob', '-reboot']: | ||
107 | continue | ||
108 | elif arg in ['-o', '-dmesg', '-ftrace', '-filter']: | ||
109 | args.next() | ||
110 | continue | ||
111 | cmdline += ' '+arg | ||
112 | if self.graph_filter != 'do_one_initcall': | ||
113 | cmdline += ' -filter "%s"' % self.graph_filter | ||
114 | cmdline += ' -o "%s"' % os.path.abspath(self.htmlfile) | ||
115 | return cmdline | ||
116 | def manualRebootRequired(self): | ||
117 | cmdline = self.kernelParams() | ||
118 | print 'To generate a new timeline manually, follow these steps:\n' | ||
119 | print '1. Add the CMDLINE string to your kernel command line.' | ||
120 | print '2. Reboot the system.' | ||
121 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' | ||
122 | print 'CMDLINE="%s"' % cmdline | ||
123 | sys.exit() | ||
124 | |||
125 | sysvals = SystemValues() | ||
126 | |||
127 | # Class: Data | ||
128 | # Description: | ||
129 | # The primary container for test data. | ||
130 | class Data(aslib.Data): | ||
131 | dmesg = {} # root data structure | ||
132 | start = 0.0 # test start | ||
133 | end = 0.0 # test end | ||
134 | dmesgtext = [] # dmesg text file in memory | ||
135 | testnumber = 0 | ||
136 | idstr = '' | ||
137 | html_device_id = 0 | ||
138 | valid = False | ||
139 | initstart = 0.0 | ||
140 | boottime = '' | ||
141 | phases = ['boot'] | ||
142 | do_one_initcall = False | ||
143 | def __init__(self, num): | ||
144 | self.testnumber = num | ||
145 | self.idstr = 'a' | ||
146 | self.dmesgtext = [] | ||
147 | self.dmesg = { | ||
148 | 'boot': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, 'color': '#dddddd'} | ||
149 | } | ||
150 | def deviceTopology(self): | ||
151 | return '' | ||
152 | def newAction(self, phase, name, start, end, ret, ulen): | ||
153 | # new device callback for a specific phase | ||
154 | self.html_device_id += 1 | ||
155 | devid = '%s%d' % (self.idstr, self.html_device_id) | ||
156 | list = self.dmesg[phase]['list'] | ||
157 | length = -1.0 | ||
158 | if(start >= 0 and end >= 0): | ||
159 | length = end - start | ||
160 | i = 2 | ||
161 | origname = name | ||
162 | while(name in list): | ||
163 | name = '%s[%d]' % (origname, i) | ||
164 | i += 1 | ||
165 | list[name] = {'name': name, 'start': start, 'end': end, | ||
166 | 'pid': 0, 'length': length, 'row': 0, 'id': devid, | ||
167 | 'ret': ret, 'ulen': ulen } | ||
168 | return name | ||
169 | def deviceMatch(self, cg): | ||
170 | if cg.end - cg.start == 0: | ||
171 | return True | ||
172 | list = self.dmesg['boot']['list'] | ||
173 | for devname in list: | ||
174 | dev = list[devname] | ||
175 | if cg.name == 'do_one_initcall': | ||
176 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): | ||
177 | dev['ftrace'] = cg | ||
178 | self.do_one_initcall = True | ||
179 | return True | ||
180 | else: | ||
181 | if(cg.start > dev['start'] and cg.end < dev['end']): | ||
182 | if 'ftraces' not in dev: | ||
183 | dev['ftraces'] = [] | ||
184 | dev['ftraces'].append(cg) | ||
185 | return True | ||
186 | return False | ||
187 | |||
188 | # ----------------- FUNCTIONS -------------------- | ||
189 | |||
190 | # Function: loadKernelLog | ||
191 | # Description: | ||
192 | # Load a raw kernel log from dmesg | ||
193 | def loadKernelLog(): | ||
194 | data = Data(0) | ||
195 | data.dmesg['boot']['start'] = data.start = ktime = 0.0 | ||
196 | sysvals.stamp = { | ||
197 | 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), | ||
198 | 'host': sysvals.hostname, | ||
199 | 'mode': 'boot', 'kernel': ''} | ||
200 | |||
201 | devtemp = dict() | ||
202 | if(sysvals.dmesgfile): | ||
203 | lf = open(sysvals.dmesgfile, 'r') | ||
204 | else: | ||
205 | lf = Popen('dmesg', stdout=PIPE).stdout | ||
206 | for line in lf: | ||
207 | line = line.replace('\r\n', '') | ||
208 | idx = line.find('[') | ||
209 | if idx > 1: | ||
210 | line = line[idx:] | ||
211 | m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line) | ||
212 | if(not m): | ||
213 | continue | ||
214 | ktime = float(m.group('ktime')) | ||
215 | if(ktime > 120): | ||
216 | break | ||
217 | msg = m.group('msg') | ||
218 | data.end = data.initstart = ktime | ||
219 | data.dmesgtext.append(line) | ||
220 | if(ktime == 0.0 and re.match('^Linux version .*', msg)): | ||
221 | if(not sysvals.stamp['kernel']): | ||
222 | sysvals.stamp['kernel'] = sysvals.kernelVersion(msg) | ||
223 | continue | ||
224 | m = re.match('.* setting system clock to (?P<t>.*) UTC.*', msg) | ||
225 | if(m): | ||
226 | bt = datetime.strptime(m.group('t'), '%Y-%m-%d %H:%M:%S') | ||
227 | bt = bt - timedelta(seconds=int(ktime)) | ||
228 | data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') | ||
229 | sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') | ||
230 | continue | ||
231 | m = re.match('^calling *(?P<f>.*)\+.*', msg) | ||
232 | if(m): | ||
233 | devtemp[m.group('f')] = ktime | ||
234 | continue | ||
235 | m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) | ||
236 | if(m): | ||
237 | data.valid = True | ||
238 | f, r, t = m.group('f', 'r', 't') | ||
239 | if(f in devtemp): | ||
240 | data.newAction('boot', f, devtemp[f], ktime, int(r), int(t)) | ||
241 | data.end = ktime | ||
242 | del devtemp[f] | ||
243 | continue | ||
244 | if(re.match('^Freeing unused kernel memory.*', msg)): | ||
245 | break | ||
246 | |||
247 | data.dmesg['boot']['end'] = data.end | ||
248 | lf.close() | ||
249 | return data | ||
250 | |||
251 | # Function: loadTraceLog | ||
252 | # Description: | ||
253 | # Check if trace is available and copy to a temp file | ||
254 | def loadTraceLog(data): | ||
255 | # load the data to a temp file if none given | ||
256 | if not sysvals.ftracefile: | ||
257 | lib = aslib.sysvals | ||
258 | aslib.rootCheck(True) | ||
259 | if not lib.verifyFtrace(): | ||
260 | doError('ftrace not available') | ||
261 | if lib.fgetVal('current_tracer').strip() != 'function_graph': | ||
262 | doError('ftrace not configured for a boot callgraph') | ||
263 | sysvals.ftracefile = '/tmp/boot_ftrace.%s.txt' % os.getpid() | ||
264 | call('cat '+lib.tpath+'trace > '+sysvals.ftracefile, shell=True) | ||
265 | if not sysvals.ftracefile: | ||
266 | doError('No trace data available') | ||
267 | |||
268 | # parse the trace log | ||
269 | ftemp = dict() | ||
270 | tp = aslib.TestProps() | ||
271 | tp.setTracerType('function_graph') | ||
272 | tf = open(sysvals.ftracefile, 'r') | ||
273 | for line in tf: | ||
274 | if line[0] == '#': | ||
275 | continue | ||
276 | m = re.match(tp.ftrace_line_fmt, line.strip()) | ||
277 | if(not m): | ||
278 | continue | ||
279 | m_time, m_proc, m_pid, m_msg, m_dur = \ | ||
280 | m.group('time', 'proc', 'pid', 'msg', 'dur') | ||
281 | if float(m_time) > data.end: | ||
282 | break | ||
283 | if(m_time and m_pid and m_msg): | ||
284 | t = aslib.FTraceLine(m_time, m_msg, m_dur) | ||
285 | pid = int(m_pid) | ||
286 | else: | ||
287 | continue | ||
288 | if t.fevent or t.fkprobe: | ||
289 | continue | ||
290 | key = (m_proc, pid) | ||
291 | if(key not in ftemp): | ||
292 | ftemp[key] = [] | ||
293 | ftemp[key].append(aslib.FTraceCallGraph(pid)) | ||
294 | cg = ftemp[key][-1] | ||
295 | if(cg.addLine(t)): | ||
296 | ftemp[key].append(aslib.FTraceCallGraph(pid)) | ||
297 | tf.close() | ||
298 | |||
299 | # add the callgraph data to the device hierarchy | ||
300 | for key in ftemp: | ||
301 | proc, pid = key | ||
302 | for cg in ftemp[key]: | ||
303 | if len(cg.list) < 1 or cg.invalid: | ||
304 | continue | ||
305 | if(not cg.postProcess()): | ||
306 | print('Sanity check failed for %s-%d' % (proc, pid)) | ||
307 | continue | ||
308 | # match cg data to devices | ||
309 | if not data.deviceMatch(cg): | ||
310 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) | ||
311 | |||
312 | # Function: colorForName | ||
313 | # Description: | ||
314 | # Generate a repeatable color from a list for a given name | ||
315 | def colorForName(name): | ||
316 | list = [ | ||
317 | ('c1', '#ec9999'), | ||
318 | ('c2', '#ffc1a6'), | ||
319 | ('c3', '#fff0a6'), | ||
320 | ('c4', '#adf199'), | ||
321 | ('c5', '#9fadea'), | ||
322 | ('c6', '#a699c1'), | ||
323 | ('c7', '#ad99b4'), | ||
324 | ('c8', '#eaffea'), | ||
325 | ('c9', '#dcecfb'), | ||
326 | ('c10', '#ffffea') | ||
327 | ] | ||
328 | i = 0 | ||
329 | total = 0 | ||
330 | count = len(list) | ||
331 | while i < len(name): | ||
332 | total += ord(name[i]) | ||
333 | i += 1 | ||
334 | return list[total % count] | ||
335 | |||
336 | def cgOverview(cg, minlen): | ||
337 | stats = dict() | ||
338 | large = [] | ||
339 | for l in cg.list: | ||
340 | if l.fcall and l.depth == 1: | ||
341 | if l.length >= minlen: | ||
342 | large.append(l) | ||
343 | if l.name not in stats: | ||
344 | stats[l.name] = [0, 0.0] | ||
345 | stats[l.name][0] += (l.length * 1000.0) | ||
346 | stats[l.name][1] += 1 | ||
347 | return (large, stats) | ||
348 | |||
349 | # Function: createBootGraph | ||
350 | # Description: | ||
351 | # Create the output html file from the resident test data | ||
352 | # Arguments: | ||
353 | # testruns: array of Data objects from parseKernelLog or parseTraceLog | ||
354 | # Output: | ||
355 | # True if the html file was created, false if it failed | ||
356 | def createBootGraph(data, embedded): | ||
357 | # html function templates | ||
358 | html_srccall = '<div id={6} title="{5}" class="srccall" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;">{0}</div>\n' | ||
359 | html_timetotal = '<table class="time1">\n<tr>'\ | ||
360 | '<td class="blue">Time from Kernel Boot to start of User Mode: <b>{0} ms</b></td>'\ | ||
361 | '</tr>\n</table>\n' | ||
362 | |||
363 | # device timeline | ||
364 | devtl = aslib.Timeline(100, 20) | ||
365 | |||
366 | # write the test title and general info header | ||
367 | devtl.createHeader(sysvals, 'noftrace') | ||
368 | |||
369 | # Generate the header for this timeline | ||
370 | t0 = data.start | ||
371 | tMax = data.end | ||
372 | tTotal = tMax - t0 | ||
373 | if(tTotal == 0): | ||
374 | print('ERROR: No timeline data') | ||
375 | return False | ||
376 | boot_time = '%.0f'%(tTotal*1000) | ||
377 | devtl.html += html_timetotal.format(boot_time) | ||
378 | |||
379 | # determine the maximum number of rows we need to draw | ||
380 | phase = 'boot' | ||
381 | list = data.dmesg[phase]['list'] | ||
382 | devlist = [] | ||
383 | for devname in list: | ||
384 | d = aslib.DevItem(0, phase, list[devname]) | ||
385 | devlist.append(d) | ||
386 | devtl.getPhaseRows(devlist) | ||
387 | devtl.calcTotalRows() | ||
388 | |||
389 | # draw the timeline background | ||
390 | devtl.createZoomBox() | ||
391 | boot = data.dmesg[phase] | ||
392 | length = boot['end']-boot['start'] | ||
393 | left = '%.3f' % (((boot['start']-t0)*100.0)/tTotal) | ||
394 | width = '%.3f' % ((length*100.0)/tTotal) | ||
395 | devtl.html += devtl.html_tblock.format(phase, left, width, devtl.scaleH) | ||
396 | devtl.html += devtl.html_phase.format('0', '100', \ | ||
397 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ | ||
398 | 'white', '') | ||
399 | |||
400 | # draw the device timeline | ||
401 | num = 0 | ||
402 | devstats = dict() | ||
403 | for devname in sorted(list): | ||
404 | cls, color = colorForName(devname) | ||
405 | dev = list[devname] | ||
406 | info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, | ||
407 | dev['ulen']/1000.0, dev['ret']) | ||
408 | devstats[dev['id']] = {'info':info} | ||
409 | dev['color'] = color | ||
410 | height = devtl.phaseRowHeight(0, phase, dev['row']) | ||
411 | top = '%.6f' % ((dev['row']*height) + devtl.scaleH) | ||
412 | left = '%.6f' % (((dev['start']-t0)*100)/tTotal) | ||
413 | width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) | ||
414 | length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) | ||
415 | devtl.html += devtl.html_device.format(dev['id'], | ||
416 | devname+length+'kernel_mode', left, top, '%.3f'%height, | ||
417 | width, devname, ' '+cls, '') | ||
418 | rowtop = devtl.phaseRowTop(0, phase, dev['row']) | ||
419 | height = '%.6f' % (devtl.rowH / 2) | ||
420 | top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) | ||
421 | if data.do_one_initcall: | ||
422 | if('ftrace' not in dev): | ||
423 | continue | ||
424 | cg = dev['ftrace'] | ||
425 | large, stats = cgOverview(cg, 0.001) | ||
426 | devstats[dev['id']]['fstat'] = stats | ||
427 | for l in large: | ||
428 | left = '%f' % (((l.time-t0)*100)/tTotal) | ||
429 | width = '%f' % (l.length*100/tTotal) | ||
430 | title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) | ||
431 | devtl.html += html_srccall.format(l.name, left, | ||
432 | top, height, width, title, 'x%d'%num) | ||
433 | num += 1 | ||
434 | continue | ||
435 | if('ftraces' not in dev): | ||
436 | continue | ||
437 | for cg in dev['ftraces']: | ||
438 | left = '%f' % (((cg.start-t0)*100)/tTotal) | ||
439 | width = '%f' % ((cg.end-cg.start)*100/tTotal) | ||
440 | cglen = (cg.end - cg.start) * 1000.0 | ||
441 | title = '%s (%0.3fms)' % (cg.name, cglen) | ||
442 | cg.id = 'x%d' % num | ||
443 | devtl.html += html_srccall.format(cg.name, left, | ||
444 | top, height, width, title, dev['id']+cg.id) | ||
445 | num += 1 | ||
446 | |||
447 | # draw the time scale, try to make the number of labels readable | ||
448 | devtl.createTimeScale(t0, tMax, tTotal, phase) | ||
449 | devtl.html += '</div>\n' | ||
450 | |||
451 | # timeline is finished | ||
452 | devtl.html += '</div>\n</div>\n' | ||
453 | |||
454 | if(sysvals.outfile == sysvals.htmlfile): | ||
455 | hf = open(sysvals.htmlfile, 'a') | ||
456 | else: | ||
457 | hf = open(sysvals.htmlfile, 'w') | ||
458 | |||
459 | # add the css if this is not an embedded run | ||
460 | extra = '\ | ||
461 | .c1 {background:rgba(209,0,0,0.4);}\n\ | ||
462 | .c2 {background:rgba(255,102,34,0.4);}\n\ | ||
463 | .c3 {background:rgba(255,218,33,0.4);}\n\ | ||
464 | .c4 {background:rgba(51,221,0,0.4);}\n\ | ||
465 | .c5 {background:rgba(17,51,204,0.4);}\n\ | ||
466 | .c6 {background:rgba(34,0,102,0.4);}\n\ | ||
467 | .c7 {background:rgba(51,0,68,0.4);}\n\ | ||
468 | .c8 {background:rgba(204,255,204,0.4);}\n\ | ||
469 | .c9 {background:rgba(169,208,245,0.4);}\n\ | ||
470 | .c10 {background:rgba(255,255,204,0.4);}\n\ | ||
471 | .vt {transform:rotate(-60deg);transform-origin:0 0;}\n\ | ||
472 | table.fstat {table-layout:fixed;padding:150px 15px 0 0;font-size:10px;column-width:30px;}\n\ | ||
473 | .fstat th {width:55px;}\n\ | ||
474 | .fstat td {text-align:left;width:35px;}\n\ | ||
475 | .srccall {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\ | ||
476 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' | ||
477 | if(not embedded): | ||
478 | aslib.addCSS(hf, sysvals, 1, False, extra) | ||
479 | |||
480 | # write the device timeline | ||
481 | hf.write(devtl.html) | ||
482 | |||
483 | # add boot specific html | ||
484 | statinfo = 'var devstats = {\n' | ||
485 | for n in sorted(devstats): | ||
486 | statinfo += '\t"%s": [\n\t\t"%s",\n' % (n, devstats[n]['info']) | ||
487 | if 'fstat' in devstats[n]: | ||
488 | funcs = devstats[n]['fstat'] | ||
489 | for f in sorted(funcs, key=funcs.get, reverse=True): | ||
490 | if funcs[f][0] < 0.01 and len(funcs) > 10: | ||
491 | break | ||
492 | statinfo += '\t\t"%f|%s|%d",\n' % (funcs[f][0], f, funcs[f][1]) | ||
493 | statinfo += '\t],\n' | ||
494 | statinfo += '};\n' | ||
495 | html = \ | ||
496 | '<div id="devicedetailtitle"></div>\n'\ | ||
497 | '<div id="devicedetail" style="display:none;">\n'\ | ||
498 | '<div id="devicedetail0">\n'\ | ||
499 | '<div id="kernel_mode" class="phaselet" style="left:0%;width:100%;background:#DDDDDD"></div>\n'\ | ||
500 | '</div>\n</div>\n'\ | ||
501 | '<script type="text/javascript">\n'+statinfo+\ | ||
502 | '</script>\n' | ||
503 | hf.write(html) | ||
504 | |||
505 | # add the callgraph html | ||
506 | if(sysvals.usecallgraph): | ||
507 | aslib.addCallgraphs(sysvals, hf, data) | ||
508 | |||
509 | # add the dmesg log as a hidden div | ||
510 | if sysvals.addlogs: | ||
511 | hf.write('<div id="dmesglog" style="display:none;">\n') | ||
512 | for line in data.dmesgtext: | ||
513 | line = line.replace('<', '<').replace('>', '>') | ||
514 | hf.write(line) | ||
515 | hf.write('</div>\n') | ||
516 | |||
517 | if(not embedded): | ||
518 | # write the footer and close | ||
519 | aslib.addScriptCode(hf, [data]) | ||
520 | hf.write('</body>\n</html>\n') | ||
521 | else: | ||
522 | # embedded out will be loaded in a page, skip the js | ||
523 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ | ||
524 | (data.start*1000, data.initstart*1000)) | ||
525 | hf.close() | ||
526 | return True | ||
527 | |||
528 | # Function: updateCron | ||
529 | # Description: | ||
530 | # (restore=False) Set the tool to run automatically on reboot | ||
531 | # (restore=True) Restore the original crontab | ||
532 | def updateCron(restore=False): | ||
533 | if not restore: | ||
534 | sysvals.rootUser(True) | ||
535 | crondir = '/var/spool/cron/crontabs/' | ||
536 | cronfile = crondir+'root' | ||
537 | backfile = crondir+'root-analyze_boot-backup' | ||
538 | if not os.path.exists(crondir): | ||
539 | doError('%s not found' % crondir) | ||
540 | out = Popen(['which', 'crontab'], stdout=PIPE).stdout.read() | ||
541 | if not out: | ||
542 | doError('crontab not found') | ||
543 | # on restore: move the backup cron back into place | ||
544 | if restore: | ||
545 | if os.path.exists(backfile): | ||
546 | shutil.move(backfile, cronfile) | ||
547 | return | ||
548 | # backup current cron and install new one with reboot | ||
549 | if os.path.exists(cronfile): | ||
550 | shutil.move(cronfile, backfile) | ||
551 | else: | ||
552 | fp = open(backfile, 'w') | ||
553 | fp.close() | ||
554 | res = -1 | ||
555 | try: | ||
556 | fp = open(backfile, 'r') | ||
557 | op = open(cronfile, 'w') | ||
558 | for line in fp: | ||
559 | if '@reboot' not in line: | ||
560 | op.write(line) | ||
561 | continue | ||
562 | fp.close() | ||
563 | op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) | ||
564 | op.close() | ||
565 | res = call('crontab %s' % cronfile, shell=True) | ||
566 | except Exception, e: | ||
567 | print 'Exception: %s' % str(e) | ||
568 | shutil.move(backfile, cronfile) | ||
569 | res = -1 | ||
570 | if res != 0: | ||
571 | doError('crontab failed') | ||
572 | |||
573 | # Function: updateGrub | ||
574 | # Description: | ||
575 | # update grub.cfg for all kernels with our parameters | ||
576 | def updateGrub(restore=False): | ||
577 | # call update-grub on restore | ||
578 | if restore: | ||
579 | try: | ||
580 | call(['update-grub'], stderr=PIPE, stdout=PIPE, | ||
581 | env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) | ||
582 | except Exception, e: | ||
583 | print 'Exception: %s\n' % str(e) | ||
584 | return | ||
585 | # verify we can do this | ||
586 | sysvals.rootUser(True) | ||
587 | grubfile = '/etc/default/grub' | ||
588 | if not os.path.exists(grubfile): | ||
589 | print 'ERROR: Unable to set the kernel parameters via grub.\n' | ||
590 | sysvals.manualRebootRequired() | ||
591 | out = Popen(['which', 'update-grub'], stdout=PIPE).stdout.read() | ||
592 | if not out: | ||
593 | print 'ERROR: Unable to set the kernel parameters via grub.\n' | ||
594 | sysvals.manualRebootRequired() | ||
595 | |||
596 | # extract the option and create a grub config without it | ||
597 | tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' | ||
598 | cmdline = '' | ||
599 | tempfile = '/etc/default/grub.analyze_boot' | ||
600 | shutil.move(grubfile, tempfile) | ||
601 | res = -1 | ||
602 | try: | ||
603 | fp = open(tempfile, 'r') | ||
604 | op = open(grubfile, 'w') | ||
605 | cont = False | ||
606 | for line in fp: | ||
607 | line = line.strip() | ||
608 | if len(line) == 0 or line[0] == '#': | ||
609 | continue | ||
610 | opt = line.split('=')[0].strip() | ||
611 | if opt == tgtopt: | ||
612 | cmdline = line.split('=', 1)[1].strip('\\') | ||
613 | if line[-1] == '\\': | ||
614 | cont = True | ||
615 | elif cont: | ||
616 | cmdline += line.strip('\\') | ||
617 | if line[-1] != '\\': | ||
618 | cont = False | ||
619 | else: | ||
620 | op.write('%s\n' % line) | ||
621 | fp.close() | ||
622 | # if the target option value is in quotes, strip them | ||
623 | sp = '"' | ||
624 | val = cmdline.strip() | ||
625 | if val[0] == '\'' or val[0] == '"': | ||
626 | sp = val[0] | ||
627 | val = val.strip(sp) | ||
628 | cmdline = val | ||
629 | # append our cmd line options | ||
630 | if len(cmdline) > 0: | ||
631 | cmdline += ' ' | ||
632 | cmdline += sysvals.kernelParams() | ||
633 | # write out the updated target option | ||
634 | op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) | ||
635 | op.close() | ||
636 | res = call('update-grub') | ||
637 | os.remove(grubfile) | ||
638 | except Exception, e: | ||
639 | print 'Exception: %s' % str(e) | ||
640 | res = -1 | ||
641 | # cleanup | ||
642 | shutil.move(tempfile, grubfile) | ||
643 | if res != 0: | ||
644 | doError('update-grub failed') | ||
645 | |||
646 | # Function: doError | ||
647 | # Description: | ||
648 | # generic error function for catastrphic failures | ||
649 | # Arguments: | ||
650 | # msg: the error message to print | ||
651 | # help: True if printHelp should be called after, False otherwise | ||
652 | def doError(msg, help=False): | ||
653 | if help == True: | ||
654 | printHelp() | ||
655 | print 'ERROR: %s\n' % msg | ||
656 | sys.exit() | ||
657 | |||
658 | # Function: printHelp | ||
659 | # Description: | ||
660 | # print out the help text | ||
661 | def printHelp(): | ||
662 | print('') | ||
663 | print('%s v%.1f' % (sysvals.title, sysvals.version)) | ||
664 | print('Usage: bootgraph <options> <command>') | ||
665 | print('') | ||
666 | print('Description:') | ||
667 | print(' This tool reads in a dmesg log of linux kernel boot and') | ||
668 | print(' creates an html representation of the boot timeline up to') | ||
669 | print(' the start of the init process.') | ||
670 | print('') | ||
671 | print(' If no specific command is given the tool reads the current dmesg') | ||
672 | print(' and/or ftrace log and outputs bootgraph.html') | ||
673 | print('') | ||
674 | print('Options:') | ||
675 | print(' -h Print this help text') | ||
676 | print(' -v Print the current tool version') | ||
677 | print(' -addlogs Add the dmesg log to the html output') | ||
678 | print(' -o file Html timeline name (default: bootgraph.html)') | ||
679 | print(' [advanced]') | ||
680 | print(' -f Use ftrace to add function detail (default: disabled)') | ||
681 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') | ||
682 | print(' -maxdepth N limit the callgraph data to N call levels (default: 2)') | ||
683 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') | ||
684 | print(' -timeprec N Number of significant digits in timestamps (0:S, 3:ms, [6:us])') | ||
685 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') | ||
686 | print(' -filter list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') | ||
687 | print(' [commands]') | ||
688 | print(' -reboot Reboot the machine automatically and generate a new timeline') | ||
689 | print(' -manual Show the requirements to generate a new timeline manually') | ||
690 | print(' -dmesg file Load a stored dmesg file (used with -ftrace)') | ||
691 | print(' -ftrace file Load a stored ftrace file (used with -dmesg)') | ||
692 | print(' -flistall Print all functions capable of being captured in ftrace') | ||
693 | print('') | ||
694 | return True | ||
695 | |||
696 | # ----------------- MAIN -------------------- | ||
697 | # exec start (skipped if script is loaded as library) | ||
698 | if __name__ == '__main__': | ||
699 | # loop through the command line arguments | ||
700 | cmd = '' | ||
701 | simplecmds = ['-updategrub', '-flistall'] | ||
702 | args = iter(sys.argv[1:]) | ||
703 | for arg in args: | ||
704 | if(arg == '-h'): | ||
705 | printHelp() | ||
706 | sys.exit() | ||
707 | elif(arg == '-v'): | ||
708 | print("Version %.1f" % sysvals.version) | ||
709 | sys.exit() | ||
710 | elif(arg in simplecmds): | ||
711 | cmd = arg[1:] | ||
712 | elif(arg == '-f'): | ||
713 | sysvals.useftrace = True | ||
714 | elif(arg == '-callgraph'): | ||
715 | sysvals.useftrace = True | ||
716 | sysvals.usecallgraph = True | ||
717 | elif(arg == '-mincg'): | ||
718 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) | ||
719 | elif(arg == '-timeprec'): | ||
720 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) | ||
721 | elif(arg == '-maxdepth'): | ||
722 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) | ||
723 | elif(arg == '-filter'): | ||
724 | try: | ||
725 | val = args.next() | ||
726 | except: | ||
727 | doError('No filter functions supplied', True) | ||
728 | aslib.rootCheck(True) | ||
729 | sysvals.setGraphFilter(val) | ||
730 | elif(arg == '-ftrace'): | ||
731 | try: | ||
732 | val = args.next() | ||
733 | except: | ||
734 | doError('No ftrace file supplied', True) | ||
735 | if(os.path.exists(val) == False): | ||
736 | doError('%s does not exist' % val) | ||
737 | sysvals.ftracefile = val | ||
738 | elif(arg == '-addlogs'): | ||
739 | sysvals.addlogs = True | ||
740 | elif(arg == '-expandcg'): | ||
741 | sysvals.cgexp = True | ||
742 | elif(arg == '-dmesg'): | ||
743 | try: | ||
744 | val = args.next() | ||
745 | except: | ||
746 | doError('No dmesg file supplied', True) | ||
747 | if(os.path.exists(val) == False): | ||
748 | doError('%s does not exist' % val) | ||
749 | if(sysvals.htmlfile == val or sysvals.outfile == val): | ||
750 | doError('Output filename collision') | ||
751 | sysvals.dmesgfile = val | ||
752 | elif(arg == '-o'): | ||
753 | try: | ||
754 | val = args.next() | ||
755 | except: | ||
756 | doError('No HTML filename supplied', True) | ||
757 | if(sysvals.dmesgfile == val or sysvals.ftracefile == val): | ||
758 | doError('Output filename collision') | ||
759 | sysvals.htmlfile = val | ||
760 | elif(arg == '-reboot'): | ||
761 | if sysvals.iscronjob: | ||
762 | doError('-reboot and -cronjob are incompatible') | ||
763 | sysvals.reboot = True | ||
764 | elif(arg == '-manual'): | ||
765 | sysvals.reboot = True | ||
766 | sysvals.manual = True | ||
767 | # remaining options are only for cron job use | ||
768 | elif(arg == '-cronjob'): | ||
769 | sysvals.iscronjob = True | ||
770 | if sysvals.reboot: | ||
771 | doError('-reboot and -cronjob are incompatible') | ||
772 | else: | ||
773 | doError('Invalid argument: '+arg, True) | ||
774 | |||
775 | if cmd != '': | ||
776 | if cmd == 'updategrub': | ||
777 | updateGrub() | ||
778 | elif cmd == 'flistall': | ||
779 | sysvals.getFtraceFilterFunctions(False) | ||
780 | sys.exit() | ||
781 | |||
782 | # update grub, setup a cronjob, and reboot | ||
783 | if sysvals.reboot: | ||
784 | if not sysvals.manual: | ||
785 | updateGrub() | ||
786 | updateCron() | ||
787 | call('reboot') | ||
788 | else: | ||
789 | sysvals.manualRebootRequired() | ||
790 | sys.exit() | ||
791 | |||
792 | # disable the cronjob | ||
793 | if sysvals.iscronjob: | ||
794 | updateCron(True) | ||
795 | updateGrub(True) | ||
796 | |||
797 | data = loadKernelLog() | ||
798 | if sysvals.useftrace: | ||
799 | loadTraceLog(data) | ||
800 | if sysvals.iscronjob: | ||
801 | try: | ||
802 | sysvals.fsetVal('0', 'tracing_on') | ||
803 | except: | ||
804 | pass | ||
805 | |||
806 | if(sysvals.outfile and sysvals.phoronix): | ||
807 | fp = open(sysvals.outfile, 'w') | ||
808 | fp.write('pass %s initstart %.3f end %.3f boot %s\n' % | ||
809 | (data.valid, data.initstart*1000, data.end*1000, data.boottime)) | ||
810 | fp.close() | ||
811 | if(not data.valid): | ||
812 | if sysvals.dmesgfile: | ||
813 | doError('No initcall data found in %s' % sysvals.dmesgfile) | ||
814 | else: | ||
815 | doError('No initcall data found, is initcall_debug enabled?') | ||
816 | |||
817 | print(' Host: %s' % sysvals.hostname) | ||
818 | print(' Test time: %s' % sysvals.testtime) | ||
819 | print(' Boot time: %s' % data.boottime) | ||
820 | print('Kernel Version: %s' % sysvals.kernel) | ||
821 | print(' Kernel start: %.3f' % (data.start * 1000)) | ||
822 | print(' init start: %.3f' % (data.initstart * 1000)) | ||
823 | |||
824 | createBootGraph(data, sysvals.phoronix) | ||
diff --git a/scripts/analyze_suspend.py b/tools/power/pm-graph/analyze_suspend.py index 20cdb2bc1dae..a9206e67fc1f 100755 --- a/scripts/analyze_suspend.py +++ b/tools/power/pm-graph/analyze_suspend.py | |||
@@ -12,10 +12,6 @@ | |||
12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
13 | # more details. | 13 | # more details. |
14 | # | 14 | # |
15 | # You should have received a copy of the GNU General Public License along with | ||
16 | # this program; if not, write to the Free Software Foundation, Inc., | ||
17 | # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | # | ||
19 | # Authors: | 15 | # Authors: |
20 | # Todd Brandt <todd.e.brandt@linux.intel.com> | 16 | # Todd Brandt <todd.e.brandt@linux.intel.com> |
21 | # | 17 | # |
@@ -23,7 +19,7 @@ | |||
23 | # Home Page | 19 | # Home Page |
24 | # https://01.org/suspendresume | 20 | # https://01.org/suspendresume |
25 | # Source repo | 21 | # Source repo |
26 | # https://github.com/01org/suspendresume | 22 | # https://github.com/01org/pm-graph |
27 | # | 23 | # |
28 | # Description: | 24 | # Description: |
29 | # This tool is designed to assist kernel and OS developers in optimizing | 25 | # This tool is designed to assist kernel and OS developers in optimizing |
@@ -71,14 +67,16 @@ from subprocess import call, Popen, PIPE | |||
71 | # A global, single-instance container used to | 67 | # A global, single-instance container used to |
72 | # store system values and test parameters | 68 | # store system values and test parameters |
73 | class SystemValues: | 69 | class SystemValues: |
70 | title = 'SleepGraph' | ||
71 | version = '4.6' | ||
74 | ansi = False | 72 | ansi = False |
75 | version = '4.5' | ||
76 | verbose = False | 73 | verbose = False |
77 | addlogs = False | 74 | addlogs = False |
78 | mindevlen = 0.0 | 75 | mindevlen = 0.0 |
79 | mincglen = 0.0 | 76 | mincglen = 0.0 |
80 | cgphase = '' | 77 | cgphase = '' |
81 | cgtest = -1 | 78 | cgtest = -1 |
79 | max_graph_depth = 0 | ||
82 | callloopmaxgap = 0.0001 | 80 | callloopmaxgap = 0.0001 |
83 | callloopmaxlen = 0.005 | 81 | callloopmaxlen = 0.005 |
84 | srgap = 0 | 82 | srgap = 0 |
@@ -106,8 +104,8 @@ class SystemValues: | |||
106 | ftracefile = '' | 104 | ftracefile = '' |
107 | htmlfile = '' | 105 | htmlfile = '' |
108 | embedded = False | 106 | embedded = False |
109 | rtcwake = False | 107 | rtcwake = True |
110 | rtcwaketime = 10 | 108 | rtcwaketime = 15 |
111 | rtcpath = '' | 109 | rtcpath = '' |
112 | devicefilter = [] | 110 | devicefilter = [] |
113 | stamp = 0 | 111 | stamp = 0 |
@@ -235,6 +233,12 @@ class SystemValues: | |||
235 | self.rtcpath = rtc | 233 | self.rtcpath = rtc |
236 | if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()): | 234 | if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()): |
237 | self.ansi = True | 235 | self.ansi = True |
236 | def rootUser(self, fatal=False): | ||
237 | if 'USER' in os.environ and os.environ['USER'] == 'root': | ||
238 | return True | ||
239 | if fatal: | ||
240 | doError('This command must be run as root') | ||
241 | return False | ||
238 | def setPrecision(self, num): | 242 | def setPrecision(self, num): |
239 | if num < 0 or num > 6: | 243 | if num < 0 or num > 6: |
240 | return | 244 | return |
@@ -564,7 +568,7 @@ class SystemValues: | |||
564 | self.fsetVal('global', 'trace_clock') | 568 | self.fsetVal('global', 'trace_clock') |
565 | # set trace buffer to a huge value | 569 | # set trace buffer to a huge value |
566 | self.fsetVal('nop', 'current_tracer') | 570 | self.fsetVal('nop', 'current_tracer') |
567 | self.fsetVal('100000', 'buffer_size_kb') | 571 | self.fsetVal('131073', 'buffer_size_kb') |
568 | # go no further if this is just a status check | 572 | # go no further if this is just a status check |
569 | if testing: | 573 | if testing: |
570 | return | 574 | return |
@@ -583,7 +587,7 @@ class SystemValues: | |||
583 | self.fsetVal('nofuncgraph-overhead', 'trace_options') | 587 | self.fsetVal('nofuncgraph-overhead', 'trace_options') |
584 | self.fsetVal('context-info', 'trace_options') | 588 | self.fsetVal('context-info', 'trace_options') |
585 | self.fsetVal('graph-time', 'trace_options') | 589 | self.fsetVal('graph-time', 'trace_options') |
586 | self.fsetVal('0', 'max_graph_depth') | 590 | self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth') |
587 | cf = ['dpm_run_callback'] | 591 | cf = ['dpm_run_callback'] |
588 | if(self.usetraceeventsonly): | 592 | if(self.usetraceeventsonly): |
589 | cf += ['dpm_prepare', 'dpm_complete'] | 593 | cf += ['dpm_prepare', 'dpm_complete'] |
@@ -639,6 +643,12 @@ class SystemValues: | |||
639 | return '\x1B[%d;40m%s\x1B[m' % (color, str) | 643 | return '\x1B[%d;40m%s\x1B[m' % (color, str) |
640 | 644 | ||
641 | sysvals = SystemValues() | 645 | sysvals = SystemValues() |
646 | suspendmodename = { | ||
647 | 'freeze': 'Freeze (S0)', | ||
648 | 'standby': 'Standby (S1)', | ||
649 | 'mem': 'Suspend (S3)', | ||
650 | 'disk': 'Hibernate (S4)' | ||
651 | } | ||
642 | 652 | ||
643 | # Class: DevProps | 653 | # Class: DevProps |
644 | # Description: | 654 | # Description: |
@@ -1013,6 +1023,8 @@ class Data: | |||
1013 | tmp = dict() | 1023 | tmp = dict() |
1014 | for devname in list: | 1024 | for devname in list: |
1015 | dev = list[devname] | 1025 | dev = list[devname] |
1026 | if dev['length'] == 0: | ||
1027 | continue | ||
1016 | tmp[dev['start']] = devname | 1028 | tmp[dev['start']] = devname |
1017 | for t in sorted(tmp): | 1029 | for t in sorted(tmp): |
1018 | slist.append(tmp[t]) | 1030 | slist.append(tmp[t]) |
@@ -1477,12 +1489,14 @@ class FTraceLine: | |||
1477 | # Each instance is tied to a single device in a single phase, and is | 1489 | # Each instance is tied to a single device in a single phase, and is |
1478 | # comprised of an ordered list of FTraceLine objects | 1490 | # comprised of an ordered list of FTraceLine objects |
1479 | class FTraceCallGraph: | 1491 | class FTraceCallGraph: |
1492 | id = '' | ||
1480 | start = -1.0 | 1493 | start = -1.0 |
1481 | end = -1.0 | 1494 | end = -1.0 |
1482 | list = [] | 1495 | list = [] |
1483 | invalid = False | 1496 | invalid = False |
1484 | depth = 0 | 1497 | depth = 0 |
1485 | pid = 0 | 1498 | pid = 0 |
1499 | name = '' | ||
1486 | def __init__(self, pid): | 1500 | def __init__(self, pid): |
1487 | self.start = -1.0 | 1501 | self.start = -1.0 |
1488 | self.end = -1.0 | 1502 | self.end = -1.0 |
@@ -1631,9 +1645,17 @@ class FTraceCallGraph: | |||
1631 | return True | 1645 | return True |
1632 | return False | 1646 | return False |
1633 | def postProcess(self, debug=False): | 1647 | def postProcess(self, debug=False): |
1648 | if len(self.list) > 0: | ||
1649 | self.name = self.list[0].name | ||
1634 | stack = dict() | 1650 | stack = dict() |
1635 | cnt = 0 | 1651 | cnt = 0 |
1652 | last = 0 | ||
1636 | for l in self.list: | 1653 | for l in self.list: |
1654 | # ftrace bug: reported duration is not reliable | ||
1655 | # check each leaf and clip it at max possible length | ||
1656 | if(last and last.freturn and last.fcall): | ||
1657 | if last.length > l.time - last.time: | ||
1658 | last.length = l.time - last.time | ||
1637 | if(l.fcall and not l.freturn): | 1659 | if(l.fcall and not l.freturn): |
1638 | stack[l.depth] = l | 1660 | stack[l.depth] = l |
1639 | cnt += 1 | 1661 | cnt += 1 |
@@ -1643,11 +1665,12 @@ class FTraceCallGraph: | |||
1643 | print 'Post Process Error: Depth missing' | 1665 | print 'Post Process Error: Depth missing' |
1644 | l.debugPrint() | 1666 | l.debugPrint() |
1645 | return False | 1667 | return False |
1646 | # transfer total time from return line to call line | 1668 | # calculate call length from call/return lines |
1647 | stack[l.depth].length = l.length | 1669 | stack[l.depth].length = l.time - stack[l.depth].time |
1648 | stack.pop(l.depth) | 1670 | stack.pop(l.depth) |
1649 | l.length = 0 | 1671 | l.length = 0 |
1650 | cnt -= 1 | 1672 | cnt -= 1 |
1673 | last = l | ||
1651 | if(cnt == 0): | 1674 | if(cnt == 0): |
1652 | # trace caught the whole call tree | 1675 | # trace caught the whole call tree |
1653 | return True | 1676 | return True |
@@ -1664,8 +1687,8 @@ class FTraceCallGraph: | |||
1664 | 'dpm_prepare': 'suspend_prepare', | 1687 | 'dpm_prepare': 'suspend_prepare', |
1665 | 'dpm_complete': 'resume_complete' | 1688 | 'dpm_complete': 'resume_complete' |
1666 | } | 1689 | } |
1667 | if(self.list[0].name in borderphase): | 1690 | if(self.name in borderphase): |
1668 | p = borderphase[self.list[0].name] | 1691 | p = borderphase[self.name] |
1669 | list = data.dmesg[p]['list'] | 1692 | list = data.dmesg[p]['list'] |
1670 | for devname in list: | 1693 | for devname in list: |
1671 | dev = list[devname] | 1694 | dev = list[devname] |
@@ -1690,7 +1713,7 @@ class FTraceCallGraph: | |||
1690 | break | 1713 | break |
1691 | return found | 1714 | return found |
1692 | def newActionFromFunction(self, data): | 1715 | def newActionFromFunction(self, data): |
1693 | name = self.list[0].name | 1716 | name = self.name |
1694 | if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']: | 1717 | if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']: |
1695 | return | 1718 | return |
1696 | fs = self.start | 1719 | fs = self.start |
@@ -1710,7 +1733,7 @@ class FTraceCallGraph: | |||
1710 | phase, myname = out | 1733 | phase, myname = out |
1711 | data.dmesg[phase]['list'][myname]['ftrace'] = self | 1734 | data.dmesg[phase]['list'][myname]['ftrace'] = self |
1712 | def debugPrint(self): | 1735 | def debugPrint(self): |
1713 | print('[%f - %f] %s (%d)') % (self.start, self.end, self.list[0].name, self.pid) | 1736 | print('[%f - %f] %s (%d)') % (self.start, self.end, self.name, self.pid) |
1714 | for l in self.list: | 1737 | for l in self.list: |
1715 | if(l.freturn and l.fcall): | 1738 | if(l.freturn and l.fcall): |
1716 | print('%f (%02d): %s(); (%.3f us)' % (l.time, \ | 1739 | print('%f (%02d): %s(); (%.3f us)' % (l.time, \ |
@@ -1738,7 +1761,7 @@ class DevItem: | |||
1738 | # A container for a device timeline which calculates | 1761 | # A container for a device timeline which calculates |
1739 | # all the html properties to display it correctly | 1762 | # all the html properties to display it correctly |
1740 | class Timeline: | 1763 | class Timeline: |
1741 | html = {} | 1764 | html = '' |
1742 | height = 0 # total timeline height | 1765 | height = 0 # total timeline height |
1743 | scaleH = 20 # timescale (top) row height | 1766 | scaleH = 20 # timescale (top) row height |
1744 | rowH = 30 # device row height | 1767 | rowH = 30 # device row height |
@@ -1746,14 +1769,28 @@ class Timeline: | |||
1746 | rows = 0 # total timeline rows | 1769 | rows = 0 # total timeline rows |
1747 | rowlines = dict() | 1770 | rowlines = dict() |
1748 | rowheight = dict() | 1771 | rowheight = dict() |
1772 | html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n' | ||
1773 | html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n' | ||
1774 | html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n' | ||
1775 | html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n' | ||
1749 | def __init__(self, rowheight, scaleheight): | 1776 | def __init__(self, rowheight, scaleheight): |
1750 | self.rowH = rowheight | 1777 | self.rowH = rowheight |
1751 | self.scaleH = scaleheight | 1778 | self.scaleH = scaleheight |
1752 | self.html = { | 1779 | self.html = '' |
1753 | 'header': '', | 1780 | def createHeader(self, sv, suppress=''): |
1754 | 'timeline': '', | 1781 | if(not sv.stamp['time']): |
1755 | 'legend': '', | 1782 | return |
1756 | } | 1783 | self.html += '<div class="version"><a href="https://01.org/suspendresume">%s v%s</a></div>' \ |
1784 | % (sv.title, sv.version) | ||
1785 | if sv.logmsg and 'log' not in suppress: | ||
1786 | self.html += '<button id="showtest" class="logbtn">log</button>' | ||
1787 | if sv.addlogs and 'dmesg' not in suppress: | ||
1788 | self.html += '<button id="showdmesg" class="logbtn">dmesg</button>' | ||
1789 | if sv.addlogs and sv.ftracefile and 'ftrace' not in suppress: | ||
1790 | self.html += '<button id="showftrace" class="logbtn">ftrace</button>' | ||
1791 | headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n' | ||
1792 | self.html += headline_stamp.format(sv.stamp['host'], sv.stamp['kernel'], | ||
1793 | sv.stamp['mode'], sv.stamp['time']) | ||
1757 | # Function: getDeviceRows | 1794 | # Function: getDeviceRows |
1758 | # Description: | 1795 | # Description: |
1759 | # determine how may rows the device funcs will take | 1796 | # determine how may rows the device funcs will take |
@@ -1880,10 +1917,8 @@ class Timeline: | |||
1880 | break | 1917 | break |
1881 | top += self.rowheight[test][phase][i] | 1918 | top += self.rowheight[test][phase][i] |
1882 | return top | 1919 | return top |
1883 | # Function: calcTotalRows | ||
1884 | # Description: | ||
1885 | # Calculate the heights and offsets for the header and rows | ||
1886 | def calcTotalRows(self): | 1920 | def calcTotalRows(self): |
1921 | # Calculate the heights and offsets for the header and rows | ||
1887 | maxrows = 0 | 1922 | maxrows = 0 |
1888 | standardphases = [] | 1923 | standardphases = [] |
1889 | for t in self.rowlines: | 1924 | for t in self.rowlines: |
@@ -1901,6 +1936,20 @@ class Timeline: | |||
1901 | for t, p in standardphases: | 1936 | for t, p in standardphases: |
1902 | for i in sorted(self.rowheight[t][p]): | 1937 | for i in sorted(self.rowheight[t][p]): |
1903 | self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p]) | 1938 | self.rowheight[t][p][i] = self.bodyH/len(self.rowlines[t][p]) |
1939 | def createZoomBox(self, mode='command', testcount=1): | ||
1940 | # Create bounding box, add buttons | ||
1941 | html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n' | ||
1942 | html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n' | ||
1943 | html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>' | ||
1944 | html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n' | ||
1945 | if mode != 'command': | ||
1946 | if testcount > 1: | ||
1947 | self.html += html_devlist2 | ||
1948 | self.html += html_devlist1.format('1') | ||
1949 | else: | ||
1950 | self.html += html_devlist1.format('') | ||
1951 | self.html += html_zoombox | ||
1952 | self.html += html_timeline.format('dmesg', self.height) | ||
1904 | # Function: createTimeScale | 1953 | # Function: createTimeScale |
1905 | # Description: | 1954 | # Description: |
1906 | # Create the timescale for a timeline block | 1955 | # Create the timescale for a timeline block |
@@ -1913,7 +1962,7 @@ class Timeline: | |||
1913 | # The html code needed to display the time scale | 1962 | # The html code needed to display the time scale |
1914 | def createTimeScale(self, m0, mMax, tTotal, mode): | 1963 | def createTimeScale(self, m0, mMax, tTotal, mode): |
1915 | timescale = '<div class="t" style="right:{0}%">{1}</div>\n' | 1964 | timescale = '<div class="t" style="right:{0}%">{1}</div>\n' |
1916 | rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">Resume</div>\n' | 1965 | rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n' |
1917 | output = '<div class="timescale">\n' | 1966 | output = '<div class="timescale">\n' |
1918 | # set scale for timeline | 1967 | # set scale for timeline |
1919 | mTotal = mMax - m0 | 1968 | mTotal = mMax - m0 |
@@ -1926,21 +1975,20 @@ class Timeline: | |||
1926 | divEdge = (mTotal - tS*(divTotal-1))*100/mTotal | 1975 | divEdge = (mTotal - tS*(divTotal-1))*100/mTotal |
1927 | for i in range(divTotal): | 1976 | for i in range(divTotal): |
1928 | htmlline = '' | 1977 | htmlline = '' |
1929 | if(mode == 'resume'): | 1978 | if(mode == 'suspend'): |
1930 | pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal)) | ||
1931 | val = '%0.fms' % (float(i)*tS*1000) | ||
1932 | htmlline = timescale.format(pos, val) | ||
1933 | if(i == 0): | ||
1934 | htmlline = rline | ||
1935 | else: | ||
1936 | pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge) | 1979 | pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge) |
1937 | val = '%0.fms' % (float(i-divTotal+1)*tS*1000) | 1980 | val = '%0.fms' % (float(i-divTotal+1)*tS*1000) |
1938 | if(i == divTotal - 1): | 1981 | if(i == divTotal - 1): |
1939 | val = 'Suspend' | 1982 | val = mode |
1983 | htmlline = timescale.format(pos, val) | ||
1984 | else: | ||
1985 | pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal)) | ||
1986 | val = '%0.fms' % (float(i)*tS*1000) | ||
1940 | htmlline = timescale.format(pos, val) | 1987 | htmlline = timescale.format(pos, val) |
1988 | if(i == 0): | ||
1989 | htmlline = rline.format(mode) | ||
1941 | output += htmlline | 1990 | output += htmlline |
1942 | output += '</div>\n' | 1991 | self.html += output+'</div>\n' |
1943 | return output | ||
1944 | 1992 | ||
1945 | # Class: TestProps | 1993 | # Class: TestProps |
1946 | # Description: | 1994 | # Description: |
@@ -2009,7 +2057,7 @@ class ProcessMonitor: | |||
2009 | val['kern'] = kern | 2057 | val['kern'] = kern |
2010 | if ujiff > 0 or kjiff > 0: | 2058 | if ujiff > 0 or kjiff > 0: |
2011 | running[pid] = ujiff + kjiff | 2059 | running[pid] = ujiff + kjiff |
2012 | result = process.wait() | 2060 | process.wait() |
2013 | out = '' | 2061 | out = '' |
2014 | for pid in running: | 2062 | for pid in running: |
2015 | jiffies = running[pid] | 2063 | jiffies = running[pid] |
@@ -2071,26 +2119,6 @@ def parseStamp(line, data): | |||
2071 | if not sysvals.stamp: | 2119 | if not sysvals.stamp: |
2072 | sysvals.stamp = data.stamp | 2120 | sysvals.stamp = data.stamp |
2073 | 2121 | ||
2074 | # Function: diffStamp | ||
2075 | # Description: | ||
2076 | # compare the host, kernel, and mode fields in 3 stamps | ||
2077 | # Arguments: | ||
2078 | # stamp1: string array with mode, kernel, and host | ||
2079 | # stamp2: string array with mode, kernel, and host | ||
2080 | # Return: | ||
2081 | # True if stamps differ, False if they're the same | ||
2082 | def diffStamp(stamp1, stamp2): | ||
2083 | if 'host' in stamp1 and 'host' in stamp2: | ||
2084 | if stamp1['host'] != stamp2['host']: | ||
2085 | return True | ||
2086 | if 'kernel' in stamp1 and 'kernel' in stamp2: | ||
2087 | if stamp1['kernel'] != stamp2['kernel']: | ||
2088 | return True | ||
2089 | if 'mode' in stamp1 and 'mode' in stamp2: | ||
2090 | if stamp1['mode'] != stamp2['mode']: | ||
2091 | return True | ||
2092 | return False | ||
2093 | |||
2094 | # Function: doesTraceLogHaveTraceEvents | 2122 | # Function: doesTraceLogHaveTraceEvents |
2095 | # Description: | 2123 | # Description: |
2096 | # Quickly determine if the ftrace log has some or all of the trace events | 2124 | # Quickly determine if the ftrace log has some or all of the trace events |
@@ -2722,7 +2750,7 @@ def parseTraceLog(): | |||
2722 | # create blocks for orphan cg data | 2750 | # create blocks for orphan cg data |
2723 | for sortkey in sorted(sortlist): | 2751 | for sortkey in sorted(sortlist): |
2724 | cg = sortlist[sortkey] | 2752 | cg = sortlist[sortkey] |
2725 | name = cg.list[0].name | 2753 | name = cg.name |
2726 | if sysvals.isCallgraphFunc(name): | 2754 | if sysvals.isCallgraphFunc(name): |
2727 | vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name)) | 2755 | vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name)) |
2728 | cg.newActionFromFunction(data) | 2756 | cg.newActionFromFunction(data) |
@@ -3100,149 +3128,154 @@ def parseKernelLog(data): | |||
3100 | data.fixupInitcallsThatDidntReturn() | 3128 | data.fixupInitcallsThatDidntReturn() |
3101 | return True | 3129 | return True |
3102 | 3130 | ||
3131 | def callgraphHTML(sv, hf, num, cg, title, color, devid): | ||
3132 | html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n' | ||
3133 | html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n' | ||
3134 | html_func_end = '</article>\n' | ||
3135 | html_func_leaf = '<article>{0} {1}</article>\n' | ||
3136 | |||
3137 | cgid = devid | ||
3138 | if cg.id: | ||
3139 | cgid += cg.id | ||
3140 | cglen = (cg.end - cg.start) * 1000 | ||
3141 | if cglen < sv.mincglen: | ||
3142 | return num | ||
3143 | |||
3144 | fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>' | ||
3145 | flen = fmt % (cglen, cg.start, cg.end) | ||
3146 | hf.write(html_func_top.format(cgid, color, num, title, flen)) | ||
3147 | num += 1 | ||
3148 | for line in cg.list: | ||
3149 | if(line.length < 0.000000001): | ||
3150 | flen = '' | ||
3151 | else: | ||
3152 | fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>' | ||
3153 | flen = fmt % (line.length*1000, line.time) | ||
3154 | if(line.freturn and line.fcall): | ||
3155 | hf.write(html_func_leaf.format(line.name, flen)) | ||
3156 | elif(line.freturn): | ||
3157 | hf.write(html_func_end) | ||
3158 | else: | ||
3159 | hf.write(html_func_start.format(num, line.name, flen)) | ||
3160 | num += 1 | ||
3161 | hf.write(html_func_end) | ||
3162 | return num | ||
3163 | |||
3164 | def addCallgraphs(sv, hf, data): | ||
3165 | hf.write('<section id="callgraphs" class="callgraph">\n') | ||
3166 | # write out the ftrace data converted to html | ||
3167 | num = 0 | ||
3168 | for p in data.phases: | ||
3169 | if sv.cgphase and p != sv.cgphase: | ||
3170 | continue | ||
3171 | list = data.dmesg[p]['list'] | ||
3172 | for devname in data.sortedDevices(p): | ||
3173 | dev = list[devname] | ||
3174 | color = 'white' | ||
3175 | if 'color' in data.dmesg[p]: | ||
3176 | color = data.dmesg[p]['color'] | ||
3177 | if 'color' in dev: | ||
3178 | color = dev['color'] | ||
3179 | name = devname | ||
3180 | if(devname in sv.devprops): | ||
3181 | name = sv.devprops[devname].altName(devname) | ||
3182 | if sv.suspendmode in suspendmodename: | ||
3183 | name += ' '+p | ||
3184 | if('ftrace' in dev): | ||
3185 | cg = dev['ftrace'] | ||
3186 | num = callgraphHTML(sv, hf, num, cg, | ||
3187 | name, color, dev['id']) | ||
3188 | if('ftraces' in dev): | ||
3189 | for cg in dev['ftraces']: | ||
3190 | num = callgraphHTML(sv, hf, num, cg, | ||
3191 | name+' → '+cg.name, color, dev['id']) | ||
3192 | |||
3193 | hf.write('\n\n </section>\n') | ||
3194 | |||
3103 | # Function: createHTMLSummarySimple | 3195 | # Function: createHTMLSummarySimple |
3104 | # Description: | 3196 | # Description: |
3105 | # Create summary html file for a series of tests | 3197 | # Create summary html file for a series of tests |
3106 | # Arguments: | 3198 | # Arguments: |
3107 | # testruns: array of Data objects from parseTraceLog | 3199 | # testruns: array of Data objects from parseTraceLog |
3108 | def createHTMLSummarySimple(testruns, htmlfile): | 3200 | def createHTMLSummarySimple(testruns, htmlfile, folder): |
3109 | # print out the basic summary of all the tests | ||
3110 | hf = open(htmlfile, 'w') | ||
3111 | |||
3112 | # write the html header first (html head, css code, up to body start) | 3201 | # write the html header first (html head, css code, up to body start) |
3113 | html = '<!DOCTYPE html>\n<html>\n<head>\n\ | 3202 | html = '<!DOCTYPE html>\n<html>\n<head>\n\ |
3114 | <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ | 3203 | <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ |
3115 | <title>AnalyzeSuspend Summary</title>\n\ | 3204 | <title>SleepGraph Summary</title>\n\ |
3116 | <style type=\'text/css\'>\n\ | 3205 | <style type=\'text/css\'>\n\ |
3117 | body {overflow-y: scroll;}\n\ | 3206 | .stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\ |
3118 | .stamp {width: 100%;text-align:center;background-color:#495E09;line-height:30px;color:white;font: 25px Arial;}\n\ | ||
3119 | table {width:100%;border-collapse: collapse;}\n\ | 3207 | table {width:100%;border-collapse: collapse;}\n\ |
3120 | .summary {font: 22px Arial;border:1px solid;}\n\ | 3208 | .summary {border:1px solid;}\n\ |
3121 | th {border: 1px solid black;background-color:#A7C942;color:white;}\n\ | 3209 | th {border: 1px solid black;background:#222;color:white;}\n\ |
3122 | td {text-align: center;}\n\ | 3210 | td {font: 16px "Times New Roman";text-align: center;}\n\ |
3123 | tr.alt td {background-color:#EAF2D3;}\n\ | 3211 | tr.alt td {background:#ddd;}\n\ |
3124 | tr.avg td {background-color:#BDE34C;}\n\ | 3212 | tr.avg td {background:#aaa;}\n\ |
3125 | a:link {color: #90B521;}\n\ | ||
3126 | a:visited {color: #495E09;}\n\ | ||
3127 | a:hover {color: #B1DF28;}\n\ | ||
3128 | a:active {color: #FFFFFF;}\n\ | ||
3129 | </style>\n</head>\n<body>\n' | 3213 | </style>\n</head>\n<body>\n' |
3130 | 3214 | ||
3131 | # group test header | 3215 | # group test header |
3132 | count = len(testruns) | 3216 | html += '<div class="stamp">%s (%d tests)</div>\n' % (folder, len(testruns)) |
3133 | headline_stamp = '<div class="stamp">{0} {1} {2} {3} ({4} tests)</div>\n' | ||
3134 | html += headline_stamp.format(sysvals.stamp['host'], | ||
3135 | sysvals.stamp['kernel'], sysvals.stamp['mode'], | ||
3136 | sysvals.stamp['time'], count) | ||
3137 | |||
3138 | # check to see if all the tests have the same value | ||
3139 | stampcolumns = False | ||
3140 | for data in testruns: | ||
3141 | if diffStamp(sysvals.stamp, data.stamp): | ||
3142 | stampcolumns = True | ||
3143 | break | ||
3144 | |||
3145 | th = '\t<th>{0}</th>\n' | 3217 | th = '\t<th>{0}</th>\n' |
3146 | td = '\t<td>{0}</td>\n' | 3218 | td = '\t<td>{0}</td>\n' |
3147 | tdlink = '\t<td><a href="{0}">Click Here</a></td>\n' | 3219 | tdlink = '\t<td><a href="{0}">html</a></td>\n' |
3148 | 3220 | ||
3149 | # table header | 3221 | # table header |
3150 | html += '<table class="summary">\n<tr>\n' | 3222 | html += '<table class="summary">\n<tr>\n' + th.format('#') +\ |
3151 | html += th.format("Test #") | 3223 | th.format('Mode') + th.format('Host') + th.format('Kernel') +\ |
3152 | if stampcolumns: | 3224 | th.format('Test Time') + th.format('Suspend') + th.format('Resume') +\ |
3153 | html += th.format("Hostname") | 3225 | th.format('Detail') + '</tr>\n' |
3154 | html += th.format("Kernel Version") | ||
3155 | html += th.format("Suspend Mode") | ||
3156 | html += th.format("Test Time") | ||
3157 | html += th.format("Suspend Time") | ||
3158 | html += th.format("Resume Time") | ||
3159 | html += th.format("Detail") | ||
3160 | html += '</tr>\n' | ||
3161 | 3226 | ||
3162 | # test data, 1 row per test | 3227 | # test data, 1 row per test |
3163 | sTimeAvg = 0.0 | 3228 | avg = '<tr class="avg"><td></td><td></td><td></td><td></td>'+\ |
3164 | rTimeAvg = 0.0 | 3229 | '<td>Average of {0} {1} tests</td><td>{2}</td><td>{3}</td><td></td></tr>\n' |
3165 | num = 1 | 3230 | sTimeAvg = rTimeAvg = 0.0 |
3166 | for data in testruns: | 3231 | mode = '' |
3167 | # data.end is the end of post_resume | 3232 | num = 0 |
3168 | resumeEnd = data.dmesg['resume_complete']['end'] | 3233 | for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'])): |
3234 | if mode != data['mode']: | ||
3235 | # test average line | ||
3236 | if(num > 0): | ||
3237 | sTimeAvg /= (num - 1) | ||
3238 | rTimeAvg /= (num - 1) | ||
3239 | html += avg.format('%d' % (num - 1), mode, | ||
3240 | '%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg) | ||
3241 | sTimeAvg = rTimeAvg = 0.0 | ||
3242 | mode = data['mode'] | ||
3243 | num = 1 | ||
3244 | # alternate row color | ||
3169 | if num % 2 == 1: | 3245 | if num % 2 == 1: |
3170 | html += '<tr class="alt">\n' | 3246 | html += '<tr class="alt">\n' |
3171 | else: | 3247 | else: |
3172 | html += '<tr>\n' | 3248 | html += '<tr>\n' |
3173 | 3249 | html += td.format("%d" % num) | |
3174 | # test num | ||
3175 | html += td.format("test %d" % num) | ||
3176 | num += 1 | 3250 | num += 1 |
3177 | if stampcolumns: | 3251 | # basic info |
3178 | # host name | 3252 | for item in ['mode', 'host', 'kernel', 'time']: |
3179 | val = "unknown" | ||
3180 | if('host' in data.stamp): | ||
3181 | val = data.stamp['host'] | ||
3182 | html += td.format(val) | ||
3183 | # host kernel | ||
3184 | val = "unknown" | 3253 | val = "unknown" |
3185 | if('kernel' in data.stamp): | 3254 | if(item in data): |
3186 | val = data.stamp['kernel'] | 3255 | val = data[item] |
3187 | html += td.format(val) | 3256 | html += td.format(val) |
3188 | # suspend mode | ||
3189 | val = "unknown" | ||
3190 | if('mode' in data.stamp): | ||
3191 | val = data.stamp['mode'] | ||
3192 | html += td.format(val) | ||
3193 | # test time | ||
3194 | val = "unknown" | ||
3195 | if('time' in data.stamp): | ||
3196 | val = data.stamp['time'] | ||
3197 | html += td.format(val) | ||
3198 | # suspend time | 3257 | # suspend time |
3199 | sTime = (data.tSuspended - data.start)*1000 | 3258 | sTime = float(data['suspend']) |
3200 | sTimeAvg += sTime | 3259 | sTimeAvg += sTime |
3201 | html += td.format("%3.3f ms" % sTime) | 3260 | html += td.format('%.3f ms' % sTime) |
3202 | # resume time | 3261 | # resume time |
3203 | rTime = (resumeEnd - data.tResumed)*1000 | 3262 | rTime = float(data['resume']) |
3204 | rTimeAvg += rTime | 3263 | rTimeAvg += rTime |
3205 | html += td.format("%3.3f ms" % rTime) | 3264 | html += td.format('%.3f ms' % rTime) |
3206 | # link to the output html | 3265 | # link to the output html |
3207 | html += tdlink.format(data.outfile) | 3266 | html += tdlink.format(data['url']) + '</tr>\n' |
3208 | 3267 | # last test average line | |
3209 | html += '</tr>\n' | 3268 | if(num > 0): |
3210 | 3269 | sTimeAvg /= (num - 1) | |
3211 | # last line: test average | 3270 | rTimeAvg /= (num - 1) |
3212 | if(count > 0): | 3271 | html += avg.format('%d' % (num - 1), mode, |
3213 | sTimeAvg /= count | 3272 | '%3.3f ms' % sTimeAvg, '%3.3f ms' % rTimeAvg) |
3214 | rTimeAvg /= count | ||
3215 | html += '<tr class="avg">\n' | ||
3216 | html += td.format('Average') # name | ||
3217 | if stampcolumns: | ||
3218 | html += td.format('') # host | ||
3219 | html += td.format('') # kernel | ||
3220 | html += td.format('') # mode | ||
3221 | html += td.format('') # time | ||
3222 | html += td.format("%3.3f ms" % sTimeAvg) # suspend time | ||
3223 | html += td.format("%3.3f ms" % rTimeAvg) # resume time | ||
3224 | html += td.format('') # output link | ||
3225 | html += '</tr>\n' | ||
3226 | 3273 | ||
3227 | # flush the data to file | 3274 | # flush the data to file |
3228 | hf.write(html+'</table>\n') | 3275 | hf = open(htmlfile, 'w') |
3229 | hf.write('</body>\n</html>\n') | 3276 | hf.write(html+'</table>\n</body>\n</html>\n') |
3230 | hf.close() | 3277 | hf.close() |
3231 | 3278 | ||
3232 | def htmlTitle(): | ||
3233 | modename = { | ||
3234 | 'freeze': 'Freeze (S0)', | ||
3235 | 'standby': 'Standby (S1)', | ||
3236 | 'mem': 'Suspend (S3)', | ||
3237 | 'disk': 'Hibernate (S4)' | ||
3238 | } | ||
3239 | kernel = sysvals.stamp['kernel'] | ||
3240 | host = sysvals.hostname[0].upper()+sysvals.hostname[1:] | ||
3241 | mode = sysvals.suspendmode | ||
3242 | if sysvals.suspendmode in modename: | ||
3243 | mode = modename[sysvals.suspendmode] | ||
3244 | return host+' '+mode+' '+kernel | ||
3245 | |||
3246 | def ordinal(value): | 3279 | def ordinal(value): |
3247 | suffix = 'th' | 3280 | suffix = 'th' |
3248 | if value < 10 or value > 19: | 3281 | if value < 10 or value > 19: |
@@ -3272,24 +3305,11 @@ def createHTML(testruns): | |||
3272 | kerror = True | 3305 | kerror = True |
3273 | data.normalizeTime(testruns[-1].tSuspended) | 3306 | data.normalizeTime(testruns[-1].tSuspended) |
3274 | 3307 | ||
3275 | x2changes = ['', 'absolute'] | ||
3276 | if len(testruns) > 1: | ||
3277 | x2changes = ['1', 'relative'] | ||
3278 | # html function templates | 3308 | # html function templates |
3279 | headline_version = '<div class="version"><a href="https://01.org/suspendresume">AnalyzeSuspend v%s</a></div>' % sysvals.version | ||
3280 | headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n' | ||
3281 | html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail%s</button>' % x2changes[0] | ||
3282 | html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n' | ||
3283 | html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n' | ||
3284 | html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n' | ||
3285 | html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n' | ||
3286 | html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n' | ||
3287 | html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">ERROR→</div>\n' | 3309 | html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">ERROR→</div>\n' |
3288 | html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n' | 3310 | html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n' |
3289 | html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n' | 3311 | html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n' |
3290 | html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background-color:{4}">{5}</div>\n' | 3312 | html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}"> {2}</div>\n' |
3291 | html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n' | ||
3292 | html_legend = '<div id="p{3}" class="square" style="left:{0}%;background-color:{1}"> {2}</div>\n' | ||
3293 | html_timetotal = '<table class="time1">\n<tr>'\ | 3313 | html_timetotal = '<table class="time1">\n<tr>'\ |
3294 | '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\ | 3314 | '<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\ |
3295 | '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\ | 3315 | '<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\ |
@@ -3311,20 +3331,18 @@ def createHTML(testruns): | |||
3311 | '</tr>\n</table>\n' | 3331 | '</tr>\n</table>\n' |
3312 | 3332 | ||
3313 | # html format variables | 3333 | # html format variables |
3314 | hoverZ = 'z-index:8;' | ||
3315 | if sysvals.usedevsrc: | ||
3316 | hoverZ = '' | ||
3317 | scaleH = 20 | 3334 | scaleH = 20 |
3318 | scaleTH = 20 | ||
3319 | if kerror: | 3335 | if kerror: |
3320 | scaleH = 40 | 3336 | scaleH = 40 |
3321 | scaleTH = 60 | ||
3322 | 3337 | ||
3323 | # device timeline | 3338 | # device timeline |
3324 | vprint('Creating Device Timeline...') | 3339 | vprint('Creating Device Timeline...') |
3325 | 3340 | ||
3326 | devtl = Timeline(30, scaleH) | 3341 | devtl = Timeline(30, scaleH) |
3327 | 3342 | ||
3343 | # write the test title and general info header | ||
3344 | devtl.createHeader(sysvals) | ||
3345 | |||
3328 | # Generate the header for this timeline | 3346 | # Generate the header for this timeline |
3329 | for data in testruns: | 3347 | for data in testruns: |
3330 | tTotal = data.end - data.start | 3348 | tTotal = data.end - data.start |
@@ -3346,7 +3364,7 @@ def createHTML(testruns): | |||
3346 | if(len(testruns) > 1): | 3364 | if(len(testruns) > 1): |
3347 | testdesc = ordinal(data.testnumber+1)+' '+testdesc | 3365 | testdesc = ordinal(data.testnumber+1)+' '+testdesc |
3348 | thtml = html_timetotal3.format(run_time, testdesc) | 3366 | thtml = html_timetotal3.format(run_time, testdesc) |
3349 | devtl.html['header'] += thtml | 3367 | devtl.html += thtml |
3350 | elif data.fwValid: | 3368 | elif data.fwValid: |
3351 | suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0)) | 3369 | suspend_time = '%.0f'%(sktime + (data.fwSuspend/1000000.0)) |
3352 | resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0)) | 3370 | resume_time = '%.0f'%(rktime + (data.fwResume/1000000.0)) |
@@ -3363,10 +3381,10 @@ def createHTML(testruns): | |||
3363 | else: | 3381 | else: |
3364 | thtml = html_timetotal2.format(suspend_time, low_time, \ | 3382 | thtml = html_timetotal2.format(suspend_time, low_time, \ |
3365 | resume_time, testdesc1, stitle, rtitle) | 3383 | resume_time, testdesc1, stitle, rtitle) |
3366 | devtl.html['header'] += thtml | 3384 | devtl.html += thtml |
3367 | sftime = '%.3f'%(data.fwSuspend / 1000000.0) | 3385 | sftime = '%.3f'%(data.fwSuspend / 1000000.0) |
3368 | rftime = '%.3f'%(data.fwResume / 1000000.0) | 3386 | rftime = '%.3f'%(data.fwResume / 1000000.0) |
3369 | devtl.html['header'] += html_timegroups.format('%.3f'%sktime, \ | 3387 | devtl.html += html_timegroups.format('%.3f'%sktime, \ |
3370 | sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode) | 3388 | sftime, rftime, '%.3f'%rktime, testdesc2, sysvals.suspendmode) |
3371 | else: | 3389 | else: |
3372 | suspend_time = '%.3f' % sktime | 3390 | suspend_time = '%.3f' % sktime |
@@ -3382,7 +3400,7 @@ def createHTML(testruns): | |||
3382 | else: | 3400 | else: |
3383 | thtml = html_timetotal2.format(suspend_time, low_time, \ | 3401 | thtml = html_timetotal2.format(suspend_time, low_time, \ |
3384 | resume_time, testdesc, stitle, rtitle) | 3402 | resume_time, testdesc, stitle, rtitle) |
3385 | devtl.html['header'] += thtml | 3403 | devtl.html += thtml |
3386 | 3404 | ||
3387 | # time scale for potentially multiple datasets | 3405 | # time scale for potentially multiple datasets |
3388 | t0 = testruns[0].start | 3406 | t0 = testruns[0].start |
@@ -3429,15 +3447,8 @@ def createHTML(testruns): | |||
3429 | devtl.getPhaseRows(threadlist, devtl.rows) | 3447 | devtl.getPhaseRows(threadlist, devtl.rows) |
3430 | devtl.calcTotalRows() | 3448 | devtl.calcTotalRows() |
3431 | 3449 | ||
3432 | # create bounding box, add buttons | ||
3433 | if sysvals.suspendmode != 'command': | ||
3434 | devtl.html['timeline'] += html_devlist1 | ||
3435 | if len(testruns) > 1: | ||
3436 | devtl.html['timeline'] += html_devlist2 | ||
3437 | devtl.html['timeline'] += html_zoombox | ||
3438 | devtl.html['timeline'] += html_timeline.format('dmesg', devtl.height) | ||
3439 | |||
3440 | # draw the full timeline | 3450 | # draw the full timeline |
3451 | devtl.createZoomBox(sysvals.suspendmode, len(testruns)) | ||
3441 | phases = {'suspend':[],'resume':[]} | 3452 | phases = {'suspend':[],'resume':[]} |
3442 | for phase in data.dmesg: | 3453 | for phase in data.dmesg: |
3443 | if 'resume' in phase: | 3454 | if 'resume' in phase: |
@@ -3452,37 +3463,36 @@ def createHTML(testruns): | |||
3452 | # draw suspend and resume blocks separately | 3463 | # draw suspend and resume blocks separately |
3453 | bname = '%s%d' % (dir[0], data.testnumber) | 3464 | bname = '%s%d' % (dir[0], data.testnumber) |
3454 | if dir == 'suspend': | 3465 | if dir == 'suspend': |
3455 | m0 = testruns[data.testnumber].start | 3466 | m0 = data.start |
3456 | mMax = testruns[data.testnumber].tSuspended | 3467 | mMax = data.tSuspended |
3457 | mTotal = mMax - m0 | ||
3458 | left = '%f' % (((m0-t0)*100.0)/tTotal) | 3468 | left = '%f' % (((m0-t0)*100.0)/tTotal) |
3459 | else: | 3469 | else: |
3460 | m0 = testruns[data.testnumber].tSuspended | 3470 | m0 = data.tSuspended |
3461 | mMax = testruns[data.testnumber].end | 3471 | mMax = data.end |
3462 | # in an x2 run, remove any gap between blocks | 3472 | # in an x2 run, remove any gap between blocks |
3463 | if len(testruns) > 1 and data.testnumber == 0: | 3473 | if len(testruns) > 1 and data.testnumber == 0: |
3464 | mMax = testruns[1].start | 3474 | mMax = testruns[1].start |
3465 | mTotal = mMax - m0 | ||
3466 | left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal) | 3475 | left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal) |
3476 | mTotal = mMax - m0 | ||
3467 | # if a timeline block is 0 length, skip altogether | 3477 | # if a timeline block is 0 length, skip altogether |
3468 | if mTotal == 0: | 3478 | if mTotal == 0: |
3469 | continue | 3479 | continue |
3470 | width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal) | 3480 | width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal) |
3471 | devtl.html['timeline'] += html_tblock.format(bname, left, width, devtl.scaleH) | 3481 | devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH) |
3472 | for b in sorted(phases[dir]): | 3482 | for b in sorted(phases[dir]): |
3473 | # draw the phase color background | 3483 | # draw the phase color background |
3474 | phase = data.dmesg[b] | 3484 | phase = data.dmesg[b] |
3475 | length = phase['end']-phase['start'] | 3485 | length = phase['end']-phase['start'] |
3476 | left = '%f' % (((phase['start']-m0)*100.0)/mTotal) | 3486 | left = '%f' % (((phase['start']-m0)*100.0)/mTotal) |
3477 | width = '%f' % ((length*100.0)/mTotal) | 3487 | width = '%f' % ((length*100.0)/mTotal) |
3478 | devtl.html['timeline'] += html_phase.format(left, width, \ | 3488 | devtl.html += devtl.html_phase.format(left, width, \ |
3479 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ | 3489 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ |
3480 | data.dmesg[b]['color'], '') | 3490 | data.dmesg[b]['color'], '') |
3481 | for e in data.errorinfo[dir]: | 3491 | for e in data.errorinfo[dir]: |
3482 | # draw red lines for any kernel errors found | 3492 | # draw red lines for any kernel errors found |
3483 | t, err = e | 3493 | t, err = e |
3484 | right = '%f' % (((mMax-t)*100.0)/mTotal) | 3494 | right = '%f' % (((mMax-t)*100.0)/mTotal) |
3485 | devtl.html['timeline'] += html_error.format(right, err) | 3495 | devtl.html += html_error.format(right, err) |
3486 | for b in sorted(phases[dir]): | 3496 | for b in sorted(phases[dir]): |
3487 | # draw the devices for this phase | 3497 | # draw the devices for this phase |
3488 | phaselist = data.dmesg[b]['list'] | 3498 | phaselist = data.dmesg[b]['list'] |
@@ -3496,7 +3506,7 @@ def createHTML(testruns): | |||
3496 | if 'htmlclass' in dev: | 3506 | if 'htmlclass' in dev: |
3497 | xtraclass = dev['htmlclass'] | 3507 | xtraclass = dev['htmlclass'] |
3498 | if 'color' in dev: | 3508 | if 'color' in dev: |
3499 | xtrastyle = 'background-color:%s;' % dev['color'] | 3509 | xtrastyle = 'background:%s;' % dev['color'] |
3500 | if(d in sysvals.devprops): | 3510 | if(d in sysvals.devprops): |
3501 | name = sysvals.devprops[d].altName(d) | 3511 | name = sysvals.devprops[d].altName(d) |
3502 | xtraclass = sysvals.devprops[d].xtraClass() | 3512 | xtraclass = sysvals.devprops[d].xtraClass() |
@@ -3521,7 +3531,7 @@ def createHTML(testruns): | |||
3521 | title += 'post_resume_process' | 3531 | title += 'post_resume_process' |
3522 | else: | 3532 | else: |
3523 | title += b | 3533 | title += b |
3524 | devtl.html['timeline'] += html_device.format(dev['id'], \ | 3534 | devtl.html += devtl.html_device.format(dev['id'], \ |
3525 | title, left, top, '%.3f'%rowheight, width, \ | 3535 | title, left, top, '%.3f'%rowheight, width, \ |
3526 | d+drv, xtraclass, xtrastyle) | 3536 | d+drv, xtraclass, xtrastyle) |
3527 | if('cpuexec' in dev): | 3537 | if('cpuexec' in dev): |
@@ -3535,7 +3545,7 @@ def createHTML(testruns): | |||
3535 | left = '%f' % (((start-m0)*100)/mTotal) | 3545 | left = '%f' % (((start-m0)*100)/mTotal) |
3536 | width = '%f' % ((end-start)*100/mTotal) | 3546 | width = '%f' % ((end-start)*100/mTotal) |
3537 | color = 'rgba(255, 0, 0, %f)' % j | 3547 | color = 'rgba(255, 0, 0, %f)' % j |
3538 | devtl.html['timeline'] += \ | 3548 | devtl.html += \ |
3539 | html_cpuexec.format(left, top, height, width, color) | 3549 | html_cpuexec.format(left, top, height, width, color) |
3540 | if('src' not in dev): | 3550 | if('src' not in dev): |
3541 | continue | 3551 | continue |
@@ -3548,20 +3558,20 @@ def createHTML(testruns): | |||
3548 | xtrastyle = '' | 3558 | xtrastyle = '' |
3549 | if e.color: | 3559 | if e.color: |
3550 | xtrastyle = 'background:%s;' % e.color | 3560 | xtrastyle = 'background:%s;' % e.color |
3551 | devtl.html['timeline'] += \ | 3561 | devtl.html += \ |
3552 | html_traceevent.format(e.title(), \ | 3562 | html_traceevent.format(e.title(), \ |
3553 | left, top, height, width, e.text(), '', xtrastyle) | 3563 | left, top, height, width, e.text(), '', xtrastyle) |
3554 | # draw the time scale, try to make the number of labels readable | 3564 | # draw the time scale, try to make the number of labels readable |
3555 | devtl.html['timeline'] += devtl.createTimeScale(m0, mMax, tTotal, dir) | 3565 | devtl.createTimeScale(m0, mMax, tTotal, dir) |
3556 | devtl.html['timeline'] += '</div>\n' | 3566 | devtl.html += '</div>\n' |
3557 | 3567 | ||
3558 | # timeline is finished | 3568 | # timeline is finished |
3559 | devtl.html['timeline'] += '</div>\n</div>\n' | 3569 | devtl.html += '</div>\n</div>\n' |
3560 | 3570 | ||
3561 | # draw a legend which describes the phases by color | 3571 | # draw a legend which describes the phases by color |
3562 | if sysvals.suspendmode != 'command': | 3572 | if sysvals.suspendmode != 'command': |
3563 | data = testruns[-1] | 3573 | data = testruns[-1] |
3564 | devtl.html['legend'] = '<div class="legend">\n' | 3574 | devtl.html += '<div class="legend">\n' |
3565 | pdelta = 100.0/len(data.phases) | 3575 | pdelta = 100.0/len(data.phases) |
3566 | pmargin = pdelta / 4.0 | 3576 | pmargin = pdelta / 4.0 |
3567 | for phase in data.phases: | 3577 | for phase in data.phases: |
@@ -3571,127 +3581,41 @@ def createHTML(testruns): | |||
3571 | id += tmp[1][0] | 3581 | id += tmp[1][0] |
3572 | order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) | 3582 | order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) |
3573 | name = string.replace(phase, '_', ' ') | 3583 | name = string.replace(phase, '_', ' ') |
3574 | devtl.html['legend'] += html_legend.format(order, \ | 3584 | devtl.html += html_legend.format(order, \ |
3575 | data.dmesg[phase]['color'], name, id) | 3585 | data.dmesg[phase]['color'], name, id) |
3576 | devtl.html['legend'] += '</div>\n' | 3586 | devtl.html += '</div>\n' |
3577 | 3587 | ||
3578 | hf = open(sysvals.htmlfile, 'w') | 3588 | hf = open(sysvals.htmlfile, 'w') |
3579 | 3589 | ||
3580 | if not sysvals.cgexp: | ||
3581 | cgchk = 'checked' | ||
3582 | cgnchk = 'not(:checked)' | ||
3583 | else: | ||
3584 | cgchk = 'not(:checked)' | ||
3585 | cgnchk = 'checked' | ||
3586 | |||
3587 | # write the html header first (html head, css code, up to body start) | ||
3588 | html_header = '<!DOCTYPE html>\n<html>\n<head>\n\ | ||
3589 | <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ | ||
3590 | <title>'+htmlTitle()+'</title>\n\ | ||
3591 | <style type=\'text/css\'>\n\ | ||
3592 | body {overflow-y:scroll;}\n\ | ||
3593 | .stamp {width:100%;text-align:center;background-color:gray;line-height:30px;color:white;font:25px Arial;}\n\ | ||
3594 | .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\ | ||
3595 | .callgraph article * {padding-left:28px;}\n\ | ||
3596 | h1 {color:black;font:bold 30px Times;}\n\ | ||
3597 | t0 {color:black;font:bold 30px Times;}\n\ | ||
3598 | t1 {color:black;font:30px Times;}\n\ | ||
3599 | t2 {color:black;font:25px Times;}\n\ | ||
3600 | t3 {color:black;font:20px Times;white-space:nowrap;}\n\ | ||
3601 | t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\ | ||
3602 | cS {font:bold 13px Times;}\n\ | ||
3603 | table {width:100%;}\n\ | ||
3604 | .gray {background-color:rgba(80,80,80,0.1);}\n\ | ||
3605 | .green {background-color:rgba(204,255,204,0.4);}\n\ | ||
3606 | .purple {background-color:rgba(128,0,128,0.2);}\n\ | ||
3607 | .yellow {background-color:rgba(255,255,204,0.4);}\n\ | ||
3608 | .time1 {font:22px Arial;border:1px solid;}\n\ | ||
3609 | .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\ | ||
3610 | td {text-align:center;}\n\ | ||
3611 | r {color:#500000;font:15px Tahoma;}\n\ | ||
3612 | n {color:#505050;font:15px Tahoma;}\n\ | ||
3613 | .tdhl {color:red;}\n\ | ||
3614 | .hide {display:none;}\n\ | ||
3615 | .pf {display:none;}\n\ | ||
3616 | .pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\ | ||
3617 | .pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\ | ||
3618 | .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\ | ||
3619 | .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\ | ||
3620 | .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\ | ||
3621 | .thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\ | ||
3622 | .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\ | ||
3623 | .thread:hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\ | ||
3624 | .thread.sec,.thread.sec:hover {background-color:black;border:0;color:white;line-height:15px;font-size:10px;}\n\ | ||
3625 | .hover {background-color:white;border:1px solid red;'+hoverZ+'}\n\ | ||
3626 | .hover.sync {background-color:white;}\n\ | ||
3627 | .hover.bg,.hover.kth,.hover.sync,.hover.ps {background-color:white;}\n\ | ||
3628 | .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\ | ||
3629 | .traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\ | ||
3630 | .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\ | ||
3631 | .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\ | ||
3632 | .phaselet {position:absolute;overflow:hidden;border:0px;text-align:center;height:100px;font-size:24px;}\n\ | ||
3633 | .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\ | ||
3634 | .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\ | ||
3635 | .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\ | ||
3636 | .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\ | ||
3637 | button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\ | ||
3638 | .logbtn {position:relative;float:right;height:25px;width:50px;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\ | ||
3639 | .devlist {position:'+x2changes[1]+';width:190px;}\n\ | ||
3640 | a:link {color:white;text-decoration:none;}\n\ | ||
3641 | a:visited {color:white;}\n\ | ||
3642 | a:hover {color:white;}\n\ | ||
3643 | a:active {color:white;}\n\ | ||
3644 | .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\ | ||
3645 | #devicedetail {height:100px;box-shadow:5px 5px 20px black;}\n\ | ||
3646 | .tblock {position:absolute;height:100%;background-color:#ddd;}\n\ | ||
3647 | .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\ | ||
3648 | .bg {z-index:1;}\n\ | ||
3649 | </style>\n</head>\n<body>\n' | ||
3650 | |||
3651 | # no header or css if its embedded | 3590 | # no header or css if its embedded |
3652 | if(sysvals.embedded): | 3591 | if(sysvals.embedded): |
3653 | hf.write('pass True tSus %.3f tRes %.3f tLow %.3f fwvalid %s tSus %.3f tRes %.3f\n' % | 3592 | hf.write('pass True tSus %.3f tRes %.3f tLow %.3f fwvalid %s tSus %.3f tRes %.3f\n' % |
3654 | (data.tSuspended-data.start, data.end-data.tSuspended, data.tLow, data.fwValid, \ | 3593 | (data.tSuspended-data.start, data.end-data.tSuspended, data.tLow, data.fwValid, \ |
3655 | data.fwSuspend/1000000, data.fwResume/1000000)) | 3594 | data.fwSuspend/1000000, data.fwResume/1000000)) |
3656 | else: | 3595 | else: |
3657 | hf.write(html_header) | 3596 | addCSS(hf, sysvals, len(testruns), kerror) |
3658 | |||
3659 | # write the test title and general info header | ||
3660 | if(sysvals.stamp['time'] != ""): | ||
3661 | hf.write(headline_version) | ||
3662 | if sysvals.logmsg: | ||
3663 | hf.write('<button id="showtest" class="logbtn">log</button>') | ||
3664 | if sysvals.addlogs and sysvals.dmesgfile: | ||
3665 | hf.write('<button id="showdmesg" class="logbtn">dmesg</button>') | ||
3666 | if sysvals.addlogs and sysvals.ftracefile: | ||
3667 | hf.write('<button id="showftrace" class="logbtn">ftrace</button>') | ||
3668 | hf.write(headline_stamp.format(sysvals.stamp['host'], | ||
3669 | sysvals.stamp['kernel'], sysvals.stamp['mode'], \ | ||
3670 | sysvals.stamp['time'])) | ||
3671 | 3597 | ||
3672 | # write the device timeline | 3598 | # write the device timeline |
3673 | hf.write(devtl.html['header']) | 3599 | hf.write(devtl.html) |
3674 | hf.write(devtl.html['timeline']) | ||
3675 | hf.write(devtl.html['legend']) | ||
3676 | hf.write('<div id="devicedetailtitle"></div>\n') | 3600 | hf.write('<div id="devicedetailtitle"></div>\n') |
3677 | hf.write('<div id="devicedetail" style="display:none;">\n') | 3601 | hf.write('<div id="devicedetail" style="display:none;">\n') |
3678 | # draw the colored boxes for the device detail section | 3602 | # draw the colored boxes for the device detail section |
3679 | for data in testruns: | 3603 | for data in testruns: |
3680 | hf.write('<div id="devicedetail%d">\n' % data.testnumber) | 3604 | hf.write('<div id="devicedetail%d">\n' % data.testnumber) |
3681 | pscolor = 'linear-gradient(to top left, #ccc, #eee)' | 3605 | pscolor = 'linear-gradient(to top left, #ccc, #eee)' |
3682 | hf.write(html_phaselet.format('pre_suspend_process', \ | 3606 | hf.write(devtl.html_phaselet.format('pre_suspend_process', \ |
3683 | '0', '0', pscolor)) | 3607 | '0', '0', pscolor)) |
3684 | for b in data.phases: | 3608 | for b in data.phases: |
3685 | phase = data.dmesg[b] | 3609 | phase = data.dmesg[b] |
3686 | length = phase['end']-phase['start'] | 3610 | length = phase['end']-phase['start'] |
3687 | left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) | 3611 | left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) |
3688 | width = '%.3f' % ((length*100.0)/tTotal) | 3612 | width = '%.3f' % ((length*100.0)/tTotal) |
3689 | hf.write(html_phaselet.format(b, left, width, \ | 3613 | hf.write(devtl.html_phaselet.format(b, left, width, \ |
3690 | data.dmesg[b]['color'])) | 3614 | data.dmesg[b]['color'])) |
3691 | hf.write(html_phaselet.format('post_resume_process', \ | 3615 | hf.write(devtl.html_phaselet.format('post_resume_process', \ |
3692 | '0', '0', pscolor)) | 3616 | '0', '0', pscolor)) |
3693 | if sysvals.suspendmode == 'command': | 3617 | if sysvals.suspendmode == 'command': |
3694 | hf.write(html_phaselet.format('cmdexec', '0', '0', pscolor)) | 3618 | hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor)) |
3695 | hf.write('</div>\n') | 3619 | hf.write('</div>\n') |
3696 | hf.write('</div>\n') | 3620 | hf.write('</div>\n') |
3697 | 3621 | ||
@@ -3701,52 +3625,7 @@ def createHTML(testruns): | |||
3701 | else: | 3625 | else: |
3702 | data = testruns[-1] | 3626 | data = testruns[-1] |
3703 | if(sysvals.usecallgraph and not sysvals.embedded): | 3627 | if(sysvals.usecallgraph and not sysvals.embedded): |
3704 | hf.write('<section id="callgraphs" class="callgraph">\n') | 3628 | addCallgraphs(sysvals, hf, data) |
3705 | # write out the ftrace data converted to html | ||
3706 | html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n' | ||
3707 | html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n' | ||
3708 | html_func_end = '</article>\n' | ||
3709 | html_func_leaf = '<article>{0} {1}</article>\n' | ||
3710 | num = 0 | ||
3711 | for p in data.phases: | ||
3712 | if sysvals.cgphase and p != sysvals.cgphase: | ||
3713 | continue | ||
3714 | list = data.dmesg[p]['list'] | ||
3715 | for devname in data.sortedDevices(p): | ||
3716 | if('ftrace' not in list[devname]): | ||
3717 | continue | ||
3718 | devid = list[devname]['id'] | ||
3719 | cg = list[devname]['ftrace'] | ||
3720 | clen = (cg.end - cg.start) * 1000 | ||
3721 | if clen < sysvals.mincglen: | ||
3722 | continue | ||
3723 | fmt = '<r>(%.3f ms @ '+sysvals.timeformat+' to '+sysvals.timeformat+')</r>' | ||
3724 | flen = fmt % (clen, cg.start, cg.end) | ||
3725 | name = devname | ||
3726 | if(devname in sysvals.devprops): | ||
3727 | name = sysvals.devprops[devname].altName(devname) | ||
3728 | if sysvals.suspendmode == 'command': | ||
3729 | ftitle = name | ||
3730 | else: | ||
3731 | ftitle = name+' '+p | ||
3732 | hf.write(html_func_top.format(devid, data.dmesg[p]['color'], \ | ||
3733 | num, ftitle, flen)) | ||
3734 | num += 1 | ||
3735 | for line in cg.list: | ||
3736 | if(line.length < 0.000000001): | ||
3737 | flen = '' | ||
3738 | else: | ||
3739 | fmt = '<n>(%.3f ms @ '+sysvals.timeformat+')</n>' | ||
3740 | flen = fmt % (line.length*1000, line.time) | ||
3741 | if(line.freturn and line.fcall): | ||
3742 | hf.write(html_func_leaf.format(line.name, flen)) | ||
3743 | elif(line.freturn): | ||
3744 | hf.write(html_func_end) | ||
3745 | else: | ||
3746 | hf.write(html_func_start.format(num, line.name, flen)) | ||
3747 | num += 1 | ||
3748 | hf.write(html_func_end) | ||
3749 | hf.write('\n\n </section>\n') | ||
3750 | 3629 | ||
3751 | # add the test log as a hidden div | 3630 | # add the test log as a hidden div |
3752 | if sysvals.logmsg: | 3631 | if sysvals.logmsg: |
@@ -3788,6 +3667,100 @@ def createHTML(testruns): | |||
3788 | hf.close() | 3667 | hf.close() |
3789 | return True | 3668 | return True |
3790 | 3669 | ||
3670 | def addCSS(hf, sv, testcount=1, kerror=False, extra=''): | ||
3671 | kernel = sv.stamp['kernel'] | ||
3672 | host = sv.hostname[0].upper()+sv.hostname[1:] | ||
3673 | mode = sv.suspendmode | ||
3674 | if sv.suspendmode in suspendmodename: | ||
3675 | mode = suspendmodename[sv.suspendmode] | ||
3676 | title = host+' '+mode+' '+kernel | ||
3677 | |||
3678 | # various format changes by flags | ||
3679 | cgchk = 'checked' | ||
3680 | cgnchk = 'not(:checked)' | ||
3681 | if sv.cgexp: | ||
3682 | cgchk = 'not(:checked)' | ||
3683 | cgnchk = 'checked' | ||
3684 | |||
3685 | hoverZ = 'z-index:8;' | ||
3686 | if sv.usedevsrc: | ||
3687 | hoverZ = '' | ||
3688 | |||
3689 | devlistpos = 'absolute' | ||
3690 | if testcount > 1: | ||
3691 | devlistpos = 'relative' | ||
3692 | |||
3693 | scaleTH = 20 | ||
3694 | if kerror: | ||
3695 | scaleTH = 60 | ||
3696 | |||
3697 | # write the html header first (html head, css code, up to body start) | ||
3698 | html_header = '<!DOCTYPE html>\n<html>\n<head>\n\ | ||
3699 | <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\ | ||
3700 | <title>'+title+'</title>\n\ | ||
3701 | <style type=\'text/css\'>\n\ | ||
3702 | body {overflow-y:scroll;}\n\ | ||
3703 | .stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\ | ||
3704 | .callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\ | ||
3705 | .callgraph article * {padding-left:28px;}\n\ | ||
3706 | h1 {color:black;font:bold 30px Times;}\n\ | ||
3707 | t0 {color:black;font:bold 30px Times;}\n\ | ||
3708 | t1 {color:black;font:30px Times;}\n\ | ||
3709 | t2 {color:black;font:25px Times;}\n\ | ||
3710 | t3 {color:black;font:20px Times;white-space:nowrap;}\n\ | ||
3711 | t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\ | ||
3712 | cS {font:bold 13px Times;}\n\ | ||
3713 | table {width:100%;}\n\ | ||
3714 | .gray {background:rgba(80,80,80,0.1);}\n\ | ||
3715 | .green {background:rgba(204,255,204,0.4);}\n\ | ||
3716 | .purple {background:rgba(128,0,128,0.2);}\n\ | ||
3717 | .yellow {background:rgba(255,255,204,0.4);}\n\ | ||
3718 | .blue {background:rgba(169,208,245,0.4);}\n\ | ||
3719 | .time1 {font:22px Arial;border:1px solid;}\n\ | ||
3720 | .time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\ | ||
3721 | td {text-align:center;}\n\ | ||
3722 | r {color:#500000;font:15px Tahoma;}\n\ | ||
3723 | n {color:#505050;font:15px Tahoma;}\n\ | ||
3724 | .tdhl {color:red;}\n\ | ||
3725 | .hide {display:none;}\n\ | ||
3726 | .pf {display:none;}\n\ | ||
3727 | .pf:'+cgchk+' + label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\ | ||
3728 | .pf:'+cgnchk+' ~ label {background:url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\ | ||
3729 | .pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\ | ||
3730 | .zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\ | ||
3731 | .timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\ | ||
3732 | .thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\ | ||
3733 | .thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\ | ||
3734 | .thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\ | ||
3735 | .thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\ | ||
3736 | .hover {background:white;border:1px solid red;'+hoverZ+'}\n\ | ||
3737 | .hover.sync {background:white;}\n\ | ||
3738 | .hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\ | ||
3739 | .jiffie {position:absolute;pointer-events: none;z-index:8;}\n\ | ||
3740 | .traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\ | ||
3741 | .traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\ | ||
3742 | .phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\ | ||
3743 | .phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\ | ||
3744 | .t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\ | ||
3745 | .err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\ | ||
3746 | .legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\ | ||
3747 | .legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\ | ||
3748 | button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\ | ||
3749 | .logbtn {position:relative;float:right;height:25px;width:50px;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\ | ||
3750 | .devlist {position:'+devlistpos+';width:190px;}\n\ | ||
3751 | a:link {color:white;text-decoration:none;}\n\ | ||
3752 | a:visited {color:white;}\n\ | ||
3753 | a:hover {color:white;}\n\ | ||
3754 | a:active {color:white;}\n\ | ||
3755 | .version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\ | ||
3756 | #devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\ | ||
3757 | .tblock {position:absolute;height:100%;background:#ddd;}\n\ | ||
3758 | .tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\ | ||
3759 | .bg {z-index:1;}\n\ | ||
3760 | '+extra+'\ | ||
3761 | </style>\n</head>\n<body>\n' | ||
3762 | hf.write(html_header) | ||
3763 | |||
3791 | # Function: addScriptCode | 3764 | # Function: addScriptCode |
3792 | # Description: | 3765 | # Description: |
3793 | # Adds the javascript code to the output html | 3766 | # Adds the javascript code to the output html |
@@ -3809,7 +3782,7 @@ def addScriptCode(hf, testruns): | |||
3809 | ' var resolution = -1;\n'\ | 3782 | ' var resolution = -1;\n'\ |
3810 | ' var dragval = [0, 0];\n'\ | 3783 | ' var dragval = [0, 0];\n'\ |
3811 | ' function redrawTimescale(t0, tMax, tS) {\n'\ | 3784 | ' function redrawTimescale(t0, tMax, tS) {\n'\ |
3812 | ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;"><cS>←R</cS></div>\';\n'\ | 3785 | ' var rline = \'<div class="t" style="left:0;border-left:1px solid black;border-right:0;">\';\n'\ |
3813 | ' var tTotal = tMax - t0;\n'\ | 3786 | ' var tTotal = tMax - t0;\n'\ |
3814 | ' var list = document.getElementsByClassName("tblock");\n'\ | 3787 | ' var list = document.getElementsByClassName("tblock");\n'\ |
3815 | ' for (var i = 0; i < list.length; i++) {\n'\ | 3788 | ' for (var i = 0; i < list.length; i++) {\n'\ |
@@ -3824,19 +3797,23 @@ def addScriptCode(hf, testruns): | |||
3824 | ' var pos = 0.0, val = 0.0;\n'\ | 3797 | ' var pos = 0.0, val = 0.0;\n'\ |
3825 | ' for (var j = 0; j < divTotal; j++) {\n'\ | 3798 | ' for (var j = 0; j < divTotal; j++) {\n'\ |
3826 | ' var htmlline = "";\n'\ | 3799 | ' var htmlline = "";\n'\ |
3827 | ' if(list[i].id[5] == "r") {\n'\ | 3800 | ' var mode = list[i].id[5];\n'\ |
3828 | ' pos = 100 - (((j)*tS*100)/mTotal);\n'\ | 3801 | ' if(mode == "s") {\n'\ |
3829 | ' val = (j)*tS;\n'\ | ||
3830 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\ | ||
3831 | ' if(j == 0)\n'\ | ||
3832 | ' htmlline = rline;\n'\ | ||
3833 | ' } else {\n'\ | ||
3834 | ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\ | 3802 | ' pos = 100 - (((j)*tS*100)/mTotal) - divEdge;\n'\ |
3835 | ' val = (j-divTotal+1)*tS;\n'\ | 3803 | ' val = (j-divTotal+1)*tS;\n'\ |
3836 | ' if(j == divTotal - 1)\n'\ | 3804 | ' if(j == divTotal - 1)\n'\ |
3837 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\ | 3805 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%"><cS>S→</cS></div>\';\n'\ |
3838 | ' else\n'\ | 3806 | ' else\n'\ |
3839 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\ | 3807 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\ |
3808 | ' } else {\n'\ | ||
3809 | ' pos = 100 - (((j)*tS*100)/mTotal);\n'\ | ||
3810 | ' val = (j)*tS;\n'\ | ||
3811 | ' htmlline = \'<div class="t" style="right:\'+pos+\'%">\'+val+\'ms</div>\';\n'\ | ||
3812 | ' if(j == 0)\n'\ | ||
3813 | ' if(mode == "r")\n'\ | ||
3814 | ' htmlline = rline+"<cS>←R</cS></div>";\n'\ | ||
3815 | ' else\n'\ | ||
3816 | ' htmlline = rline+"<cS>0ms</div>";\n'\ | ||
3840 | ' }\n'\ | 3817 | ' }\n'\ |
3841 | ' html += htmlline;\n'\ | 3818 | ' html += htmlline;\n'\ |
3842 | ' }\n'\ | 3819 | ' }\n'\ |
@@ -4002,12 +3979,80 @@ def addScriptCode(hf, testruns): | |||
4002 | ' }\n'\ | 3979 | ' }\n'\ |
4003 | ' }\n'\ | 3980 | ' }\n'\ |
4004 | ' }\n'\ | 3981 | ' }\n'\ |
3982 | ' if(typeof devstats !== \'undefined\')\n'\ | ||
3983 | ' callDetail(this.id, this.title);\n'\ | ||
4005 | ' var cglist = document.getElementById("callgraphs");\n'\ | 3984 | ' var cglist = document.getElementById("callgraphs");\n'\ |
4006 | ' if(!cglist) return;\n'\ | 3985 | ' if(!cglist) return;\n'\ |
4007 | ' var cg = cglist.getElementsByClassName("atop");\n'\ | 3986 | ' var cg = cglist.getElementsByClassName("atop");\n'\ |
4008 | ' if(cg.length < 10) return;\n'\ | 3987 | ' if(cg.length < 10) return;\n'\ |
4009 | ' for (var i = 0; i < cg.length; i++) {\n'\ | 3988 | ' for (var i = 0; i < cg.length; i++) {\n'\ |
4010 | ' if(idlist.indexOf(cg[i].id) >= 0) {\n'\ | 3989 | ' cgid = cg[i].id.split("x")[0]\n'\ |
3990 | ' if(idlist.indexOf(cgid) >= 0) {\n'\ | ||
3991 | ' cg[i].style.display = "block";\n'\ | ||
3992 | ' } else {\n'\ | ||
3993 | ' cg[i].style.display = "none";\n'\ | ||
3994 | ' }\n'\ | ||
3995 | ' }\n'\ | ||
3996 | ' }\n'\ | ||
3997 | ' function callDetail(devid, devtitle) {\n'\ | ||
3998 | ' if(!(devid in devstats) || devstats[devid].length < 1)\n'\ | ||
3999 | ' return;\n'\ | ||
4000 | ' var list = devstats[devid];\n'\ | ||
4001 | ' var tmp = devtitle.split(" ");\n'\ | ||
4002 | ' var name = tmp[0], phase = tmp[tmp.length-1];\n'\ | ||
4003 | ' var dd = document.getElementById(phase);\n'\ | ||
4004 | ' var total = parseFloat(tmp[1].slice(1));\n'\ | ||
4005 | ' var mlist = [];\n'\ | ||
4006 | ' var maxlen = 0;\n'\ | ||
4007 | ' var info = []\n'\ | ||
4008 | ' for(var i in list) {\n'\ | ||
4009 | ' if(list[i][0] == "@") {\n'\ | ||
4010 | ' info = list[i].split("|");\n'\ | ||
4011 | ' continue;\n'\ | ||
4012 | ' }\n'\ | ||
4013 | ' var tmp = list[i].split("|");\n'\ | ||
4014 | ' var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);\n'\ | ||
4015 | ' var p = (t*100.0/total).toFixed(2);\n'\ | ||
4016 | ' mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];\n'\ | ||
4017 | ' if(f.length > maxlen)\n'\ | ||
4018 | ' maxlen = f.length;\n'\ | ||
4019 | ' }\n'\ | ||
4020 | ' var pad = 5;\n'\ | ||
4021 | ' if(mlist.length == 0) pad = 30;\n'\ | ||
4022 | ' var html = \'<div style="padding-top:\'+pad+\'px"><t3> <b>\'+name+\':</b>\';\n'\ | ||
4023 | ' if(info.length > 2)\n'\ | ||
4024 | ' html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";\n'\ | ||
4025 | ' if(info.length > 3)\n'\ | ||
4026 | ' html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";\n'\ | ||
4027 | ' if(info.length > 4)\n'\ | ||
4028 | ' html += ", return=<b>"+info[4]+"</b>";\n'\ | ||
4029 | ' html += "</t3></div>";\n'\ | ||
4030 | ' if(mlist.length > 0) {\n'\ | ||
4031 | ' html += \'<table class=fstat style="padding-top:\'+(maxlen*5)+\'px;"><tr><th>Function</th>\';\n'\ | ||
4032 | ' for(var i in mlist)\n'\ | ||
4033 | ' html += "<td class=vt>"+mlist[i][0]+"</td>";\n'\ | ||
4034 | ' html += "</tr><tr><th>Calls</th>";\n'\ | ||
4035 | ' for(var i in mlist)\n'\ | ||
4036 | ' html += "<td>"+mlist[i][1]+"</td>";\n'\ | ||
4037 | ' html += "</tr><tr><th>Time(ms)</th>";\n'\ | ||
4038 | ' for(var i in mlist)\n'\ | ||
4039 | ' html += "<td>"+mlist[i][2]+"</td>";\n'\ | ||
4040 | ' html += "</tr><tr><th>Percent</th>";\n'\ | ||
4041 | ' for(var i in mlist)\n'\ | ||
4042 | ' html += "<td>"+mlist[i][3]+"</td>";\n'\ | ||
4043 | ' html += "</tr></table>";\n'\ | ||
4044 | ' }\n'\ | ||
4045 | ' dd.innerHTML = html;\n'\ | ||
4046 | ' var height = (maxlen*5)+100;\n'\ | ||
4047 | ' dd.style.height = height+"px";\n'\ | ||
4048 | ' document.getElementById("devicedetail").style.height = height+"px";\n'\ | ||
4049 | ' }\n'\ | ||
4050 | ' function callSelect() {\n'\ | ||
4051 | ' var cglist = document.getElementById("callgraphs");\n'\ | ||
4052 | ' if(!cglist) return;\n'\ | ||
4053 | ' var cg = cglist.getElementsByClassName("atop");\n'\ | ||
4054 | ' for (var i = 0; i < cg.length; i++) {\n'\ | ||
4055 | ' if(this.id == cg[i].id) {\n'\ | ||
4011 | ' cg[i].style.display = "block";\n'\ | 4056 | ' cg[i].style.display = "block";\n'\ |
4012 | ' } else {\n'\ | 4057 | ' } else {\n'\ |
4013 | ' cg[i].style.display = "none";\n'\ | 4058 | ' cg[i].style.display = "none";\n'\ |
@@ -4093,6 +4138,9 @@ def addScriptCode(hf, testruns): | |||
4093 | ' dev[i].onmouseover = deviceHover;\n'\ | 4138 | ' dev[i].onmouseover = deviceHover;\n'\ |
4094 | ' dev[i].onmouseout = deviceUnhover;\n'\ | 4139 | ' dev[i].onmouseout = deviceUnhover;\n'\ |
4095 | ' }\n'\ | 4140 | ' }\n'\ |
4141 | ' var dev = dmesg.getElementsByClassName("srccall");\n'\ | ||
4142 | ' for (var i = 0; i < dev.length; i++)\n'\ | ||
4143 | ' dev[i].onclick = callSelect;\n'\ | ||
4096 | ' zoomTimeline();\n'\ | 4144 | ' zoomTimeline();\n'\ |
4097 | ' });\n'\ | 4145 | ' });\n'\ |
4098 | '</script>\n' | 4146 | '</script>\n' |
@@ -4675,7 +4723,7 @@ def rootCheck(fatal): | |||
4675 | if(os.access(sysvals.powerfile, os.W_OK)): | 4723 | if(os.access(sysvals.powerfile, os.W_OK)): |
4676 | return True | 4724 | return True |
4677 | if fatal: | 4725 | if fatal: |
4678 | doError('This command must be run as root') | 4726 | doError('This command requires sysfs mount and root access') |
4679 | return False | 4727 | return False |
4680 | 4728 | ||
4681 | # Function: getArgInt | 4729 | # Function: getArgInt |
@@ -4767,51 +4815,62 @@ def runTest(subdir, testpath=''): | |||
4767 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' | 4815 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' |
4768 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) | 4816 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) |
4769 | 4817 | ||
4818 | def find_in_html(html, strs, div=False): | ||
4819 | for str in strs: | ||
4820 | l = len(str) | ||
4821 | i = html.find(str) | ||
4822 | if i >= 0: | ||
4823 | break | ||
4824 | if i < 0: | ||
4825 | return '' | ||
4826 | if not div: | ||
4827 | return re.search(r'[-+]?\d*\.\d+|\d+', html[i+l:i+l+50]).group() | ||
4828 | n = html[i+l:].find('</div>') | ||
4829 | if n < 0: | ||
4830 | return '' | ||
4831 | return html[i+l:i+l+n] | ||
4832 | |||
4770 | # Function: runSummary | 4833 | # Function: runSummary |
4771 | # Description: | 4834 | # Description: |
4772 | # create a summary of tests in a sub-directory | 4835 | # create a summary of tests in a sub-directory |
4773 | def runSummary(subdir, output): | 4836 | def runSummary(subdir, local=True): |
4774 | # get a list of ftrace output files | 4837 | inpath = os.path.abspath(subdir) |
4775 | files = [] | 4838 | outpath = inpath |
4839 | if local: | ||
4840 | outpath = os.path.abspath('.') | ||
4841 | print('Generating a summary of folder "%s"' % inpath) | ||
4842 | testruns = [] | ||
4776 | for dirname, dirnames, filenames in os.walk(subdir): | 4843 | for dirname, dirnames, filenames in os.walk(subdir): |
4777 | for filename in filenames: | 4844 | for filename in filenames: |
4778 | if(re.match('.*_ftrace.txt', filename)): | 4845 | if(not re.match('.*.html', filename)): |
4779 | files.append("%s/%s" % (dirname, filename)) | ||
4780 | |||
4781 | # process the files in order and get an array of data objects | ||
4782 | testruns = [] | ||
4783 | for file in sorted(files): | ||
4784 | if output: | ||
4785 | print("Test found in %s" % os.path.dirname(file)) | ||
4786 | sysvals.ftracefile = file | ||
4787 | sysvals.dmesgfile = file.replace('_ftrace.txt', '_dmesg.txt') | ||
4788 | doesTraceLogHaveTraceEvents() | ||
4789 | sysvals.usecallgraph = False | ||
4790 | if not sysvals.usetraceeventsonly: | ||
4791 | if(not os.path.exists(sysvals.dmesgfile)): | ||
4792 | print("Skipping %s: not a valid test input" % file) | ||
4793 | continue | 4846 | continue |
4794 | else: | 4847 | file = os.path.join(dirname, filename) |
4795 | if output: | 4848 | html = open(file, 'r').read(10000) |
4796 | f = os.path.basename(sysvals.ftracefile) | 4849 | suspend = find_in_html(html, |
4797 | d = os.path.basename(sysvals.dmesgfile) | 4850 | ['Kernel Suspend: ', 'Kernel Suspend Time: ']) |
4798 | print("\tInput files: %s and %s" % (f, d)) | 4851 | resume = find_in_html(html, |
4799 | testdata = loadKernelLog() | 4852 | ['Kernel Resume: ', 'Kernel Resume Time: ']) |
4800 | data = testdata[0] | 4853 | line = find_in_html(html, ['<div class="stamp">'], True) |
4801 | parseKernelLog(data) | 4854 | stmp = line.split() |
4802 | testdata = [data] | 4855 | if not suspend or not resume or len(stmp) < 4: |
4803 | appendIncompleteTraceLog(testdata) | 4856 | continue |
4804 | else: | 4857 | data = { |
4805 | if output: | 4858 | 'host': stmp[0], |
4806 | print("\tInput file: %s" % os.path.basename(sysvals.ftracefile)) | 4859 | 'kernel': stmp[1], |
4807 | testdata = parseTraceLog() | 4860 | 'mode': stmp[2], |
4808 | data = testdata[0] | 4861 | 'time': string.join(stmp[3:], ' '), |
4809 | data.normalizeTime(data.tSuspended) | 4862 | 'suspend': suspend, |
4810 | link = file.replace(subdir+'/', '').replace('_ftrace.txt', '.html') | 4863 | 'resume': resume, |
4811 | data.outfile = link | 4864 | 'url': os.path.relpath(file, outpath), |
4812 | testruns.append(data) | 4865 | } |
4813 | 4866 | if len(stmp) == 7: | |
4814 | createHTMLSummarySimple(testruns, subdir+'/summary.html') | 4867 | data['kernel'] = 'unknown' |
4868 | data['mode'] = stmp[1] | ||
4869 | data['time'] = string.join(stmp[2:], ' ') | ||
4870 | testruns.append(data) | ||
4871 | outfile = os.path.join(outpath, 'summary.html') | ||
4872 | print('Summary file: %s' % outfile) | ||
4873 | createHTMLSummarySimple(testruns, outfile, inpath) | ||
4815 | 4874 | ||
4816 | # Function: checkArgBool | 4875 | # Function: checkArgBool |
4817 | # Description: | 4876 | # Description: |
@@ -4869,9 +4928,14 @@ def configFromFile(file): | |||
4869 | sysvals.predelay = getArgInt('-predelay', value, 0, 60000, False) | 4928 | sysvals.predelay = getArgInt('-predelay', value, 0, 60000, False) |
4870 | elif(opt.lower() == 'postdelay'): | 4929 | elif(opt.lower() == 'postdelay'): |
4871 | sysvals.postdelay = getArgInt('-postdelay', value, 0, 60000, False) | 4930 | sysvals.postdelay = getArgInt('-postdelay', value, 0, 60000, False) |
4931 | elif(opt.lower() == 'maxdepth'): | ||
4932 | sysvals.max_graph_depth = getArgInt('-maxdepth', value, 0, 1000, False) | ||
4872 | elif(opt.lower() == 'rtcwake'): | 4933 | elif(opt.lower() == 'rtcwake'): |
4873 | sysvals.rtcwake = True | 4934 | if value.lower() == 'off': |
4874 | sysvals.rtcwaketime = getArgInt('-rtcwake', value, 0, 3600, False) | 4935 | sysvals.rtcwake = False |
4936 | else: | ||
4937 | sysvals.rtcwake = True | ||
4938 | sysvals.rtcwaketime = getArgInt('-rtcwake', value, 0, 3600, False) | ||
4875 | elif(opt.lower() == 'timeprec'): | 4939 | elif(opt.lower() == 'timeprec'): |
4876 | sysvals.setPrecision(getArgInt('-timeprec', value, 0, 6, False)) | 4940 | sysvals.setPrecision(getArgInt('-timeprec', value, 0, 6, False)) |
4877 | elif(opt.lower() == 'mindev'): | 4941 | elif(opt.lower() == 'mindev'): |
@@ -4969,8 +5033,8 @@ def printHelp(): | |||
4969 | modes = getModes() | 5033 | modes = getModes() |
4970 | 5034 | ||
4971 | print('') | 5035 | print('') |
4972 | print('AnalyzeSuspend v%s' % sysvals.version) | 5036 | print('%s v%s' % (sysvals.title, sysvals.version)) |
4973 | print('Usage: sudo analyze_suspend.py <options>') | 5037 | print('Usage: sudo sleepgraph <options> <commands>') |
4974 | print('') | 5038 | print('') |
4975 | print('Description:') | 5039 | print('Description:') |
4976 | print(' This tool is designed to assist kernel and OS developers in optimizing') | 5040 | print(' This tool is designed to assist kernel and OS developers in optimizing') |
@@ -4981,22 +5045,22 @@ def printHelp(): | |||
4981 | print(' a detailed view of which devices/subsystems are taking the most') | 5045 | print(' a detailed view of which devices/subsystems are taking the most') |
4982 | print(' time in suspend/resume.') | 5046 | print(' time in suspend/resume.') |
4983 | print('') | 5047 | print('') |
5048 | print(' If no specific command is given, the default behavior is to initiate') | ||
5049 | print(' a suspend/resume and capture the dmesg/ftrace output as an html timeline.') | ||
5050 | print('') | ||
4984 | print(' Generates output files in subdirectory: suspend-mmddyy-HHMMSS') | 5051 | print(' Generates output files in subdirectory: suspend-mmddyy-HHMMSS') |
4985 | print(' HTML output: <hostname>_<mode>.html') | 5052 | print(' HTML output: <hostname>_<mode>.html') |
4986 | print(' raw dmesg output: <hostname>_<mode>_dmesg.txt') | 5053 | print(' raw dmesg output: <hostname>_<mode>_dmesg.txt') |
4987 | print(' raw ftrace output: <hostname>_<mode>_ftrace.txt') | 5054 | print(' raw ftrace output: <hostname>_<mode>_ftrace.txt') |
4988 | print('') | 5055 | print('') |
4989 | print('Options:') | 5056 | print('Options:') |
4990 | print(' [general]') | ||
4991 | print(' -h Print this help text') | 5057 | print(' -h Print this help text') |
4992 | print(' -v Print the current tool version') | 5058 | print(' -v Print the current tool version') |
4993 | print(' -config fn Pull arguments and config options from file fn') | 5059 | print(' -config fn Pull arguments and config options from file fn') |
4994 | print(' -verbose Print extra information during execution and analysis') | 5060 | print(' -verbose Print extra information during execution and analysis') |
4995 | print(' -status Test to see if the system is enabled to run this tool') | ||
4996 | print(' -modes List available suspend modes') | ||
4997 | print(' -m mode Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode) | 5061 | print(' -m mode Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode) |
4998 | print(' -o subdir Override the output subdirectory') | 5062 | print(' -o subdir Override the output subdirectory') |
4999 | print(' -rtcwake t Use rtcwake to autoresume after <t> seconds (default: disabled)') | 5063 | print(' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)') |
5000 | print(' -addlogs Add the dmesg and ftrace logs to the html output') | 5064 | print(' -addlogs Add the dmesg and ftrace logs to the html output') |
5001 | print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)') | 5065 | print(' -srgap Add a visible gap in the timeline between sus/res (default: disabled)') |
5002 | print(' [advanced]') | 5066 | print(' [advanced]') |
@@ -5012,23 +5076,25 @@ def printHelp(): | |||
5012 | print(' be created in a new subdirectory with a summary page.') | 5076 | print(' be created in a new subdirectory with a summary page.') |
5013 | print(' [debug]') | 5077 | print(' [debug]') |
5014 | print(' -f Use ftrace to create device callgraphs (default: disabled)') | 5078 | print(' -f Use ftrace to create device callgraphs (default: disabled)') |
5079 | print(' -maxdepth N limit the callgraph data to N call levels (default: 0=all)') | ||
5015 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') | 5080 | print(' -expandcg pre-expand the callgraph data in the html output (default: disabled)') |
5016 | print(' -flist Print the list of functions currently being captured in ftrace') | ||
5017 | print(' -flistall Print all functions capable of being captured in ftrace') | ||
5018 | print(' -fadd file Add functions to be graphed in the timeline from a list in a text file') | 5081 | print(' -fadd file Add functions to be graphed in the timeline from a list in a text file') |
5019 | print(' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names') | 5082 | print(' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names') |
5020 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') | 5083 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') |
5021 | print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)') | 5084 | print(' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)') |
5022 | print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)') | 5085 | print(' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)') |
5023 | print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)') | 5086 | print(' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)') |
5024 | print(' [utilities]') | 5087 | print(' [commands]') |
5088 | print(' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)') | ||
5089 | print(' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)') | ||
5090 | print(' -summary directory Create a summary of all test in this dir') | ||
5091 | print(' -modes List available suspend modes') | ||
5092 | print(' -status Test to see if the system is enabled to run this tool') | ||
5025 | print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table') | 5093 | print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table') |
5026 | print(' -usbtopo Print out the current USB topology with power info') | 5094 | print(' -usbtopo Print out the current USB topology with power info') |
5027 | print(' -usbauto Enable autosuspend for all connected USB devices') | 5095 | print(' -usbauto Enable autosuspend for all connected USB devices') |
5028 | print(' [re-analyze data from previous runs]') | 5096 | print(' -flist Print the list of functions currently being captured in ftrace') |
5029 | print(' -ftrace ftracefile Create HTML output using ftrace input') | 5097 | print(' -flistall Print all functions capable of being captured in ftrace') |
5030 | print(' -dmesg dmesgfile Create HTML output using dmesg (not needed for kernel >= 3.15)') | ||
5031 | print(' -summary directory Create a summary of all test in this dir') | ||
5032 | print('') | 5098 | print('') |
5033 | return True | 5099 | return True |
5034 | 5100 | ||
@@ -5076,9 +5142,18 @@ if __name__ == '__main__': | |||
5076 | sysvals.useprocmon = True | 5142 | sysvals.useprocmon = True |
5077 | elif(arg == '-dev'): | 5143 | elif(arg == '-dev'): |
5078 | sysvals.usedevsrc = True | 5144 | sysvals.usedevsrc = True |
5145 | elif(arg == '-maxdepth'): | ||
5146 | sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000) | ||
5079 | elif(arg == '-rtcwake'): | 5147 | elif(arg == '-rtcwake'): |
5080 | sysvals.rtcwake = True | 5148 | try: |
5081 | sysvals.rtcwaketime = getArgInt('-rtcwake', args, 0, 3600) | 5149 | val = args.next() |
5150 | except: | ||
5151 | doError('No rtcwake time supplied', True) | ||
5152 | if val.lower() == 'off': | ||
5153 | sysvals.rtcwake = False | ||
5154 | else: | ||
5155 | sysvals.rtcwake = True | ||
5156 | sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False) | ||
5082 | elif(arg == '-timeprec'): | 5157 | elif(arg == '-timeprec'): |
5083 | sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6)) | 5158 | sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6)) |
5084 | elif(arg == '-mindev'): | 5159 | elif(arg == '-mindev'): |
@@ -5201,7 +5276,6 @@ if __name__ == '__main__': | |||
5201 | elif(cmd == 'usbauto'): | 5276 | elif(cmd == 'usbauto'): |
5202 | setUSBDevicesAuto() | 5277 | setUSBDevicesAuto() |
5203 | elif(cmd == 'summary'): | 5278 | elif(cmd == 'summary'): |
5204 | print("Generating a summary of folder \"%s\"" % cmdarg) | ||
5205 | runSummary(cmdarg, True) | 5279 | runSummary(cmdarg, True) |
5206 | sys.exit() | 5280 | sys.exit() |
5207 | 5281 | ||
diff --git a/tools/power/pm-graph/bootgraph.8 b/tools/power/pm-graph/bootgraph.8 new file mode 100644 index 000000000000..55272a67b0e7 --- /dev/null +++ b/tools/power/pm-graph/bootgraph.8 | |||
@@ -0,0 +1,132 @@ | |||
1 | .TH BOOTGRAPH 8 | ||
2 | .SH NAME | ||
3 | bootgraph \- Kernel boot timing analysis | ||
4 | .SH SYNOPSIS | ||
5 | .ft B | ||
6 | .B bootgraph | ||
7 | .RB [ OPTIONS ] | ||
8 | .RB [ COMMAND ] | ||
9 | .SH DESCRIPTION | ||
10 | \fBbootgraph \fP reads the dmesg log from kernel boot and | ||
11 | creates an html representation of the initcall timeline up to the start | ||
12 | of the init process. | ||
13 | .PP | ||
14 | If no specific command is given, the tool reads the current dmesg log and | ||
15 | outputs bootgraph.html. | ||
16 | .PP | ||
17 | The tool can also augment the timeline with ftrace data on custom target | ||
18 | functions as well as full trace callgraphs. | ||
19 | .SH OPTIONS | ||
20 | .TP | ||
21 | \fB-h\fR | ||
22 | Print this help text | ||
23 | .TP | ||
24 | \fB-v\fR | ||
25 | Print the current tool version | ||
26 | .TP | ||
27 | \fB-addlogs\fR | ||
28 | Add the dmesg log to the html output. It will be viewable by | ||
29 | clicking a button in the timeline. | ||
30 | .TP | ||
31 | \fB-o \fIfile\fR | ||
32 | Override the HTML output filename (default: bootgraph.html) | ||
33 | .SS "Ftrace Debug" | ||
34 | .TP | ||
35 | \fB-f\fR | ||
36 | Use ftrace to add function detail (default: disabled) | ||
37 | .TP | ||
38 | \fB-callgraph\fR | ||
39 | Use ftrace to create initcall callgraphs (default: disabled). If -filter | ||
40 | is not used there will be one callgraph per initcall. This can produce | ||
41 | very large outputs, i.e. 10MB - 100MB. | ||
42 | .TP | ||
43 | \fB-maxdepth \fIlevel\fR | ||
44 | limit the callgraph trace depth to \fIlevel\fR (default: 2). This is | ||
45 | the best way to limit the output size when using -callgraph. | ||
46 | .TP | ||
47 | \fB-mincg \fIt\fR | ||
48 | Discard all callgraphs shorter than \fIt\fR milliseconds (default: 0=all). | ||
49 | This reduces the html file size as there can be many tiny callgraphs | ||
50 | which are barely visible in the timeline. | ||
51 | The value is a float: e.g. 0.001 represents 1 us. | ||
52 | .TP | ||
53 | \fB-timeprec \fIn\fR | ||
54 | Number of significant digits in timestamps (0:S, 3:ms, [6:us]) | ||
55 | .TP | ||
56 | \fB-expandcg\fR | ||
57 | pre-expand the callgraph data in the html output (default: disabled) | ||
58 | .TP | ||
59 | \fB-filter \fI"func1,func2,..."\fR | ||
60 | Instead of tracing each initcall, trace a custom list of functions (default: do_one_initcall) | ||
61 | |||
62 | .SH COMMANDS | ||
63 | .TP | ||
64 | \fB-reboot\fR | ||
65 | Reboot the machine and generate a new timeline automatically. Works in 4 steps. | ||
66 | 1. updates grub with the required kernel parameters | ||
67 | 2. installs a cron job which re-runs the tool after reboot | ||
68 | 3. reboots the system | ||
69 | 4. after startup, extracts the data and generates the timeline | ||
70 | .TP | ||
71 | \fB-manual\fR | ||
72 | Show the requirements to generate a new timeline manually. Requires 3 steps. | ||
73 | 1. append the string to the kernel command line via your native boot manager. | ||
74 | 2. reboot the system | ||
75 | 3. after startup, re-run the tool with the same arguments and no command | ||
76 | .TP | ||
77 | \fB-dmesg \fIfile\fR | ||
78 | Create HTML output from an existing dmesg file. | ||
79 | .TP | ||
80 | \fB-ftrace \fIfile\fR | ||
81 | Create HTML output from an existing ftrace file (used with -dmesg). | ||
82 | .TP | ||
83 | \fB-flistall\fR | ||
84 | Print all ftrace functions capable of being captured. These are all the | ||
85 | possible values you can add to trace via the -filter argument. | ||
86 | |||
87 | .SH EXAMPLES | ||
88 | Create a timeline using the current dmesg log. | ||
89 | .IP | ||
90 | \f(CW$ bootgraph\fR | ||
91 | .PP | ||
92 | Create a timeline using the current dmesg and ftrace log. | ||
93 | .IP | ||
94 | \f(CW$ bootgraph -callgraph\fR | ||
95 | .PP | ||
96 | Create a timeline using the current dmesg, add the log to the html and change the name. | ||
97 | .IP | ||
98 | \f(CW$ bootgraph -addlogs -o myboot.html\fR | ||
99 | .PP | ||
100 | Capture a new boot timeline by automatically rebooting the machine. | ||
101 | .IP | ||
102 | \f(CW$ sudo bootgraph -reboot -addlogs -o latestboot.html\fR | ||
103 | .PP | ||
104 | Capture a new boot timeline with function trace data. | ||
105 | .IP | ||
106 | \f(CW$ sudo bootgraph -reboot -f\fR | ||
107 | .PP | ||
108 | Capture a new boot timeline with trace & callgraph data. Skip callgraphs smaller than 5ms. | ||
109 | .IP | ||
110 | \f(CW$ sudo bootgraph -reboot -callgraph -mincg 5\fR | ||
111 | .PP | ||
112 | Capture a new boot timeline with callgraph data over custom functions. | ||
113 | .IP | ||
114 | \f(CW$ sudo bootgraph -reboot -callgraph -filter "acpi_ps_parse_aml,msleep"\fR | ||
115 | .PP | ||
116 | Capture a brand new boot timeline with manual reboot. | ||
117 | .IP | ||
118 | \f(CW$ sudo bootgraph -callgraph -manual\fR | ||
119 | .IP | ||
120 | \f(CW$ vi /etc/default/grub # add the CMDLINE string to your kernel params\fR | ||
121 | .IP | ||
122 | \f(CW$ sudo reboot # reboot the machine\fR | ||
123 | .IP | ||
124 | \f(CW$ sudo bootgraph -callgraph # re-run the tool after restart\fR | ||
125 | .PP | ||
126 | |||
127 | .SH "SEE ALSO" | ||
128 | dmesg(1), update-grub(8), crontab(1), reboot(8) | ||
129 | .PP | ||
130 | .SH AUTHOR | ||
131 | .nf | ||
132 | Written by Todd Brandt <todd.e.brandt@linux.intel.com> | ||
diff --git a/tools/power/pm-graph/sleepgraph.8 b/tools/power/pm-graph/sleepgraph.8 new file mode 100644 index 000000000000..610e72ebbc06 --- /dev/null +++ b/tools/power/pm-graph/sleepgraph.8 | |||
@@ -0,0 +1,243 @@ | |||
1 | .TH SLEEPGRAPH 8 | ||
2 | .SH NAME | ||
3 | sleepgraph \- Suspend/Resume timing analysis | ||
4 | .SH SYNOPSIS | ||
5 | .ft B | ||
6 | .B sleepgraph | ||
7 | .RB [ OPTIONS ] | ||
8 | .RB [ COMMAND ] | ||
9 | .SH DESCRIPTION | ||
10 | \fBsleepgraph \fP is designed to assist kernel and OS developers | ||
11 | in optimizing their linux stack's suspend/resume time. Using a kernel | ||
12 | image built with a few extra options enabled, the tool will execute a | ||
13 | suspend and capture dmesg and ftrace data until resume is complete. | ||
14 | This data is transformed into a device timeline and an optional | ||
15 | callgraph to give a detailed view of which devices/subsystems are | ||
16 | taking the most time in suspend/resume. | ||
17 | .PP | ||
18 | If no specific command is given, the default behavior is to initiate | ||
19 | a suspend/resume. | ||
20 | .PP | ||
21 | Generates output files in subdirectory: suspend-yymmdd-HHMMSS | ||
22 | html timeline : <hostname>_<mode>.html | ||
23 | raw dmesg file : <hostname>_<mode>_dmesg.txt | ||
24 | raw ftrace file : <hostname>_<mode>_ftrace.txt | ||
25 | .SH OPTIONS | ||
26 | .TP | ||
27 | \fB-h\fR | ||
28 | Print the help text. | ||
29 | .TP | ||
30 | \fB-v\fR | ||
31 | Print the current tool version. | ||
32 | .TP | ||
33 | \fB-verbose\fR | ||
34 | Print extra information during execution and analysis. | ||
35 | .TP | ||
36 | \fB-config \fIfile\fR | ||
37 | Pull arguments and config options from a file. | ||
38 | .TP | ||
39 | \fB-m \fImode\fR | ||
40 | Mode to initiate for suspend e.g. standby, freeze, mem (default: mem). | ||
41 | .TP | ||
42 | \fB-o \fIsubdir\fR | ||
43 | Override the output subdirectory. Use {date}, {time}, {hostname} for current values. | ||
44 | .sp | ||
45 | e.g. suspend-{hostname}-{date}-{time} | ||
46 | .TP | ||
47 | \fB-rtcwake \fIt\fR | off | ||
48 | Use rtcwake to autoresume after \fIt\fR seconds (default: 15). Set t to "off" to | ||
49 | disable rtcwake and require a user keypress to resume. | ||
50 | .TP | ||
51 | \fB-addlogs\fR | ||
52 | Add the dmesg and ftrace logs to the html output. They will be viewable by | ||
53 | clicking buttons in the timeline. | ||
54 | |||
55 | .SS "Advanced" | ||
56 | .TP | ||
57 | \fB-cmd \fIstr\fR | ||
58 | Run the timeline over a custom suspend command, e.g. pm-suspend. By default | ||
59 | the tool forces suspend via /sys/power/state so this allows testing over | ||
60 | an OS's official suspend method. The output file will change to | ||
61 | hostname_command.html and will autodetect which suspend mode was triggered. | ||
62 | .TP | ||
63 | \fB-filter \fI"d1,d2,..."\fR | ||
64 | Filter out all but these device callbacks. These strings can be device names | ||
65 | or module names. e.g. 0000:00:02.0, ata5, i915, usb, etc. | ||
66 | .TP | ||
67 | \fB-mindev \fIt\fR | ||
68 | Discard all device callbacks shorter than \fIt\fR milliseconds (default: 0.0). | ||
69 | This reduces the html file size as there can be many tiny callbacks which are barely | ||
70 | visible. The value is a float: e.g. 0.001 represents 1 us. | ||
71 | .TP | ||
72 | \fB-proc\fR | ||
73 | Add usermode process info into the timeline (default: disabled). | ||
74 | .TP | ||
75 | \fB-dev\fR | ||
76 | Add kernel source calls and threads to the timeline (default: disabled). | ||
77 | .TP | ||
78 | \fB-x2\fR | ||
79 | Run two suspend/resumes back to back (default: disabled). | ||
80 | .TP | ||
81 | \fB-x2delay \fIt\fR | ||
82 | Include \fIt\fR ms delay between multiple test runs (default: 0 ms). | ||
83 | .TP | ||
84 | \fB-predelay \fIt\fR | ||
85 | Include \fIt\fR ms delay before 1st suspend (default: 0 ms). | ||
86 | .TP | ||
87 | \fB-postdelay \fIt\fR | ||
88 | Include \fIt\fR ms delay after last resume (default: 0 ms). | ||
89 | .TP | ||
90 | \fB-multi \fIn d\fR | ||
91 | Execute \fIn\fR consecutive tests at \fId\fR seconds intervals. The outputs will | ||
92 | be created in a new subdirectory with a summary page: suspend-xN-{date}-{time}. | ||
93 | |||
94 | .SS "Ftrace Debug" | ||
95 | .TP | ||
96 | \fB-f\fR | ||
97 | Use ftrace to create device callgraphs (default: disabled). This can produce | ||
98 | very large outputs, i.e. 10MB - 100MB. | ||
99 | .TP | ||
100 | \fB-maxdepth \fIlevel\fR | ||
101 | limit the callgraph trace depth to \fIlevel\fR (default: 0=all). This is | ||
102 | the best way to limit the output size when using callgraphs via -f. | ||
103 | .TP | ||
104 | \fB-expandcg\fR | ||
105 | pre-expand the callgraph data in the html output (default: disabled) | ||
106 | .TP | ||
107 | \fB-fadd \fIfile\fR | ||
108 | Add functions to be graphed in the timeline from a list in a text file | ||
109 | .TP | ||
110 | \fB-mincg \fIt\fR | ||
111 | Discard all callgraphs shorter than \fIt\fR milliseconds (default: 0.0). | ||
112 | This reduces the html file size as there can be many tiny callgraphs | ||
113 | which are barely visible in the timeline. | ||
114 | The value is a float: e.g. 0.001 represents 1 us. | ||
115 | .TP | ||
116 | \fB-cgphase \fIp\fR | ||
117 | Only show callgraph data for phase \fIp\fR (e.g. suspend_late). | ||
118 | .TP | ||
119 | \fB-cgtest \fIn\fR | ||
120 | In an x2 run, only show callgraph data for test \fIn\fR (e.g. 0 or 1). | ||
121 | .TP | ||
122 | \fB-timeprec \fIn\fR | ||
123 | Number of significant digits in timestamps (0:S, [3:ms], 6:us). | ||
124 | |||
125 | .SH COMMANDS | ||
126 | .TP | ||
127 | \fB-ftrace \fIfile\fR | ||
128 | Create HTML output from an existing ftrace file. | ||
129 | .TP | ||
130 | \fB-dmesg \fIfile\fR | ||
131 | Create HTML output from an existing dmesg file. | ||
132 | .TP | ||
133 | \fB-summary \fIindir\fR | ||
134 | Create a summary page of all tests in \fIindir\fR. Creates summary.html | ||
135 | in the current folder. The output page is a table of tests with | ||
136 | suspend and resume values sorted by suspend mode, host, and kernel. | ||
137 | Includes test averages by mode and links to the test html files. | ||
138 | .TP | ||
139 | \fB-modes\fR | ||
140 | List available suspend modes. | ||
141 | .TP | ||
142 | \fB-status\fR | ||
143 | Test to see if the system is able to run this tool. Use this along | ||
144 | with any options you intend to use to see if they will work. | ||
145 | .TP | ||
146 | \fB-fpdt\fR | ||
147 | Print out the contents of the ACPI Firmware Performance Data Table. | ||
148 | .TP | ||
149 | \fB-usbtopo\fR | ||
150 | Print out the current USB topology with power info. | ||
151 | .TP | ||
152 | \fB-usbauto\fR | ||
153 | Enable autosuspend for all connected USB devices. | ||
154 | .TP | ||
155 | \fB-flist\fR | ||
156 | Print the list of ftrace functions currently being captured. Functions | ||
157 | that are not available as symbols in the current kernel are shown in red. | ||
158 | By default, the tool traces a list of important suspend/resume functions | ||
159 | in order to better fill out the timeline. If the user has added their own | ||
160 | with -fadd they will also be checked. | ||
161 | .TP | ||
162 | \fB-flistall\fR | ||
163 | Print all ftrace functions capable of being captured. These are all the | ||
164 | possible values you can add to trace via the -fadd argument. | ||
165 | |||
166 | .SH EXAMPLES | ||
167 | .SS "Simple Commands" | ||
168 | Check which suspend modes are currently supported. | ||
169 | .IP | ||
170 | \f(CW$ sleepgraph -modes\fR | ||
171 | .PP | ||
172 | Read the Firmware Performance Data Table (FPDT) | ||
173 | .IP | ||
174 | \f(CW$ sudo sleepgraph -fpdt\fR | ||
175 | .PP | ||
176 | Print out the current USB power topology | ||
177 | .IP | ||
178 | \f(CW$ sleepgraph -usbtopo | ||
179 | .PP | ||
180 | Verify that you can run a command with a set of arguments | ||
181 | .IP | ||
182 | \f(CW$ sudo sleepgraph -f -rtcwake 30 -status | ||
183 | .PP | ||
184 | Generate a summary of all timelines in a particular folder. | ||
185 | .IP | ||
186 | \f(CW$ sleepgraph -summary ~/workspace/myresults/\fR | ||
187 | .PP | ||
188 | Re-generate the html output from a previous run's dmesg and ftrace log. | ||
189 | .IP | ||
190 | \f(CW$ sleepgraph -dmesg myhost_mem_dmesg.txt -ftrace myhost_mem_ftrace.txt\fR | ||
191 | .PP | ||
192 | |||
193 | .SS "Capturing Simple Timelines" | ||
194 | Execute a mem suspend with a 15 second wakeup. Include the logs in the html. | ||
195 | .IP | ||
196 | \f(CW$ sudo sleepgraph -rtcwake 15 -addlogs\fR | ||
197 | .PP | ||
198 | Execute a standby with a 15 second wakeup. Change the output folder name. | ||
199 | .IP | ||
200 | \f(CW$ sudo sleepgraph -m standby -rtcwake 15 -o "standby-{hostname}-{date}-{time}"\fR | ||
201 | .PP | ||
202 | Execute a freeze with no wakeup (require keypress). Change output folder name. | ||
203 | .IP | ||
204 | \f(CW$ sudo sleepgraph -m freeze -rtcwake off -o "freeze-{hostname}-{date}-{time}"\fR | ||
205 | .PP | ||
206 | |||
207 | .SS "Capturing Advanced Timelines" | ||
208 | Execute a suspend & include dev mode source calls, limit callbacks to 5ms or larger. | ||
209 | .IP | ||
210 | \f(CW$ sudo sleepgraph -m mem -rtcwake 15 -dev -mindev 5\fR | ||
211 | .PP | ||
212 | Run two suspends back to back, include a 500ms delay before, after, and in between runs. | ||
213 | .IP | ||
214 | \f(CW$ sudo sleepgraph -m mem -rtcwake 15 -x2 -predelay 500 -x2delay 500 -postdelay 500\fR | ||
215 | .PP | ||
216 | Do a batch run of 10 freezes with 30 seconds delay between runs. | ||
217 | .IP | ||
218 | \f(CW$ sudo sleepgraph -m freeze -rtcwake 15 -multi 10 30\fR | ||
219 | .PP | ||
220 | Execute a suspend using a custom command. | ||
221 | .IP | ||
222 | \f(CW$ sudo sleepgraph -cmd "echo mem > /sys/power/state" -rtcwake 15\fR | ||
223 | .PP | ||
224 | |||
225 | |||
226 | .SS "Capturing Timelines with Callgraph Data" | ||
227 | Add device callgraphs. Limit the trace depth and only show callgraphs 10ms or larger. | ||
228 | .IP | ||
229 | \f(CW$ sudo sleepgraph -m mem -rtcwake 15 -f -maxdepth 5 -mincg 10\fR | ||
230 | .PP | ||
231 | Capture a full callgraph across all suspend, then filter the html by a single phase. | ||
232 | .IP | ||
233 | \f(CW$ sudo sleepgraph -m mem -rtcwake 15 -f\fR | ||
234 | .IP | ||
235 | \f(CW$ sleepgraph -dmesg host_mem_dmesg.txt -ftrace host_mem_ftrace.txt -f -cgphase resume | ||
236 | .PP | ||
237 | |||
238 | .SH "SEE ALSO" | ||
239 | dmesg(1) | ||
240 | .PP | ||
241 | .SH AUTHOR | ||
242 | .nf | ||
243 | Written by Todd Brandt <todd.e.brandt@linux.intel.com> | ||
diff --git a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py index fd706ac0f347..0b24dd9d01ff 100755 --- a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py +++ b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py | |||
@@ -353,6 +353,14 @@ def split_csv(): | |||
353 | os.system('grep -m 1 common_cpu cpu.csv > cpu{:0>3}.csv'.format(index)) | 353 | os.system('grep -m 1 common_cpu cpu.csv > cpu{:0>3}.csv'.format(index)) |
354 | os.system('grep CPU_{:0>3} cpu.csv >> cpu{:0>3}.csv'.format(index, index)) | 354 | os.system('grep CPU_{:0>3} cpu.csv >> cpu{:0>3}.csv'.format(index, index)) |
355 | 355 | ||
356 | def fix_ownership(path): | ||
357 | """Change the owner of the file to SUDO_UID, if required""" | ||
358 | |||
359 | uid = os.environ.get('SUDO_UID') | ||
360 | gid = os.environ.get('SUDO_GID') | ||
361 | if uid is not None: | ||
362 | os.chown(path, int(uid), int(gid)) | ||
363 | |||
356 | def cleanup_data_files(): | 364 | def cleanup_data_files(): |
357 | """ clean up existing data files """ | 365 | """ clean up existing data files """ |
358 | 366 | ||
@@ -518,12 +526,16 @@ else: | |||
518 | 526 | ||
519 | if not os.path.exists('results'): | 527 | if not os.path.exists('results'): |
520 | os.mkdir('results') | 528 | os.mkdir('results') |
529 | # The regular user needs to own the directory, not root. | ||
530 | fix_ownership('results') | ||
521 | 531 | ||
522 | os.chdir('results') | 532 | os.chdir('results') |
523 | if os.path.exists(testname): | 533 | if os.path.exists(testname): |
524 | print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.') | 534 | print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.') |
525 | sys.exit() | 535 | sys.exit() |
526 | os.mkdir(testname) | 536 | os.mkdir(testname) |
537 | # The regular user needs to own the directory, not root. | ||
538 | fix_ownership(testname) | ||
527 | os.chdir(testname) | 539 | os.chdir(testname) |
528 | 540 | ||
529 | # Temporary (or perhaps not) | 541 | # Temporary (or perhaps not) |
@@ -566,4 +578,9 @@ plot_scaled_cpu() | |||
566 | plot_boost_cpu() | 578 | plot_boost_cpu() |
567 | plot_ghz_cpu() | 579 | plot_ghz_cpu() |
568 | 580 | ||
581 | # It is preferrable, but not necessary, that the regular user owns the files, not root. | ||
582 | for root, dirs, files in os.walk('.'): | ||
583 | for f in files: | ||
584 | fix_ownership(f) | ||
585 | |||
569 | os.chdir('../../') | 586 | os.chdir('../../') |