22import sys
33import json
44import re
5+ from collections import defaultdict
6+
7+ """
8+ This script parses profiling output from cf-agent and lists policy bundles, promises, and functions sorted by execution time.
9+
10+ Usage:
11+ $ sudo /var/cfengine/cf-agent -Kp
12+ $ python3 cf-profile.py profiling_output.json --bundles --promises --functions
13+ """
14+
515
616def parse_args ():
717 parser = ArgumentParser ()
818
19+ parser .add_argument ("profiling_output" )
920 parser .add_argument ("--top" , type = int , default = 10 )
1021 parser .add_argument ("--bundles" , action = "store_true" )
1122 parser .add_argument ("--promises" , action = "store_true" )
1223 parser .add_argument ("--functions" , action = "store_true" )
1324
1425 return parser .parse_args ()
1526
16- def profile (data , args ):
1727
18- events = sorted ([event for event in data ["events" ]], key = lambda x : x ["elapsed" ], reverse = True )
28+ def format_elapsed_time (elapsed_ns ):
29+ elapsed_ms = float (elapsed_ns ) / 1e6
30+
31+ if elapsed_ms < 1000 :
32+ return "%.2f ms" % elapsed_ms
33+ elif elapsed_ms < 60000 :
34+ elapsed_s = elapsed_ms / 1000.0
35+ return "%.2fs" % elapsed_s
36+ else :
37+ elapsed_s = elapsed_ms / 1000.0
38+ minutes = int (elapsed_s // 60 )
39+ seconds = int (elapsed_s % 60 )
40+ return "%dm%ds" % (minutes , seconds )
41+
42+
43+ def format_label (event_type , name ):
44+ if event_type == "function" :
45+ return "%s() %s call" % (name , event_type )
46+ elif name == "methods" :
47+ return "bundle invocation"
48+ else :
49+ return "%s %s" % (name , event_type )
50+
51+
52+ def format_columns (events , top ):
53+
54+ labels = []
55+
56+ for event in events [:top ]:
57+ label = format_label (event ["type" ], event ["name" ])
58+ location = "%s:%s" % (event ["filename" ], event ["offset" ]["line" ])
59+ time = format_elapsed_time (event ["elapsed" ])
1960
20- filter = []
61+ labels .append ((label , location , time ))
62+
63+ return labels
64+
65+
66+ def get_max_column_lengths (lines , indent = 4 ):
67+
68+ max_type , max_location , max_time = 0 , 0 , 0
69+
70+ for label , location , time_ms in lines :
71+ max_type = max (max_type , len (label ))
72+ max_location = max (max_location , len (location ))
73+ max_time = max (max_time , len (time_ms ))
74+
75+ return max_type + indent , max_location + indent , max_time + indent
76+
77+
78+ def profile (data , args ):
79+
80+ events = data ["events" ]
81+ filter = defaultdict (list )
2182
2283 if args .bundles :
23- filter .append ("bundle" )
84+ filter ["type" ].append ("bundle" )
85+ filter ["name" ].append ("methods" )
2486
2587 if args .promises :
26- filter .append ("promise" )
88+ filter ["name" ] += list (
89+ set (
90+ event ["name" ]
91+ for event in events
92+ if event ["type" ] == "promise" and event ["name" ] != "methods"
93+ )
94+ )
2795
2896 if args .functions :
29- filter .append ("function" )
97+ filter ["type" ].append ("function" )
98+
99+ # filter events
100+ if filter is not None :
101+ events = [
102+ event
103+ for field in filter .keys ()
104+ for event in events
105+ if event [field ] in filter [field ]
106+ ]
30107
31- if filter :
32- events = [ event for event in events if event [ "type" ] in filter ]
108+ # sort events
109+ events = sorted ( events , key = lambda x : x [ "elapsed" ], reverse = True )
33110
34- print ( "%-60s %-90s %20s" % ( "Component" , "Location" , "Time" ) )
35- for t in events [: args . top ]:
111+ lines = format_columns ( events , args . top )
112+ line_format = "%-{}s %-{}s %{}s" . format ( * get_max_column_lengths ( lines ))
36113
37- label = "%s %s" % (t ["type" ], t ["name" ])
38- location = "%s:%s" % (t ["filename" ], t ["offset" ]["line" ])
39- time_ms = "%.2f ms" % (float (t ["elapsed" ]) / 1e6 )
114+ # print top k filtered events
115+ print (line_format % ("Type" , "Location" , "Time" ))
116+ for label , location , time_ms in lines :
117+ print (line_format % (label , location , time_ms ))
40118
41- print ("%-60s %-90s %20s" % (label , location , time_ms ))
42119
43120def main ():
44121 args = parse_args ()
45- m = re .search (r"\{[.\s\S]*\}" , sys .stdin .read ())
46- data = json .loads (m .group (0 ))
122+ # m = re.search(r"\{[.\s\S]*\}", sys.stdin.read())
123+ with open (args .profiling_output , "r" ) as f :
124+ data = json .load (f )
47125
48126 profile (data , args )
49127
50128
51129if __name__ == "__main__" :
52- main ()
130+ main ()
0 commit comments