1#!/usr/bin/env python
2# SPDX-License-Identifier: GPL-2.0-only
3
4"""
5Copyright 2008 (c) Frederic Weisbecker <fweisbec@gmail.com>
6
7This script parses a trace provided by the function tracer in
8kernel/trace/trace_functions.c
9The resulted trace is processed into a tree to produce a more human
10view of the call stack by drawing textual but hierarchical tree of
11calls. Only the functions's names and the call time are provided.
12
13Usage:
14	Be sure that you have CONFIG_FUNCTION_TRACER
15	# mount -t tracefs nodev /sys/kernel/tracing
16	# echo function > /sys/kernel/tracing/current_tracer
17	$ cat /sys/kernel/tracing/trace_pipe > ~/raw_trace_func
18	Wait some times but not too much, the script is a bit slow.
19	Break the pipe (Ctrl + Z)
20	$ scripts/tracing/draw_functrace.py < ~/raw_trace_func > draw_functrace
21	Then you have your drawn trace in draw_functrace
22"""
23
24
25import sys, re
26
27class CallTree:
28	""" This class provides a tree representation of the functions
29		call stack. If a function has no parent in the kernel (interrupt,
30		syscall, kernel thread...) then it is attached to a virtual parent
31		called ROOT.
32	"""
33	ROOT = None
34
35	def __init__(self, func, time = None, parent = None):
36		self._func = func
37		self._time = time
38		if parent is None:
39			self._parent = CallTree.ROOT
40		else:
41			self._parent = parent
42		self._children = []
43
44	def calls(self, func, calltime):
45		""" If a function calls another one, call this method to insert it
46			into the tree at the appropriate place.
47			@return: A reference to the newly created child node.
48		"""
49		child = CallTree(func, calltime, self)
50		self._children.append(child)
51		return child
52
53	def getParent(self, func):
54		""" Retrieve the last parent of the current node that
55			has the name given by func. If this function is not
56			on a parent, then create it as new child of root
57			@return: A reference to the parent.
58		"""
59		tree = self
60		while tree != CallTree.ROOT and tree._func != func:
61			tree = tree._parent
62		if tree == CallTree.ROOT:
63			child = CallTree.ROOT.calls(func, None)
64			return child
65		return tree
66
67	def __repr__(self):
68		return self.__toString("", True)
69
70	def __toString(self, branch, lastChild):
71		if self._time is not None:
72			s = "%s----%s (%s)\n" % (branch, self._func, self._time)
73		else:
74			s = "%s----%s\n" % (branch, self._func)
75
76		i = 0
77		if lastChild:
78			branch = branch[:-1] + " "
79		while i < len(self._children):
80			if i != len(self._children) - 1:
81				s += "%s" % self._children[i].__toString(branch +\
82								"    |", False)
83			else:
84				s += "%s" % self._children[i].__toString(branch +\
85								"    |", True)
86			i += 1
87		return s
88
89class BrokenLineException(Exception):
90	"""If the last line is not complete because of the pipe breakage,
91	   we want to stop the processing and ignore this line.
92	"""
93	pass
94
95class CommentLineException(Exception):
96	""" If the line is a comment (as in the beginning of the trace file),
97	    just ignore it.
98	"""
99	pass
100
101
102def parseLine(line):
103	line = line.strip()
104	if line.startswith("#"):
105		raise CommentLineException
106	m = re.match("[^]]+?\\] +([a-z.]+) +([0-9.]+): (\\w+) <-(\\w+)", line)
107	if m is None:
108		raise BrokenLineException
109	return (m.group(2), m.group(3), m.group(4))
110
111
112def main():
113	CallTree.ROOT = CallTree("Root (Nowhere)", None, None)
114	tree = CallTree.ROOT
115
116	for line in sys.stdin:
117		try:
118			calltime, callee, caller = parseLine(line)
119		except BrokenLineException:
120			break
121		except CommentLineException:
122			continue
123		tree = tree.getParent(caller)
124		tree = tree.calls(callee, calltime)
125
126	print(CallTree.ROOT)
127
128if __name__ == "__main__":
129	main()
130