| """ |
| Utilities for working with Valgrind. |
| """ |
| from lnt.util import logger |
| |
| # See: |
| # http://valgrind.org/docs/manual/cl-format.html#cl-format.overview |
| # for reference on the calltree data format specification. |
| |
| |
| class CalltreeParseError(Exception): |
| pass |
| |
| |
| class CalltreeData(object): |
| @staticmethod |
| def frompath(path): |
| with open(path) as file: |
| return CalltreeData.fromfile(file, path) |
| |
| @staticmethod |
| def fromfile(file, path): |
| # I love valgrind, but this is really a horribly lame data format. Oh |
| # well. |
| |
| it = iter(file) |
| |
| # Read the header. |
| description_lines = [] |
| command = None |
| events = None |
| positions = initial_positions = ['line'] |
| for ln in it: |
| # If there is no colon in the line, we have reached the end of the |
| # header. |
| if ':' not in ln: |
| break |
| |
| key, value = ln.split(':', 1) |
| if key == 'desc': |
| description_lines.append(value.strip()) |
| elif key == 'cmd': |
| if command is not None: |
| logger.warning("unexpected multiple 'cmd' keys in %r" % |
| (path,)) |
| command = value.strip() |
| elif key == 'events': |
| if events is not None: |
| logger.warning("unexpected multiple 'events' keys in %r" % |
| (path,)) |
| events = value.split() |
| elif key == 'positions': |
| if positions is not initial_positions: |
| logger.warning( |
| "unexpected multiple 'positions' keys in %r" % |
| (path,)) |
| positions = value.split() |
| else: |
| logger.warning("found unknown key %r in %r" % (key, path)) |
| |
| # Validate that required fields were present. |
| if events is None: |
| raise CalltreeParseError("missing required 'events' key in header") |
| |
| # Construct an instance. |
| data = CalltreeData(events, "\n".join(description_lines), command) |
| |
| # Read the file data. |
| num_samples = len(positions) + len(events) |
| current_file = None |
| current_function = None |
| summary_samples = None |
| for ln in it: |
| # Check if this is the closing summary line. |
| if ln.startswith('summary'): |
| key, value = ln.split(':', 1) |
| summary_samples = list(map(int, value.split())) |
| break |
| |
| # Check if this is an update to the current file or function. |
| if ln.startswith('fl='): |
| current_file = ln[3:-1] |
| elif ln.startswith('fn='): |
| current_function = ln[3:-1] |
| else: |
| # Otherwise, this is a data record. |
| samples = list(map(int, ln.split())) |
| if len(samples) != num_samples: |
| raise CalltreeParseError( |
| "invalid record line, unexpected sample count") |
| data.records.append((current_file, |
| current_function, |
| samples)) |
| |
| # Validate that there are no more remaining records. |
| for ln in it: |
| raise CalltreeParseError("unexpected line in footer: %r" % (ln,)) |
| |
| # Validate that the summary line was present. |
| if summary_samples is None: |
| raise CalltreeParseError( |
| "missing required 'summary' key in footer") |
| |
| data.summary = summary_samples |
| |
| return data |
| |
| def __init__(self, events, description=None, command=None): |
| self.events = events |
| self.description = description |
| self.command = command |
| self.records = [] |
| self.summary = None |