Skip to content

Commit 16f8793

Browse files
committed
Added stacktrace and flamegraph
Signed-off-by: Victor Moene <[email protected]>
1 parent 7b78671 commit 16f8793

File tree

3 files changed

+195
-54
lines changed

3 files changed

+195
-54
lines changed

contrib/cf-profile/cf-profile.py

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,152 @@
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")
24+
parser.add_argument("--flamegraph", action="store_true")
1325

1426
return parser.parse_args()
1527

16-
def profile(data, args):
1728

18-
events = sorted([event for event in data["events"]], key=lambda x: x["elapsed"], reverse=True)
29+
def format_elapsed_time(elapsed_ns):
30+
elapsed_ms = float(elapsed_ns) / 1e6
31+
32+
if elapsed_ms < 1000:
33+
return "%.2f ms" % elapsed_ms
34+
elif elapsed_ms < 60000:
35+
elapsed_s = elapsed_ms / 1000.0
36+
return "%.2fs" % elapsed_s
37+
else:
38+
elapsed_s = elapsed_ms / 1000.0
39+
minutes = int(elapsed_s // 60)
40+
seconds = int(elapsed_s % 60)
41+
return "%dm%ds" % (minutes, seconds)
42+
43+
44+
def format_label(event_type, name):
45+
if event_type == "function":
46+
return "%s() %s call" % (name, event_type)
47+
elif name == "methods":
48+
return "bundle invocation"
49+
else:
50+
return "%s %s" % (name, event_type)
51+
52+
53+
def format_columns(events, top):
54+
55+
labels = []
56+
57+
for event in events[:top]:
58+
label = format_label(event["type"], event["name"])
59+
location = "%s:%s" % (event["filename"], event["offset"]["line"])
60+
time = format_elapsed_time(event["elapsed"])
61+
62+
labels.append((label, location, time))
63+
64+
return labels
65+
1966

20-
filter = []
67+
def get_max_column_lengths(lines, indent=4):
68+
69+
max_type, max_location, max_time = 0, 0, 0
70+
71+
for label, location, time_ms in lines:
72+
max_type = max(max_type, len(label))
73+
max_location = max(max_location, len(location))
74+
max_time = max(max_time, len(time_ms))
75+
76+
return max_type + indent, max_location + indent, max_time + indent
77+
78+
79+
def profile(data, args):
80+
81+
events = data["events"]
82+
filter = defaultdict(list)
2183

2284
if args.bundles:
23-
filter.append("bundle")
85+
filter["type"].append("bundle")
86+
filter["name"].append("methods")
2487

2588
if args.promises:
26-
filter.append("promise")
89+
filter["name"] += list(
90+
set(
91+
event["name"]
92+
for event in events
93+
if event["type"] == "promise" and event["name"] != "methods"
94+
)
95+
)
2796

2897
if args.functions:
29-
filter.append("function")
98+
filter["type"].append("function")
3099

31-
if filter:
32-
events = [event for event in events if event["type"] in filter]
100+
# filter events
101+
if filter is not None:
102+
events = [
103+
event
104+
for field in filter.keys()
105+
for event in events
106+
if event[field] in filter[field]
107+
]
33108

34-
print("%-60s %-90s %20s" % ("Component", "Location", "Time"))
35-
for t in events[:args.top]:
109+
# sort events
110+
events = sorted(events, key=lambda x: x["elapsed"], reverse=True)
36111

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)
112+
lines = format_columns(events, args.top)
113+
line_format = "%-{}s %-{}s %{}s".format(*get_max_column_lengths(lines))
40114

41-
print("%-60s %-90s %20s" % (label, location, time_ms))
115+
# print top k filtered events
116+
print(line_format % ("Type", "Location", "Time"))
117+
for label, location, time_ms in lines:
118+
print(line_format % (label, location, time_ms))
119+
120+
def generate_callstacks(data):
121+
events = data["events"]
122+
123+
# hashmap = {"policy" : 0}
124+
# for event in events:
125+
# hashmap[event["id"]] = event["elapsed"]
126+
# print(json.dumps(hashmap, indent=4))
127+
128+
# for event in sorted(events, key=lambda x: x["id"].count(";"), reverse=True):
129+
# parent = ";".join(event["id"].split(";")[:-1])
130+
# hashmap[parent] -= event["elapsed"]
131+
132+
133+
with open("stack.txt", "w") as f:
134+
for event in events:
135+
f.write("%s %d\n" % (event["callstack"], event["elapsed"]))
42136

43137
def main():
44138
args = parse_args()
45139
m = re.search(r"\{[.\s\S]*\}", sys.stdin.read())
140+
# with open(args.profiling_output, "r") as f:
141+
# data = json.load(f)
46142
data = json.loads(m.group(0))
47143

48-
profile(data, args)
144+
145+
if args.flamegraph:
146+
generate_callstacks(data)
147+
else:
148+
profile(data, args)
149+
49150

50151

51152
if __name__ == "__main__":
52-
main()
153+
main()

libpromises/eval_context.c

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,13 @@ static bool EvalContextClassPut(EvalContext *ctx, const char *ns, const char *na
127127
static const char *EvalContextCurrentNamespace(const EvalContext *ctx);
128128
static ClassRef IDRefQualify(const EvalContext *ctx, const char *id);
129129

130-
static EventFrame *EventFrameCreate(char *type, const char *name, const char *source, SourceOffset offset);
130+
static EventFrame *EventFrameCreate(char *type, const char *name, const char *source, SourceOffset offset, char *id);
131131
static void EventFrameDestroy(EventFrame *event);
132+
133+
static char *BundleGetCallStackID(const Bundle *bp);
134+
static char *PromiseGetCallStackID(const Promise *pp);
135+
static char *FunctionGetCallStackID(const FnCall *fp);
136+
132137
static EventFrame *BundleToEventFrame(const Bundle *bp);
133138
static EventFrame *PromiseToEventFrame(const Promise *pp);
134139
static EventFrame *FunctionToEventFrame(const FnCall *fp);
@@ -349,6 +354,20 @@ static StackFrame *LastStackFrameByType(const EvalContext *ctx, StackFrameType t
349354
return NULL;
350355
}
351356

357+
static StackFrame *LastStackFrameByEvent(const EvalContext *ctx)
358+
{
359+
for (size_t i = 0; i < SeqLength(ctx->stack); i++)
360+
{
361+
StackFrame *frame = LastStackFrame(ctx, i);
362+
if (frame->event != NULL)
363+
{
364+
return frame;
365+
}
366+
}
367+
368+
return NULL;
369+
}
370+
352371
static LogLevel AdjustLogLevel(LogLevel base, LogLevel adjust)
353372
{
354373
if (adjust == -1)
@@ -1443,7 +1462,22 @@ void EvalContextStackPushBundleFrame(EvalContext *ctx, const Bundle *owner, cons
14431462
assert(ctx != NULL);
14441463
assert(!LastStackFrame(ctx, 0) || LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_PROMISE_ITERATION);
14451464

1446-
EvalContextStackPushFrame(ctx, StackFrameNewBundle(owner, inherits_previous, ctx->profiling));
1465+
StackFrame *prev_frame = LastStackFrameByEvent(ctx);
1466+
StackFrame *frame = StackFrameNewBundle(owner, inherits_previous, ctx->profiling);
1467+
1468+
EventFrame *pushed_event = frame->event;
1469+
EvalContextStackPushFrame(ctx, frame);
1470+
if (pushed_event != NULL)
1471+
{
1472+
if (prev_frame == NULL || prev_frame->event == NULL)
1473+
{
1474+
pushed_event->stacktrace = SafeStringDuplicate(pushed_event->id);
1475+
}
1476+
else
1477+
{
1478+
pushed_event->stacktrace = StringFormat("%s;%s", prev_frame->event->stacktrace, pushed_event->id);
1479+
}
1480+
}
14471481

14481482
if (RlistLen(args) > 0)
14491483
{
@@ -1523,9 +1557,22 @@ void EvalContextStackPushPromiseFrame(EvalContext *ctx, const Promise *owner)
15231557

15241558
EvalContextVariableClearMatch(ctx);
15251559

1560+
StackFrame *prev_frame = LastStackFrameByEvent(ctx);
15261561
StackFrame *frame = StackFrameNewPromise(owner, ctx->profiling);
15271562

15281563
EvalContextStackPushFrame(ctx, frame);
1564+
EventFrame *pushed_event = frame->event;
1565+
if (pushed_event != NULL)
1566+
{
1567+
if (prev_frame == NULL || prev_frame->event == NULL)
1568+
{
1569+
pushed_event->stacktrace = SafeStringDuplicate(pushed_event->id);
1570+
}
1571+
else
1572+
{
1573+
pushed_event->stacktrace = StringFormat("%s;%s", prev_frame->event->stacktrace, pushed_event->id);
1574+
}
1575+
}
15291576

15301577
// create an empty table
15311578
frame->data.promise.vars = VariableTableNew();
@@ -3898,7 +3945,7 @@ void EvalContextSetProfiling(EvalContext *ctx, bool profiling)
38983945
ctx->profiling = profiling;
38993946
}
39003947

3901-
static EventFrame *EventFrameCreate(char *type, const char *name, const char *source, SourceOffset offset)
3948+
static EventFrame *EventFrameCreate(char *type, const char *name, const char *source, SourceOffset offset, char *id)
39023949
{
39033950
EventFrame *event = (EventFrame *) xmalloc(sizeof(EventFrame));
39043951

@@ -3907,7 +3954,8 @@ static EventFrame *EventFrameCreate(char *type, const char *name, const char *so
39073954
event->filename = GetAbsolutePath(source);
39083955
event->elapsed = 0;
39093956
event->offset = offset;
3910-
event->id = StringFormat("%s_%s_%s_%ld_%ld", type, name, source, offset.start, offset.line);
3957+
event->id = id;
3958+
event->stacktrace = "";
39113959

39123960
return event;
39133961
}
@@ -3933,6 +3981,7 @@ static JsonElement *EventToJson(EventFrame *event)
39333981
JsonObjectAppendString(json_event, "filename", event->filename);
39343982
JsonObjectAppendString(json_event, "id", event->id);
39353983
JsonObjectAppendInteger64(json_event, "elapsed", event->elapsed);
3984+
JsonObjectAppendString(json_event, "callstack", event->stacktrace);
39363985

39373986
JsonElement *offset = JsonObjectCreate(4);
39383987
JsonObjectAppendInteger(offset, "start", event->offset.start);
@@ -3948,19 +3997,19 @@ static JsonElement *EventToJson(EventFrame *event)
39483997
static EventFrame *BundleToEventFrame(const Bundle *bp)
39493998
{
39503999
assert(bp != NULL);
3951-
return EventFrameCreate("bundle", bp->name, bp->source_path, bp->offset);
4000+
return EventFrameCreate("bundle", bp->name, bp->source_path, bp->offset, BundleGetCallStackID(bp));
39524001
}
39534002

39544003
static EventFrame *PromiseToEventFrame(const Promise *pp)
39554004
{
39564005
assert(pp != NULL);
3957-
return EventFrameCreate("promise", PromiseGetPromiseType(pp), PromiseGetBundle(pp)->source_path, pp->offset);
4006+
return EventFrameCreate("promise", PromiseGetPromiseType(pp), PromiseGetBundle(pp)->source_path, pp->offset, PromiseGetCallStackID(pp));
39584007
}
39594008

39604009
static EventFrame *FunctionToEventFrame(const FnCall *fp)
39614010
{
39624011
assert(fp != NULL);
3963-
return EventFrameCreate("function", fp->name, PromiseGetBundle(fp->caller)->source_path, fp->caller->offset);
4012+
return EventFrameCreate("function", fp->name, PromiseGetBundle(fp->caller)->source_path, fp->caller->offset, FunctionGetCallStackID(fp));
39644013
}
39654014

39664015
void EvalContextAddFunctionEvent(EvalContext *ctx, const FnCall *fp, int64_t start)
@@ -3994,31 +4043,6 @@ void EvalContextProfilingStart(EvalContext *ctx)
39944043
ctx->profiler.elapsed = EvalContextEventStart();
39954044
}
39964045

3997-
static HashMap *SumEventFrames(Seq *events)
3998-
{
3999-
HashMap *map = HashMapNew((MapHashFn) StringHash, (MapKeyEqualFn) StringEqual, NULL, NULL, 10);
4000-
4001-
size_t length = SeqLength(events);
4002-
EventFrame *curr;
4003-
EventFrame *prev;
4004-
MapKeyValue *mkv;
4005-
for (int i = 0; i < length; i++)
4006-
{
4007-
curr = SeqAt(events, i);
4008-
mkv = HashMapGet(map, curr->id);
4009-
4010-
if (mkv == NULL)
4011-
{
4012-
HashMapInsert(map, curr->id, curr);
4013-
continue;
4014-
}
4015-
prev = mkv->value;
4016-
prev->elapsed += curr->elapsed;
4017-
}
4018-
4019-
return map;
4020-
}
4021-
40224046
void EvalContextProfilingEnd(EvalContext *ctx, const Policy *policy)
40234047
{
40244048
assert(ctx != NULL);
@@ -4034,19 +4058,16 @@ void EvalContextProfilingEnd(EvalContext *ctx, const Policy *policy)
40344058

40354059
ctx->profiler.elapsed = end - start;
40364060

4037-
HashMap *map = SumEventFrames(ctx->profiler.events);
40384061
JsonElement *profiling = JsonObjectCreate(2);
40394062

40404063
JsonElement *json_policy = PolicyToJson(policy);
40414064
JsonObjectAppendObject(profiling, "policy", json_policy);
40424065

40434066
JsonElement *events = JsonArrayCreate(10);
40444067
{
4045-
HashMapIterator iter = HashMapIteratorInit(map);
4046-
MapKeyValue *mkv;
4047-
while ((mkv = HashMapIteratorNext(&iter)) != NULL)
4068+
for (size_t i = 0; i < SeqLength(ctx->profiler.events); i++)
40484069
{
4049-
EventFrame *event = mkv->value;
4070+
EventFrame *event = SeqAt(ctx->profiler.events, i);
40504071
JsonArrayAppendObject(events, EventToJson(event));
40514072
}
40524073
}
@@ -4060,6 +4081,24 @@ void EvalContextProfilingEnd(EvalContext *ctx, const Policy *policy)
40604081
JsonDestroy(profiling);
40614082
}
40624083

4084+
static char *BundleGetCallStackID(const Bundle *bp)
4085+
{
4086+
assert(bp != NULL);
4087+
return StringFormat("bundle_%s_%s:%s_%s:%ld_%ld-%ld", bp->type, bp->ns, bp->name, bp->source_path, bp->offset.line, bp->offset.start, bp->offset.end);
4088+
}
4089+
4090+
static char *PromiseGetCallStackID(const Promise *pp)
4091+
{
4092+
assert(pp != NULL);
4093+
return StringFormat("promise_%s_%ld_%ld-%ld", PromiseGetPromiseType(pp), pp->offset.line, pp->offset.start, pp->offset.end);
4094+
}
4095+
4096+
static char *FunctionGetCallStackID(const FnCall *fp)
4097+
{
4098+
assert(fp != NULL);
4099+
return StringFormat("function_%s_%ld_%ld-%ld", fp->name, fp->caller->offset.line, fp->caller->offset.start, fp->caller->offset.end);
4100+
}
4101+
40634102
// ##############################################################
40644103

40654104
void EvalContextSetCommonEvalOrder(EvalContext *ctx, EvalContextEvalOrder eval_order)
@@ -4094,4 +4133,4 @@ bool EvalContextIsClassicOrder(EvalContext *ctx)
40944133
// The fallback is to use what is defined in body common control,
40954134
// or if not defined there either, default to true (normal order)
40964135
return (ctx->common_eval_order != EVAL_ORDER_TOP_DOWN);
4097-
}
4136+
}

libpromises/eval_context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ typedef struct {
4747
char *filename;
4848
SourceOffset offset;
4949
int64_t elapsed;
50+
char *stacktrace;
5051
} EventFrame;
5152

5253
typedef enum

0 commit comments

Comments
 (0)