diff options
Diffstat (limited to 'tools/power/pm-graph/analyze_boot.py')
-rwxr-xr-x | tools/power/pm-graph/analyze_boot.py | 586 |
1 files changed, 387 insertions, 199 deletions
diff --git a/tools/power/pm-graph/analyze_boot.py b/tools/power/pm-graph/analyze_boot.py index 3e1dcbbf1adc..e83df141a597 100755 --- a/tools/power/pm-graph/analyze_boot.py +++ b/tools/power/pm-graph/analyze_boot.py | |||
@@ -42,7 +42,7 @@ import analyze_suspend as aslib | |||
42 | # store system values and test parameters | 42 | # store system values and test parameters |
43 | class SystemValues(aslib.SystemValues): | 43 | class SystemValues(aslib.SystemValues): |
44 | title = 'BootGraph' | 44 | title = 'BootGraph' |
45 | version = 2.0 | 45 | version = '2.1' |
46 | hostname = 'localhost' | 46 | hostname = 'localhost' |
47 | testtime = '' | 47 | testtime = '' |
48 | kernel = '' | 48 | kernel = '' |
@@ -50,9 +50,14 @@ class SystemValues(aslib.SystemValues): | |||
50 | ftracefile = '' | 50 | ftracefile = '' |
51 | htmlfile = 'bootgraph.html' | 51 | htmlfile = 'bootgraph.html' |
52 | outfile = '' | 52 | outfile = '' |
53 | phoronix = False | 53 | testdir = '' |
54 | addlogs = False | 54 | testdirprefix = 'boot' |
55 | embedded = False | ||
56 | testlog = False | ||
57 | dmesglog = False | ||
58 | ftracelog = False | ||
55 | useftrace = False | 59 | useftrace = False |
60 | usecallgraph = False | ||
56 | usedevsrc = True | 61 | usedevsrc = True |
57 | suspendmode = 'boot' | 62 | suspendmode = 'boot' |
58 | max_graph_depth = 2 | 63 | max_graph_depth = 2 |
@@ -61,10 +66,12 @@ class SystemValues(aslib.SystemValues): | |||
61 | manual = False | 66 | manual = False |
62 | iscronjob = False | 67 | iscronjob = False |
63 | timeformat = '%.6f' | 68 | timeformat = '%.6f' |
69 | bootloader = 'grub' | ||
70 | blexec = [] | ||
64 | def __init__(self): | 71 | def __init__(self): |
65 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): | 72 | if('LOG_FILE' in os.environ and 'TEST_RESULTS_IDENTIFIER' in os.environ): |
66 | self.phoronix = True | 73 | self.embedded = True |
67 | self.addlogs = True | 74 | self.dmesglog = True |
68 | self.outfile = os.environ['LOG_FILE'] | 75 | self.outfile = os.environ['LOG_FILE'] |
69 | self.htmlfile = os.environ['LOG_FILE'] | 76 | self.htmlfile = os.environ['LOG_FILE'] |
70 | self.hostname = platform.node() | 77 | self.hostname = platform.node() |
@@ -76,42 +83,80 @@ class SystemValues(aslib.SystemValues): | |||
76 | self.kernel = self.kernelVersion(val) | 83 | self.kernel = self.kernelVersion(val) |
77 | else: | 84 | else: |
78 | self.kernel = 'unknown' | 85 | self.kernel = 'unknown' |
86 | self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S') | ||
79 | def kernelVersion(self, msg): | 87 | def kernelVersion(self, msg): |
80 | return msg.split()[2] | 88 | return msg.split()[2] |
89 | def checkFtraceKernelVersion(self): | ||
90 | val = tuple(map(int, self.kernel.split('-')[0].split('.'))) | ||
91 | if val >= (4, 10, 0): | ||
92 | return True | ||
93 | return False | ||
81 | def kernelParams(self): | 94 | def kernelParams(self): |
82 | cmdline = 'initcall_debug log_buf_len=32M' | 95 | cmdline = 'initcall_debug log_buf_len=32M' |
83 | if self.useftrace: | 96 | if self.useftrace: |
84 | cmdline += ' trace_buf_size=128M trace_clock=global '\ | 97 | if self.cpucount > 0: |
98 | bs = min(self.memtotal / 2, 2*1024*1024) / self.cpucount | ||
99 | else: | ||
100 | bs = 131072 | ||
101 | cmdline += ' trace_buf_size=%dK trace_clock=global '\ | ||
85 | 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ | 102 | 'trace_options=nooverwrite,funcgraph-abstime,funcgraph-cpu,'\ |
86 | 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ | 103 | 'funcgraph-duration,funcgraph-proc,funcgraph-tail,'\ |
87 | 'nofuncgraph-overhead,context-info,graph-time '\ | 104 | 'nofuncgraph-overhead,context-info,graph-time '\ |
88 | 'ftrace=function_graph '\ | 105 | 'ftrace=function_graph '\ |
89 | 'ftrace_graph_max_depth=%d '\ | 106 | 'ftrace_graph_max_depth=%d '\ |
90 | 'ftrace_graph_filter=%s' % \ | 107 | 'ftrace_graph_filter=%s' % \ |
91 | (self.max_graph_depth, self.graph_filter) | 108 | (bs, self.max_graph_depth, self.graph_filter) |
92 | return cmdline | 109 | return cmdline |
93 | def setGraphFilter(self, val): | 110 | def setGraphFilter(self, val): |
94 | fp = open(self.tpath+'available_filter_functions') | 111 | master = self.getBootFtraceFilterFunctions() |
95 | master = fp.read().split('\n') | 112 | fs = '' |
96 | fp.close() | ||
97 | for i in val.split(','): | 113 | for i in val.split(','): |
98 | func = i.strip() | 114 | func = i.strip() |
115 | if func == '': | ||
116 | doError('badly formatted filter function string') | ||
117 | if '[' in func or ']' in func: | ||
118 | doError('loadable module functions not allowed - "%s"' % func) | ||
119 | if ' ' in func: | ||
120 | doError('spaces found in filter functions - "%s"' % func) | ||
99 | if func not in master: | 121 | if func not in master: |
100 | doError('function "%s" not available for ftrace' % func) | 122 | doError('function "%s" not available for ftrace' % func) |
101 | self.graph_filter = val | 123 | if not fs: |
124 | fs = func | ||
125 | else: | ||
126 | fs += ','+func | ||
127 | if not fs: | ||
128 | doError('badly formatted filter function string') | ||
129 | self.graph_filter = fs | ||
130 | def getBootFtraceFilterFunctions(self): | ||
131 | self.rootCheck(True) | ||
132 | fp = open(self.tpath+'available_filter_functions') | ||
133 | fulllist = fp.read().split('\n') | ||
134 | fp.close() | ||
135 | list = [] | ||
136 | for i in fulllist: | ||
137 | if not i or ' ' in i or '[' in i or ']' in i: | ||
138 | continue | ||
139 | list.append(i) | ||
140 | return list | ||
141 | def myCronJob(self, line): | ||
142 | if '@reboot' not in line: | ||
143 | return False | ||
144 | if 'bootgraph' in line or 'analyze_boot.py' in line or '-cronjob' in line: | ||
145 | return True | ||
146 | return False | ||
102 | def cronjobCmdString(self): | 147 | def cronjobCmdString(self): |
103 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) | 148 | cmdline = '%s -cronjob' % os.path.abspath(sys.argv[0]) |
104 | args = iter(sys.argv[1:]) | 149 | args = iter(sys.argv[1:]) |
105 | for arg in args: | 150 | for arg in args: |
106 | if arg in ['-h', '-v', '-cronjob', '-reboot']: | 151 | if arg in ['-h', '-v', '-cronjob', '-reboot']: |
107 | continue | 152 | continue |
108 | elif arg in ['-o', '-dmesg', '-ftrace', '-filter']: | 153 | elif arg in ['-o', '-dmesg', '-ftrace', '-func']: |
109 | args.next() | 154 | args.next() |
110 | continue | 155 | continue |
111 | cmdline += ' '+arg | 156 | cmdline += ' '+arg |
112 | if self.graph_filter != 'do_one_initcall': | 157 | if self.graph_filter != 'do_one_initcall': |
113 | cmdline += ' -filter "%s"' % self.graph_filter | 158 | cmdline += ' -func "%s"' % self.graph_filter |
114 | cmdline += ' -o "%s"' % os.path.abspath(self.htmlfile) | 159 | cmdline += ' -o "%s"' % os.path.abspath(self.testdir) |
115 | return cmdline | 160 | return cmdline |
116 | def manualRebootRequired(self): | 161 | def manualRebootRequired(self): |
117 | cmdline = self.kernelParams() | 162 | cmdline = self.kernelParams() |
@@ -121,6 +166,39 @@ class SystemValues(aslib.SystemValues): | |||
121 | print '3. After reboot, re-run this tool with the same arguments but no command (w/o -reboot or -manual).\n' | 166 | 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 | 167 | print 'CMDLINE="%s"' % cmdline |
123 | sys.exit() | 168 | sys.exit() |
169 | def getExec(self, cmd): | ||
170 | dirlist = ['/sbin', '/bin', '/usr/sbin', '/usr/bin', | ||
171 | '/usr/local/sbin', '/usr/local/bin'] | ||
172 | for path in dirlist: | ||
173 | cmdfull = os.path.join(path, cmd) | ||
174 | if os.path.exists(cmdfull): | ||
175 | return cmdfull | ||
176 | return '' | ||
177 | def blGrub(self): | ||
178 | blcmd = '' | ||
179 | for cmd in ['update-grub', 'grub-mkconfig', 'grub2-mkconfig']: | ||
180 | if blcmd: | ||
181 | break | ||
182 | blcmd = self.getExec(cmd) | ||
183 | if not blcmd: | ||
184 | doError('[GRUB] missing update command') | ||
185 | if not os.path.exists('/etc/default/grub'): | ||
186 | doError('[GRUB] missing /etc/default/grub') | ||
187 | if 'grub2' in blcmd: | ||
188 | cfg = '/boot/grub2/grub.cfg' | ||
189 | else: | ||
190 | cfg = '/boot/grub/grub.cfg' | ||
191 | if not os.path.exists(cfg): | ||
192 | doError('[GRUB] missing %s' % cfg) | ||
193 | if 'update-grub' in blcmd: | ||
194 | self.blexec = [blcmd] | ||
195 | else: | ||
196 | self.blexec = [blcmd, '-o', cfg] | ||
197 | def getBootLoader(self): | ||
198 | if self.bootloader == 'grub': | ||
199 | self.blGrub() | ||
200 | else: | ||
201 | doError('unknown boot loader: %s' % self.bootloader) | ||
124 | 202 | ||
125 | sysvals = SystemValues() | 203 | sysvals = SystemValues() |
126 | 204 | ||
@@ -136,20 +214,23 @@ class Data(aslib.Data): | |||
136 | idstr = '' | 214 | idstr = '' |
137 | html_device_id = 0 | 215 | html_device_id = 0 |
138 | valid = False | 216 | valid = False |
139 | initstart = 0.0 | 217 | tUserMode = 0.0 |
140 | boottime = '' | 218 | boottime = '' |
141 | phases = ['boot'] | 219 | phases = ['kernel', 'user'] |
142 | do_one_initcall = False | 220 | do_one_initcall = False |
143 | def __init__(self, num): | 221 | def __init__(self, num): |
144 | self.testnumber = num | 222 | self.testnumber = num |
145 | self.idstr = 'a' | 223 | self.idstr = 'a' |
146 | self.dmesgtext = [] | 224 | self.dmesgtext = [] |
147 | self.dmesg = { | 225 | self.dmesg = { |
148 | 'boot': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, 'color': '#dddddd'} | 226 | 'kernel': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, |
227 | 'order': 0, 'color': 'linear-gradient(to bottom, #fff, #bcf)'}, | ||
228 | 'user': {'list': dict(), 'start': -1.0, 'end': -1.0, 'row': 0, | ||
229 | 'order': 1, 'color': '#fff'} | ||
149 | } | 230 | } |
150 | def deviceTopology(self): | 231 | def deviceTopology(self): |
151 | return '' | 232 | return '' |
152 | def newAction(self, phase, name, start, end, ret, ulen): | 233 | def newAction(self, phase, name, pid, start, end, ret, ulen): |
153 | # new device callback for a specific phase | 234 | # new device callback for a specific phase |
154 | self.html_device_id += 1 | 235 | self.html_device_id += 1 |
155 | devid = '%s%d' % (self.idstr, self.html_device_id) | 236 | devid = '%s%d' % (self.idstr, self.html_device_id) |
@@ -163,41 +244,46 @@ class Data(aslib.Data): | |||
163 | name = '%s[%d]' % (origname, i) | 244 | name = '%s[%d]' % (origname, i) |
164 | i += 1 | 245 | i += 1 |
165 | list[name] = {'name': name, 'start': start, 'end': end, | 246 | list[name] = {'name': name, 'start': start, 'end': end, |
166 | 'pid': 0, 'length': length, 'row': 0, 'id': devid, | 247 | 'pid': pid, 'length': length, 'row': 0, 'id': devid, |
167 | 'ret': ret, 'ulen': ulen } | 248 | 'ret': ret, 'ulen': ulen } |
168 | return name | 249 | return name |
169 | def deviceMatch(self, cg): | 250 | def deviceMatch(self, pid, cg): |
170 | if cg.end - cg.start == 0: | 251 | if cg.end - cg.start == 0: |
171 | return True | 252 | return True |
172 | list = self.dmesg['boot']['list'] | 253 | for p in data.phases: |
173 | for devname in list: | 254 | list = self.dmesg[p]['list'] |
174 | dev = list[devname] | 255 | for devname in list: |
175 | if cg.name == 'do_one_initcall': | 256 | dev = list[devname] |
176 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): | 257 | if pid != dev['pid']: |
177 | dev['ftrace'] = cg | 258 | continue |
178 | self.do_one_initcall = True | 259 | if cg.name == 'do_one_initcall': |
179 | return True | 260 | if(cg.start <= dev['start'] and cg.end >= dev['end'] and dev['length'] > 0): |
180 | else: | 261 | dev['ftrace'] = cg |
181 | if(cg.start > dev['start'] and cg.end < dev['end']): | 262 | self.do_one_initcall = True |
182 | if 'ftraces' not in dev: | 263 | return True |
183 | dev['ftraces'] = [] | 264 | else: |
184 | dev['ftraces'].append(cg) | 265 | if(cg.start > dev['start'] and cg.end < dev['end']): |
185 | return True | 266 | if 'ftraces' not in dev: |
267 | dev['ftraces'] = [] | ||
268 | dev['ftraces'].append(cg) | ||
269 | return True | ||
186 | return False | 270 | return False |
187 | 271 | ||
188 | # ----------------- FUNCTIONS -------------------- | 272 | # ----------------- FUNCTIONS -------------------- |
189 | 273 | ||
190 | # Function: loadKernelLog | 274 | # Function: parseKernelLog |
191 | # Description: | 275 | # Description: |
192 | # Load a raw kernel log from dmesg | 276 | # parse a kernel log for boot data |
193 | def loadKernelLog(): | 277 | def parseKernelLog(): |
278 | phase = 'kernel' | ||
194 | data = Data(0) | 279 | data = Data(0) |
195 | data.dmesg['boot']['start'] = data.start = ktime = 0.0 | 280 | data.dmesg['kernel']['start'] = data.start = ktime = 0.0 |
196 | sysvals.stamp = { | 281 | sysvals.stamp = { |
197 | 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), | 282 | 'time': datetime.now().strftime('%B %d %Y, %I:%M:%S %p'), |
198 | 'host': sysvals.hostname, | 283 | 'host': sysvals.hostname, |
199 | 'mode': 'boot', 'kernel': ''} | 284 | 'mode': 'boot', 'kernel': ''} |
200 | 285 | ||
286 | tp = aslib.TestProps() | ||
201 | devtemp = dict() | 287 | devtemp = dict() |
202 | if(sysvals.dmesgfile): | 288 | if(sysvals.dmesgfile): |
203 | lf = open(sysvals.dmesgfile, 'r') | 289 | lf = open(sysvals.dmesgfile, 'r') |
@@ -205,6 +291,13 @@ def loadKernelLog(): | |||
205 | lf = Popen('dmesg', stdout=PIPE).stdout | 291 | lf = Popen('dmesg', stdout=PIPE).stdout |
206 | for line in lf: | 292 | for line in lf: |
207 | line = line.replace('\r\n', '') | 293 | line = line.replace('\r\n', '') |
294 | # grab the stamp and sysinfo | ||
295 | if re.match(tp.stampfmt, line): | ||
296 | tp.stamp = line | ||
297 | continue | ||
298 | elif re.match(tp.sysinfofmt, line): | ||
299 | tp.sysinfo = line | ||
300 | continue | ||
208 | idx = line.find('[') | 301 | idx = line.find('[') |
209 | if idx > 1: | 302 | if idx > 1: |
210 | line = line[idx:] | 303 | line = line[idx:] |
@@ -215,7 +308,6 @@ def loadKernelLog(): | |||
215 | if(ktime > 120): | 308 | if(ktime > 120): |
216 | break | 309 | break |
217 | msg = m.group('msg') | 310 | msg = m.group('msg') |
218 | data.end = data.initstart = ktime | ||
219 | data.dmesgtext.append(line) | 311 | data.dmesgtext.append(line) |
220 | if(ktime == 0.0 and re.match('^Linux version .*', msg)): | 312 | if(ktime == 0.0 and re.match('^Linux version .*', msg)): |
221 | if(not sysvals.stamp['kernel']): | 313 | if(not sysvals.stamp['kernel']): |
@@ -228,43 +320,39 @@ def loadKernelLog(): | |||
228 | data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') | 320 | data.boottime = bt.strftime('%Y-%m-%d_%H:%M:%S') |
229 | sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') | 321 | sysvals.stamp['time'] = bt.strftime('%B %d %Y, %I:%M:%S %p') |
230 | continue | 322 | continue |
231 | m = re.match('^calling *(?P<f>.*)\+.*', msg) | 323 | m = re.match('^calling *(?P<f>.*)\+.* @ (?P<p>[0-9]*)', msg) |
232 | if(m): | 324 | if(m): |
233 | devtemp[m.group('f')] = ktime | 325 | func = m.group('f') |
326 | pid = int(m.group('p')) | ||
327 | devtemp[func] = (ktime, pid) | ||
234 | continue | 328 | continue |
235 | m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) | 329 | m = re.match('^initcall *(?P<f>.*)\+.* returned (?P<r>.*) after (?P<t>.*) usecs', msg) |
236 | if(m): | 330 | if(m): |
237 | data.valid = True | 331 | data.valid = True |
332 | data.end = ktime | ||
238 | f, r, t = m.group('f', 'r', 't') | 333 | f, r, t = m.group('f', 'r', 't') |
239 | if(f in devtemp): | 334 | if(f in devtemp): |
240 | data.newAction('boot', f, devtemp[f], ktime, int(r), int(t)) | 335 | start, pid = devtemp[f] |
241 | data.end = ktime | 336 | data.newAction(phase, f, pid, start, ktime, int(r), int(t)) |
242 | del devtemp[f] | 337 | del devtemp[f] |
243 | continue | 338 | continue |
244 | if(re.match('^Freeing unused kernel memory.*', msg)): | 339 | if(re.match('^Freeing unused kernel memory.*', msg)): |
245 | break | 340 | data.tUserMode = ktime |
246 | 341 | data.dmesg['kernel']['end'] = ktime | |
247 | data.dmesg['boot']['end'] = data.end | 342 | data.dmesg['user']['start'] = ktime |
343 | phase = 'user' | ||
344 | |||
345 | if tp.stamp: | ||
346 | sysvals.stamp = 0 | ||
347 | tp.parseStamp(data, sysvals) | ||
348 | data.dmesg['user']['end'] = data.end | ||
248 | lf.close() | 349 | lf.close() |
249 | return data | 350 | return data |
250 | 351 | ||
251 | # Function: loadTraceLog | 352 | # Function: parseTraceLog |
252 | # Description: | 353 | # Description: |
253 | # Check if trace is available and copy to a temp file | 354 | # Check if trace is available and copy to a temp file |
254 | def loadTraceLog(data): | 355 | def parseTraceLog(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 | 356 | # parse the trace log |
269 | ftemp = dict() | 357 | ftemp = dict() |
270 | tp = aslib.TestProps() | 358 | tp = aslib.TestProps() |
@@ -306,9 +394,29 @@ def loadTraceLog(data): | |||
306 | print('Sanity check failed for %s-%d' % (proc, pid)) | 394 | print('Sanity check failed for %s-%d' % (proc, pid)) |
307 | continue | 395 | continue |
308 | # match cg data to devices | 396 | # match cg data to devices |
309 | if not data.deviceMatch(cg): | 397 | if not data.deviceMatch(pid, cg): |
310 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) | 398 | print ' BAD: %s %s-%d [%f - %f]' % (cg.name, proc, pid, cg.start, cg.end) |
311 | 399 | ||
400 | # Function: retrieveLogs | ||
401 | # Description: | ||
402 | # Create copies of dmesg and/or ftrace for later processing | ||
403 | def retrieveLogs(): | ||
404 | # check ftrace is configured first | ||
405 | if sysvals.useftrace: | ||
406 | tracer = sysvals.fgetVal('current_tracer').strip() | ||
407 | if tracer != 'function_graph': | ||
408 | doError('ftrace not configured for a boot callgraph') | ||
409 | # create the folder and get dmesg | ||
410 | sysvals.systemInfo(aslib.dmidecode(sysvals.mempath)) | ||
411 | sysvals.initTestOutput('boot') | ||
412 | sysvals.writeDatafileHeader(sysvals.dmesgfile) | ||
413 | call('dmesg >> '+sysvals.dmesgfile, shell=True) | ||
414 | if not sysvals.useftrace: | ||
415 | return | ||
416 | # get ftrace | ||
417 | sysvals.writeDatafileHeader(sysvals.ftracefile) | ||
418 | call('cat '+sysvals.tpath+'trace >> '+sysvals.ftracefile, shell=True) | ||
419 | |||
312 | # Function: colorForName | 420 | # Function: colorForName |
313 | # Description: | 421 | # Description: |
314 | # Generate a repeatable color from a list for a given name | 422 | # Generate a repeatable color from a list for a given name |
@@ -353,18 +461,19 @@ def cgOverview(cg, minlen): | |||
353 | # testruns: array of Data objects from parseKernelLog or parseTraceLog | 461 | # testruns: array of Data objects from parseKernelLog or parseTraceLog |
354 | # Output: | 462 | # Output: |
355 | # True if the html file was created, false if it failed | 463 | # True if the html file was created, false if it failed |
356 | def createBootGraph(data, embedded): | 464 | def createBootGraph(data): |
357 | # html function templates | 465 | # 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' | 466 | 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>'\ | 467 | 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>'\ | 468 | '<td class="blue">Init process starts @ <b>{0} ms</b></td>'\ |
469 | '<td class="blue">Last initcall ends @ <b>{1} ms</b></td>'\ | ||
361 | '</tr>\n</table>\n' | 470 | '</tr>\n</table>\n' |
362 | 471 | ||
363 | # device timeline | 472 | # device timeline |
364 | devtl = aslib.Timeline(100, 20) | 473 | devtl = aslib.Timeline(100, 20) |
365 | 474 | ||
366 | # write the test title and general info header | 475 | # write the test title and general info header |
367 | devtl.createHeader(sysvals, 'noftrace') | 476 | devtl.createHeader(sysvals) |
368 | 477 | ||
369 | # Generate the header for this timeline | 478 | # Generate the header for this timeline |
370 | t0 = data.start | 479 | t0 = data.start |
@@ -373,84 +482,98 @@ def createBootGraph(data, embedded): | |||
373 | if(tTotal == 0): | 482 | if(tTotal == 0): |
374 | print('ERROR: No timeline data') | 483 | print('ERROR: No timeline data') |
375 | return False | 484 | return False |
376 | boot_time = '%.0f'%(tTotal*1000) | 485 | user_mode = '%.0f'%(data.tUserMode*1000) |
377 | devtl.html += html_timetotal.format(boot_time) | 486 | last_init = '%.0f'%(tTotal*1000) |
487 | devtl.html += html_timetotal.format(user_mode, last_init) | ||
378 | 488 | ||
379 | # determine the maximum number of rows we need to draw | 489 | # determine the maximum number of rows we need to draw |
380 | phase = 'boot' | ||
381 | list = data.dmesg[phase]['list'] | ||
382 | devlist = [] | 490 | devlist = [] |
383 | for devname in list: | 491 | for p in data.phases: |
384 | d = aslib.DevItem(0, phase, list[devname]) | 492 | list = data.dmesg[p]['list'] |
385 | devlist.append(d) | 493 | for devname in list: |
386 | devtl.getPhaseRows(devlist) | 494 | d = aslib.DevItem(0, p, list[devname]) |
495 | devlist.append(d) | ||
496 | devtl.getPhaseRows(devlist, 0, 'start') | ||
387 | devtl.calcTotalRows() | 497 | devtl.calcTotalRows() |
388 | 498 | ||
389 | # draw the timeline background | 499 | # draw the timeline background |
390 | devtl.createZoomBox() | 500 | devtl.createZoomBox() |
391 | boot = data.dmesg[phase] | 501 | devtl.html += devtl.html_tblock.format('boot', '0', '100', devtl.scaleH) |
392 | length = boot['end']-boot['start'] | 502 | for p in data.phases: |
393 | left = '%.3f' % (((boot['start']-t0)*100.0)/tTotal) | 503 | phase = data.dmesg[p] |
394 | width = '%.3f' % ((length*100.0)/tTotal) | 504 | length = phase['end']-phase['start'] |
395 | devtl.html += devtl.html_tblock.format(phase, left, width, devtl.scaleH) | 505 | left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal) |
396 | devtl.html += devtl.html_phase.format('0', '100', \ | 506 | width = '%.3f' % ((length*100.0)/tTotal) |
397 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ | 507 | devtl.html += devtl.html_phase.format(left, width, \ |
398 | 'white', '') | 508 | '%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \ |
509 | phase['color'], '') | ||
399 | 510 | ||
400 | # draw the device timeline | 511 | # draw the device timeline |
401 | num = 0 | 512 | num = 0 |
402 | devstats = dict() | 513 | devstats = dict() |
403 | for devname in sorted(list): | 514 | for phase in data.phases: |
404 | cls, color = colorForName(devname) | 515 | list = data.dmesg[phase]['list'] |
405 | dev = list[devname] | 516 | for devname in sorted(list): |
406 | info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, | 517 | cls, color = colorForName(devname) |
407 | dev['ulen']/1000.0, dev['ret']) | 518 | dev = list[devname] |
408 | devstats[dev['id']] = {'info':info} | 519 | info = '@|%.3f|%.3f|%.3f|%d' % (dev['start']*1000.0, dev['end']*1000.0, |
409 | dev['color'] = color | 520 | dev['ulen']/1000.0, dev['ret']) |
410 | height = devtl.phaseRowHeight(0, phase, dev['row']) | 521 | devstats[dev['id']] = {'info':info} |
411 | top = '%.6f' % ((dev['row']*height) + devtl.scaleH) | 522 | dev['color'] = color |
412 | left = '%.6f' % (((dev['start']-t0)*100)/tTotal) | 523 | height = devtl.phaseRowHeight(0, phase, dev['row']) |
413 | width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) | 524 | top = '%.6f' % ((dev['row']*height) + devtl.scaleH) |
414 | length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) | 525 | left = '%.6f' % (((dev['start']-t0)*100)/tTotal) |
415 | devtl.html += devtl.html_device.format(dev['id'], | 526 | width = '%.6f' % (((dev['end']-dev['start'])*100)/tTotal) |
416 | devname+length+'kernel_mode', left, top, '%.3f'%height, | 527 | length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000) |
417 | width, devname, ' '+cls, '') | 528 | devtl.html += devtl.html_device.format(dev['id'], |
418 | rowtop = devtl.phaseRowTop(0, phase, dev['row']) | 529 | devname+length+phase+'_mode', left, top, '%.3f'%height, |
419 | height = '%.6f' % (devtl.rowH / 2) | 530 | width, devname, ' '+cls, '') |
420 | top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) | 531 | rowtop = devtl.phaseRowTop(0, phase, dev['row']) |
421 | if data.do_one_initcall: | 532 | height = '%.6f' % (devtl.rowH / 2) |
422 | if('ftrace' not in dev): | 533 | top = '%.6f' % (rowtop + devtl.scaleH + (devtl.rowH / 2)) |
534 | if data.do_one_initcall: | ||
535 | if('ftrace' not in dev): | ||
536 | continue | ||
537 | cg = dev['ftrace'] | ||
538 | large, stats = cgOverview(cg, 0.001) | ||
539 | devstats[dev['id']]['fstat'] = stats | ||
540 | for l in large: | ||
541 | left = '%f' % (((l.time-t0)*100)/tTotal) | ||
542 | width = '%f' % (l.length*100/tTotal) | ||
543 | title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) | ||
544 | devtl.html += html_srccall.format(l.name, left, | ||
545 | top, height, width, title, 'x%d'%num) | ||
546 | num += 1 | ||
547 | continue | ||
548 | if('ftraces' not in dev): | ||
423 | continue | 549 | continue |
424 | cg = dev['ftrace'] | 550 | for cg in dev['ftraces']: |
425 | large, stats = cgOverview(cg, 0.001) | 551 | left = '%f' % (((cg.start-t0)*100)/tTotal) |
426 | devstats[dev['id']]['fstat'] = stats | 552 | width = '%f' % ((cg.end-cg.start)*100/tTotal) |
427 | for l in large: | 553 | cglen = (cg.end - cg.start) * 1000.0 |
428 | left = '%f' % (((l.time-t0)*100)/tTotal) | 554 | title = '%s (%0.3fms)' % (cg.name, cglen) |
429 | width = '%f' % (l.length*100/tTotal) | 555 | cg.id = 'x%d' % num |
430 | title = '%s (%0.3fms)' % (l.name, l.length * 1000.0) | 556 | devtl.html += html_srccall.format(cg.name, left, |
431 | devtl.html += html_srccall.format(l.name, left, | 557 | top, height, width, title, dev['id']+cg.id) |
432 | top, height, width, title, 'x%d'%num) | ||
433 | num += 1 | 558 | 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 | 559 | ||
447 | # draw the time scale, try to make the number of labels readable | 560 | # draw the time scale, try to make the number of labels readable |
448 | devtl.createTimeScale(t0, tMax, tTotal, phase) | 561 | devtl.createTimeScale(t0, tMax, tTotal, 'boot') |
449 | devtl.html += '</div>\n' | 562 | devtl.html += '</div>\n' |
450 | 563 | ||
451 | # timeline is finished | 564 | # timeline is finished |
452 | devtl.html += '</div>\n</div>\n' | 565 | devtl.html += '</div>\n</div>\n' |
453 | 566 | ||
567 | # draw a legend which describes the phases by color | ||
568 | devtl.html += '<div class="legend">\n' | ||
569 | pdelta = 20.0 | ||
570 | pmargin = 36.0 | ||
571 | for phase in data.phases: | ||
572 | order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin) | ||
573 | devtl.html += devtl.html_legend.format(order, \ | ||
574 | data.dmesg[phase]['color'], phase+'_mode', phase[0]) | ||
575 | devtl.html += '</div>\n' | ||
576 | |||
454 | if(sysvals.outfile == sysvals.htmlfile): | 577 | if(sysvals.outfile == sysvals.htmlfile): |
455 | hf = open(sysvals.htmlfile, 'a') | 578 | hf = open(sysvals.htmlfile, 'a') |
456 | else: | 579 | else: |
@@ -474,7 +597,7 @@ def createBootGraph(data, embedded): | |||
474 | .fstat td {text-align:left;width:35px;}\n\ | 597 | .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\ | 598 | .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' | 599 | .srccall:hover {color:white;font-weight:bold;border:1px solid white;}\n' |
477 | if(not embedded): | 600 | if(not sysvals.embedded): |
478 | aslib.addCSS(hf, sysvals, 1, False, extra) | 601 | aslib.addCSS(hf, sysvals, 1, False, extra) |
479 | 602 | ||
480 | # write the device timeline | 603 | # write the device timeline |
@@ -495,9 +618,11 @@ def createBootGraph(data, embedded): | |||
495 | html = \ | 618 | html = \ |
496 | '<div id="devicedetailtitle"></div>\n'\ | 619 | '<div id="devicedetailtitle"></div>\n'\ |
497 | '<div id="devicedetail" style="display:none;">\n'\ | 620 | '<div id="devicedetail" style="display:none;">\n'\ |
498 | '<div id="devicedetail0">\n'\ | 621 | '<div id="devicedetail0">\n' |
499 | '<div id="kernel_mode" class="phaselet" style="left:0%;width:100%;background:#DDDDDD"></div>\n'\ | 622 | for p in data.phases: |
500 | '</div>\n</div>\n'\ | 623 | phase = data.dmesg[p] |
624 | html += devtl.html_phaselet.format(p+'_mode', '0', '100', phase['color']) | ||
625 | html += '</div>\n</div>\n'\ | ||
501 | '<script type="text/javascript">\n'+statinfo+\ | 626 | '<script type="text/javascript">\n'+statinfo+\ |
502 | '</script>\n' | 627 | '</script>\n' |
503 | hf.write(html) | 628 | hf.write(html) |
@@ -507,21 +632,21 @@ def createBootGraph(data, embedded): | |||
507 | aslib.addCallgraphs(sysvals, hf, data) | 632 | aslib.addCallgraphs(sysvals, hf, data) |
508 | 633 | ||
509 | # add the dmesg log as a hidden div | 634 | # add the dmesg log as a hidden div |
510 | if sysvals.addlogs: | 635 | if sysvals.dmesglog: |
511 | hf.write('<div id="dmesglog" style="display:none;">\n') | 636 | hf.write('<div id="dmesglog" style="display:none;">\n') |
512 | for line in data.dmesgtext: | 637 | for line in data.dmesgtext: |
513 | line = line.replace('<', '<').replace('>', '>') | 638 | line = line.replace('<', '<').replace('>', '>') |
514 | hf.write(line) | 639 | hf.write(line) |
515 | hf.write('</div>\n') | 640 | hf.write('</div>\n') |
516 | 641 | ||
517 | if(not embedded): | 642 | if(not sysvals.embedded): |
518 | # write the footer and close | 643 | # write the footer and close |
519 | aslib.addScriptCode(hf, [data]) | 644 | aslib.addScriptCode(hf, [data]) |
520 | hf.write('</body>\n</html>\n') | 645 | hf.write('</body>\n</html>\n') |
521 | else: | 646 | else: |
522 | # embedded out will be loaded in a page, skip the js | 647 | # embedded out will be loaded in a page, skip the js |
523 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ | 648 | hf.write('<div id=bounds style=display:none>%f,%f</div>' % \ |
524 | (data.start*1000, data.initstart*1000)) | 649 | (data.start*1000, data.end*1000)) |
525 | hf.close() | 650 | hf.close() |
526 | return True | 651 | return True |
527 | 652 | ||
@@ -533,17 +658,20 @@ def updateCron(restore=False): | |||
533 | if not restore: | 658 | if not restore: |
534 | sysvals.rootUser(True) | 659 | sysvals.rootUser(True) |
535 | crondir = '/var/spool/cron/crontabs/' | 660 | crondir = '/var/spool/cron/crontabs/' |
536 | cronfile = crondir+'root' | 661 | if not os.path.exists(crondir): |
537 | backfile = crondir+'root-analyze_boot-backup' | 662 | crondir = '/var/spool/cron/' |
538 | if not os.path.exists(crondir): | 663 | if not os.path.exists(crondir): |
539 | doError('%s not found' % crondir) | 664 | doError('%s not found' % crondir) |
540 | out = Popen(['which', 'crontab'], stdout=PIPE).stdout.read() | 665 | cronfile = crondir+'root' |
541 | if not out: | 666 | backfile = crondir+'root-analyze_boot-backup' |
667 | cmd = sysvals.getExec('crontab') | ||
668 | if not cmd: | ||
542 | doError('crontab not found') | 669 | doError('crontab not found') |
543 | # on restore: move the backup cron back into place | 670 | # on restore: move the backup cron back into place |
544 | if restore: | 671 | if restore: |
545 | if os.path.exists(backfile): | 672 | if os.path.exists(backfile): |
546 | shutil.move(backfile, cronfile) | 673 | shutil.move(backfile, cronfile) |
674 | call([cmd, cronfile]) | ||
547 | return | 675 | return |
548 | # backup current cron and install new one with reboot | 676 | # backup current cron and install new one with reboot |
549 | if os.path.exists(cronfile): | 677 | if os.path.exists(cronfile): |
@@ -556,13 +684,13 @@ def updateCron(restore=False): | |||
556 | fp = open(backfile, 'r') | 684 | fp = open(backfile, 'r') |
557 | op = open(cronfile, 'w') | 685 | op = open(cronfile, 'w') |
558 | for line in fp: | 686 | for line in fp: |
559 | if '@reboot' not in line: | 687 | if not sysvals.myCronJob(line): |
560 | op.write(line) | 688 | op.write(line) |
561 | continue | 689 | continue |
562 | fp.close() | 690 | fp.close() |
563 | op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) | 691 | op.write('@reboot python %s\n' % sysvals.cronjobCmdString()) |
564 | op.close() | 692 | op.close() |
565 | res = call('crontab %s' % cronfile, shell=True) | 693 | res = call([cmd, cronfile]) |
566 | except Exception, e: | 694 | except Exception, e: |
567 | print 'Exception: %s' % str(e) | 695 | print 'Exception: %s' % str(e) |
568 | shutil.move(backfile, cronfile) | 696 | shutil.move(backfile, cronfile) |
@@ -577,25 +705,16 @@ def updateGrub(restore=False): | |||
577 | # call update-grub on restore | 705 | # call update-grub on restore |
578 | if restore: | 706 | if restore: |
579 | try: | 707 | try: |
580 | call(['update-grub'], stderr=PIPE, stdout=PIPE, | 708 | call(sysvals.blexec, stderr=PIPE, stdout=PIPE, |
581 | env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) | 709 | env={'PATH': '.:/sbin:/usr/sbin:/usr/bin:/sbin:/bin'}) |
582 | except Exception, e: | 710 | except Exception, e: |
583 | print 'Exception: %s\n' % str(e) | 711 | print 'Exception: %s\n' % str(e) |
584 | return | 712 | 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 | 713 | # extract the option and create a grub config without it |
714 | sysvals.rootUser(True) | ||
597 | tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' | 715 | tgtopt = 'GRUB_CMDLINE_LINUX_DEFAULT' |
598 | cmdline = '' | 716 | cmdline = '' |
717 | grubfile = '/etc/default/grub' | ||
599 | tempfile = '/etc/default/grub.analyze_boot' | 718 | tempfile = '/etc/default/grub.analyze_boot' |
600 | shutil.move(grubfile, tempfile) | 719 | shutil.move(grubfile, tempfile) |
601 | res = -1 | 720 | res = -1 |
@@ -622,7 +741,7 @@ def updateGrub(restore=False): | |||
622 | # if the target option value is in quotes, strip them | 741 | # if the target option value is in quotes, strip them |
623 | sp = '"' | 742 | sp = '"' |
624 | val = cmdline.strip() | 743 | val = cmdline.strip() |
625 | if val[0] == '\'' or val[0] == '"': | 744 | if val and (val[0] == '\'' or val[0] == '"'): |
626 | sp = val[0] | 745 | sp = val[0] |
627 | val = val.strip(sp) | 746 | val = val.strip(sp) |
628 | cmdline = val | 747 | cmdline = val |
@@ -633,7 +752,7 @@ def updateGrub(restore=False): | |||
633 | # write out the updated target option | 752 | # write out the updated target option |
634 | op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) | 753 | op.write('\n%s=%s%s%s\n' % (tgtopt, sp, cmdline, sp)) |
635 | op.close() | 754 | op.close() |
636 | res = call('update-grub') | 755 | res = call(sysvals.blexec) |
637 | os.remove(grubfile) | 756 | os.remove(grubfile) |
638 | except Exception, e: | 757 | except Exception, e: |
639 | print 'Exception: %s' % str(e) | 758 | print 'Exception: %s' % str(e) |
@@ -641,10 +760,18 @@ def updateGrub(restore=False): | |||
641 | # cleanup | 760 | # cleanup |
642 | shutil.move(tempfile, grubfile) | 761 | shutil.move(tempfile, grubfile) |
643 | if res != 0: | 762 | if res != 0: |
644 | doError('update-grub failed') | 763 | doError('update grub failed') |
645 | 764 | ||
646 | # Function: doError | 765 | # Function: updateKernelParams |
647 | # Description: | 766 | # Description: |
767 | # update boot conf for all kernels with our parameters | ||
768 | def updateKernelParams(restore=False): | ||
769 | # find the boot loader | ||
770 | sysvals.getBootLoader() | ||
771 | if sysvals.bootloader == 'grub': | ||
772 | updateGrub(restore) | ||
773 | |||
774 | # Function: doError Description: | ||
648 | # generic error function for catastrphic failures | 775 | # generic error function for catastrphic failures |
649 | # Arguments: | 776 | # Arguments: |
650 | # msg: the error message to print | 777 | # msg: the error message to print |
@@ -660,7 +787,7 @@ def doError(msg, help=False): | |||
660 | # print out the help text | 787 | # print out the help text |
661 | def printHelp(): | 788 | def printHelp(): |
662 | print('') | 789 | print('') |
663 | print('%s v%.1f' % (sysvals.title, sysvals.version)) | 790 | print('%s v%s' % (sysvals.title, sysvals.version)) |
664 | print('Usage: bootgraph <options> <command>') | 791 | print('Usage: bootgraph <options> <command>') |
665 | print('') | 792 | print('') |
666 | print('Description:') | 793 | print('Description:') |
@@ -669,13 +796,19 @@ def printHelp(): | |||
669 | print(' the start of the init process.') | 796 | print(' the start of the init process.') |
670 | print('') | 797 | print('') |
671 | print(' If no specific command is given the tool reads the current dmesg') | 798 | print(' If no specific command is given the tool reads the current dmesg') |
672 | print(' and/or ftrace log and outputs bootgraph.html') | 799 | print(' and/or ftrace log and creates a timeline') |
800 | print('') | ||
801 | print(' Generates output files in subdirectory: boot-yymmdd-HHMMSS') | ||
802 | print(' HTML output: <hostname>_boot.html') | ||
803 | print(' raw dmesg output: <hostname>_boot_dmesg.txt') | ||
804 | print(' raw ftrace output: <hostname>_boot_ftrace.txt') | ||
673 | print('') | 805 | print('') |
674 | print('Options:') | 806 | print('Options:') |
675 | print(' -h Print this help text') | 807 | print(' -h Print this help text') |
676 | print(' -v Print the current tool version') | 808 | print(' -v Print the current tool version') |
677 | print(' -addlogs Add the dmesg log to the html output') | 809 | print(' -addlogs Add the dmesg log to the html output') |
678 | print(' -o file Html timeline name (default: bootgraph.html)') | 810 | print(' -o name Overrides the output subdirectory name when running a new test') |
811 | print(' default: boot-{date}-{time}') | ||
679 | print(' [advanced]') | 812 | print(' [advanced]') |
680 | print(' -f Use ftrace to add function detail (default: disabled)') | 813 | print(' -f Use ftrace to add function detail (default: disabled)') |
681 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') | 814 | print(' -callgraph Add callgraph detail, can be very large (default: disabled)') |
@@ -683,13 +816,18 @@ def printHelp(): | |||
683 | print(' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)') | 816 | 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])') | 817 | 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)') | 818 | 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)') | 819 | print(' -func list Limit ftrace to comma-delimited list of functions (default: do_one_initcall)') |
687 | print(' [commands]') | 820 | print(' -cgfilter S Filter the callgraph output in the timeline') |
821 | print(' -bl name Use the following boot loader for kernel params (default: grub)') | ||
688 | print(' -reboot Reboot the machine automatically and generate a new timeline') | 822 | print(' -reboot Reboot the machine automatically and generate a new timeline') |
689 | print(' -manual Show the requirements to generate a new timeline manually') | 823 | print(' -manual Show the steps to generate a new timeline manually (used with -reboot)') |
690 | print(' -dmesg file Load a stored dmesg file (used with -ftrace)') | 824 | print('') |
691 | print(' -ftrace file Load a stored ftrace file (used with -dmesg)') | 825 | print('Other commands:') |
692 | print(' -flistall Print all functions capable of being captured in ftrace') | 826 | print(' -flistall Print all functions capable of being captured in ftrace') |
827 | print(' -sysinfo Print out system info extracted from BIOS') | ||
828 | print(' [redo]') | ||
829 | print(' -dmesg file Create HTML output using dmesg input (used with -ftrace)') | ||
830 | print(' -ftrace file Create HTML output using ftrace input (used with -dmesg)') | ||
693 | print('') | 831 | print('') |
694 | return True | 832 | return True |
695 | 833 | ||
@@ -698,14 +836,15 @@ def printHelp(): | |||
698 | if __name__ == '__main__': | 836 | if __name__ == '__main__': |
699 | # loop through the command line arguments | 837 | # loop through the command line arguments |
700 | cmd = '' | 838 | cmd = '' |
701 | simplecmds = ['-updategrub', '-flistall'] | 839 | testrun = True |
840 | simplecmds = ['-sysinfo', '-kpupdate', '-flistall', '-checkbl'] | ||
702 | args = iter(sys.argv[1:]) | 841 | args = iter(sys.argv[1:]) |
703 | for arg in args: | 842 | for arg in args: |
704 | if(arg == '-h'): | 843 | if(arg == '-h'): |
705 | printHelp() | 844 | printHelp() |
706 | sys.exit() | 845 | sys.exit() |
707 | elif(arg == '-v'): | 846 | elif(arg == '-v'): |
708 | print("Version %.1f" % sysvals.version) | 847 | print("Version %s" % sysvals.version) |
709 | sys.exit() | 848 | sys.exit() |
710 | elif(arg in simplecmds): | 849 | elif(arg in simplecmds): |
711 | cmd = arg[1:] | 850 | cmd = arg[1:] |
@@ -716,16 +855,32 @@ if __name__ == '__main__': | |||
716 | sysvals.usecallgraph = True | 855 | sysvals.usecallgraph = True |
717 | elif(arg == '-mincg'): | 856 | elif(arg == '-mincg'): |
718 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) | 857 | sysvals.mincglen = aslib.getArgFloat('-mincg', args, 0.0, 10000.0) |
858 | elif(arg == '-cgfilter'): | ||
859 | try: | ||
860 | val = args.next() | ||
861 | except: | ||
862 | doError('No callgraph functions supplied', True) | ||
863 | sysvals.setDeviceFilter(val) | ||
864 | elif(arg == '-bl'): | ||
865 | try: | ||
866 | val = args.next() | ||
867 | except: | ||
868 | doError('No boot loader name supplied', True) | ||
869 | if val.lower() not in ['grub']: | ||
870 | doError('Unknown boot loader: %s' % val, True) | ||
871 | sysvals.bootloader = val.lower() | ||
719 | elif(arg == '-timeprec'): | 872 | elif(arg == '-timeprec'): |
720 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) | 873 | sysvals.setPrecision(aslib.getArgInt('-timeprec', args, 0, 6)) |
721 | elif(arg == '-maxdepth'): | 874 | elif(arg == '-maxdepth'): |
722 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) | 875 | sysvals.max_graph_depth = aslib.getArgInt('-maxdepth', args, 0, 1000) |
723 | elif(arg == '-filter'): | 876 | elif(arg == '-func'): |
724 | try: | 877 | try: |
725 | val = args.next() | 878 | val = args.next() |
726 | except: | 879 | except: |
727 | doError('No filter functions supplied', True) | 880 | doError('No filter functions supplied', True) |
728 | aslib.rootCheck(True) | 881 | sysvals.useftrace = True |
882 | sysvals.usecallgraph = True | ||
883 | sysvals.rootCheck(True) | ||
729 | sysvals.setGraphFilter(val) | 884 | sysvals.setGraphFilter(val) |
730 | elif(arg == '-ftrace'): | 885 | elif(arg == '-ftrace'): |
731 | try: | 886 | try: |
@@ -734,9 +889,10 @@ if __name__ == '__main__': | |||
734 | doError('No ftrace file supplied', True) | 889 | doError('No ftrace file supplied', True) |
735 | if(os.path.exists(val) == False): | 890 | if(os.path.exists(val) == False): |
736 | doError('%s does not exist' % val) | 891 | doError('%s does not exist' % val) |
892 | testrun = False | ||
737 | sysvals.ftracefile = val | 893 | sysvals.ftracefile = val |
738 | elif(arg == '-addlogs'): | 894 | elif(arg == '-addlogs'): |
739 | sysvals.addlogs = True | 895 | sysvals.dmesglog = True |
740 | elif(arg == '-expandcg'): | 896 | elif(arg == '-expandcg'): |
741 | sysvals.cgexp = True | 897 | sysvals.cgexp = True |
742 | elif(arg == '-dmesg'): | 898 | elif(arg == '-dmesg'): |
@@ -748,18 +904,15 @@ if __name__ == '__main__': | |||
748 | doError('%s does not exist' % val) | 904 | doError('%s does not exist' % val) |
749 | if(sysvals.htmlfile == val or sysvals.outfile == val): | 905 | if(sysvals.htmlfile == val or sysvals.outfile == val): |
750 | doError('Output filename collision') | 906 | doError('Output filename collision') |
907 | testrun = False | ||
751 | sysvals.dmesgfile = val | 908 | sysvals.dmesgfile = val |
752 | elif(arg == '-o'): | 909 | elif(arg == '-o'): |
753 | try: | 910 | try: |
754 | val = args.next() | 911 | val = args.next() |
755 | except: | 912 | except: |
756 | doError('No HTML filename supplied', True) | 913 | doError('No subdirectory name supplied', True) |
757 | if(sysvals.dmesgfile == val or sysvals.ftracefile == val): | 914 | sysvals.testdir = sysvals.setOutputFolder(val) |
758 | doError('Output filename collision') | ||
759 | sysvals.htmlfile = val | ||
760 | elif(arg == '-reboot'): | 915 | elif(arg == '-reboot'): |
761 | if sysvals.iscronjob: | ||
762 | doError('-reboot and -cronjob are incompatible') | ||
763 | sysvals.reboot = True | 916 | sysvals.reboot = True |
764 | elif(arg == '-manual'): | 917 | elif(arg == '-manual'): |
765 | sysvals.reboot = True | 918 | sysvals.reboot = True |
@@ -767,58 +920,93 @@ if __name__ == '__main__': | |||
767 | # remaining options are only for cron job use | 920 | # remaining options are only for cron job use |
768 | elif(arg == '-cronjob'): | 921 | elif(arg == '-cronjob'): |
769 | sysvals.iscronjob = True | 922 | sysvals.iscronjob = True |
770 | if sysvals.reboot: | ||
771 | doError('-reboot and -cronjob are incompatible') | ||
772 | else: | 923 | else: |
773 | doError('Invalid argument: '+arg, True) | 924 | doError('Invalid argument: '+arg, True) |
774 | 925 | ||
926 | # compatibility errors and access checks | ||
927 | if(sysvals.iscronjob and (sysvals.reboot or \ | ||
928 | sysvals.dmesgfile or sysvals.ftracefile or cmd)): | ||
929 | doError('-cronjob is meant for batch purposes only') | ||
930 | if(sysvals.reboot and (sysvals.dmesgfile or sysvals.ftracefile)): | ||
931 | doError('-reboot and -dmesg/-ftrace are incompatible') | ||
932 | if cmd or sysvals.reboot or sysvals.iscronjob or testrun: | ||
933 | sysvals.rootCheck(True) | ||
934 | if (testrun and sysvals.useftrace) or cmd == 'flistall': | ||
935 | if not sysvals.verifyFtrace(): | ||
936 | doError('Ftrace is not properly enabled') | ||
937 | |||
938 | # run utility commands | ||
939 | sysvals.cpuInfo() | ||
775 | if cmd != '': | 940 | if cmd != '': |
776 | if cmd == 'updategrub': | 941 | if cmd == 'kpupdate': |
777 | updateGrub() | 942 | updateKernelParams() |
778 | elif cmd == 'flistall': | 943 | elif cmd == 'flistall': |
779 | sysvals.getFtraceFilterFunctions(False) | 944 | for f in sysvals.getBootFtraceFilterFunctions(): |
945 | print f | ||
946 | elif cmd == 'checkbl': | ||
947 | sysvals.getBootLoader() | ||
948 | print 'Boot Loader: %s\n%s' % (sysvals.bootloader, sysvals.blexec) | ||
949 | elif(cmd == 'sysinfo'): | ||
950 | sysvals.printSystemInfo() | ||
780 | sys.exit() | 951 | sys.exit() |
781 | 952 | ||
782 | # update grub, setup a cronjob, and reboot | 953 | # reboot: update grub, setup a cronjob, and reboot |
783 | if sysvals.reboot: | 954 | if sysvals.reboot: |
955 | if (sysvals.useftrace or sysvals.usecallgraph) and \ | ||
956 | not sysvals.checkFtraceKernelVersion(): | ||
957 | doError('Ftrace functionality requires kernel v4.10 or newer') | ||
784 | if not sysvals.manual: | 958 | if not sysvals.manual: |
785 | updateGrub() | 959 | updateKernelParams() |
786 | updateCron() | 960 | updateCron() |
787 | call('reboot') | 961 | call('reboot') |
788 | else: | 962 | else: |
789 | sysvals.manualRebootRequired() | 963 | sysvals.manualRebootRequired() |
790 | sys.exit() | 964 | sys.exit() |
791 | 965 | ||
792 | # disable the cronjob | 966 | # cronjob: remove the cronjob, grub changes, and disable ftrace |
793 | if sysvals.iscronjob: | 967 | if sysvals.iscronjob: |
794 | updateCron(True) | 968 | updateCron(True) |
795 | updateGrub(True) | 969 | updateKernelParams(True) |
970 | try: | ||
971 | sysvals.fsetVal('0', 'tracing_on') | ||
972 | except: | ||
973 | pass | ||
796 | 974 | ||
797 | data = loadKernelLog() | 975 | # testrun: generate copies of the logs |
798 | if sysvals.useftrace: | 976 | if testrun: |
799 | loadTraceLog(data) | 977 | retrieveLogs() |
800 | if sysvals.iscronjob: | 978 | else: |
801 | try: | 979 | sysvals.setOutputFile() |
802 | sysvals.fsetVal('0', 'tracing_on') | ||
803 | except: | ||
804 | pass | ||
805 | 980 | ||
806 | if(sysvals.outfile and sysvals.phoronix): | 981 | # process the log data |
807 | fp = open(sysvals.outfile, 'w') | 982 | if sysvals.dmesgfile: |
808 | fp.write('pass %s initstart %.3f end %.3f boot %s\n' % | 983 | data = parseKernelLog() |
809 | (data.valid, data.initstart*1000, data.end*1000, data.boottime)) | 984 | if(not data.valid): |
810 | fp.close() | ||
811 | if(not data.valid): | ||
812 | if sysvals.dmesgfile: | ||
813 | doError('No initcall data found in %s' % sysvals.dmesgfile) | 985 | doError('No initcall data found in %s' % sysvals.dmesgfile) |
814 | else: | 986 | if sysvals.useftrace and sysvals.ftracefile: |
815 | doError('No initcall data found, is initcall_debug enabled?') | 987 | parseTraceLog(data) |
988 | else: | ||
989 | doError('dmesg file required') | ||
816 | 990 | ||
817 | print(' Host: %s' % sysvals.hostname) | 991 | print(' Host: %s' % sysvals.hostname) |
818 | print(' Test time: %s' % sysvals.testtime) | 992 | print(' Test time: %s' % sysvals.testtime) |
819 | print(' Boot time: %s' % data.boottime) | 993 | print(' Boot time: %s' % data.boottime) |
820 | print('Kernel Version: %s' % sysvals.kernel) | 994 | print('Kernel Version: %s' % sysvals.kernel) |
821 | print(' Kernel start: %.3f' % (data.start * 1000)) | 995 | print(' Kernel start: %.3f' % (data.start * 1000)) |
822 | print(' init start: %.3f' % (data.initstart * 1000)) | 996 | print('Usermode start: %.3f' % (data.tUserMode * 1000)) |
997 | print('Last Init Call: %.3f' % (data.end * 1000)) | ||
998 | |||
999 | # handle embedded output logs | ||
1000 | if(sysvals.outfile and sysvals.embedded): | ||
1001 | fp = open(sysvals.outfile, 'w') | ||
1002 | fp.write('pass %s initstart %.3f end %.3f boot %s\n' % | ||
1003 | (data.valid, data.tUserMode*1000, data.end*1000, data.boottime)) | ||
1004 | fp.close() | ||
1005 | |||
1006 | createBootGraph(data) | ||
823 | 1007 | ||
824 | createBootGraph(data, sysvals.phoronix) | 1008 | # if running as root, change output dir owner to sudo_user |
1009 | if testrun and os.path.isdir(sysvals.testdir) and \ | ||
1010 | os.getuid() == 0 and 'SUDO_USER' in os.environ: | ||
1011 | cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1' | ||
1012 | call(cmd.format(os.environ['SUDO_USER'], sysvals.testdir), shell=True) | ||