callgrind.c
Go to the documentation of this file.
1 
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <time.h>
17 #include <errno.h>
18 #include <kuroko/kuroko.h>
19 #include <kuroko/vm.h>
20 #include <kuroko/debug.h>
21 #include <kuroko/util.h>
22 #include "../src/opcode_enum.h"
23 
24 #include "common.h"
25 
26 extern KrkValue krk_operator_add (KrkValue a, KrkValue b);
27 extern KrkValue krk_operator_sub (KrkValue a, KrkValue b);
28 
29 static int usage(char * argv[]) {
30  fprintf(stderr, "usage: %s [-f LOG_FILE] [-q] [-m] FILE [args...]\n", argv[0]);
31  return 1;
32 }
33 
34 static int help(char * argv[]) {
35  usage(argv);
36  fprintf(stderr,
37  "Generate callgrind-format trace files.\n"
38  "\n"
39  "Options:\n"
40  " -f LOG_FILE Output callgrind-format data to LOG_FILE.\n"
41  " -q Do not print execution summary.\n"
42  " -m Interpret first argument as a module name rather than a file.\n"
43  "\n"
44  " --help Show this help text.\n"
45  "\n");
46  return 0;
47 }
48 
49 static KrkValue lineCache; /* {sourceCO: {line: count}} */
50 static KrkValue callCache; /* {sourceCO: {(codeobject,targetLine,sourceLine): [calls,instructions,time]}} */
51 static KrkValue timeCache; /* {sourceCO: time} */
52 
53 static size_t lastFrameCount = 0; /* Previously seen frame count, to track function entry/exit */
54 static size_t instrCounter = 0; /* Total counter of executed instructions */
55 static size_t functionsEntered = 0; /* Number of function entries seen */
56 static int quiet = 0;
57 static int moduleAsMain = 0;
58 
68 static void time_diff(struct timespec in_time, char * buf) {
69  struct timespec outTime, diff;
70  clock_gettime(CLOCK_MONOTONIC, &outTime);
71  diff.tv_sec = outTime.tv_sec - in_time.tv_sec;
72  diff.tv_nsec = outTime.tv_nsec - in_time.tv_nsec;
73  if (diff.tv_nsec < 0) {
74  diff.tv_sec--;
75  diff.tv_nsec += 1000000000L;
76  }
77  snprintf(buf,50,"%lld%.9ld", (long long)diff.tv_sec, diff.tv_nsec);
78 }
79 
80 struct FrameMetadata {
81  KrkCodeObject * target_obj; /* Function being entered */
82  size_t target_line; /* First line seen in the entered function */
83  size_t count; /* Instruction count on entry */
84  size_t source_line; /* Line we came from in the caller */
85  KrkCodeObject * source_obj; /* Function we came from in the caller */
86  struct timespec in_time; /* Time of frame entry */
87 };
88 
89 static size_t maxFrameCount = KRK_CALL_FRAMES_MAX;
90 static struct FrameMetadata * frameMetadata;
91 
92 int krk_callgrind_debuggerHook(KrkCallFrame * frame) {
93  instrCounter++;
94 
95  if (krk_currentThread.frameCount != lastFrameCount) {
96  if (maxFrameCount != krk_currentThread.maximumCallDepth) {
97  frameMetadata = realloc(frameMetadata, sizeof(*frameMetadata) * krk_currentThread.maximumCallDepth);
98  maxFrameCount = krk_currentThread.maximumCallDepth;
99  }
100  if (krk_currentThread.frameCount > lastFrameCount) {
101  /* When we detect function entry, record details of the function being called
102  * and what called in, and record the current time. */
103  KrkCallFrame * prev = lastFrameCount ? &krk_currentThread.frames[lastFrameCount-1] : NULL;
104  frameMetadata[lastFrameCount].target_obj = frame->closure->function;
105  frameMetadata[lastFrameCount].target_line = krk_lineNumber(&frame->closure->function->chunk, frame->ip - frame->closure->function->chunk.code);
106  frameMetadata[lastFrameCount].count = instrCounter;
107  frameMetadata[lastFrameCount].source_obj = prev ? prev->closure->function : NULL;
108  frameMetadata[lastFrameCount].source_line = prev ? krk_lineNumber(&prev->closure->function->chunk, prev->ip - 1 - prev->closure->function->chunk.code) : 0;
109  clock_gettime(CLOCK_MONOTONIC, &frameMetadata[lastFrameCount].in_time);
110  functionsEntered++;
111  } else {
112  size_t procFrame = lastFrameCount - 1;
113  while (procFrame >= krk_currentThread.frameCount) {
114  if (procFrame == 0) {
115  /* This is the outermost call, eg. <module>, returning to the interpreter. We still want to make
116  * sure we collect the time spent in the call and add it to the time for the function, so that
117  * the time spent inside other function calls is subtracted to get the time spent actually
118  * executing code in the module. */
119  char tmp[50];
120  time_diff(frameMetadata[procFrame].in_time, tmp);
121  KrkValue diffTime = krk_parse_int(tmp,strlen(tmp),10);
122  krk_push(diffTime);
123  KrkValue totalTime = INTEGER_VAL(0);
124  krk_tableGet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].target_obj),&totalTime);
125  krk_push(krk_operator_add(totalTime,diffTime));
126  krk_tableSet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].target_obj),krk_peek(0));
127  krk_pop();
128  krk_pop();
129  break;
130  }
131 
132  /* Set up dict for call mappings */
133  KrkValue ndict = NONE_VAL();
134  if (!krk_tableGet(AS_DICT(callCache),OBJECT_VAL(frameMetadata[procFrame].source_obj),&ndict)) {
135  ndict = krk_dict_of(0,NULL,0);
136  krk_push(ndict);
137  krk_tableSet(AS_DICT(callCache),OBJECT_VAL(frameMetadata[procFrame].source_obj),ndict);
138  krk_pop();
139  }
140 
141  KrkTuple * t = krk_newTuple(3);
142  krk_push(OBJECT_VAL(t));
143 
144  /* Target code object */
145  t->values.values[t->values.count++] = OBJECT_VAL(frameMetadata[procFrame].target_obj);
146 
147  /* Target and source lines */
148  t->values.values[t->values.count++] = INTEGER_VAL(frameMetadata[procFrame].target_line);
149  t->values.values[t->values.count++] = INTEGER_VAL(frameMetadata[procFrame].source_line);
150 
151  KrkValue nlist = NONE_VAL();
152  if (!krk_tableGet(AS_DICT(ndict),krk_peek(0),&nlist)) {
153  nlist = krk_list_of(3,(const KrkValue[]){INTEGER_VAL(0),INTEGER_VAL(0),INTEGER_VAL(0)},0);
154  krk_push(nlist);
155  krk_tableSet(AS_DICT(ndict),krk_peek(1),krk_peek(0));
156  krk_pop();
157  }
158  krk_pop(); /* tuple */
159 
160  /* totalCalls += 1 */
161  krk_push(krk_operator_add(AS_LIST(nlist)->values[0],INTEGER_VAL(1)));
162  AS_LIST(nlist)->values[0] = krk_pop();
163 
164  /* totalCosts += last cost */
165  char tmp[50];
166  snprintf(tmp,50,"%zu", instrCounter - frameMetadata[procFrame].count);
167  KrkValue diffCount = krk_parse_int(tmp,strlen(tmp),10);
168  krk_push(diffCount);
169  krk_push(krk_operator_add(AS_LIST(nlist)->values[1],diffCount));
170  AS_LIST(nlist)->values[1] = krk_pop();
171  krk_pop(); /* diffCount */
172 
173  time_diff(frameMetadata[procFrame].in_time, tmp); /* Time for this call */
174  KrkValue diffTime = krk_parse_int(tmp,strlen(tmp),10);
175  krk_push(diffTime);
176  krk_push(krk_operator_add(AS_LIST(nlist)->values[2],diffTime));
177  AS_LIST(nlist)->values[2] = krk_pop();
178 
179  KrkValue myTime = INTEGER_VAL(0); /* Time spent here */
180  krk_tableGet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].target_obj),&myTime);
181  krk_push(krk_operator_add(myTime,diffTime));
182  krk_tableSet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].target_obj),krk_peek(0));
183  krk_pop(); /* myTime + diffTime */
184 
185  KrkValue parentTime = INTEGER_VAL(0); /* Was not spent there */
186  krk_tableGet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].source_obj),&parentTime);
187  krk_push(krk_operator_sub(parentTime,diffTime));
188  krk_tableSet(AS_DICT(timeCache),OBJECT_VAL(frameMetadata[procFrame].source_obj),krk_peek(0));
189  krk_pop(); /* parentTime - diffTime */
190 
191  krk_pop(); /* diffTime */
192 
193  memset(&frameMetadata[procFrame], 0, sizeof(struct FrameMetadata));
194  procFrame--;
195  }
196  }
197  lastFrameCount = krk_currentThread.frameCount;
198  }
199 
200  if (!frame) return KRK_DEBUGGER_QUIT;
201 
202  /* Set up dict for instruction counts */
203  KrkValue ndict = NONE_VAL();
204  if (!krk_tableGet(AS_DICT(lineCache),OBJECT_VAL(frame->closure->function),&ndict)) {
205  ndict = krk_dict_of(0,NULL,0);
206  krk_push(ndict);
207  krk_tableSet(AS_DICT(lineCache),OBJECT_VAL(frame->closure->function),ndict);
208  krk_pop();
209  }
210 
211  KrkValue lineNumber = INTEGER_VAL(krk_lineNumber(&frame->closure->function->chunk, frame->ip - frame->closure->function->chunk.code));
212 
213  /* Add one to instruction count for this line */
214  KrkValue count = INTEGER_VAL(0);
215  krk_tableGet(AS_DICT(ndict), lineNumber, &count);
216  krk_push(krk_operator_add(count, INTEGER_VAL(1)));
217  krk_tableSet(AS_DICT(ndict), lineNumber, krk_peek(0));
218  krk_pop(); /* count + 1 */
219 
220  return KRK_DEBUGGER_STEP;
221 }
222 
223 static void fprint_krk(FILE * f, const char * fmt, ...) {
224  va_list args;
225  va_start(args, fmt);
226  struct StringBuilder sb = {0};
227  krk_pushStringBuilderFormatV(&sb, fmt, args);
228  fwrite(sb.bytes, sb.length, 1, f);
230 }
231 
232 int main(int argc, char *argv[]) {
233  char outfile[1024];
234  snprintf(outfile,1024,"callgrind.out.%d",getpid());
235 
236  int opt;
237  while ((opt = getopt(argc, argv, "+:f:qm-:")) != -1) {
238  switch (opt) {
239  case 'f':
240  snprintf(outfile,1024,"%s", optarg);
241  break;
242  case 'q':
243  quiet = 1;
244  break;
245  case 'm':
246  moduleAsMain = 1;
247  break;
248  case '?':
249  if (optopt != '-') {
250  fprintf(stderr, "%s: unrocognized option '%c'\n", argv[0], optopt);
251  return 1;
252  }
253  optarg = argv[optind]+1;
254  /* fall through */
255  case '-':
256  if (!strcmp(optarg,"help")) {
257  return help(argv);
258  } else {
259  fprintf(stderr, "%s: unrecognized option: '--%s'\n", argv[0], optarg);
260  return 1;
261  }
262  }
263  }
264  if (optind == argc) {
265  return usage(argv);
266  }
267 
268  findInterpreter(argv);
269  krk_initVM(KRK_THREAD_SINGLE_STEP);
270  frameMetadata = malloc(sizeof(*frameMetadata) * krk_currentThread.maximumCallDepth);
271  krk_debug_registerCallback(krk_callgrind_debuggerHook);
272  KrkValue argList = addArgs(argc,argv);
273 
274  krk_startModule("__callgrind__");
275 
276  lineCache = krk_dict_of(0,NULL,0);
277  krk_attachNamedValue(&krk_currentThread.module->fields,"__line_cache__",lineCache);
278  callCache = krk_dict_of(0,NULL,0);
279  krk_attachNamedValue(&krk_currentThread.module->fields,"__call_cache__",callCache);
280  timeCache = krk_dict_of(0,NULL,0);
281  krk_attachNamedValue(&krk_currentThread.module->fields,"__time_cache__",timeCache);
282 
283  if (moduleAsMain) {
284  krk_importModule(AS_STRING(AS_LIST(argList)->values[0]), S("__main__"));
285  if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) krk_dumpTraceback();
286  /* no need to reset here */
287  } else {
288  krk_startModule("__main__");
289  krk_runfile(argv[optind],argv[optind]);
290  }
291 
292  if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) {
293  krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION);
294  if (!quiet) fprintf(stderr, "== Executed ended by exception ==\n");
295  } else {
296  if (!quiet) fprintf(stderr, "== Execution completed ==\n");
297  }
298  krk_callgrind_debuggerHook(NULL);
299 
300  if (!quiet) {
301  fprintf(stderr, "%10zu total instruction%s\n", instrCounter, (instrCounter != 1) ? "s" : "");
302  fprintf(stderr, "%10zu function%s calls\n", functionsEntered, (functionsEntered != 1) ? "s" : "");
303  }
304 
305  FILE * f = fopen(outfile,"w");
306  if (!f) {
307  fprintf(stderr, "%s: %s: %s\n", argv[0], outfile, strerror(errno));
308  return 1;
309  }
310 
311  fprintf(f,"# callgrind format\n");
312  fprintf(f,"creator: Kuroko\n");
313  fprintf(f,"positions: line\n");
314  fprintf(f,"events: instructions nanoseconds\n");
315  fprint_krk(f,"cmd: %R\n", argList);
316  fprintf(f,"summary: %zu ", instrCounter);
317  char tmp[50];
318  time_diff(frameMetadata[0].in_time, tmp);
319  fprintf(f,"%s\n", tmp);
320 
321 
322  for (size_t j = 0; j < AS_DICT(lineCache)->used; ++j) {
323  if (IS_KWARGS(AS_DICT(lineCache)->entries[j].key)) continue;
324 
325  KrkCodeObject * function = (void*)AS_OBJECT(AS_DICT(lineCache)->entries[j].key);
326  KrkValue ndict = AS_DICT(lineCache)->entries[j].value;
327 
328  fprintf(f,"fl=%s\n", function->chunk.filename->chars);
329  fprintf(f,"fn=%s@%p\n", function->qualname ? function->qualname->chars : function->name->chars, (void*)function);
330 
331  KrkValue timeValue = NONE_VAL();
332  krk_tableGet(AS_DICT(timeCache), OBJECT_VAL(function), &timeValue);
333  if (!IS_NONE(timeValue)) {
334  fprintf(f,"%zu ", (size_t)krk_lineNumber(&function->chunk, 0));
335  fprint_krk(f,"0 %R\n", timeValue);
336  }
337 
338  for (size_t k = 0; k < AS_DICT(ndict)->used; ++k) {
339  if (IS_KWARGS(AS_DICT(ndict)->entries[k].key)) continue;
340  fprint_krk(f,"%R %R 0\n", AS_DICT(ndict)->entries[k].key, AS_DICT(ndict)->entries[k].value);
341  }
342 
343  KrkValue cdict = NONE_VAL();
344  krk_tableGet(AS_DICT(callCache), OBJECT_VAL(function), &cdict);
345  if (!IS_NONE(cdict)) {
346  for (size_t l = 0; l < AS_DICT(cdict)->used; ++l) {
347  if (IS_KWARGS(AS_DICT(cdict)->entries[l].key)) continue;
348 
349  KrkCodeObject * target = (void*)AS_OBJECT(AS_TUPLE(AS_DICT(cdict)->entries[l].key)->values.values[0]);
350  KrkValue targetLine = AS_TUPLE(AS_DICT(cdict)->entries[l].key)->values.values[1];
351  KrkValue sourceLine = AS_TUPLE(AS_DICT(cdict)->entries[l].key)->values.values[2];
352  KrkValue totalCalls = AS_LIST(AS_DICT(cdict)->entries[l].value)->values[0];
353  KrkValue totalCost = AS_LIST(AS_DICT(cdict)->entries[l].value)->values[1];
354  KrkValue totalTime = AS_LIST(AS_DICT(cdict)->entries[l].value)->values[2];
355 
356  fprintf(f,"cfi=%s\n", target->chunk.filename->chars);
357  fprintf(f,"cfn=%s@%p\n", target->qualname ? target->qualname->chars : target->name->chars, (void*)target);
358  fprint_krk(f,"calls=%R %R\n%R %R %R\n", totalCalls, targetLine, sourceLine, totalCost, totalTime);
359  }
360  }
361  }
362 
363  fclose(f);
364 
365  krk_freeVM();
366  return 0;
367 }
368 
Functions for debugging bytecode execution.
int krk_debug_registerCallback(KrkDebugCallback hook)
Register a debugger callback.
Definition: debug.c:643
void krk_dumpTraceback(void)
If there is an active exception, print a traceback to stderr.
Definition: exceptions.c:366
Top-level header with configuration macros.
Represents a managed call state in a VM thread.
Definition: vm.h:44
KrkClosure * closure
Definition: vm.h:45
uint8_t * ip
Definition: vm.h:46
size_t krk_lineNumber(KrkChunk *chunk, size_t offset)
Obtain the line number for a byte offset into a bytecode chunk.
Definition: chunk.c:75
KrkCodeObject * function
The codeobject containing the bytecode run when this function is called.
Definition: object.h:197
Code object.
Definition: object.h:163
KrkChunk chunk
Bytecode data.
Definition: object.h:170
KrkString * name
Name of the function.
Definition: object.h:171
KrkString * qualname
The dotted name of the function.
Definition: object.h:178
KrkValue krk_dict_of(int argc, const KrkValue argv[], int hasKw)
Create a dict object.
Definition: obj_dict.c:19
KrkTable fields
Attributes table.
Definition: object.h:284
KrkValue krk_list_of(int argc, const KrkValue argv[], int hasKw)
Create a list object.
Definition: obj_list.c:30
char * chars
UTF8 canonical data.
Definition: object.h:97
int krk_tableGet(KrkTable *table, KrkValue key, KrkValue *value)
Obtain the value associated with a key in a table.
Definition: table.c:211
int krk_tableSet(KrkTable *table, KrkValue key, KrkValue value)
Assign a value to a key in a table.
Definition: table.c:148
void krk_attachNamedValue(KrkTable *table, const char name[], KrkValue obj)
Attach a value to an attribute table.
Definition: vm.c:794
size_t frameCount
Definition: vm.h:156
unsigned int maximumCallDepth
Definition: vm.h:166
KrkCallFrame * frames
Definition: vm.h:155
int flags
Definition: vm.h:165
KrkInstance * module
Definition: vm.h:163
Immutable sequence of arbitrary values.
Definition: object.h:323
KrkValueArray values
Stores the length, capacity, and actual values of the tuple.
Definition: object.h:325
KrkTuple * krk_newTuple(size_t length)
Create a new tuple.
Definition: object.c:357
void krk_freeVM(void)
Release resources from the VM.
Definition: vm.c:953
void krk_initVM(int flags)
Initialize the VM at program startup.
Definition: vm.c:868
KrkValue * values
Definition: value.h:78
size_t count
Definition: value.h:77
Stack reference or primative value.
Inline flexible string array.
Definition: util.h:162
Utilities for creating native bindings.
KrkValue krk_discardStringBuilder(struct StringBuilder *sb)
Discard the contents of a string builder.
Definition: obj_str.c:1123
Core API for the bytecode virtual machine.
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
int krk_importModule(KrkString *name, KrkString *runAs)
Load the dotted name name with the final element as runAs.
Definition: vm.c:1378
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:131
KrkValue krk_runfile(const char *fileName, const char *fromFile)
Load and run a source file and return when execution completes.
Definition: vm.c:3236
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118
KrkInstance * krk_startModule(const char *name)
Set up a new module object in the current thread.
Definition: vm.c:3209
#define KRK_CALL_FRAMES_MAX
Maximum depth of the call stack in managed-code function calls.
Definition: vm.h:20
KrkValue krk_peek(int distance)
Peek down from the top of the stack.
Definition: vm.c:139