1 | /* |
2 | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
5 | * |
6 | * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V. |
7 | */ |
8 | |
9 | /* (c) M Kersten */ |
10 | |
11 | #include "monetdb_config.h" |
12 | #include "eventparser.h" |
13 | |
14 | char *statenames[]= {"" ,"start" ,"done" ,"action" ,"ping" ,"wait" ,"system" }; |
15 | |
16 | char *maltypes[MAXMALARGS]; |
17 | char *malvariables[MAXMALARGS]; |
18 | char *malvalues[MAXMALARGS]; |
19 | int malcount[MAXMALARGS]; |
20 | int malargc; |
21 | int malretc; |
22 | |
23 | int malsize; |
24 | int debug=0; |
25 | char *currentquery=0; |
26 | int eventcounter = 0; |
27 | |
28 | #define DATETIME_CHAR_LENGTH 27 |
29 | |
30 | static void |
31 | clearArguments(void) |
32 | { |
33 | int i; |
34 | |
35 | if( currentquery){ |
36 | free(currentquery); |
37 | currentquery = 0; |
38 | } |
39 | |
40 | for(i = 0; i < MAXMALARGS; i++){ |
41 | if( malvariables[i]){ |
42 | free(malvariables[i]); |
43 | malvariables[i] = 0; |
44 | } |
45 | if( malvalues[i]){ |
46 | free(malvalues[i]); |
47 | malvalues[i] = 0; |
48 | } |
49 | if( maltypes[i]){ |
50 | free(maltypes[i]); |
51 | maltypes[i] = 0; |
52 | } |
53 | } |
54 | } |
55 | |
56 | static void |
57 | dumpArguments(void) |
58 | { |
59 | int i; |
60 | for( i=0; i < MAXMALARGS; i++) |
61 | if( maltypes[i]) |
62 | printf("[%d] variable %s value %s type %s\n" , i, (malvariables[i]?malvariables[i]:"" ), malvalues[i], maltypes[i]); |
63 | } |
64 | |
65 | char * |
66 | stripQuotes(char *currentquery) |
67 | { |
68 | const char *c; |
69 | char *q, *qry; |
70 | if( currentquery ==0) |
71 | return NULL; |
72 | q = qry = (char *) malloc(strlen(currentquery) * 2); |
73 | if( q == NULL){ |
74 | fprintf(stderr,"Could not allocate query buffer of size %zu\n" , strlen(currentquery) * 2); |
75 | exit(-1); |
76 | } |
77 | c= currentquery; |
78 | if( *c == '"') c++; |
79 | for (; *c; ){ |
80 | if ( strncmp(c,"\\\\t" ,3) == 0){ |
81 | *q++ = '\t'; |
82 | c+=3; |
83 | } else |
84 | if ( strncmp(c,"\\\\n" ,3) == 0){ |
85 | *q++ = '\n'; |
86 | c+=3; |
87 | } else if ( strncmp(c,"\\\"" ,2) == 0){ |
88 | *q++= '"'; |
89 | c+=2; |
90 | } else if ( strncmp(c,"\\\\" ,2) == 0){ |
91 | c+= 2; |
92 | } else *q++ = *c++; |
93 | } |
94 | *q =0; |
95 | return qry; |
96 | } |
97 | |
98 | |
99 | void |
100 | resetEventRecord(EventRecord *ev) |
101 | { |
102 | if( ev->version) free(ev->version); |
103 | if( ev->release) free(ev->release); |
104 | if( ev->memory) free(ev->memory); |
105 | if( ev->threads) free(ev->threads); |
106 | if( ev->host) free(ev->host); |
107 | if( ev->package) free(ev->package); |
108 | |
109 | if( ev->function) free(ev->function); |
110 | if( ev->user) free(ev->user); |
111 | if( ev->time) free(ev->time); |
112 | if( ev->stmt) free(ev->stmt); |
113 | if( ev->fcn) free(ev->fcn); |
114 | if( ev->numa) free(ev->numa); |
115 | if(ev->beauty) free(ev->beauty); |
116 | if(ev->prereq) free(ev->prereq); |
117 | *ev = (EventRecord) { |
118 | .eventnr = -1, |
119 | }; |
120 | clearArguments(); |
121 | } |
122 | |
123 | /* simple json key:value object parser for event record. |
124 | * each event pair on a single row, which is required for dealing with string values |
125 | * It avoids lots of escaped charactor recognition, simply take with mserver delivers |
126 | * Returns 1 if the closing bracket is found. 0 to continue, -1 upon error |
127 | */ |
128 | |
129 | #define skipto(C) { while(*c && *c != C) c++; if (*c != C) return -1;} |
130 | #define skipstr() { while (*c && *c !='"') {if (*c =='\\') c++;if(*c)c++;} if (*c != '"') return -1;} |
131 | |
132 | /* |
133 | * The decomposition of the argument components is postponed |
134 | * We just keep the concatenated json string |
135 | */ |
136 | |
137 | static int |
138 | parseArgument(char *txt, EventRecord *ev) |
139 | { |
140 | char *s,*t; |
141 | int i=0; |
142 | // assume single strictly formatted key-value list line |
143 | (void) txt; |
144 | (void) ev; |
145 | s= strstr(txt,"index\":\"" ); |
146 | if( s){ |
147 | i = atoi(s + 8); |
148 | if( i <0 || i >= MAXMALARGS ) |
149 | return 0; |
150 | } |
151 | t= strstr(txt,"name\":\"" ); |
152 | s= strstr(txt,"\",value\":\"" ); |
153 | if( s && t){ |
154 | t+= 7; |
155 | *s =0; |
156 | malvariables[i] = strdup(t); |
157 | s+= 10; |
158 | } |
159 | t= strstr(txt,"\",type\":\"" ); |
160 | if( s && t){ |
161 | *t = 0; |
162 | *s =0; |
163 | malvariables[i] = strdup(t); |
164 | t+= 7; |
165 | } |
166 | if( s && t){ |
167 | *t = 0; |
168 | t+= 9; |
169 | s+= 10; |
170 | maltypes[i] = strdup(t); |
171 | } |
172 | return 0; |
173 | } |
174 | |
175 | int |
176 | keyvalueparser(char *txt, EventRecord *ev) |
177 | { |
178 | char *c, *s, *key, *val; |
179 | c = txt; |
180 | |
181 | if( strstr(c,"\"argument\":" ) || strstr(c,"\"result\":" )) |
182 | return parseArgument(txt,ev); |
183 | if( *c == '{'){ |
184 | resetEventRecord(ev); |
185 | memset(malvariables, 0, sizeof(malvariables)); |
186 | memset(malvalues, 0, sizeof(malvalues)); |
187 | ev->eventnr= eventcounter++; |
188 | return 0; |
189 | } |
190 | if( *c == '}'){ |
191 | dumpArguments(); |
192 | return 1; |
193 | } |
194 | |
195 | skipto('"'); |
196 | key = ++c; |
197 | skipstr(); |
198 | *c++ = 0; |
199 | skipto(':'); |
200 | c++; |
201 | while( *c && isspace((unsigned char) *c)) c++; |
202 | if( *c == '"'){ |
203 | val = ++c; |
204 | skipstr(); |
205 | *c = 0; |
206 | } else val =c; |
207 | |
208 | if( strstr(key,"ctime" )){ |
209 | ev->usec = atol(val); |
210 | return 0; |
211 | } |
212 | if( strstr(key,"clk" )){ |
213 | time_t sec; |
214 | uint64_t microsec; |
215 | struct tm curr_time; |
216 | |
217 | c = strchr(val,'.'); |
218 | if (c != NULL) { |
219 | *c = '\0'; |
220 | c++; |
221 | } |
222 | |
223 | sec = atol(val); |
224 | microsec = sec % 1000000; |
225 | sec /= 1000000; |
226 | #ifdef HAVE_LOCALTIME_R |
227 | (void)localtime_r(&sec, &curr_time); |
228 | #else |
229 | curr_time = *localtime(&sec); |
230 | #endif |
231 | ev->time = malloc(DATETIME_CHAR_LENGTH*sizeof(char)); |
232 | snprintf(ev->time, DATETIME_CHAR_LENGTH, "%d/%02d/%02d %02d:%02d:%02d.%" PRIu64, |
233 | curr_time.tm_year + 1900, curr_time.tm_mon, curr_time.tm_mday, |
234 | curr_time.tm_hour, curr_time.tm_min, curr_time.tm_sec, microsec); |
235 | ev->clkticks = sec * 1000000; |
236 | if (c != NULL) { |
237 | int64_t usec; |
238 | /* microseconds */ |
239 | usec = strtoll(c, NULL, 10); |
240 | assert(usec >= 0 && usec < 1000000); |
241 | ev->clkticks += usec; |
242 | } |
243 | if (ev->clkticks < 0) { |
244 | fprintf(stderr, "parser: read negative value %" PRId64" from\n'%s'\n" , ev->clkticks, val); |
245 | } |
246 | return 0; |
247 | } |
248 | if( strstr(key,"function" )){ ev->function= strdup(val); return 0;} |
249 | if( strstr(key,"user" )){ ev->user= strdup(val); return 0;} |
250 | if( strstr(key,"tag" )){ ev->tag= atoi(val); return 0;} |
251 | if( strstr(key,"thread" )){ ev->thread= atoi(val); return 0;} |
252 | if( strstr(key,"pc" )){ ev->pc= atoi(val); return 0;} |
253 | if( strstr(key,"state" )){ |
254 | if( strstr(val,"start" )) ev->state= MDB_START; |
255 | if( strstr(val,"done" )) ev->state= MDB_DONE; |
256 | if( strstr(val,"ping" )) ev->state= MDB_PING; |
257 | if( strstr(val,"wait" )) ev->state= MDB_WAIT; |
258 | if( strstr(val,"system" )) ev->state= MDB_SYSTEM; |
259 | return 0; |
260 | } |
261 | |
262 | if( strstr(key,"usec" )) { ev->ticks= atoi(val); return 0;} |
263 | if( strstr(key,"rss" )) { ev->rss= atol(val); return 0;} |
264 | if( strstr(key,"size" )) { ev->size= atol(val); return 0;} |
265 | if( strstr(key,"inblock" )) { ev->inblock= atol(val); return 0;} |
266 | if( strstr(key,"oublock" )) { ev->oublock= atol(val); return 0;} |
267 | if( strstr(key,"majflt" )) { ev->majflt= atol(val); return 0;} |
268 | if( strstr(key,"swaps" )) { ev->swaps= atol(val); return 0;} |
269 | if( strstr(key,"nvcsw" )) { ev->csw= atol(val); return 0;} |
270 | if( strstr(key,"stmt" )) { |
271 | ev->stmt= strdup(val); |
272 | if( (key = strstr(val,"querylog.define(" ) ) ){ |
273 | s =c= strstr(key,"\\\":str" ); |
274 | if( s){ |
275 | s = strstr(c+6,":str," ); |
276 | if( s) |
277 | malsize = atol(s+5); |
278 | } |
279 | c= strstr(key,"\\\":str" ); |
280 | if(c) *c = 0; |
281 | c= strchr(key,'('); |
282 | if (c) { |
283 | while(*c && *c != '"') c++; |
284 | if( *c == '"') c++; |
285 | currentquery = stripQuotes(c); |
286 | } |
287 | } |
288 | s= strstr(val," := " ); |
289 | if( s) { |
290 | s += 4; |
291 | c= strchr(s,'('); |
292 | if( c){ |
293 | *c =0; |
294 | ev->fcn= strdup(s); |
295 | } |
296 | } else{ |
297 | c= strchr(val,'('); |
298 | if( c){ |
299 | *c =0; |
300 | ev->fcn= strdup(val); |
301 | } |
302 | } |
303 | return 0; |
304 | } |
305 | if( strstr(key,"short" )) { ev->beauty= strdup(val); return 0; } |
306 | if( strstr(key,"prereq" )) { ev->prereq= strdup(val); return 0;} |
307 | if( strstr(key,"cpuload" )) { |
308 | ev->function= strdup("" ); |
309 | ev->stmt= strdup(val); return 0; |
310 | } |
311 | |
312 | if( strstr(key,"version" )) { ev->version= strdup(val); return 0;} |
313 | if( strstr(key,"release" )) { ev->release= strdup(val); return 0;} |
314 | if( strstr(key,"host" )) { ev->host= strdup(val); return 0;} |
315 | if( strstr(key,"memory" )) { ev->memory= strdup(val); return 0;} |
316 | if( strstr(key,"threads" )) { ev->threads= strdup(val); return 0;} |
317 | if( strstr(key,"oid" )) { ev->oid= atoi(val); return 0;} |
318 | if( strstr(key,"package" )) { ev->package= strdup(val); return 0;} |
319 | return 0; |
320 | } |
321 | |
322 | void |
323 | eventdump(void) |
324 | { int i; |
325 | for(i=0; i < malargc; i++) |
326 | fprintf(stderr,"arg[%d] %s %s %d\n" ,i,malvariables[i], maltypes[i], malcount[i]); |
327 | for(i=0; i < malretc; i++) |
328 | fprintf(stderr,"var[%d] %s\n" ,i,malvariables[i]); |
329 | } |
330 | |
331 | int |
332 | lineparser(char *row, EventRecord *ev) |
333 | { |
334 | char *c, *cc, *v =0; |
335 | struct tm stm; |
336 | |
337 | malargc = 0; |
338 | malretc = 0; |
339 | memset(malvariables, 0, sizeof(malvariables)); |
340 | /* check basic validaty first */ |
341 | if (row[0] =='#'){ |
342 | return 1; /* ok, but nothing filled in */ |
343 | } |
344 | if (row[0] != '[') |
345 | return -1; |
346 | if ((cc= strrchr(row,']')) == 0 || *(cc+1) !=0) |
347 | return -1; |
348 | |
349 | /* scan event record number */ |
350 | c = row+1; |
351 | ev->eventnr = atoi(c + 1); |
352 | |
353 | /* scan event time" */ |
354 | c = strchr(c + 1, '"'); |
355 | if (c == NULL) |
356 | return -3; |
357 | /* convert time to epoch in seconds*/ |
358 | cc =c; |
359 | stm = (struct tm) {0}; |
360 | #ifdef HAVE_STRPTIME |
361 | c = strptime(c + 1, "%H:%M:%S" , &stm); |
362 | ev->clkticks = (((int64_t) stm.tm_hour * 60 + stm.tm_min) * 60 + stm.tm_sec) * 1000000; |
363 | if (c == NULL) |
364 | return -3; |
365 | #else |
366 | int pos; |
367 | if (sscanf(c + 1, "%d:%d:%d%n" , &stm.tm_hour, &stm.tm_min, &stm.tm_sec, &pos) < 3) |
368 | return -3; |
369 | c += pos + 1; |
370 | #endif |
371 | if (*c == '.') { |
372 | int64_t usec; |
373 | /* microseconds */ |
374 | usec = strtoll(c + 1, NULL, 10); |
375 | assert(usec >= 0 && usec < 1000000); |
376 | ev->clkticks += usec; |
377 | } |
378 | c = strchr(c + 1, '"'); |
379 | if (c == NULL) |
380 | return -3; |
381 | if (ev->clkticks < 0) { |
382 | fprintf(stderr, "parser: read negative value %" PRId64" from\n'%s'\n" , ev->clkticks, cc); |
383 | } |
384 | c++; |
385 | |
386 | /* skip pc tag */ |
387 | { // decode qry[pc]tag |
388 | char *nme = c; |
389 | c= strchr(c+1,'['); |
390 | if( c == 0) |
391 | return -4; |
392 | *c = 0; |
393 | ev->function= strdup(nme); |
394 | *c = '['; |
395 | ev->pc = atoi(c+1); |
396 | c= strchr(c+1,']'); |
397 | if ( c == 0) |
398 | return -4; |
399 | ev->tag = atoi(c+1); |
400 | } |
401 | c = strchr(c+1, ','); |
402 | if (c == 0) |
403 | return -4; |
404 | |
405 | /* scan thread */ |
406 | ev->thread = atoi(c+1); |
407 | |
408 | /* scan status */ |
409 | c = strchr(c, '"'); |
410 | if (c == 0) |
411 | return -5; |
412 | if (strncmp(c + 1, "start" , 5) == 0) { |
413 | ev->state = MDB_START; |
414 | c += 6; |
415 | } else if (strncmp(c + 1, "done" , 4) == 0) { |
416 | ev->state = MDB_DONE; |
417 | c += 5; |
418 | } else if (strncmp(c + 1, "ping" , 4) == 0) { |
419 | ev->state = MDB_PING; |
420 | c += 5; |
421 | } else if (strncmp(c + 1, "system" , 6) == 0) { |
422 | ev->state = MDB_SYSTEM; |
423 | c += 5; |
424 | } else if (strncmp(c + 1, "wait" , 4) == 0) { |
425 | ev->state = MDB_WAIT; |
426 | c += 5; |
427 | } else { |
428 | ev->state = 0; |
429 | c = strchr(c + 1, '"'); |
430 | if (c == 0) |
431 | return -5; |
432 | } |
433 | |
434 | |
435 | /* scan usec */ |
436 | c = strchr(c + 1, ','); |
437 | if (c == 0) |
438 | return -6; |
439 | ev->ticks = strtoll(c + 1, NULL, 10); |
440 | |
441 | /* scan rssMB */ |
442 | c = strchr(c + 1, ','); |
443 | if (c == 0) |
444 | return -7; |
445 | ev->rss = strtoll(c + 1, NULL, 10); |
446 | |
447 | /* scan tmpMB */ |
448 | c = strchr(c + 1, ','); |
449 | if (c == 0) |
450 | return -8; |
451 | ev->size = strtoll(c + 1, NULL, 10); |
452 | |
453 | #ifdef NUMAPROFILING |
454 | for(; *c && *c !='"'; c++) ; |
455 | ev->numa = c+1; |
456 | for(c++; *c && *c !='"'; c++) |
457 | ; |
458 | if (*c == 0) |
459 | return -1; |
460 | *c = 0; |
461 | ev->numa= strdup(numa); |
462 | *c = '"'; |
463 | #endif |
464 | |
465 | /* scan inblock */ |
466 | c = strchr(c + 1, ','); |
467 | if (c == 0) |
468 | return -9; |
469 | ev->inblock = strtoll(c + 1, NULL, 10); |
470 | |
471 | /* scan oublock */ |
472 | c = strchr(c + 1, ','); |
473 | if (c == 0) |
474 | return -10; |
475 | ev->oublock = strtoll(c + 1, NULL, 10); |
476 | |
477 | /* scan majflt */ |
478 | c = strchr(c + 1, ','); |
479 | if (c == 0) |
480 | return -11; |
481 | ev->majflt = strtoll(c + 1, NULL, 10); |
482 | |
483 | /* scan swaps */ |
484 | c = strchr(c + 1, ','); |
485 | if (c == 0) |
486 | return -12; |
487 | ev->swaps = strtoll(c + 1, NULL, 10); |
488 | |
489 | /* scan context switches */ |
490 | c = strchr(c + 1, ','); |
491 | if (c == 0) |
492 | return -13; |
493 | ev->csw = strtoll(c + 1, NULL, 10); |
494 | |
495 | /* parse the MAL call, check basic validity */ |
496 | c = strchr(c, '"'); |
497 | if (c == 0) |
498 | return -15; |
499 | c++; |
500 | ev->fcn = strdup(c); |
501 | ev->stmt = strdup(ev->fcn); |
502 | c= ev->fcn; |
503 | if( *c != '[') |
504 | { |
505 | v=c; |
506 | c = strstr(c + 1, ":= " ); |
507 | if (c) { |
508 | *c = 0; |
509 | parseArgument( (*v == '('? v++:v),ev); |
510 | malretc =malargc; |
511 | *c=':'; |
512 | ev->fcn = c + 2; |
513 | /* find genuine function calls */ |
514 | while (isspace((unsigned char) *ev->fcn) && *ev->fcn) |
515 | ev->fcn++; |
516 | if (strchr(ev->fcn, '.') == 0) |
517 | ev->fcn = 0; |
518 | } |
519 | if( ev->fcn){ |
520 | v= strchr(ev->fcn+1,';'); |
521 | if ( v ) *v = 0; |
522 | } |
523 | } |
524 | |
525 | if (ev->fcn && (v=strchr(ev->fcn, '('))){ |
526 | *v = 0; |
527 | if( v) |
528 | parseArgument(v+1,ev); |
529 | } else { //assigment statements |
530 | v= ev->stmt; |
531 | v = strstr(ev->stmt, ":= " ); |
532 | if( v) |
533 | parseArgument(v+3,ev); |
534 | } |
535 | if (ev->stmt && (v=strstr(ev->stmt, ";\",\t" ))) |
536 | *v = 0; |
537 | return 0; |
538 | } |
539 | |
540 | void |
541 | renderJSONevent(FILE *fd, EventRecord *ev, int notfirst) |
542 | { |
543 | int i; |
544 | |
545 | if( notfirst) |
546 | fprintf(fd,"},\n{" ); |
547 | fprintf(fd,"\"user\":\"%s\",\n" ,ev->user?ev->user:"monetdb" ); |
548 | fprintf(fd,"\"clk\":%" PRId64",\n" ,ev->usec); |
549 | fprintf(fd,"\"ctime\":\"%s\",\n" ,ev->time); |
550 | fprintf(fd,"\"thread\":%d,\n" ,ev->thread); |
551 | fprintf(fd,"\"function\":\"%s\",\n" ,ev->function); |
552 | fprintf(fd,"\"pc\":%d,\n" ,ev->pc); |
553 | fprintf(fd,"\"tag\":%d,\n" ,ev->tag); |
554 | switch(ev->state){ |
555 | case MDB_START: |
556 | fprintf(fd,"\"state\":\"start\",\n" ); |
557 | break; |
558 | case MDB_DONE: |
559 | fprintf(fd,"\"state\":\"done\",\n" ); |
560 | break; |
561 | case MDB_PING: |
562 | fprintf(fd,"\"state\":\"ping\",\n" ); |
563 | break; |
564 | case MDB_WAIT: |
565 | fprintf(fd,"\"state\":\"wait\",\n" ); |
566 | break; |
567 | case MDB_SYSTEM: |
568 | fprintf(fd,"\"state\":\"system\",\n" ); |
569 | break; |
570 | } |
571 | fprintf(fd,"\"usec\":%" PRId64",\n" ,ev->ticks); |
572 | fprintf(fd,"\"rss\":%" PRId64",\n" ,ev->rss); |
573 | fprintf(fd,"\"size\":%" PRId64",\n" ,ev->size); |
574 | if( strstr(ev->stmt," ]" )) |
575 | *strstr(ev->stmt," ]" ) = 0; |
576 | fprintf(fd,"\"stmt\":\"%s\",\n" ,ev->stmt); |
577 | fprintf(fd,"\"short\":\"%s\",\n" ,ev->beauty?ev->beauty:ev->stmt); |
578 | fprintf(fd,"\"prereq\":[]" ); |
579 | if(malretc > 0){ |
580 | fprintf(fd,",\n\"ret\":[" ); |
581 | } |
582 | for(i=0; i<malretc; i++){ |
583 | if(i== malretc) |
584 | fprintf(fd,"],\n\"arg\":[" ); |
585 | else |
586 | if( i) fprintf(fd,",\n" ); |
587 | fprintf(fd,"{\"index\":%d,\"name\":\"%s\",\"type\":\"%s\", \"value\":\"%s\",\"eol\":%d}" , i, "" ,"" ,"" ,i); |
588 | } |
589 | if(malretc > 0) |
590 | fprintf(fd,"],\n" ); |
591 | else fprintf(fd,"\n" ); |
592 | } |
593 | |