parsing.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. # This file is part of pybootchartgui.
  2. # pybootchartgui is free software: you can redistribute it and/or modify
  3. # it under the terms of the GNU General Public License as published by
  4. # the Free Software Foundation, either version 3 of the License, or
  5. # (at your option) any later version.
  6. # pybootchartgui is distributed in the hope that it will be useful,
  7. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. # GNU General Public License for more details.
  10. # You should have received a copy of the GNU General Public License
  11. # along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>.
  12. import os
  13. import string
  14. import re
  15. import sys
  16. import tarfile
  17. import time
  18. from collections import defaultdict
  19. from functools import reduce
  20. from .samples import *
  21. from .process_tree import ProcessTree
  22. if sys.version_info >= (3, 0):
  23. long = int
  24. # Parsing produces as its end result a 'Trace'
  25. class Trace:
  26. def __init__(self, writer, paths, options):
  27. self.processes = {}
  28. self.start = {}
  29. self.end = {}
  30. self.min = None
  31. self.max = None
  32. self.headers = None
  33. self.disk_stats = []
  34. self.ps_stats = None
  35. self.taskstats = None
  36. self.cpu_stats = []
  37. self.cmdline = None
  38. self.kernel = None
  39. self.kernel_tree = None
  40. self.filename = None
  41. self.parent_map = None
  42. self.mem_stats = []
  43. self.monitor_disk = None
  44. self.times = [] # Always empty, but expected by draw.py when drawing system charts.
  45. if len(paths):
  46. parse_paths (writer, self, paths)
  47. if not self.valid():
  48. raise ParseError("empty state: '%s' does not contain a valid bootchart" % ", ".join(paths))
  49. if options.full_time:
  50. self.min = min(self.start.keys())
  51. self.max = max(self.end.keys())
  52. # Rendering system charts depends on start and end
  53. # time. Provide them where the original drawing code expects
  54. # them, i.e. in proc_tree.
  55. class BitbakeProcessTree:
  56. def __init__(self, start_time, end_time):
  57. self.start_time = start_time
  58. self.end_time = end_time
  59. self.duration = self.end_time - self.start_time
  60. self.proc_tree = BitbakeProcessTree(min(self.start.keys()),
  61. max(self.end.keys()))
  62. return
  63. # Turn that parsed information into something more useful
  64. # link processes into a tree of pointers, calculate statistics
  65. self.compile(writer)
  66. # Crop the chart to the end of the first idle period after the given
  67. # process
  68. if options.crop_after:
  69. idle = self.crop (writer, options.crop_after)
  70. else:
  71. idle = None
  72. # Annotate other times as the first start point of given process lists
  73. self.times = [ idle ]
  74. if options.annotate:
  75. for procnames in options.annotate:
  76. names = [x[:15] for x in procnames.split(",")]
  77. for proc in self.ps_stats.process_map.values():
  78. if proc.cmd in names:
  79. self.times.append(proc.start_time)
  80. break
  81. else:
  82. self.times.append(None)
  83. self.proc_tree = ProcessTree(writer, self.kernel, self.ps_stats,
  84. self.ps_stats.sample_period,
  85. self.headers.get("profile.process"),
  86. options.prune, idle, self.taskstats,
  87. self.parent_map is not None)
  88. if self.kernel is not None:
  89. self.kernel_tree = ProcessTree(writer, self.kernel, None, 0,
  90. self.headers.get("profile.process"),
  91. False, None, None, True)
  92. def valid(self):
  93. return len(self.processes) != 0
  94. return self.headers != None and self.disk_stats != None and \
  95. self.ps_stats != None and self.cpu_stats != None
  96. def add_process(self, process, start, end):
  97. self.processes[process] = [start, end]
  98. if start not in self.start:
  99. self.start[start] = []
  100. if process not in self.start[start]:
  101. self.start[start].append(process)
  102. if end not in self.end:
  103. self.end[end] = []
  104. if process not in self.end[end]:
  105. self.end[end].append(process)
  106. def compile(self, writer):
  107. def find_parent_id_for(pid):
  108. if pid is 0:
  109. return 0
  110. ppid = self.parent_map.get(pid)
  111. if ppid:
  112. # many of these double forks are so short lived
  113. # that we have no samples, or process info for them
  114. # so climb the parent hierarcy to find one
  115. if int (ppid * 1000) not in self.ps_stats.process_map:
  116. # print "Pid '%d' short lived with no process" % ppid
  117. ppid = find_parent_id_for (ppid)
  118. # else:
  119. # print "Pid '%d' has an entry" % ppid
  120. else:
  121. # print "Pid '%d' missing from pid map" % pid
  122. return 0
  123. return ppid
  124. # merge in the cmdline data
  125. if self.cmdline is not None:
  126. for proc in self.ps_stats.process_map.values():
  127. rpid = int (proc.pid // 1000)
  128. if rpid in self.cmdline:
  129. cmd = self.cmdline[rpid]
  130. proc.exe = cmd['exe']
  131. proc.args = cmd['args']
  132. # else:
  133. # print "proc %d '%s' not in cmdline" % (rpid, proc.exe)
  134. # re-parent any stray orphans if we can
  135. if self.parent_map is not None:
  136. for process in self.ps_stats.process_map.values():
  137. ppid = find_parent_id_for (int(process.pid // 1000))
  138. if ppid:
  139. process.ppid = ppid * 1000
  140. # stitch the tree together with pointers
  141. for process in self.ps_stats.process_map.values():
  142. process.set_parent (self.ps_stats.process_map)
  143. # count on fingers variously
  144. for process in self.ps_stats.process_map.values():
  145. process.calc_stats (self.ps_stats.sample_period)
  146. def crop(self, writer, crop_after):
  147. def is_idle_at(util, start, j):
  148. k = j + 1
  149. while k < len(util) and util[k][0] < start + 300:
  150. k += 1
  151. k = min(k, len(util)-1)
  152. if util[j][1] >= 0.25:
  153. return False
  154. avgload = sum(u[1] for u in util[j:k+1]) / (k-j+1)
  155. if avgload < 0.25:
  156. return True
  157. else:
  158. return False
  159. def is_idle(util, start):
  160. for j in range(0, len(util)):
  161. if util[j][0] < start:
  162. continue
  163. return is_idle_at(util, start, j)
  164. else:
  165. return False
  166. names = [x[:15] for x in crop_after.split(",")]
  167. for proc in self.ps_stats.process_map.values():
  168. if proc.cmd in names or proc.exe in names:
  169. writer.info("selected proc '%s' from list (start %d)"
  170. % (proc.cmd, proc.start_time))
  171. break
  172. if proc is None:
  173. writer.warn("no selected crop proc '%s' in list" % crop_after)
  174. cpu_util = [(sample.time, sample.user + sample.sys + sample.io) for sample in self.cpu_stats]
  175. disk_util = [(sample.time, sample.util) for sample in self.disk_stats]
  176. idle = None
  177. for i in range(0, len(cpu_util)):
  178. if cpu_util[i][0] < proc.start_time:
  179. continue
  180. if is_idle_at(cpu_util, cpu_util[i][0], i) \
  181. and is_idle(disk_util, cpu_util[i][0]):
  182. idle = cpu_util[i][0]
  183. break
  184. if idle is None:
  185. writer.warn ("not idle after proc '%s'" % crop_after)
  186. return None
  187. crop_at = idle + 300
  188. writer.info ("cropping at time %d" % crop_at)
  189. while len (self.cpu_stats) \
  190. and self.cpu_stats[-1].time > crop_at:
  191. self.cpu_stats.pop()
  192. while len (self.disk_stats) \
  193. and self.disk_stats[-1].time > crop_at:
  194. self.disk_stats.pop()
  195. self.ps_stats.end_time = crop_at
  196. cropped_map = {}
  197. for key, value in self.ps_stats.process_map.items():
  198. if (value.start_time <= crop_at):
  199. cropped_map[key] = value
  200. for proc in cropped_map.values():
  201. proc.duration = min (proc.duration, crop_at - proc.start_time)
  202. while len (proc.samples) \
  203. and proc.samples[-1].time > crop_at:
  204. proc.samples.pop()
  205. self.ps_stats.process_map = cropped_map
  206. return idle
  207. class ParseError(Exception):
  208. """Represents errors during parse of the bootchart."""
  209. def __init__(self, value):
  210. self.value = value
  211. def __str__(self):
  212. return self.value
  213. def _parse_headers(file):
  214. """Parses the headers of the bootchart."""
  215. def parse(acc, line):
  216. (headers, last) = acc
  217. if '=' in line:
  218. last, value = map (lambda x: x.strip(), line.split('=', 1))
  219. else:
  220. value = line.strip()
  221. headers[last] += value
  222. return headers, last
  223. return reduce(parse, file.read().split('\n'), (defaultdict(str),''))[0]
  224. def _parse_timed_blocks(file):
  225. """Parses (ie., splits) a file into so-called timed-blocks. A
  226. timed-block consists of a timestamp on a line by itself followed
  227. by zero or more lines of data for that point in time."""
  228. def parse(block):
  229. lines = block.split('\n')
  230. if not lines:
  231. raise ParseError('expected a timed-block consisting a timestamp followed by data lines')
  232. try:
  233. return (int(lines[0]), lines[1:])
  234. except ValueError:
  235. raise ParseError("expected a timed-block, but timestamp '%s' is not an integer" % lines[0])
  236. blocks = file.read().split('\n\n')
  237. return [parse(block) for block in blocks if block.strip() and not block.endswith(' not running\n')]
  238. def _parse_proc_ps_log(writer, file):
  239. """
  240. * See proc(5) for details.
  241. *
  242. * {pid, comm, state, ppid, pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime,
  243. * cutime, cstime, priority, nice, 0, itrealvalue, starttime, vsize, rss, rlim, startcode, endcode, startstack,
  244. * kstkesp, kstkeip}
  245. """
  246. processMap = {}
  247. ltime = 0
  248. timed_blocks = _parse_timed_blocks(file)
  249. for time, lines in timed_blocks:
  250. for line in lines:
  251. if not line: continue
  252. tokens = line.split(' ')
  253. if len(tokens) < 21:
  254. continue
  255. offset = [index for index, token in enumerate(tokens[1:]) if token[-1] == ')'][0]
  256. pid, cmd, state, ppid = int(tokens[0]), ' '.join(tokens[1:2+offset]), tokens[2+offset], int(tokens[3+offset])
  257. userCpu, sysCpu, stime = int(tokens[13+offset]), int(tokens[14+offset]), int(tokens[21+offset])
  258. # magic fixed point-ness ...
  259. pid *= 1000
  260. ppid *= 1000
  261. if pid in processMap:
  262. process = processMap[pid]
  263. process.cmd = cmd.strip('()') # why rename after latest name??
  264. else:
  265. process = Process(writer, pid, cmd.strip('()'), ppid, min(time, stime))
  266. processMap[pid] = process
  267. if process.last_user_cpu_time is not None and process.last_sys_cpu_time is not None and ltime is not None:
  268. userCpuLoad, sysCpuLoad = process.calc_load(userCpu, sysCpu, max(1, time - ltime))
  269. cpuSample = CPUSample('null', userCpuLoad, sysCpuLoad, 0.0)
  270. process.samples.append(ProcessSample(time, state, cpuSample))
  271. process.last_user_cpu_time = userCpu
  272. process.last_sys_cpu_time = sysCpu
  273. ltime = time
  274. if len (timed_blocks) < 2:
  275. return None
  276. startTime = timed_blocks[0][0]
  277. avgSampleLength = (ltime - startTime)/(len (timed_blocks) - 1)
  278. return ProcessStats (writer, processMap, len (timed_blocks), avgSampleLength, startTime, ltime)
  279. def _parse_taskstats_log(writer, file):
  280. """
  281. * See bootchart-collector.c for details.
  282. *
  283. * { pid, ppid, comm, cpu_run_real_total, blkio_delay_total, swapin_delay_total }
  284. *
  285. """
  286. processMap = {}
  287. pidRewrites = {}
  288. ltime = None
  289. timed_blocks = _parse_timed_blocks(file)
  290. for time, lines in timed_blocks:
  291. # we have no 'stime' from taskstats, so prep 'init'
  292. if ltime is None:
  293. process = Process(writer, 1, '[init]', 0, 0)
  294. processMap[1000] = process
  295. ltime = time
  296. # continue
  297. for line in lines:
  298. if not line: continue
  299. tokens = line.split(' ')
  300. if len(tokens) != 6:
  301. continue
  302. opid, ppid, cmd = int(tokens[0]), int(tokens[1]), tokens[2]
  303. cpu_ns, blkio_delay_ns, swapin_delay_ns = long(tokens[-3]), long(tokens[-2]), long(tokens[-1]),
  304. # make space for trees of pids
  305. opid *= 1000
  306. ppid *= 1000
  307. # when the process name changes, we re-write the pid.
  308. if opid in pidRewrites:
  309. pid = pidRewrites[opid]
  310. else:
  311. pid = opid
  312. cmd = cmd.strip('(').strip(')')
  313. if pid in processMap:
  314. process = processMap[pid]
  315. if process.cmd != cmd:
  316. pid += 1
  317. pidRewrites[opid] = pid
  318. # print "process mutation ! '%s' vs '%s' pid %s -> pid %s\n" % (process.cmd, cmd, opid, pid)
  319. process = process.split (writer, pid, cmd, ppid, time)
  320. processMap[pid] = process
  321. else:
  322. process.cmd = cmd;
  323. else:
  324. process = Process(writer, pid, cmd, ppid, time)
  325. processMap[pid] = process
  326. delta_cpu_ns = (float) (cpu_ns - process.last_cpu_ns)
  327. delta_blkio_delay_ns = (float) (blkio_delay_ns - process.last_blkio_delay_ns)
  328. delta_swapin_delay_ns = (float) (swapin_delay_ns - process.last_swapin_delay_ns)
  329. # make up some state data ...
  330. if delta_cpu_ns > 0:
  331. state = "R"
  332. elif delta_blkio_delay_ns + delta_swapin_delay_ns > 0:
  333. state = "D"
  334. else:
  335. state = "S"
  336. # retain the ns timing information into a CPUSample - that tries
  337. # with the old-style to be a %age of CPU used in this time-slice.
  338. if delta_cpu_ns + delta_blkio_delay_ns + delta_swapin_delay_ns > 0:
  339. # print "proc %s cpu_ns %g delta_cpu %g" % (cmd, cpu_ns, delta_cpu_ns)
  340. cpuSample = CPUSample('null', delta_cpu_ns, 0.0,
  341. delta_blkio_delay_ns,
  342. delta_swapin_delay_ns)
  343. process.samples.append(ProcessSample(time, state, cpuSample))
  344. process.last_cpu_ns = cpu_ns
  345. process.last_blkio_delay_ns = blkio_delay_ns
  346. process.last_swapin_delay_ns = swapin_delay_ns
  347. ltime = time
  348. if len (timed_blocks) < 2:
  349. return None
  350. startTime = timed_blocks[0][0]
  351. avgSampleLength = (ltime - startTime)/(len(timed_blocks)-1)
  352. return ProcessStats (writer, processMap, len (timed_blocks), avgSampleLength, startTime, ltime)
  353. def _parse_proc_stat_log(file):
  354. samples = []
  355. ltimes = None
  356. for time, lines in _parse_timed_blocks(file):
  357. # skip emtpy lines
  358. if not lines:
  359. continue
  360. # CPU times {user, nice, system, idle, io_wait, irq, softirq}
  361. tokens = lines[0].split()
  362. times = [ int(token) for token in tokens[1:] ]
  363. if ltimes:
  364. user = float((times[0] + times[1]) - (ltimes[0] + ltimes[1]))
  365. system = float((times[2] + times[5] + times[6]) - (ltimes[2] + ltimes[5] + ltimes[6]))
  366. idle = float(times[3] - ltimes[3])
  367. iowait = float(times[4] - ltimes[4])
  368. aSum = max(user + system + idle + iowait, 1)
  369. samples.append( CPUSample(time, user/aSum, system/aSum, iowait/aSum) )
  370. ltimes = times
  371. # skip the rest of statistics lines
  372. return samples
  373. def _parse_reduced_log(file, sample_class):
  374. samples = []
  375. for time, lines in _parse_timed_blocks(file):
  376. samples.append(sample_class(time, *[float(x) for x in lines[0].split()]))
  377. return samples
  378. def _parse_proc_disk_stat_log(file):
  379. """
  380. Parse file for disk stats, but only look at the whole device, eg. sda,
  381. not sda1, sda2 etc. The format of relevant lines should be:
  382. {major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq}
  383. """
  384. disk_regex_re = re.compile ('^([hsv]d.|mtdblock\d|mmcblk\d|cciss/c\d+d\d+.*)$')
  385. # this gets called an awful lot.
  386. def is_relevant_line(linetokens):
  387. if len(linetokens) != 14:
  388. return False
  389. disk = linetokens[2]
  390. return disk_regex_re.match(disk)
  391. disk_stat_samples = []
  392. for time, lines in _parse_timed_blocks(file):
  393. sample = DiskStatSample(time)
  394. relevant_tokens = [linetokens for linetokens in map (lambda x: x.split(),lines) if is_relevant_line(linetokens)]
  395. for tokens in relevant_tokens:
  396. disk, rsect, wsect, use = tokens[2], int(tokens[5]), int(tokens[9]), int(tokens[12])
  397. sample.add_diskdata([rsect, wsect, use])
  398. disk_stat_samples.append(sample)
  399. disk_stats = []
  400. for sample1, sample2 in zip(disk_stat_samples[:-1], disk_stat_samples[1:]):
  401. interval = sample1.time - sample2.time
  402. if interval == 0:
  403. interval = 1
  404. sums = [ a - b for a, b in zip(sample1.diskdata, sample2.diskdata) ]
  405. readTput = sums[0] / 2.0 * 100.0 / interval
  406. writeTput = sums[1] / 2.0 * 100.0 / interval
  407. util = float( sums[2] ) / 10 / interval
  408. util = max(0.0, min(1.0, util))
  409. disk_stats.append(DiskSample(sample2.time, readTput, writeTput, util))
  410. return disk_stats
  411. def _parse_reduced_proc_meminfo_log(file):
  412. """
  413. Parse file for global memory statistics with
  414. 'MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree' values
  415. (in that order) directly stored on one line.
  416. """
  417. used_values = ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree',)
  418. mem_stats = []
  419. for time, lines in _parse_timed_blocks(file):
  420. sample = MemSample(time)
  421. for name, value in zip(used_values, lines[0].split()):
  422. sample.add_value(name, int(value))
  423. if sample.valid():
  424. mem_stats.append(DrawMemSample(sample))
  425. return mem_stats
  426. def _parse_proc_meminfo_log(file):
  427. """
  428. Parse file for global memory statistics.
  429. The format of relevant lines should be: ^key: value( unit)?
  430. """
  431. used_values = ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree',)
  432. mem_stats = []
  433. meminfo_re = re.compile(r'([^ \t:]+):\s*(\d+).*')
  434. for time, lines in _parse_timed_blocks(file):
  435. sample = MemSample(time)
  436. for line in lines:
  437. match = meminfo_re.match(line)
  438. if not match:
  439. raise ParseError("Invalid meminfo line \"%s\"" % line)
  440. sample.add_value(match.group(1), int(match.group(2)))
  441. if sample.valid():
  442. mem_stats.append(DrawMemSample(sample))
  443. return mem_stats
  444. def _parse_monitor_disk_log(file):
  445. """
  446. Parse file with information about amount of diskspace used.
  447. The format of relevant lines should be: ^volume path: number-of-bytes?
  448. """
  449. disk_stats = []
  450. diskinfo_re = re.compile(r'^(.+):\s*(\d+)$')
  451. for time, lines in _parse_timed_blocks(file):
  452. sample = DiskSpaceSample(time)
  453. for line in lines:
  454. match = diskinfo_re.match(line)
  455. if not match:
  456. raise ParseError("Invalid monitor_disk line \"%s\"" % line)
  457. sample.add_value(match.group(1), int(match.group(2)))
  458. if sample.valid():
  459. disk_stats.append(sample)
  460. return disk_stats
  461. # if we boot the kernel with: initcall_debug printk.time=1 we can
  462. # get all manner of interesting data from the dmesg output
  463. # We turn this into a pseudo-process tree: each event is
  464. # characterised by a
  465. # we don't try to detect a "kernel finished" state - since the kernel
  466. # continues to do interesting things after init is called.
  467. #
  468. # sample input:
  469. # [ 0.000000] ACPI: FACP 3f4fc000 000F4 (v04 INTEL Napa 00000001 MSFT 01000013)
  470. # ...
  471. # [ 0.039993] calling migration_init+0x0/0x6b @ 1
  472. # [ 0.039993] initcall migration_init+0x0/0x6b returned 1 after 0 usecs
  473. def _parse_dmesg(writer, file):
  474. timestamp_re = re.compile ("^\[\s*(\d+\.\d+)\s*]\s+(.*)$")
  475. split_re = re.compile ("^(\S+)\s+([\S\+_-]+) (.*)$")
  476. processMap = {}
  477. idx = 0
  478. inc = 1.0 / 1000000
  479. kernel = Process(writer, idx, "k-boot", 0, 0.1)
  480. processMap['k-boot'] = kernel
  481. base_ts = False
  482. max_ts = 0
  483. for line in file.read().split('\n'):
  484. t = timestamp_re.match (line)
  485. if t is None:
  486. # print "duff timestamp " + line
  487. continue
  488. time_ms = float (t.group(1)) * 1000
  489. # looks like we may have a huge diff after the clock
  490. # has been set up. This could lead to huge graph:
  491. # so huge we will be killed by the OOM.
  492. # So instead of using the plain timestamp we will
  493. # use a delta to first one and skip the first one
  494. # for convenience
  495. if max_ts == 0 and not base_ts and time_ms > 1000:
  496. base_ts = time_ms
  497. continue
  498. max_ts = max(time_ms, max_ts)
  499. if base_ts:
  500. # print "fscked clock: used %f instead of %f" % (time_ms - base_ts, time_ms)
  501. time_ms -= base_ts
  502. m = split_re.match (t.group(2))
  503. if m is None:
  504. continue
  505. # print "match: '%s'" % (m.group(1))
  506. type = m.group(1)
  507. func = m.group(2)
  508. rest = m.group(3)
  509. if t.group(2).startswith ('Write protecting the') or \
  510. t.group(2).startswith ('Freeing unused kernel memory'):
  511. kernel.duration = time_ms / 10
  512. continue
  513. # print "foo: '%s' '%s' '%s'" % (type, func, rest)
  514. if type == "calling":
  515. ppid = kernel.pid
  516. p = re.match ("\@ (\d+)", rest)
  517. if p is not None:
  518. ppid = float (p.group(1)) // 1000
  519. # print "match: '%s' ('%g') at '%s'" % (func, ppid, time_ms)
  520. name = func.split ('+', 1) [0]
  521. idx += inc
  522. processMap[func] = Process(writer, ppid + idx, name, ppid, time_ms / 10)
  523. elif type == "initcall":
  524. # print "finished: '%s' at '%s'" % (func, time_ms)
  525. if func in processMap:
  526. process = processMap[func]
  527. process.duration = (time_ms / 10) - process.start_time
  528. else:
  529. print("corrupted init call for %s" % (func))
  530. elif type == "async_waiting" or type == "async_continuing":
  531. continue # ignore
  532. return processMap.values()
  533. #
  534. # Parse binary pacct accounting file output if we have one
  535. # cf. /usr/include/linux/acct.h
  536. #
  537. def _parse_pacct(writer, file):
  538. # read LE int32
  539. def _read_le_int32(file):
  540. byts = file.read(4)
  541. return (ord(byts[0])) | (ord(byts[1]) << 8) | \
  542. (ord(byts[2]) << 16) | (ord(byts[3]) << 24)
  543. parent_map = {}
  544. parent_map[0] = 0
  545. while file.read(1) != "": # ignore flags
  546. ver = file.read(1)
  547. if ord(ver) < 3:
  548. print("Invalid version 0x%x" % (ord(ver)))
  549. return None
  550. file.seek (14, 1) # user, group etc.
  551. pid = _read_le_int32 (file)
  552. ppid = _read_le_int32 (file)
  553. # print "Parent of %d is %d" % (pid, ppid)
  554. parent_map[pid] = ppid
  555. file.seek (4 + 4 + 16, 1) # timings
  556. file.seek (16, 1) # acct_comm
  557. return parent_map
  558. def _parse_paternity_log(writer, file):
  559. parent_map = {}
  560. parent_map[0] = 0
  561. for line in file.read().split('\n'):
  562. if not line:
  563. continue
  564. elems = line.split(' ') # <Child> <Parent>
  565. if len (elems) >= 2:
  566. # print "paternity of %d is %d" % (int(elems[0]), int(elems[1]))
  567. parent_map[int(elems[0])] = int(elems[1])
  568. else:
  569. print("Odd paternity line '%s'" % (line))
  570. return parent_map
  571. def _parse_cmdline_log(writer, file):
  572. cmdLines = {}
  573. for block in file.read().split('\n\n'):
  574. lines = block.split('\n')
  575. if len (lines) >= 3:
  576. # print "Lines '%s'" % (lines[0])
  577. pid = int (lines[0])
  578. values = {}
  579. values['exe'] = lines[1].lstrip(':')
  580. args = lines[2].lstrip(':').split('\0')
  581. args.pop()
  582. values['args'] = args
  583. cmdLines[pid] = values
  584. return cmdLines
  585. def _parse_bitbake_buildstats(writer, state, filename, file):
  586. paths = filename.split("/")
  587. task = paths[-1]
  588. pn = paths[-2]
  589. start = None
  590. end = None
  591. for line in file:
  592. if line.startswith("Started:"):
  593. start = int(float(line.split()[-1]))
  594. elif line.startswith("Ended:"):
  595. end = int(float(line.split()[-1]))
  596. if start and end:
  597. state.add_process(pn + ":" + task, start, end)
  598. def get_num_cpus(headers):
  599. """Get the number of CPUs from the system.cpu header property. As the
  600. CPU utilization graphs are relative, the number of CPUs currently makes
  601. no difference."""
  602. if headers is None:
  603. return 1
  604. if headers.get("system.cpu.num"):
  605. return max (int (headers.get("system.cpu.num")), 1)
  606. cpu_model = headers.get("system.cpu")
  607. if cpu_model is None:
  608. return 1
  609. mat = re.match(".*\\((\\d+)\\)", cpu_model)
  610. if mat is None:
  611. return 1
  612. return max (int(mat.group(1)), 1)
  613. def _do_parse(writer, state, filename, file):
  614. writer.info("parsing '%s'" % filename)
  615. t1 = time.process_time()
  616. name = os.path.basename(filename)
  617. if name == "proc_diskstats.log":
  618. state.disk_stats = _parse_proc_disk_stat_log(file)
  619. elif name == "reduced_proc_diskstats.log":
  620. state.disk_stats = _parse_reduced_log(file, DiskSample)
  621. elif name == "proc_stat.log":
  622. state.cpu_stats = _parse_proc_stat_log(file)
  623. elif name == "reduced_proc_stat.log":
  624. state.cpu_stats = _parse_reduced_log(file, CPUSample)
  625. elif name == "proc_meminfo.log":
  626. state.mem_stats = _parse_proc_meminfo_log(file)
  627. elif name == "reduced_proc_meminfo.log":
  628. state.mem_stats = _parse_reduced_proc_meminfo_log(file)
  629. elif name == "cmdline2.log":
  630. state.cmdline = _parse_cmdline_log(writer, file)
  631. elif name == "monitor_disk.log":
  632. state.monitor_disk = _parse_monitor_disk_log(file)
  633. elif not filename.endswith('.log'):
  634. _parse_bitbake_buildstats(writer, state, filename, file)
  635. t2 = time.process_time()
  636. writer.info(" %s seconds" % str(t2-t1))
  637. return state
  638. def parse_file(writer, state, filename):
  639. if state.filename is None:
  640. state.filename = filename
  641. basename = os.path.basename(filename)
  642. with open(filename, "r") as file:
  643. return _do_parse(writer, state, filename, file)
  644. def parse_paths(writer, state, paths):
  645. for path in paths:
  646. if state.filename is None:
  647. state.filename = path
  648. root, extension = os.path.splitext(path)
  649. if not(os.path.exists(path)):
  650. writer.warn("warning: path '%s' does not exist, ignoring." % path)
  651. continue
  652. #state.filename = path
  653. if os.path.isdir(path):
  654. files = sorted([os.path.join(path, f) for f in os.listdir(path)])
  655. state = parse_paths(writer, state, files)
  656. elif extension in [".tar", ".tgz", ".gz"]:
  657. if extension == ".gz":
  658. root, extension = os.path.splitext(root)
  659. if extension != ".tar":
  660. writer.warn("warning: can only handle zipped tar files, not zipped '%s'-files; ignoring" % extension)
  661. continue
  662. tf = None
  663. try:
  664. writer.status("parsing '%s'" % path)
  665. tf = tarfile.open(path, 'r:*')
  666. for name in tf.getnames():
  667. state = _do_parse(writer, state, name, tf.extractfile(name))
  668. except tarfile.ReadError as error:
  669. raise ParseError("error: could not read tarfile '%s': %s." % (path, error))
  670. finally:
  671. if tf != None:
  672. tf.close()
  673. else:
  674. state = parse_file(writer, state, path)
  675. return state
  676. def split_res(res, options):
  677. """ Split the res into n pieces """
  678. res_list = []
  679. if options.num > 1:
  680. s_list = sorted(res.start.keys())
  681. frag_size = len(s_list) / float(options.num)
  682. # Need the top value
  683. if frag_size > int(frag_size):
  684. frag_size = int(frag_size + 1)
  685. else:
  686. frag_size = int(frag_size)
  687. start = 0
  688. end = frag_size
  689. while start < end:
  690. state = Trace(None, [], None)
  691. if options.full_time:
  692. state.min = min(res.start.keys())
  693. state.max = max(res.end.keys())
  694. for i in range(start, end):
  695. # Add this line for reference
  696. #state.add_process(pn + ":" + task, start, end)
  697. for p in res.start[s_list[i]]:
  698. state.add_process(p, s_list[i], res.processes[p][1])
  699. start = end
  700. end = end + frag_size
  701. if end > len(s_list):
  702. end = len(s_list)
  703. res_list.append(state)
  704. else:
  705. res_list.append(res)
  706. return res_list