Skip to content

Commit d0315db

Browse files
committed
Write profiling output to profiling_output.json
Signed-off-by: Victor Moene <victor.moene@northern.tech>
1 parent 005358a commit d0315db

File tree

2 files changed

+98
-18
lines changed

2 files changed

+98
-18
lines changed

contrib/cf-profile/cf-profile.py

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,129 @@
22
import sys
33
import json
44
import 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

616
def 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

43120
def 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

51129
if __name__ == "__main__":
52-
main()
130+
main()

libpromises/eval_context.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4041,9 +4041,11 @@ void EvalContextProfilingEnd(EvalContext *ctx, const Policy *policy)
40414041
JsonObjectAppendArray(profiling, "events", events);
40424042

40434043
// write
4044-
Writer *writer = FileWriter(stdout);
4044+
FILE *output_file = safe_fopen("./profiling_output.json", "w");
4045+
Writer *writer = FileWriter(output_file);
40454046
JsonWrite(writer, profiling, 2);
40464047
WriterClose(writer);
4048+
Log(LOG_LEVEL_INFO, "Wrote profiling output to ./profiling_output.json");
40474049

40484050
JsonDestroy(profiling);
40494051
}

0 commit comments

Comments
 (0)