""" 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