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