More documentation
This commit is contained in:
parent
9fb993f0d1
commit
4ddc69562f
3 changed files with 89 additions and 22 deletions
|
@ -167,22 +167,15 @@ def cl_deps_to_tr_deps(matches: Set[Tuple], tr: TrContext, cl: ClContext):
|
|||
##################################################
|
||||
|
||||
def owner_node(sym: TrCallbackSymbol):
|
||||
cb_objs = sym.callback_objs
|
||||
owners = [cb_obj.owner for cb_obj in cb_objs]
|
||||
owner_nodes = set()
|
||||
for owner in owners:
|
||||
match owner:
|
||||
case TrSubscriptionObject() as sub:
|
||||
sub: TrSubscriptionObject
|
||||
owner_nodes.add(sub.subscription.node)
|
||||
case TrTimer() as tmr:
|
||||
tmr: TrTimer
|
||||
owner_nodes.update(tmr.nodes)
|
||||
|
||||
if len(owner_nodes) == 1:
|
||||
return owner_nodes.pop()
|
||||
|
||||
return None
|
||||
cb_obj = sym.callback_obj
|
||||
owner = cb_obj.owner
|
||||
match owner:
|
||||
case TrSubscriptionObject() as sub:
|
||||
sub: TrSubscriptionObject
|
||||
return sub.subscription.node
|
||||
case TrTimer() as tmr:
|
||||
tmr: TrTimer
|
||||
return tmr.node
|
||||
|
||||
viable_matchings = {}
|
||||
for tr_cb in tr_cbs:
|
||||
|
@ -299,6 +292,16 @@ def match(tr: TrContext, cl: ClContext):
|
|||
|
||||
|
||||
def match_and_modify_children(node: ASTEntry, match_func):
|
||||
"""
|
||||
If `node` is an `ASTNode`, try to apply `match_func` for every possible
|
||||
tuple (chidren[:i], children[i:], node).
|
||||
If `match_func` returns a non-None value, overwrite `node.children` with that result.
|
||||
This function is not recursive but `match_func` can be.
|
||||
Return `node` in all cases.
|
||||
:param node: The node of which the children are applied `match_func` to
|
||||
:param match_func: The match/modify function. Has to return either `None` or a list of `ASTEntry` and has to accept arguments `seq_head`, `seq_tail`, `node`.
|
||||
:return: `node`
|
||||
"""
|
||||
if not isinstance(node, ASTNode):
|
||||
return node
|
||||
|
||||
|
@ -312,12 +315,18 @@ def match_and_modify_children(node: ASTEntry, match_func):
|
|||
return node
|
||||
|
||||
|
||||
def sanitize(sig: str):
|
||||
def sanitize(sig: str) -> ASTNode | None:
|
||||
"""
|
||||
Tokenizes and parses `sig` into pseudo-C++, then applies a set of rules on the resulting AST to filter unwanted boilerplate expressions.
|
||||
Use `repr(sanitize(sig))` to get the sanitized `sig` as a string.
|
||||
:param sig: A function signature, e.g. from tracing or Clang data.
|
||||
:return: The sanitized AST.
|
||||
"""
|
||||
try:
|
||||
ast = build_ast(sig)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Could not build AST for {sig}, returning it as-is. {e}")
|
||||
return str
|
||||
return None
|
||||
|
||||
def _remove_qualifiers(node: ASTEntry):
|
||||
match node:
|
||||
|
@ -442,6 +451,13 @@ def sanitize(sig: str):
|
|||
|
||||
|
||||
def traverse(node: ASTEntry, action) -> ASTEntry | None:
|
||||
"""
|
||||
Traverse every node in `node`'s subtree, including itself, and apply `action` to it.
|
||||
Return the result of `action(node)` recursively.
|
||||
:param node: The `ASTEntry` to start traversal at
|
||||
:param action: A transformation taking one `ASTEntry` argument and returning an `ASTEntry` or `None`.
|
||||
:return: The transformed AST or `None`
|
||||
"""
|
||||
match node:
|
||||
case ASTNode():
|
||||
children = []
|
||||
|
@ -459,14 +475,25 @@ def traverse(node: ASTEntry, action) -> ASTEntry | None:
|
|||
return action(node)
|
||||
|
||||
|
||||
def build_ast(sig: str):
|
||||
def build_ast(sig: str) -> ASTNode:
|
||||
"""
|
||||
Tokenize and parse `sig` into a pseudo-C++ abstract syntax tree (AST).
|
||||
:param sig: The signature to parse
|
||||
:return: The corresponding AST
|
||||
"""
|
||||
tokens = tokenize(sig)
|
||||
|
||||
ast = ASTNode("ast", [], None)
|
||||
# The state of brackets/braces/parens is stored as a stack. This stack has to be matched in reverse by tokens until
|
||||
# all brackets/braces/parens are closed. Only contains opening ones, never closing ones.
|
||||
parens_stack = []
|
||||
|
||||
# The current node is the subtree which is worked on currently, start at the top-level `ast`
|
||||
current_node = ast
|
||||
for token in tokens:
|
||||
match token.kind:
|
||||
# If the token is an opening bracket/brace/paren, add it to the stack and create a new AST node
|
||||
# for its content. Work on that node next.
|
||||
case TKind.ang_open | TKind.curl_open | TKind.brack_open | TKind.par_open:
|
||||
parens_stack.append(token.kind)
|
||||
brack_content_ast_node = ASTNode(f"{token.spelling}{BRACK_SPELLING_MAP[token.spelling]}",
|
||||
|
@ -476,18 +503,24 @@ def build_ast(sig: str):
|
|||
end=ASTLeaf(BRACK_MAP[token.kind], BRACK_SPELLING_MAP[token.spelling]))
|
||||
current_node.children.append(brack_content_ast_node)
|
||||
current_node = brack_content_ast_node
|
||||
# If the token is a closing bracket/brace/paren, check if it matches the last open bracket on the stack
|
||||
# and pop it. If it does not match, raise an error.
|
||||
# Continue work on the parent node.
|
||||
case TKind.ang_close | TKind.curl_close | TKind.brack_close | TKind.par_close:
|
||||
if not parens_stack or BRACK_MAP.inv[token.kind] != parens_stack[-1]:
|
||||
expect_str = parens_stack[-1] if parens_stack else "nothing"
|
||||
raise ValueError(
|
||||
f"Invalid brackets: encountered {token.spelling} when expecting {expect_str} in '{sig}'")
|
||||
parens_stack.pop()
|
||||
parens_stack.pop() # Remove
|
||||
current_node = current_node.parent
|
||||
# Ignore whitespace
|
||||
case TKind.whitespace:
|
||||
continue
|
||||
# For any other token, add it as a child of the current node and continue processing the current node
|
||||
case _:
|
||||
current_node.children.append(token)
|
||||
|
||||
# If there are any open brackets remaining on the stack, raise a ValueError indicating unclosed brackets
|
||||
if parens_stack:
|
||||
raise ValueError(f"Token stream finished but unclosed brackets remain: {parens_stack} in '{sig}'")
|
||||
|
||||
|
@ -495,6 +528,11 @@ def build_ast(sig: str):
|
|||
|
||||
|
||||
def tokenize(sig: str) -> List[ASTLeaf]:
|
||||
"""
|
||||
Tokenize `sig` according to the ruled defined in`TKind`.
|
||||
:param sig: The signature to tokenize
|
||||
:return: The tokenization, as a list of `ASTLeaf`
|
||||
"""
|
||||
token_matchers = [t.value for t in TKind]
|
||||
tokens = list(re.finditer('|'.join(token_matchers), sig))
|
||||
|
||||
|
@ -513,6 +551,7 @@ def tokenize(sig: str) -> List[ASTLeaf]:
|
|||
return tokens
|
||||
|
||||
|
||||
# Debug code to troubleshoot errors
|
||||
if __name__ == "__main__":
|
||||
with open("../cache/cl_objects_7b616c9c48.pkl", "rb") as f:
|
||||
print("Loading Clang Objects... ", end='')
|
||||
|
|
|
@ -10,8 +10,10 @@ from message_tree.message_tree_structure import E2EBreakdownItem
|
|||
|
||||
def e2e_breakdown_type_hist(items: List[E2EBreakdownItem]):
|
||||
"""
|
||||
Given a list of e2e breakdown instances of the form `("<type>", <duration>)`, plots a histogram for each encountered
|
||||
type.
|
||||
Given a list of e2e breakdown instances of the form `("<type>", <duration>)`, plots a histogram for each encountered type.
|
||||
Be careful not to mix items of different points in the DFG (i.e. do NOT input dataflows here).
|
||||
:param items: The list of items to be turned into a histogram
|
||||
:return: The figure of the plot
|
||||
"""
|
||||
plot_types = ("dds", "idle", "cpu")
|
||||
assert all(item.type in plot_types for item in items)
|
||||
|
@ -34,6 +36,12 @@ def e2e_breakdown_type_hist(items: List[E2EBreakdownItem]):
|
|||
|
||||
|
||||
def e2e_breakdown_stack(*paths: List[E2EBreakdownItem]):
|
||||
"""
|
||||
Plot a timeseries of stacked DDS/Idle/CPU latencies from `paths`.
|
||||
Each path has to be the same length
|
||||
:param paths: The E2E paths to plot
|
||||
:return: The figure of the plot
|
||||
"""
|
||||
fig: Figure
|
||||
ax: Axes
|
||||
fig, ax = plt.subplots(num="E2E type breakdown stackplot")
|
||||
|
|
|
@ -1,23 +1,43 @@
|
|||
from collections import namedtuple
|
||||
|
||||
|
||||
# Representation of the smallest unit in E2E calculations (e.g. a DDS time, a calculation time, an idle time)
|
||||
E2EBreakdownItem = namedtuple("E2EBreakdownItem", ("type", "duration", "location"))
|
||||
# A recursive tree structure used to represent dependencies
|
||||
DepTree = namedtuple("DepTree", ("head", "deps"))
|
||||
|
||||
|
||||
def depth(tree: DepTree, lvl=0):
|
||||
"""
|
||||
The depth of `tree` (the length of the longest path from root to any leaf).
|
||||
Capped at 1000.
|
||||
:param tree: The tree
|
||||
:param lvl: Internal, leave at 0; Used for recursion threshold
|
||||
:return: The depth
|
||||
"""
|
||||
if lvl > 1000:
|
||||
return 0
|
||||
return 1 + max(map(lambda d: depth(d, lvl + 1), tree.deps), default=0)
|
||||
|
||||
|
||||
def size(tree: DepTree, lvl=0):
|
||||
"""
|
||||
The number of nodes (including root, inner and leaf nodes) of `tree`
|
||||
:param tree: The tree
|
||||
:param lvl: Internal, leave at 0; Used for recursion threshold for trees over 1000 entries deep.
|
||||
:return: The number of nodes in `tree`
|
||||
"""
|
||||
if lvl > 1000:
|
||||
return 0
|
||||
return 1 + sum(map(lambda d: size(d, lvl + 1), tree.deps))
|
||||
|
||||
|
||||
def fanout(tree: DepTree):
|
||||
"""
|
||||
The number of leaves of `tree`.
|
||||
:param tree: The tree
|
||||
:return: The number of leaves
|
||||
"""
|
||||
if not tree.deps:
|
||||
return 1
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue