Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(60)

Unified Diff: tools/isolate/trace_inputs.py

Issue 10442068: Add more consitent exception generation and handling (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix small bug Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/isolate/trace_inputs_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/isolate/trace_inputs.py
diff --git a/tools/isolate/trace_inputs.py b/tools/isolate/trace_inputs.py
index ae3da6f428958f46c6ec6f59ec9bb0227a694aed..4f1d653c4a0bf71bd0b5c011f7b2446f8cb6b4b5 100755
--- a/tools/isolate/trace_inputs.py
+++ b/tools/isolate/trace_inputs.py
@@ -48,6 +48,18 @@ KEY_TRACKED = 'isolate_dependency_tracked'
KEY_UNTRACKED = 'isolate_dependency_untracked'
+class TracingFailure(Exception):
+ """An exception occured during tracing."""
+ def __init__(self, description, pid, line_number, line, *args):
+ super(TracingFailure, self).__init__(
+ description, pid, line_number, line, *args)
+ self.description = description
+ self.pid = pid
+ self.line_number = line_number
+ self.line = line
+ self.extra = args
+
+
## OS-specific functions
if sys.platform == 'win32':
@@ -124,6 +136,7 @@ if sys.platform == 'win32':
_MAPPING = {}
def __init__(self):
+ """Lazy loads the cache."""
if not self._MAPPING:
# This is related to UNC resolver on windows. Ignore that.
self._MAPPING['\\Device\\Mup'] = None
@@ -132,18 +145,26 @@ if sys.platform == 'win32':
try:
letter = '%s:' % letter
mapped = QueryDosDevice(letter)
- # It can happen. Assert until we see it happens in the wild. In
- # practice, prefer the lower drive letter.
- assert mapped not in self._MAPPING
- if mapped not in self._MAPPING:
+ if mapped in self._MAPPING:
+ logging.warn(
+ ('Two drives: \'%s\' and \'%s\', are mapped to the same disk'
+ '. Drive letters are a user-mode concept and the kernel '
+ 'traces only have NT path, so all accesses will be '
+ 'associated with the first drive letter, independent of the '
+ 'actual letter used by the code') % (
+ self._MAPPING[mapped], letter))
+ else:
self._MAPPING[mapped] = letter
except WindowsError: # pylint: disable=E0602
pass
- def to_dos(self, path):
- """Converts a native NT path to DOS path."""
+ def to_win32(self, path):
+ """Converts a native NT path to Win32/DOS compatible path."""
match = re.match(r'(^\\Device\\[a-zA-Z0-9]+)(\\.*)?$', path)
- assert match, path
+ if not match:
+ raise ValueError(
+ 'Can\'t convert %s into a Win32 compatible path' % path,
+ path)
if not match.group(1) in self._MAPPING:
# Unmapped partitions may be accessed by windows for the
# fun of it while the test is running. Discard these.
@@ -164,7 +185,10 @@ if sys.platform == 'win32':
On Windows, removes any leading '\\?\'.
"""
- assert isabs(path), path
+ if not isabs(path):
+ raise ValueError(
+ 'Can\'t get native path case for a non-absolute path: %s' % path,
+ path)
# Windows used to have an option to turn on case sensitivity on non Win32
# subsystem but that's out of scope here and isn't supported anymore.
# Go figure why GetShortPathName() is needed.
@@ -194,7 +218,10 @@ elif sys.platform == 'darwin':
def get_native_path_case(path):
"""Returns the native path case for an existing file."""
- assert isabs(path), path
+ if not isabs(path):
+ raise ValueError(
+ 'Can\'t get native path case for a non-absolute path: %s' % path,
+ path)
# Technically, it's only HFS+ on OSX that is case insensitive. It's the
# default setting on HFS+ but can be changed.
rel_ref, _ = Carbon.File.FSPathMakeRef(path)
@@ -216,7 +243,10 @@ else: # OSes other than Windows and OSX.
TODO(maruel): This is not strictly true. Implement if necessary.
"""
- assert isabs(path), path
+ if not isabs(path):
+ raise ValueError(
+ 'Can\'t get native path case for a non-absolute path: %s' % path,
+ path)
# Give up on cygwin, as GetLongPathName() can't be called.
return path
@@ -292,8 +322,8 @@ def process_quoted_arguments(text):
state = NEED_QUOTE
current_argument = ''
out = []
- for i in text:
- if i == '"':
+ for index, char in enumerate(text):
+ if char == '"':
if state == NEED_QUOTE:
state = INSIDE_STRING
elif state == INSIDE_STRING:
@@ -302,18 +332,24 @@ def process_quoted_arguments(text):
current_argument = ''
state = NEED_COMMA_OR_DOT
else:
- assert False, text
- elif i == ',':
+ raise ValueError(
+ 'Can\'t process char at column %d for: %r' % (index, text),
+ index,
+ text)
+ elif char == ',':
if state in (NEED_COMMA_OR_DOT, NEED_COMMA):
state = NEED_SPACE
else:
- assert False, text
- elif i == ' ':
+ raise ValueError(
+ 'Can\'t process char at column %d for: %r' % (index, text),
+ index,
+ text)
+ elif char == ' ':
if state == NEED_SPACE:
state = NEED_QUOTE
if state == INSIDE_STRING:
- current_argument += i
- elif i == '.':
+ current_argument += char
+ elif char == '.':
if state == NEED_COMMA_OR_DOT:
# The string is incomplete, this mean the strace -s flag should be
# increased.
@@ -323,15 +359,24 @@ def process_quoted_arguments(text):
elif state == NEED_DOT_3:
state = NEED_COMMA
elif state == INSIDE_STRING:
- current_argument += i
+ current_argument += char
else:
- assert False, text
+ raise ValueError(
+ 'Can\'t process char at column %d for: %r' % (index, text),
+ index,
+ text)
else:
if state == INSIDE_STRING:
- current_argument += i
+ current_argument += char
else:
- assert False, text
- assert state in (NEED_COMMA, NEED_COMMA_OR_DOT)
+ raise ValueError(
+ 'Can\'t process char at column %d for: %r' % (index, text),
+ index,
+ text)
+ if state not in (NEED_COMMA, NEED_COMMA_OR_DOT):
+ raise ValueError(
+ 'String is incorrectly terminated: %r' % text,
+ text)
return out
@@ -347,6 +392,7 @@ class ApiBase(object):
"""
def __init__(self, root, pid, initial_cwd, parentid):
"""root is a reference to the Context."""
+ # Check internal consistency.
assert isinstance(root, ApiBase.Context)
assert isinstance(pid, int), repr(pid)
self.root = weakref.ref(root)
@@ -447,6 +493,7 @@ class Results(object):
# Shouldn't be used normally, only exists to simplify algorithms.
self.nb_files = 1
+ # Check internal consistency.
assert path, path
assert bool(root) != bool(isabs(path)), (root, path)
assert (
@@ -481,6 +528,7 @@ class Results(object):
def strip_root(self, root):
"""Returns a clone of itself with 'root' stripped off."""
+ # Check internal consistency.
assert isabs(root) and root.endswith(os.path.sep), root
if not self.full_path.startswith(root):
return None
@@ -515,15 +563,17 @@ class Results(object):
self.pid = pid
self.files = sorted(
(Results.File(None, f) for f in files), key=lambda x: x.path)
- assert len(set(f.path for f in self.files)) == len(self.files), [
- f.path for f in self.files]
- assert isinstance(children, list)
- assert isinstance(self.files, list)
self.children = children
self.executable = executable
self.command = command
self.initial_cwd = initial_cwd
+ # Check internal consistency.
+ assert len(set(f.path for f in self.files)) == len(self.files), [
+ f.path for f in self.files]
+ assert isinstance(self.children, list)
+ assert isinstance(self.files, list)
+
@property
def all(self):
for child in self.children:
@@ -762,111 +812,143 @@ class Strace(ApiBase):
# Ignore signals.
return
- match = self.RE_KILLED.match(line)
- if match:
- self.handle_exit_group(match.group(1), None, None)
- return
-
- match = self.RE_UNFINISHED.match(line)
- if match:
- assert match.group(1) not in self._pending_calls
- self._pending_calls[match.group(1)] = match.group(1) + match.group(2)
- return
-
- match = self.RE_UNAVAILABLE.match(line)
- if match:
- # This usually means a process was killed and a pending call was
- # canceled.
- # TODO(maruel): Look up the last exit_group() trace just above and
- # make sure any self._pending_calls[anything] is properly flushed.
- return
-
- match = self.RE_RESUMED.match(line)
- if match:
- assert match.group(1) in self._pending_calls, self._pending_calls
- pending = self._pending_calls.pop(match.group(1))
- # Reconstruct the line.
- line = pending + match.group(2)
-
- match = self.RE_HEADER.match(line)
- assert match, (self.pid, self._line_number, line)
- if match.group(1) == self.UNNAMED_FUNCTION:
- return
- handler = getattr(self, 'handle_%s' % match.group(1), None)
- assert handler, (self.pid, self._line_number, line)
try:
- return handler(
- match.group(1),
- match.group(2),
- match.group(3))
- except Exception:
- print >> sys.stderr, (self.pid, self._line_number, line)
+ match = self.RE_KILLED.match(line)
+ if match:
+ # Converts a '+++ killied by Foo +++' trace into an exit_group().
+ self.handle_exit_group(match.group(1), None)
+ return
+
+ match = self.RE_UNFINISHED.match(line)
+ if match:
+ if match.group(1) in self._pending_calls:
+ raise TracingFailure(
+ 'Found two unfinished calls for the same function',
+ None, None, None,
+ self._pending_calls)
+ self._pending_calls[match.group(1)] = (
+ match.group(1) + match.group(2))
+ return
+
+ match = self.RE_UNAVAILABLE.match(line)
+ if match:
+ # This usually means a process was killed and a pending call was
+ # canceled.
+ # TODO(maruel): Look up the last exit_group() trace just above and
+ # make sure any self._pending_calls[anything] is properly flushed.
+ return
+
+ match = self.RE_RESUMED.match(line)
+ if match:
+ if match.group(1) not in self._pending_calls:
+ raise TracingFailure(
+ 'Found a resumed call that was not logged as unfinished',
+ None, None, None,
+ self._pending_calls)
+ pending = self._pending_calls.pop(match.group(1))
+ # Reconstruct the line.
+ line = pending + match.group(2)
+
+ match = self.RE_HEADER.match(line)
+ if not match:
+ raise TracingFailure(
+ 'Found an invalid line: %s' % line,
+ None, None, None)
+ if match.group(1) == self.UNNAMED_FUNCTION:
+ return
+ handler = getattr(self, 'handle_%s' % match.group(1), None)
+ if not handler:
+ raise TracingFailure(
+ 'Found a unhandled trace: %s' % match.group(1),
+ None, None, None)
+
+ return handler(match.group(2), match.group(3))
+ except TracingFailure, e:
+ # Hack in the values since the handler could be a static function.
+ e.pid = self.pid
+ e.line = line
+ e.line_number = self._line_number
+ # Re-raise the modified exception.
raise
-
- def handle_chdir(self, _function, args, result):
+ except (KeyError, NotImplementedError, ValueError), e:
+ raise TracingFailure(
+ 'Trace generated a %s exception: %s' % (
+ e.__class__.__name__, str(e)),
+ self.pid,
+ self._line_number,
+ line,
+ e)
+
+ def handle_chdir(self, args, result):
"""Updates cwd."""
- assert result.startswith('0'), 'Unexecpected fail: %s' % result
+ if not result.startswith('0'):
+ return
cwd = self.RE_CHDIR.match(args).group(1)
self.cwd = self.RelativePath(self, cwd)
logging.debug('handle_chdir(%d, %s)' % (self.pid, self.cwd))
- def handle_clone(self, _function, _args, result):
+ def handle_clone(self, _args, result):
"""Transfers cwd."""
if result == '? ERESTARTNOINTR (To be restarted)':
return
# Update the other process right away.
childpid = int(result)
child = self.root().get_or_set_proc(childpid)
+ if child.parentid is not None or childpid in self.children:
+ raise TracingFailure(
+ 'Found internal inconsitency in process lifetime detection',
+ None, None, None)
+
# Copy the cwd object.
child.initial_cwd = self.get_cwd()
- assert child.parentid is None
child.parentid = self.pid
# It is necessary because the logs are processed out of order.
- assert childpid not in self.children
self.children.append(childpid)
- def handle_close(self, _function, _args, _result):
+ def handle_close(self, _args, _result):
pass
- def handle_execve(self, _function, args, result):
+ def handle_execve(self, args, result):
if result != '0':
return
m = self.RE_EXECVE.match(args)
filepath = m.group(1)
- self._handle_file(filepath, result)
+ self._handle_file(filepath)
self.executable = self.RelativePath(self.get_cwd(), filepath)
self.command = process_quoted_arguments(m.group(2))
- def handle_exit_group(self, _function, _args, _result):
+ def handle_exit_group(self, _args, _result):
"""Removes cwd."""
self.cwd = None
@staticmethod
- def handle_fork(_function, args, result):
- assert False, (args, result)
+ def handle_fork(_args, _result):
+ raise NotImplementedError('Unexpected/unimplemented trace fork()')
- def handle_open(self, _function, args, result):
+ def handle_open(self, args, result):
+ if result.startswith('-1'):
+ return
args = (self.RE_OPEN3.match(args) or self.RE_OPEN2.match(args)).groups()
if 'O_DIRECTORY' in args[1]:
return
- self._handle_file(args[0], result)
+ self._handle_file(args[0])
- def handle_rename(self, _function, args, result):
+ def handle_rename(self, args, result):
+ if result.startswith('-1'):
+ return
args = self.RE_RENAME.match(args).groups()
- self._handle_file(args[0], result)
- self._handle_file(args[1], result)
+ self._handle_file(args[0])
+ self._handle_file(args[1])
@staticmethod
- def handle_stat64(_function, args, result):
- assert False, (args, result)
+ def handle_stat64(_args, _result):
+ raise NotImplementedError('Unexpected/unimplemented trace stat64()')
@staticmethod
- def handle_vfork(_function, args, result):
- assert False, (args, result)
+ def handle_vfork(_args, _result):
+ raise NotImplementedError('Unexpected/unimplemented trace vfork()')
- def _handle_file(self, filepath, result):
- if result.startswith('-1'):
- return
+ def _handle_file(self, filepath):
filepath = self.RelativePath(self.get_cwd(), filepath)
self.add_file(filepath)
@@ -882,25 +964,48 @@ class Strace(ApiBase):
return self.initial_cwd
def on_line(self, pid, line):
+ """Transfers control into the Process.on_line() function."""
self.get_or_set_proc(pid).on_line(line.strip())
def to_results(self):
"""Finds back the root process and verify consistency."""
# TODO(maruel): Absolutely unecessary, fix me.
root = [p for p in self.processes.itervalues() if not p.parentid]
- assert len(root) == 1
+ if len(root) != 1:
+ raise TracingFailure(
+ 'Found internal inconsitency in process lifetime detection',
+ None,
+ None,
+ None,
+ root)
process = root[0].to_results_process()
- assert sorted(self.processes) == sorted(p.pid for p in process.all)
+ if sorted(self.processes) != sorted(p.pid for p in process.all):
+ raise TracingFailure(
+ 'Found internal inconsitency in process lifetime detection',
+ None,
+ None,
+ None,
+ sorted(self.processes),
+ sorted(p.pid for p in process.all))
return Results(process)
def get_or_set_proc(self, pid):
"""Returns the Context.Process instance for this pid or creates a new one.
"""
- assert isinstance(pid, int) and pid
+ if not pid or not isinstance(pid, int):
+ raise TracingFailure(
+ 'Unpexpected value for pid: %r' % pid,
+ pid,
+ None,
+ None,
+ pid)
return self.processes.setdefault(pid, self.Process(self, pid))
@classmethod
def traces(cls):
+ """Returns the list of all handled traces to pass this as an argument to
+ strace.
+ """
prefix = 'handle_'
return [i[len(prefix):] for i in dir(cls.Process) if i.startswith(prefix)]
@@ -941,7 +1046,6 @@ class Strace(ApiBase):
out = child.communicate()[0]
# Once it's done, write metadata into the log file to be able to follow the
# pid files.
- assert not os.path.isfile(logname)
with open(logname, 'wb') as f:
json.dump(
{
@@ -1322,7 +1426,7 @@ class Dtrace(ApiBase):
'}\n') + cls.D_CODE
class Context(ApiBase.Context):
- # This is the most common format. index pid function(args)
+ # Format: index pid function(args)
RE_HEADER = re.compile(r'^\d+ (\d+) ([a-zA-Z_\-]+)\((.*?)\)$')
# Arguments parsing.
@@ -1345,27 +1449,61 @@ class Dtrace(ApiBase):
# First process to be started by self._tracer_pid.
self._initial_pid = None
self._initial_cwd = None
+ self._line_number = 0
def on_line(self, line):
+ self._line_number += 1
match = self.RE_HEADER.match(line)
- assert match, line
+ if not match:
+ raise TracingFailure(
+ 'Found malformed line: %s' % line,
+ None,
+ self._line_number,
+ line)
fn = getattr(
self,
'handle_%s' % match.group(2).replace('-', '_'),
self._handle_ignored)
- return fn(
- int(match.group(1)),
- match.group(3))
+ # It is guaranteed to succeed because of the regexp. Or at least I thought
+ # it would.
+ pid = int(match.group(1))
+ try:
+ return fn(pid, match.group(3))
+ except TracingFailure, e:
+ # Hack in the values since the handler could be a static function.
+ e.pid = pid
+ e.line = line
+ e.line_number = self._line_number
+ # Re-raise the modified exception.
+ raise
+ except (KeyError, NotImplementedError, ValueError), e:
+ raise TracingFailure(
+ 'Trace generated a %s exception: %s' % (
+ e.__class__.__name__, str(e)),
+ pid,
+ self._line_number,
+ line,
+ e)
def to_results(self):
"""Uses self._initial_pid to determine the initial process."""
process = self.processes[self._initial_pid].to_results_process()
- assert sorted(self.processes) == sorted(p.pid for p in process.all), (
- sorted(self.processes), sorted(p.pid for p in process.all))
+ # Internal concistency check.
+ if sorted(self.processes) != sorted(p.pid for p in process.all):
+ raise TracingFailure(
+ 'Found internal inconsitency in process lifetime detection',
+ None,
+ None,
+ None,
+ sorted(self.processes),
+ sorted(p.pid for p in process.all))
return Results(process)
def handle_dtrace_BEGIN(self, pid, args):
- assert not self._tracer_pid and not self._initial_pid
+ if self._tracer_pid or self._initial_pid:
+ raise TracingFailure(
+ 'Found internal inconsitency in dtrace_BEGIN log line',
+ None, None, None)
self._tracer_pid = pid
self._initial_cwd = self.RE_DTRACE_BEGIN.match(args).group(1)
@@ -1376,10 +1514,22 @@ class Dtrace(ApiBase):
are child of the traced processes so there is no need to verify the
process hierarchy.
"""
- assert pid not in self.processes
+ if pid in self.processes:
+ raise TracingFailure(
+ 'Found internal inconsitency in proc_start: %d started two times' %
+ pid,
+ None, None, None)
match = self.RE_PROC_START.match(args)
+ if not match:
+ raise TracingFailure(
+ 'Failed to parse arguments: %s' % args,
+ None, None, None)
ppid = int(match.group(1))
- assert (ppid == self._tracer_pid) != bool(self._initial_pid)
+ if (ppid == self._tracer_pid) == bool(self._initial_pid):
+ raise TracingFailure(
+ ( 'Parent process is _tracer_pid(%d) but _initial_pid(%d) is '
+ 'already set') % (self._tracer_pid, self._initial_pid),
+ None, None, None)
if not self._initial_pid:
self._initial_pid = pid
ppid = None
@@ -1408,16 +1558,24 @@ class Dtrace(ApiBase):
:)
"""
match = self.RE_EXECVE.match(args)
- assert match, args
+ if not match:
+ raise TracingFailure(
+ 'Failed to parse arguments: %s' % args,
+ None, None, None)
proc = self.processes[pid]
proc.executable = match.group(1)
proc.command = process_quoted_arguments(match.group(3))
- assert int(match.group(2)) == len(proc.command), args
+ if int(match.group(2)) != len(proc.command):
+ raise TracingFailure(
+ 'Failed to parse execve() arguments: %s' % args,
+ None, None, None)
def handle_chdir(self, pid, args):
"""Updates cwd."""
- assert self._tracer_pid
- assert pid in self.processes
+ if not self._tracer_pid or pid not in self.processes:
+ raise TracingFailure(
+ 'Found inconsistency in the log, is it an old log?',
+ None, None, None)
cwd = self.RE_CHDIR.match(args).group(1)
if not cwd.startswith('/'):
cwd2 = os.path.join(self.processes[pid].cwd, cwd)
@@ -1432,7 +1590,10 @@ class Dtrace(ApiBase):
def handle_open(self, pid, args):
match = self.RE_OPEN.match(args)
- assert match, (pid, args)
+ if not match:
+ raise TracingFailure(
+ 'Failed to parse arguments: %s' % args,
+ None, None, None)
flag = int(match.group(2), 16)
if self.O_DIRECTORY & flag == self.O_DIRECTORY:
# Ignore directories.
@@ -1441,7 +1602,10 @@ class Dtrace(ApiBase):
def handle_rename(self, pid, args):
match = self.RE_RENAME.match(args)
- assert match, (pid, args)
+ if not match:
+ raise TracingFailure(
+ 'Failed to parse arguments: %s' % args,
+ None, None, None)
self._handle_file(pid, match.group(1))
self._handle_file(pid, match.group(2))
@@ -1459,7 +1623,7 @@ class Dtrace(ApiBase):
@staticmethod
def _handle_ignored(pid, args):
"""Is called for all the event traces that are not handled."""
- assert False, '%d ??(%s)' % (pid, args)
+ raise NotImplementedError('Please implement me')
@staticmethod
def clean_trace(logname):
@@ -1566,15 +1730,16 @@ class Dtrace(ApiBase):
lines = [l for l in logfile if l.strip()]
errors = [l for l in lines if l.startswith('dtrace:')]
if errors:
- print >> sys.stderr, 'Failed to load: %s' % logname
- print >> sys.stderr, '\n'.join(errors)
- assert not errors, errors
+ raise TracingFailure(
+ 'Found errors in the trace: %s' % '\n'.join(errors),
+ None, None, None, logname)
try:
lines = sorted(lines, key=lambda l: int(l.split(' ', 1)[0]))
except ValueError:
- print >> sys.stderr, 'Failed to load: %s' % logname
- print >> sys.stderr, '\n'.join(lines)
- raise
+ raise TracingFailure(
+ 'Found errors in the trace: %s' % '\n'.join(
+ l for l in lines if l.split(' ', 1)[0].isdigit()),
+ None, None, None, logname)
with open(logname, 'wb') as logfile:
logfile.write(''.join(lines))
@@ -1595,6 +1760,29 @@ class LogmanTrace(ApiBase):
Ignores directories.
"""
+ # The basic headers.
+ EXPECTED_HEADER = [
+ u'Event Name',
+ u'Type',
+ u'Event ID',
+ u'Version',
+ u'Channel',
+ u'Level', # 5
+ u'Opcode',
+ u'Task',
+ u'Keyword',
+ u'PID',
+ u'TID', # 10
+ u'Processor Number',
+ u'Instance ID',
+ u'Parent Instance ID',
+ u'Activity ID',
+ u'Related Activity ID', # 15
+ u'Clock-Time',
+ u'Kernel(ms)', # Both have a resolution of ~15ms which makes them
+ u'User(ms)', # pretty much useless.
+ u'User Data', # Extra arguments that are event-specific.
+ ]
# Only the useful headers common to all entries are listed there. Any column
# at 19 or higher is dependent on the specific event.
EVENT_NAME = 0
@@ -1603,6 +1791,7 @@ class LogmanTrace(ApiBase):
TID = 10
PROCESSOR_ID = 11
TIMESTAMP = 16
+ NULL_GUID = '{00000000-0000-0000-0000-000000000000}'
class Process(ApiBase.Context.Process):
def __init__(self, *args):
@@ -1622,83 +1811,82 @@ class LogmanTrace(ApiBase):
self._initial_pid = None
self._line_number = 0
- def on_csv_line(self, line):
+ def on_csv_line(self, raw_line):
"""Processes a CSV Event line."""
# So much white space!
- line = [i.strip() for i in line]
+ line = [i.strip() for i in raw_line]
self._line_number += 1
- if self._line_number == 1:
- assert line == [
- u'Event Name',
- u'Type',
- u'Event ID',
- u'Version',
- u'Channel',
- u'Level', # 5
- u'Opcode',
- u'Task',
- u'Keyword',
- u'PID',
- u'TID', # 10
- u'Processor Number',
- u'Instance ID',
- u'Parent Instance ID',
- u'Activity ID',
- u'Related Activity ID', # 15
- u'Clock-Time',
- u'Kernel(ms)', # Both have a resolution of ~15ms which makes them
- u'User(ms)', # pretty much useless.
- u'User Data',
- ]
- return
-
- # As you can see, the CSV is full of useful non-redundant information:
- # Event ID
- assert line[2] == '0'
- # Version
- assert line[3] in ('2', '3'), line[3]
- # Channel
- assert line[4] == '0'
- # Level
- assert line[5] == '0'
- # Task
- assert line[7] == '0'
- # Keyword
- assert line[8] == '0x0000000000000000'
- # Instance ID
- assert line[12] == ''
- # Parent Instance ID
- assert line[13] == ''
- # Activity ID
- assert line[14] == '{00000000-0000-0000-0000-000000000000}'
- # Related Activity ID
- assert line[15] == ''
-
- if line[0].startswith('{'):
- # Skip GUIDs.
- return
+ try:
+ if self._line_number == 1:
+ if line != self.EXPECTED_HEADER:
+ raise TracingFailure(
+ 'Found malformed header: %s' % ' '.join(line),
+ None, None, None)
+ return
- # Convert the PID in-place from hex.
- line[self.PID] = int(line[self.PID], 16)
+ # As you can see, the CSV is full of useful non-redundant information:
+ if (line[2] != '0' or # Event ID
+ line[3] not in ('2', '3') or # Version
+ line[4] != '0' or # Channel
+ line[5] != '0' or # Level
+ line[7] != '0' or # Task
+ line[8] != '0x0000000000000000' or # Keyword
+ line[12] != '' or # Instance ID
+ line[13] != '' or # Parent Instance ID
+ line[14] != self.NULL_GUID or # Activity ID
+ line[15] != ''): # Related Activity ID
+ raise TracingFailure(
+ 'Found unexpected values in line: %s' % ' '.join(line),
+ None, None, None)
+ if line[0].startswith('{'):
+ # Skip GUIDs.
+ return
- # By Opcode
- handler = getattr(
- self,
- 'handle_%s_%s' % (line[self.EVENT_NAME], line[self.TYPE]),
- None)
- if not handler:
- # Try to get an universal fallback
- handler = getattr(self, 'handle_%s_Any' % line[self.EVENT_NAME], None)
- if handler:
+ # Convert the PID in-place from hex.
+ line[self.PID] = int(line[self.PID], 16)
+
+ # By Opcode
+ handler = getattr(
+ self,
+ 'handle_%s_%s' % (line[self.EVENT_NAME], line[self.TYPE]),
+ None)
+ if not handler:
+ # Try to get an universal fallback for the event class.
+ handler = getattr(self, 'handle_%s_Any' % line[self.EVENT_NAME], None)
+ if not handler:
+ raise TracingFailure(
+ 'Unexpected event %s_%s' % (
+ line[self.EVENT_NAME], line[self.TYPE]),
+ None, None, None)
handler(line)
- else:
- assert False, '%s_%s' % (line[self.EVENT_NAME], line[self.TYPE])
+ except TracingFailure, e:
+ # Hack in the values since the handler could be a static function.
+ e.pid = line[self.PID]
+ e.line = line
+ e.line_number = self._line_number
+ # Re-raise the modified exception.
+ raise
+ except (KeyError, NotImplementedError, ValueError), e:
+ raise TracingFailure(
+ 'Trace generated a %s exception: %s' % (
+ e.__class__.__name__, str(e)),
+ line[self.PID],
+ self._line_number,
+ line,
+ e)
def to_results(self):
"""Uses self._initial_pid to determine the initial process."""
process = self.processes[self._initial_pid].to_results_process()
- assert sorted(self.processes) == sorted(p.pid for p in process.all), (
- sorted(self.processes), sorted(p.pid for p in process.all))
+ # Internal concistency check.
+ if sorted(self.processes) != sorted(p.pid for p in process.all):
+ raise TracingFailure(
+ 'Found internal inconsitency in process lifetime detection',
+ None,
+ None,
+ None,
+ sorted(self.processes),
+ sorted(p.pid for p in process.all))
return Results(process)
def _thread_to_process(self, tid):
@@ -1709,7 +1897,7 @@ class LogmanTrace(ApiBase):
@staticmethod
def handle_EventTrace_Header(line):
"""Verifies no event was dropped, e.g. no buffer overrun occured."""
- #BUFFER_SIZE = 19
+ BUFFER_SIZE = 19
#VERSION = 20
#PROVIDER_VERSION = 21
#NUMBER_OF_PROCESSORS = 22
@@ -1731,7 +1919,11 @@ class LogmanTrace(ApiBase):
#BUFFERS_LOST = 38
#SESSION_NAME_STRING = 39
#LOG_FILE_NAME_STRING = 40
- assert line[EVENTS_LOST] == '0'
+ if line[EVENTS_LOST] != '0':
+ raise TracingFailure(
+ ( 'Events were lost during trace, please increase the buffer size '
+ 'from %s') % line[BUFFER_SIZE],
+ None, None, None)
def handle_EventTrace_Any(self, line):
pass
@@ -1786,7 +1978,7 @@ class LogmanTrace(ApiBase):
# Ignore directories and bare drive right away.
if raw_path.endswith(os.path.sep):
return
- filename = self._drive_map.to_dos(raw_path)
+ filename = self._drive_map.to_win32(raw_path)
# Ignore bare drive right away. Some may still fall through with format
# like '\\?\X:'
if len(filename) == 2:
@@ -1828,22 +2020,35 @@ class LogmanTrace(ApiBase):
if ppid == self._tracer_pid:
# Need to ignore processes we don't know about because the log is
# system-wide. self._tracer_pid shall start only one process.
- assert not self._initial_pid
+ if self._initial_pid:
+ raise TracingFailure(
+ ( 'Parent process is _tracer_pid(%d) but _initial_pid(%d) is '
+ 'already set') % (self._tracer_pid, self._initial_pid),
+ None, None, None)
self._initial_pid = pid
ppid = None
elif ppid not in self.processes:
# Ignore
return
- assert pid not in self.processes
+ if pid in self.processes:
+ raise TracingFailure(
+ 'Process %d started two times' % pid,
+ None, None, None)
proc = self.processes[pid] = self.Process(self, pid, None, ppid)
+
+ if (not line[IMAGE_FILE_NAME].startswith('"') or
+ not line[IMAGE_FILE_NAME].endswith('"')):
+ raise TracingFailure(
+ 'Command line is not properly quoted: %s' % line[IMAGE_FILE_NAME],
+ None, None, None)
+
# TODO(maruel): Process escapes.
- assert (
- line[COMMAND_LINE].startswith('"') and
- line[COMMAND_LINE].endswith('"'))
+ if (not line[COMMAND_LINE].startswith('"') or
+ not line[COMMAND_LINE].endswith('"')):
+ raise TracingFailure(
+ 'Command line is not properly quoted: %s' % line[COMMAND_LINE],
+ None, None, None)
proc.command = CommandLineToArgvW(line[COMMAND_LINE][1:-1])
- assert (
- line[IMAGE_FILE_NAME].startswith('"') and
- line[IMAGE_FILE_NAME].endswith('"'))
proc.executable = line[IMAGE_FILE_NAME][1:-1]
# proc.command[0] may be the absolute path of 'executable' but it may be
# anything else too. If it happens that command[0] ends with executable,
@@ -2020,7 +2225,7 @@ class LogmanTrace(ApiBase):
elif logformat == 'xml':
cmd_convert.extend(['-of', 'XML'])
else:
- assert False, logformat
+ raise ValueError('Unexpected log format \'%s\'' % logformat)
logging.debug('Running: %s' % cmd_convert)
# This can takes tens of minutes for large logs.
subprocess.check_call(
« no previous file with comments | « no previous file | tools/isolate/trace_inputs_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698