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, S Manegold
10 * The easiest calling method is something like:
11 * tomograph -d demo --atlast=10
12 * which connects to the demo database server and
13 * will collect the tomograph pages for at most 10 SQL queries
14 * For each page a gnuplot file, a data file, and the event trace
15 * are collected for more focussed analysis.
16 *
17*/
18
19#include "monetdb_config.h"
20#include "stream.h"
21#include "stream_socket.h"
22#include "mapi.h"
23#include <string.h>
24#include <sys/stat.h>
25#include <signal.h>
26#ifdef HAVE_UNISTD_H
27# include <unistd.h>
28#endif
29#include "mprompt.h"
30#include "dotmonetdb.h"
31#include "eventparser.h"
32
33#ifndef HAVE_GETOPT_LONG
34# include "monet_getopt.h"
35#else
36# ifdef HAVE_GETOPT_H
37# include "getopt.h"
38# endif
39#endif
40
41#ifdef HAVE_NETDB_H
42# include <netdb.h>
43# include <netinet/in.h>
44#endif
45
46#ifndef INVALID_SOCKET
47#define INVALID_SOCKET (-1)
48#endif
49
50
51#define die(dbh, hdl) \
52 do { \
53 if (hdl) \
54 mapi_explain_query(hdl, stderr); \
55 else if (dbh) \
56 mapi_explain(dbh, stderr); \
57 else \
58 fprintf(stderr, "!! command failed\n"); \
59 goto stop_disconnect; \
60 } while (0)
61
62#define doQ(X) \
63 do { \
64 if ((hdl = mapi_query(dbh, X)) == NULL || \
65 mapi_error(dbh) != MOK) \
66 die(dbh, hdl); \
67 } while (0)
68
69static stream *conn = NULL;
70static char hostname[128];
71static char *filename = NULL;
72static int beat = 0;
73static int json = 0;
74static Mapi dbh;
75static MapiHdl hdl = NULL;
76static FILE *trace = NULL;
77
78/*
79 * Tuple level reformatting
80 */
81
82static void
83renderEvent(EventRecord *ev){
84 FILE *s;
85 if(trace != NULL)
86 s = trace;
87 else
88 s = stdout;
89 if( ev->eventnr ==0 && ev->version){
90 fprintf(s, "[ ");
91 fprintf(s, "0, ");
92 fprintf(s, "0, ");
93 fprintf(s, "\"\", " );
94 fprintf(s, "0, ");
95 fprintf(s, "\"system\", ");
96 fprintf(s, "0, ");
97 fprintf(s, "0, ");
98 fprintf(s, "0, ");
99 fprintf(s, "0, ");
100 fprintf(s, "0, ");
101 fprintf(s, "0, ");
102 fprintf(s, "\"");
103 fprintf(s, "version:%s, release:%s, threads:%s, memory:%s, host:%s, oid:%d, package:%s ",
104 ev->version, ev->release, ev->threads, ev->memory, ev->host, ev->oid, ev->package);
105 fprintf(s, "\" ]\n");
106 return ;
107 }
108 if( ev->eventnr < 0)
109 return;
110 fprintf(s, "[ ");
111 fprintf(s, "%"PRId64", ", ev->eventnr);
112 fprintf(s, "\"%s\", ", ev->time);
113 if( ev->function && *ev->function)
114 fprintf(s, "\"%s[%d]%d\", ", ev->function, ev->pc, ev->tag);
115 else
116 fprintf(s, "\"\", ");
117 fprintf(s, "%d, ", ev->thread);
118 switch(ev->state){
119 case MDB_START: fprintf(s, "\"start\", "); break;
120 case MDB_DONE: fprintf(s, "\"done \", "); break;
121 case MDB_WAIT: fprintf(s, "\"wait \", "); break;
122 case MDB_PING: fprintf(s, "\"ping \", "); break;
123 case MDB_SYSTEM: fprintf(s, "\"system\", ");
124 }
125 fprintf(s, "%"PRId64", ", ev->ticks);
126 fprintf(s, "%"PRId64", ", ev->rss);
127 fprintf(s, "%"PRId64", ", ev->size);
128 fprintf(s, "%"PRId64", ", ev->inblock);
129 fprintf(s, "%"PRId64", ", ev->oublock);
130 fprintf(s, "%"PRId64", ", ev->majflt);
131 fprintf(s, "%"PRId64", ", ev->swaps);
132 fprintf(s, "%"PRId64", ", ev->csw);
133 fprintf(s, "\"%s\" ]\n", ev->stmt);
134}
135
136static void
137convertOldFormat(char *inputfile)
138{ FILE *fdin;
139 char basefile[BUFSIZ];
140 char *buf, *e;
141 int notfirst = 0, i;
142 size_t bufsize;
143 size_t len;
144 EventRecord event;
145
146 buf = malloc(BUFSIZ);
147 if (buf == NULL) {
148 fprintf(stderr, "Could not allocate memory\n");
149 return;
150 }
151 bufsize = BUFSIZ;
152 fprintf(stderr, "Converting a file to JSON\n");
153
154 fdin = fopen(inputfile,"r");
155 if( fdin == NULL){
156 fprintf(stderr,"Could not open the input file %s\n", inputfile);
157 free(buf);
158 return;
159 }
160 /* find file name extension */
161 e = strrchr(inputfile, '.');
162 if (e != NULL) {
163 char *s;
164 /* if last dot before last /, ignore the dot */
165 if ((s = strrchr(inputfile, '/')) != NULL && s > e)
166 e = NULL;
167#if DIR_SEP != '/'
168 /* on Windows, look at both directory separators */
169 else if ((s = strrchr(inputfile, DIR_SEP)) != NULL && s > e)
170 e = NULL;
171#endif
172 }
173 if (e == NULL)
174 i = (int) strlen(inputfile);
175 else
176 i = (int) (e - inputfile);
177 snprintf(basefile, BUFSIZ, "%.*s.json", i, inputfile);
178 trace = fopen(basefile, "w");
179 if( trace == NULL){
180 fprintf(stderr,"Could not create the output file %s\n", basefile);
181 free(buf);
182 fclose(fdin);
183 return;
184 }
185 fprintf(trace,"[\n{");
186 len = 0;
187 event = (EventRecord) {0};
188 while (fgets(buf + len, (int) (bufsize - len), fdin) != NULL) {
189 while ((e = strchr(buf + len, '\n')) == NULL) {
190 /* rediculously long line */
191 len += strlen(buf + len); /* i.e. len = strlen(buf) */
192 bufsize += BUFSIZ;
193 if ((e = realloc(buf, bufsize)) == NULL) {
194 free(buf);
195 fclose(fdin);
196 fclose(trace);
197 fprintf(stderr, "Could not allocate memory\n");
198 return;
199 }
200 buf = e;
201 if (fgets(buf + len, (int) (bufsize - len), fdin) == NULL) {
202 /* incomplete line */
203 e = NULL; /* no newline to zap */
204 break;
205 }
206 }
207 if (e)
208 *e = 0; /* zap newline */
209 i = lineparser(buf, &event);
210 if (i == 0) {
211 renderJSONevent(trace, &event, notfirst);
212 resetEventRecord(&event);
213 notfirst = 1;
214 }
215 }
216 fprintf(trace,"}]\n");
217 free(buf);
218 fclose(fdin);
219 fclose(trace);
220 return;
221}
222
223static void
224usageStethoscope(void)
225{
226 fprintf(stderr, "stethoscope [options] \n");
227 fprintf(stderr, " -d | --dbname=<database_name>\n");
228 fprintf(stderr, " -u | --user=<user>\n");
229 fprintf(stderr, " -P | --password=<password>\n");
230 fprintf(stderr, " -p | --port=<portnr>\n");
231 fprintf(stderr, " -h | --host=<hostname>\n");
232 fprintf(stderr, " -c | --convert=<old formated file>\n");
233 fprintf(stderr, " -j | --json\n");
234 fprintf(stderr, " -o | --output=<file>\n");
235 fprintf(stderr, " -b | --beat=<delay> in milliseconds (default 50)\n");
236 fprintf(stderr, " -D | --debug\n");
237 fprintf(stderr, " -? | --help\n");
238 exit(-1);
239}
240
241/* Any signal should be captured and turned into a graceful
242 * termination of the profiling session. */
243
244static void
245stopListening(int i)
246{
247 fprintf(stderr,"stethoscope: signal %d received\n",i);
248 if( dbh)
249 doQ("profiler.stop();");
250stop_disconnect:
251 // show follow up action only once
252 /*
253 if(trace) {
254 fflush(trace);
255 int res = fclose(trace);
256 assert(res==0);
257 }
258 */
259 if(dbh)
260 mapi_disconnect(dbh);
261 /* exit(0); */
262}
263
264int
265main(int argc, char **argv)
266{
267 ssize_t n;
268 size_t len, buflen;
269 char *host = NULL;
270 char *conversion = NULL;
271 int portnr = 0;
272 char *dbname = NULL;
273 char *uri = NULL;
274 char *user = NULL;
275 char *password = NULL;
276 char buf[BUFSIZ], *buffer, *e, *response;
277 int done = 0;
278 EventRecord *ev = calloc(1, sizeof(EventRecord));
279
280 static struct option long_options[13] = {
281 { "dbname", 1, 0, 'd' },
282 { "user", 1, 0, 'u' },
283 { "port", 1, 0, 'p' },
284 { "password", 1, 0, 'P' },
285 { "host", 1, 0, 'h' },
286 { "help", 0, 0, '?' },
287 { "convert", 1, 0, 'c'},
288 { "json", 0, 0, 'j'},
289 { "pretty", 0, 0, 'y'},
290 { "output", 1, 0, 'o' },
291 { "debug", 0, 0, 'D' },
292 { "beat", 1, 0, 'b' },
293 { 0, 0, 0, 0 }
294 };
295
296 if( ev == NULL) {
297 fprintf(stderr,"could not allocate space\n");
298 exit(-1);
299 }
300
301 /* parse config file first, command line options override */
302 parse_dotmonetdb(&user, &password, &dbname, NULL, NULL, NULL, NULL);
303
304 while (1) {
305 int option_index = 0;
306 int c = getopt_long(argc, argv, "d:u:p:P:h:?jyo:Db:",
307 long_options, &option_index);
308 if (c == -1)
309 break;
310 switch (c) {
311 case 'D':
312 debug = 1;
313 break;
314 case 'b':
315 beat = atoi(optarg ? optarg : "5000");
316 break;
317 case 'd':
318 if (dbname)
319 free(dbname);
320 dbname = strdup(optarg);
321 break;
322 case 'u':
323 if (user)
324 free(user);
325 user = strdup(optarg);
326 /* force password prompt */
327 if (password)
328 free(password);
329 password = NULL;
330 break;
331 case 'P':
332 if (password)
333 free(password);
334 password = strdup(optarg);
335 break;
336 case 'p':
337 if (optarg)
338 portnr = atoi(optarg);
339 break;
340 case 'h':
341 host = optarg;
342 break;
343 case 'c':
344 conversion = optarg;
345 break;
346 case 'j':
347 json = 1;
348 break;
349 case 'o':
350 filename = strdup(optarg);
351 printf("-- Output directed towards %s\n", filename);
352 break;
353 case '?':
354 usageStethoscope();
355 /* a bit of a hack: look at the option that the
356 current `c' is based on and see if we recognize
357 it: if -? or --help, exit with 0, else with -1 */
358 exit(strcmp(argv[optind - 1], "-?") == 0 || strcmp(argv[optind - 1], "--help") == 0 ? 0 : -1);
359 default:
360 usageStethoscope();
361 exit(-1);
362 }
363 }
364
365 if( conversion){
366 convertOldFormat(conversion);
367 return 0;
368 }
369
370 if(dbname == NULL){
371 usageStethoscope();
372 exit(-1);
373 }
374
375 if(debug)
376 printf("stethoscope -d %s -o %s\n",dbname,filename);
377
378 if (dbname != NULL && strncmp(dbname, "mapi:monetdb://", 15) == 0) {
379 uri = dbname;
380 dbname = NULL;
381 }
382
383#ifdef SIGPIPE
384 signal(SIGPIPE, stopListening);
385#endif
386#ifdef SIGHUP
387 signal(SIGHUP, stopListening);
388#endif
389#ifdef SIGQUIT
390 signal(SIGQUIT, stopListening);
391#endif
392 signal(SIGINT, stopListening);
393 signal(SIGTERM, stopListening);
394 /* close(0); */
395
396 if (user == NULL)
397 user = simple_prompt("user", BUFSIZ, 1, prompt_getlogin());
398 if (password == NULL)
399 password = simple_prompt("password", BUFSIZ, 0, NULL);
400
401 /* our hostname, how remote servers have to contact us */
402 gethostname(hostname, sizeof(hostname));
403
404 /* set up the profiler */
405 if (uri)
406 dbh = mapi_mapiuri(uri, user, password, "mal");
407 else
408 dbh = mapi_mapi(host, portnr, user, password, "mal", dbname);
409 if (dbh == NULL || mapi_error(dbh))
410 die(dbh, hdl);
411 mapi_reconnect(dbh);
412 if (mapi_error(dbh))
413 die(dbh, hdl);
414 host = strdup(mapi_get_host(dbh));
415 if(debug)
416 fprintf(stderr,"-- connection with server %s\n", uri ? uri : host);
417
418 snprintf(buf,BUFSIZ-1,"profiler.setheartbeat(%d);",beat);
419 if( debug)
420 fprintf(stderr,"-- %s\n",buf);
421 doQ(buf);
422
423 snprintf(buf, BUFSIZ, "profiler.openstream();");
424 if( debug)
425 fprintf(stderr,"--%s\n",buf);
426 doQ(buf);
427
428 if(filename != NULL) {
429 trace = fopen(filename,"w");
430
431 if( trace == NULL) {
432 fprintf(stderr,"Could not create file '%s', printing to stdout instead...\n", filename);
433 filename = NULL;
434 }
435 }
436
437 len = 0;
438 buflen = BUFSIZ;
439 buffer = (char *) malloc(buflen);
440 if( buffer == NULL){
441 fprintf(stderr,"Could not create input buffer\n");
442 exit(-1);
443 }
444 conn = mapi_get_from(dbh);
445 while ((n = mnstr_read(conn, buffer + len, 1, buflen - len-1)) >= 0) {
446 if (n == 0 &&
447 (n = mnstr_read(conn, buffer + len, 1, buflen - len-1)) <= 0)
448 break;
449 buffer[len + n] = 0;
450 response = buffer;
451 if( debug)
452 printf("%s", response);
453 if(json) {
454 if(trace != NULL) {
455 fprintf(trace, "%s", response + len);
456 fflush(trace);
457 } else {
458 printf("%s", response + len);
459 fflush(stdout);
460 }
461 }
462 while ((e = strchr(response, '\n')) != NULL) {
463 *e = 0;
464 if(!json) {
465 //printf("%s\n", response);
466 done= keyvalueparser(response,ev);
467 if( done== 1){
468 renderEvent(ev);
469 resetEventRecord(ev);
470 }
471 }
472 response = e + 1;
473 }
474
475 /* handle the case that the line is too long to
476 * fit in the buffer */
477 if( response == buffer){
478 char *new = (char *) realloc(buffer, buflen + BUFSIZ);
479 if( new == NULL){
480 fprintf(stderr,"Could not extend input buffer\n");
481 exit(-1);
482 }
483 buffer = new;
484 buflen += BUFSIZ;
485 len += n;
486 }
487 /* handle the case the buffer contains more than one
488 * line, and the last line is not completely read yet.
489 * Copy the first part of the incomplete line to the
490 * beginning of the buffer */
491 else if (*response) {
492 if (debug)
493 printf("LASTLINE:%s", response);
494 len = strlen(response);
495 snprintf(buffer, len + 1, "%s", response);
496 } else /* reset this line of buffer */
497 len = 0;
498 }
499
500 doQ("profiler.stop();");
501stop_disconnect:
502 if(dbh)
503 mapi_disconnect(dbh);
504 if(trace)
505 fclose(trace);
506 printf("-- connection with server %s closed\n", uri ? uri : host);
507 return 0;
508}
509