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
14char *statenames[]= {"","start","done","action","ping","wait","system"};
15
16char *maltypes[MAXMALARGS];
17char *malvariables[MAXMALARGS];
18char *malvalues[MAXMALARGS];
19int malcount[MAXMALARGS];
20int malargc;
21int malretc;
22
23int malsize;
24int debug=0;
25char *currentquery=0;
26int eventcounter = 0;
27
28#define DATETIME_CHAR_LENGTH 27
29
30static void
31clearArguments(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
56static void
57dumpArguments(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
65char *
66stripQuotes(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
99void
100resetEventRecord(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
137static int
138parseArgument(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
175int
176keyvalueparser(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
322void
323eventdump(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
331int
332lineparser(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
540void
541renderJSONevent(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