292 lines
10 KiB
Python
292 lines
10 KiB
Python
""" NNAPI systrace parser - call tree data structure and manipulation
|
|
|
|
Used by parser.tracker to gather and interpret the traces for a single thread.
|
|
|
|
'SPEC: foo' refers to specific rows in the
|
|
"NNAPI systrace contract between code and parser" document
|
|
|
|
"""
|
|
|
|
from parser.naming import subphases, layer_order
|
|
from parser.naming import LAYER_APPLICATION, LAYER_RUNTIME, LAYER_UTILITY, LAYER_IGNORE
|
|
from parser.naming import LAYER_IPC
|
|
from parser.naming import PHASE_EXECUTION, PHASE_INITIALIZATION, PHASE_OVERALL, PHASE_UNSPECIFIED
|
|
|
|
class SingleThreadCallTree(object):
|
|
""" Tree of scoped tracepoints. Implements:
|
|
- Creation of the tree from trace data
|
|
- Transformation of the tree into a clear representation
|
|
of time spent per layer and phase
|
|
- Validation that the resulting tree follows expectations
|
|
"""
|
|
# Creation of tree from trace data
|
|
def __init__(self):
|
|
self.root = CallTreeNode(None, None, None, None, None, None)
|
|
self.current = self.root
|
|
self.stack = []
|
|
|
|
def push(self, start_time_s, mark, layer, phase, app_phase, subtract):
|
|
node = self.current.add(start_time_s, mark, layer, phase, app_phase, subtract)
|
|
self.stack.append(self.current)
|
|
self.current = node
|
|
|
|
def push_dummy(self, start_time_s):
|
|
node = self.current.add_dummy(start_time_s)
|
|
self.stack.append(self.current)
|
|
self.current = node
|
|
|
|
def pop(self, end_time_s):
|
|
self.current.end_time_s = end_time_s
|
|
ret = self.current
|
|
self.current = self.stack.pop()
|
|
return ret
|
|
|
|
# Transformation of the tree
|
|
|
|
# Remove dummies created by SPEC:"Switch phase during function"
|
|
def remove_dummies(self):
|
|
to_be_removed = []
|
|
def recurse(node):
|
|
if node.is_dummy():
|
|
to_be_removed.append(node)
|
|
for c in node.children:
|
|
recurse(c)
|
|
recurse(self.root)
|
|
for node in to_be_removed:
|
|
node.remove()
|
|
|
|
# Remove tracing nodes we are not interested in
|
|
def remove_ignored(self):
|
|
to_be_removed = []
|
|
def recurse(node):
|
|
if node.layer == LAYER_IGNORE:
|
|
to_be_removed.append(node)
|
|
for c in node.children:
|
|
recurse(c)
|
|
recurse(self.root)
|
|
for node in to_be_removed:
|
|
node.remove()
|
|
|
|
|
|
# For nodes that are in the wrong place in the tree: create a copy of the node
|
|
# in the right place and mark the original to be subtracted from timing.
|
|
# SPEC: Subtracting time when nesting is violated
|
|
# SPEC: Onetime initialization code
|
|
def copy_subtracted_init_and_wrong_la(self):
|
|
to_be_subtracted = []
|
|
def recurse(node):
|
|
if node.subtract:
|
|
to_be_subtracted.append(node)
|
|
elif node.phase() == PHASE_INITIALIZATION and node.parent.phase() != PHASE_INITIALIZATION:
|
|
to_be_subtracted.append(node)
|
|
elif (node.parent and node.parent.layer == LAYER_APPLICATION and
|
|
(node.layer == LAYER_RUNTIME or node.layer == LAYER_IPC) and
|
|
node.parent.phase() != node.phase() and node.parent.phase() != PHASE_OVERALL and
|
|
node.phase() != PHASE_EXECUTION and node.phase() not in subphases[PHASE_EXECUTION]):
|
|
# The application level phase may be wrong, we move the runtime nodes
|
|
# if necessary.
|
|
to_be_subtracted.append(node)
|
|
for c in node.children:
|
|
recurse(c)
|
|
recurse(self.root)
|
|
for node in to_be_subtracted:
|
|
layer = node.layer
|
|
# Find where to add the subtracted time
|
|
assert node.parent
|
|
new_parent = node.parent
|
|
if node.subtract:
|
|
explanation = "from [SUB]"
|
|
# Move [SUB] up to right layer
|
|
while ((new_parent.layer != layer or new_parent.is_added_detail()) and
|
|
not new_parent.is_root()):
|
|
new_parent = new_parent.parent
|
|
elif node.phase() == PHASE_INITIALIZATION:
|
|
explanation = "from phase PI"
|
|
# Move PI up to root
|
|
while not new_parent.is_root():
|
|
new_parent = new_parent.parent
|
|
else:
|
|
# Move missing LA phase up one
|
|
explanation = "for LA_" + node.phase()
|
|
new_parent = new_parent.parent
|
|
if not new_parent.is_root():
|
|
new_parent = new_parent.parent
|
|
added = new_parent.add(
|
|
node.start_time_s, node.mark + "(copied " + explanation + ")",
|
|
node.layer, node.phase(), node.app_phase, subtract=False)
|
|
added.end_time_s = node.end_time_s
|
|
node.subtract = True
|
|
|
|
# The application may not have added tracing for all phases, this
|
|
# adds intermediate LA nodes where needed.
|
|
def add_missing_la_nodes(self):
|
|
la_to_be_added = []
|
|
def recurse(node):
|
|
if not node.is_added_detail() and not node.subtract:
|
|
if ((node.layer == LAYER_RUNTIME or node.layer == LAYER_IPC) and
|
|
# Wrong LA node
|
|
(node.parent.layer == LAYER_APPLICATION and
|
|
node.parent.phase() != PHASE_OVERALL and
|
|
node.parent.phase() != node.phase()) and
|
|
# LR_PE and subphases need to be special
|
|
node.phase() != PHASE_EXECUTION and
|
|
node.phase() not in subphases[PHASE_EXECUTION]):
|
|
la_to_be_added.append(node)
|
|
for c in node.children:
|
|
recurse(c)
|
|
recurse(self.root)
|
|
for node in la_to_be_added:
|
|
node.add_intermediate_parent(LAYER_APPLICATION, node.phase(), node.app_phase)
|
|
|
|
# Validation
|
|
# SPEC: Local call to other layer
|
|
def validate_nesting(self):
|
|
self.debugstring = ""
|
|
def recurse(node, indent):
|
|
[mark, layer, phase] = [node.mark, node.layer, node.phase()]
|
|
prev_layer = (node.parent and node.parent.layer)
|
|
prev_phase = (node.parent and node.parent.phase())
|
|
self.debugstring += " ".join(map(str, [indent, mark, layer, phase,
|
|
prev_phase, prev_layer,
|
|
"subtract", node.subtract,
|
|
"\n"]))
|
|
# Check that phases nest as we expect:
|
|
assert((prev_phase is None) or # Entering from top without application trace
|
|
(phase == prev_phase) or # Same phase
|
|
(phase == PHASE_UNSPECIFIED) or # Utility function
|
|
(phase == PHASE_INITIALIZATION) or # One-time initialization
|
|
(phase in subphases.get(prev_phase, [])) or # Subphase as designed
|
|
(phase in subphases.get(PHASE_EXECUTION) and # Nested subphase missing
|
|
PHASE_EXECUTION in subphases.get(prev_phase, [])) or
|
|
node.subtract # Marker for wrong nesting
|
|
), self.debugstring
|
|
# Check that layers nest as we expect:
|
|
assert ((prev_layer is None) or
|
|
(layer == LAYER_UTILITY) or
|
|
(layer == prev_layer) or
|
|
(layer in layer_order.get(prev_layer, [])) or
|
|
node.subtract), self.debugstring
|
|
for c in node.children:
|
|
recurse(c, indent + ' ')
|
|
recurse(self.root, '')
|
|
|
|
# Auxiliary functionality
|
|
def print(self):
|
|
print(self.to_str())
|
|
|
|
def to_str(self):
|
|
return self.root.to_str()
|
|
|
|
def is_empty(self):
|
|
return not self.root.children
|
|
|
|
|
|
|
|
class CallTreeNode(object):
|
|
""" Single scoped tracepoint. """
|
|
def __init__(self, start_time_s, mark, layer, phase, app_phase, subtract):
|
|
self.children = []
|
|
self.start_time_s = start_time_s
|
|
self.mark = mark
|
|
self.layer = layer
|
|
self.phase_ = phase
|
|
self.app_phase = app_phase
|
|
self.subtract = subtract
|
|
self.end_time_s = None
|
|
self.parent = None
|
|
|
|
def is_root(self):
|
|
return self.start_time_s is None
|
|
|
|
def is_dummy(self):
|
|
return not self.is_root() and self.mark is None
|
|
|
|
def phase(self):
|
|
if self.phase_ == PHASE_UNSPECIFIED:
|
|
return self.parent.phase()
|
|
else:
|
|
return self.phase_
|
|
|
|
def is_added_detail(self):
|
|
if self.is_root():
|
|
return False
|
|
if self.parent.is_root():
|
|
return False
|
|
if self.layer != self.parent.layer:
|
|
return False
|
|
if self.phase() in subphases.get(self.parent.phase(), []):
|
|
return False
|
|
if self.phase() == PHASE_INITIALIZATION and self.parent.phase() != PHASE_INITIALIZATION:
|
|
return False
|
|
if self.subtract:
|
|
return False
|
|
return True
|
|
|
|
def elapsed_ms(self):
|
|
if (self.end_time_s is None) or (self.start_time_s is None):
|
|
return None
|
|
return (float(self.end_time_s) - float(self.start_time_s)) * 1000.0
|
|
|
|
def elapsed_less_subtracted_ms(self):
|
|
ret = self.elapsed_ms()
|
|
if ret is None:
|
|
return None
|
|
for c in self.children:
|
|
ret = ret - c.subtracted_ms()
|
|
return ret
|
|
|
|
def subtracted_ms(self):
|
|
subtract = 0.0
|
|
if self.is_added_detail():
|
|
for c in self.children:
|
|
subtract = subtract + c.subtracted_ms()
|
|
elif self.subtract:
|
|
subtract = self.elapsed_ms()
|
|
return subtract
|
|
|
|
def add(self, start_time_s, mark, layer, phase, app_phase, subtract):
|
|
node = CallTreeNode(start_time_s, mark, layer, phase, app_phase, subtract)
|
|
node.parent = self
|
|
self.children.append(node)
|
|
return node
|
|
|
|
def add_intermediate_parent(self, layer, phase, app_phase):
|
|
node = CallTreeNode(self.start_time_s,
|
|
" ".join([self.mark, "( added intermediate",
|
|
layer, phase, ")"]),
|
|
layer, phase, app_phase, subtract=False)
|
|
node.end_time_s = float(self.start_time_s) + self.elapsed_less_subtracted_ms() / 1000.0
|
|
node.parent = self.parent
|
|
for i in range(0, len(self.parent.children)):
|
|
if self.parent.children[i] == self:
|
|
self.parent.children[i] = node
|
|
break
|
|
self.parent = node
|
|
node.children.append(self)
|
|
|
|
def add_dummy(self, start_time_s):
|
|
node = CallTreeNode(start_time_s, None, None, None, None, None)
|
|
node.parent = self
|
|
self.children.append(node)
|
|
return node
|
|
|
|
def remove(self):
|
|
self.parent.children.remove(self)
|
|
self.parent.children.extend(self.children)
|
|
for c in self.children:
|
|
c.parent = self.parent
|
|
self.parent = None
|
|
|
|
def to_str(self, indent=''):
|
|
if not self.is_root():
|
|
ret = " ".join(map(str, [indent, self.app_phase, self.mark,
|
|
self.elapsed_less_subtracted_ms(),
|
|
"subtract: ", self.subtract, "\n"]))
|
|
else:
|
|
ret = " ".join([indent, "ROOT", "\n"])
|
|
if self.children:
|
|
ret += (indent + " children:\n")
|
|
for c in self.children:
|
|
ret += c.to_str(indent + ' ')
|
|
return ret
|