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 --atlas=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 | #include <time.h> |
42 | |
43 | #ifdef NATIVE_WIN32 |
44 | #include <direct.h> |
45 | #endif |
46 | |
47 | #define die(dbh, hdl) \ |
48 | do { \ |
49 | if (hdl) \ |
50 | mapi_explain_query(hdl, stderr); \ |
51 | else if (dbh) \ |
52 | mapi_explain(dbh, stderr); \ |
53 | else \ |
54 | fprintf(stderr, "!! command failed\n"); \ |
55 | goto stop_disconnect; \ |
56 | } while (0) |
57 | |
58 | #define doQ(X) \ |
59 | do { \ |
60 | if ((hdl = mapi_query(dbh, X)) == NULL || \ |
61 | mapi_error(dbh) != MOK) \ |
62 | die(dbh, hdl); \ |
63 | } while (0) |
64 | |
65 | static stream *conn = NULL; |
66 | static char hostname[128]; |
67 | static char *dbname = NULL; |
68 | #ifdef NATIVE_WIN32 |
69 | static char *dirpath= "cache\\" ; |
70 | #else |
71 | static char *dirpath= "cache/" ; |
72 | #endif |
73 | static char *prefix = "tomograph" ; |
74 | static char basefile[BUFSIZ]; |
75 | static FILE *tracefd; |
76 | static int64_t startrange = 0, endrange = 0; |
77 | static char *inputfile = NULL; |
78 | static char *title = 0; |
79 | static int beat = 5000; |
80 | static int cpus = 0; |
81 | static int atlas= 32; |
82 | static int atlaspage = 0; |
83 | static FILE *gnudata; |
84 | static Mapi dbh; |
85 | static MapiHdl hdl = NULL; |
86 | |
87 | /* |
88 | * Parsing the argument list of a MAL call to obtain un-quoted string values |
89 | */ |
90 | static int capturing=0; |
91 | |
92 | #define MAXTHREADS 1048 |
93 | #define MAXBOX 32678 /* should be > MAXTHREADS */ |
94 | |
95 | static int crossings[MAXTHREADS][MAXTHREADS]; |
96 | static int target[MAXTHREADS]; |
97 | static int source[MAXTHREADS]; |
98 | |
99 | /* color map management, fixed */ |
100 | /* see http://www.uni-hamburg.de/Wiss/FB/15/Sustainability/schneider/gnuplot/colors.htm */ |
101 | /* The initial dictionary is geared towars TPCH-use */ |
102 | typedef struct COLOR { |
103 | int freq; |
104 | int64_t timeused; |
105 | char *mod, *fcn, *col; |
106 | } Color; |
107 | |
108 | #define NUM_COLORS ((int) (sizeof(dictionary) / sizeof(RGB))) |
109 | #define MAX_LEGEND_SHORT 30 /* max. size of colormap / legend */ |
110 | #define PERCENTAGE 0.01 /* threshold for time filter */ |
111 | |
112 | typedef struct { |
113 | char *name; |
114 | char *hsv; |
115 | int red, green, blue; |
116 | } RGB; |
117 | |
118 | /* the color table is randomized */ |
119 | RGB |
120 | dictionary[] = { |
121 | /* 227 */ { "saddlebrown" , "#8B4513" , 139, 69, 19 }, |
122 | /* 557 */ { "navyblue" , "#9FAFDF" , 159, 175, 223 }, |
123 | /* 734 */ { "lightyellow" , "#FFFFE0" , 255, 255, 224 }, |
124 | /* 750 */ { "azure" , "#F0FFFF" , 240, 255, 255 }, |
125 | /* 496 */ { "turquoise" , "#40E0D0" , 64, 224, 208 }, |
126 | /* 750 */ { "mintcream" , "#F5FFFA" , 245, 255, 250 }, |
127 | /* 278 */ { "darkcyan" , "#008B8B" , 0, 139, 139 }, |
128 | /* 239 */ { "darkolivegreen" , "#556B2F" , 85, 107, 47 }, |
129 | /* 128 */ { "maroon" , "#800000" , 128, 0, 0 }, |
130 | /* 735 */ { "whitesmoke" , "#F5F5F5" , 245, 245, 245 }, |
131 | /* 315 */ { "dimgray" , "#696969" , 105, 105, 105 }, |
132 | /* 492 */ { "salmon" , "#FA8072" , 250, 128, 114 }, |
133 | /* 353 */ { "mediumvioletred" , "#C71585" , 199, 21, 133 }, |
134 | /* 477 */ { "mediumaquamarine" , "#66CDAA" , 102, 205, 170 }, |
135 | /* 139 */ { "darkblue" , "#00008B" , 0, 0, 139 }, |
136 | /* 710 */ { "beige" , "#F5F5DC" , 245, 245, 220 }, |
137 | /* 352 */ { "mediumseagreen" , "#3CB371" , 60, 179, 113 }, |
138 | /* 413 */ { "cadetblue" , "#5F9EA0" , 95, 158, 160 }, |
139 | /* 660 */ { "gainsboro" , "#DCDCDC" , 220, 220, 220 }, |
140 | /* 465 */ { "mediumslateblue" , "#7B68EE" , 123, 104, 238 }, |
141 | /* 623 */ { "thistle" , "#D8BFD8" , 216, 191, 216 }, |
142 | /* 745 */ { "floralwhite" , "#FFFAF0" , 255, 250, 240 }, |
143 | /* 710 */ { "lemonchiffon" , "#FFFACD" , 255, 250, 205 }, |
144 | /* 765 x { "white", "#FFFFFF", 255, 255, 255 }, */ |
145 | /* 256 */ { "olive" , "#808000" , 128, 128, 0 }, |
146 | /* 255 */ { "red" , "#FF0000" , 255, 0, 0 }, |
147 | /* 380 */ { "steelblue" , "#4682B4" , 70, 130, 180 }, |
148 | /* 664 */ { "moccasin" , "#FFE4B5" , 255, 228, 181 }, |
149 | /* 395 */ { "darkorange" , "#FF8C00" , 255, 140, 0 }, |
150 | /* 507 */ { "darkgray" , "#A9A9A9" , 169, 169, 169 }, |
151 | /* 272 */ { "seagreen" , "#2E8B57" , 46, 139, 87 }, |
152 | /* 755 */ { "snow" , "#FFFAFA" , 255, 250, 250 }, |
153 | /* 284 */ { "olivedrab" , "#6B8E23" , 107, 142, 35 }, |
154 | /* 382 */ { "chartreuse" , "#7FFF00" , 127, 255, 0 }, |
155 | /* 478 */ { "palevioletred" , "#DB7093" , 219, 112, 147 }, |
156 | /* 710 */ { "lavender" , "#E6E6FA" , 230, 230, 250 }, |
157 | /* 162 */ { "midnightblue" , "#191970" , 25, 25, 112 }, |
158 | /* 708 */ { "mistyrose" , "#FFE4E1" , 255, 228, 225 }, |
159 | /* 425 */ { "tomato" , "#FF6347" , 255, 99, 71 }, |
160 | /* 576 */ { "skyblue" , "#87CEEB" , 135, 206, 235 }, |
161 | /* 420 */ { "orange" , "#FFA500" , 255, 165, 0 }, |
162 | /* 128 */ { "green" , "#008000" , 0, 128, 0 }, |
163 | /* 740 */ { "lavenderblush" , "#FFF0F5" , 255, 240, 245 }, |
164 | /* 380 */ { "lightseagreen" , "#20B2AA" , 32, 178, 170 }, |
165 | /* 415 */ { "darkturquoise" , "#00CED1" , 0, 206, 209 }, |
166 | /* 526 */ { "lightgreen" , "#90EE90" , 144, 238, 144 }, |
167 | /* 395 */ { "royalblue" , "#4169E1" , 65, 105, 225 }, |
168 | /* 205 */ { "darkslategray" , "#2F4F4F" , 47, 79, 79 }, |
169 | /* 415 */ { "goldenrod" , "#DAA520" , 218, 165, 32 }, |
170 | /* 735 */ { "honeydew" , "#F0FFF0" , 240, 255, 240 }, |
171 | /* 246 */ { "firebrick" , "#B22222" , 178, 34, 34 }, |
172 | /* 255 */ { "lime" , "#00FF00" , 0, 255, 0 }, |
173 | /* 381 */ { "gray" , "#7F7F7F" , 127, 127, 127 }, |
174 | /* 128 */ { "navy" , "#000080" , 0, 0, 128 }, |
175 | /* 479 */ { "darkkhaki" , "#BDB76B" , 189, 183, 107 }, |
176 | /* 345 */ { "chocolate" , "#D2691E" , 210, 105, 30 }, |
177 | /* 474 */ { "rosybrown" , "#BC8F8F" , 188, 143, 143 }, |
178 | /* 255 */ { "blue" , "#0000FF" , 0, 0, 255 }, |
179 | /* 408 */ { "lightslategray" , "#778899" , 119, 136, 153 }, |
180 | /* 504 */ { "sandybrown" , "#F4A460" , 244, 164, 96 }, |
181 | /* 728 */ { "oldlace" , "#FDF5E6" , 253, 245, 230 }, |
182 | /* 249 */ { "brown" , "#A52A2A" , 165, 42, 42 }, |
183 | /* 743 */ { "aliceblue" , "#F0F8FF" , 240, 248, 255 }, |
184 | /* 630 */ { "lightpink" , "#FFB6C1" , 255, 182, 193 }, |
185 | /* 0 x { "black", "#000000", 0, 0, 0 }, */ |
186 | /* 404 */ { "mediumspringgreen" , "#00FA9A" , 0, 250, 154 }, |
187 | /* 486 */ { "cornflowerblue" , "#6495ED" , 100, 149, 237 }, |
188 | /* 723 */ { "cornsilk" , "#FFF8DC" , 255, 248, 220 }, |
189 | /* 710 */ { "lightgoldenrodyellow" , "#FAFAD2" , 250, 250, 210 }, |
190 | /* 207 */ { "forestgreen" , "#228B22" , 34, 139, 34 }, |
191 | /* 707 */ { "papayawhip" , "#FFEFD5" , 255, 239, 213 }, |
192 | /* 640 */ { "palegoldenrod" , "#EEE8AA" , 238, 232, 170 }, |
193 | /* 278 */ { "darkmagenta" , "#8B008B" , 139, 0, 139 }, |
194 | /* 576 */ { "silver" , "#C0C0C0" , 192, 192, 192 }, |
195 | /* 305 */ { "limegreen" , "#32CD32" , 50, 205, 50 }, |
196 | /* 429 */ { "dodgerblue" , "#1E90FF" , 30, 144, 255 }, |
197 | /* 324 */ { "orangered" , "#FF4500" , 255, 69, 0 }, |
198 | /* 540 */ { "hotpink" , "#FF69B4" , 255, 105, 180 }, |
199 | /* 205 */ { "mediumblue" , "#0000CD" , 0, 0, 205 }, |
200 | /* 734 */ { "lightcyan" , "#E0FFFF" , 224, 255, 255 }, |
201 | /* 407 */ { "blueviolet" , "#8A2BE2" , 138, 43, 226 }, |
202 | /* 256 */ { "purple" , "#800080" , 128, 0, 128 }, |
203 | /* 389 */ { "indianred" , "#CD5C5C" , 205, 92, 92 }, |
204 | /* 384 */ { "slategray" , "#708090" , 112, 128, 144 }, |
205 | /* 750 */ { "ivory" , "#FFFFF0" , 255, 255, 240 }, |
206 | /* 650 */ { "navajowhite" , "#FFDEAD" , 255, 222, 173 }, |
207 | /* 382 */ { "springgreen" , "#00FF7F" , 0, 255, 127 }, |
208 | /* 606 */ { "violet" , "#EE82EE" , 238, 130, 238 }, |
209 | /* 510 */ { "aqua" , "#00FFFF" , 0, 255, 255 }, |
210 | /* 510 */ { "cyan" , "#00FFFF" , 0, 255, 255 }, |
211 | /* 359 */ { "darkviolet" , "#9400D3" , 148, 0, 211 }, |
212 | /* 602 */ { "plum" , "#DDA0DD" , 221, 160, 221 }, |
213 | /* 591 */ { "lightskyblue" , "#87CEFA" , 135, 206, 250 }, |
214 | /* 720 */ { "linen" , "#FAF0E6" , 250, 240, 230 }, |
215 | /* 555 */ { "palegreen" , "#98FB98" , 152, 251, 152 }, |
216 | /* 272 */ { "darkslateblue" , "#483D8B" , 72, 61, 139 }, |
217 | /* 462 */ { "coral" , "#FF7F50" , 255, 127, 80 }, |
218 | /* 544 */ { "orchid" , "#DA70D6" , 218, 112, 214 }, |
219 | /* 505 */ { "darksalmon" , "#E9967A" , 233, 150, 122 }, |
220 | /* 650 */ { "pink" , "#FFC0CB" , 255, 192, 203 }, |
221 | /* 300 */ { "crimson" , "#DC143C" , 220, 20, 60 }, |
222 | /* 394 */ { "yellowgreen" , "#9ACD32" , 139, 205, 50 }, |
223 | /* 700 */ { "antiquewhite" , "#FAEBD7" , 250, 235, 215 }, |
224 | /* 407 */ { "darkorchid" , "#9932CC" , 153, 50, 204 }, |
225 | /* 651 */ { "paleturquoise" , "#AFEEEE" , 175, 238, 238 }, |
226 | /* 633 */ { "lightgrey" , "#D3D3D3" , 211, 211, 211 }, |
227 | /* 679 */ { "bisque" , "#FFE4C4" , 255, 228, 196 }, |
228 | /* 422 */ { "deeppink" , "#FF1493" , 255, 20, 147 }, |
229 | /* 139 */ { "darkred" , "#8B0000" , 139, 0, 0 }, |
230 | /* 475 */ { "greenyellow" , "#ADFF2F" , 173, 255, 47 }, |
231 | /* 205 */ { "indigo" , "#4B0082" , 75, 0, 130 }, |
232 | /* 446 */ { "deepskyblue" , "#00BFFF" , 0, 191, 255 }, |
233 | /* 329 */ { "darkgoldenrod" , "#B8860B" , 184, 134, 11 }, |
234 | /* 470 */ { "gold" , "#FFD700" , 255, 215, 0 }, |
235 | /* 610 */ { "khaki" , "#F0E68C" , 240, 230, 140 }, |
236 | /* 485 */ { "mediumturquoise" , "#48D1CC" , 72, 209, 204 }, |
237 | /* 100 */ { "darkgreen" , "#006400" , 0, 100, 0 }, |
238 | /* 510 */ { "fuchsia" , "#FF00FF" , 255, 0, 255 }, |
239 | /* 510 */ { "magenta" , "#FF00FF" , 255, 0, 255 }, |
240 | /* 751 */ { "ghostwhite" , "#F8F8FF" , 248, 248, 255 }, |
241 | /* 658 */ { "peachpuff" , "#FFDAB9" , 255, 218, 185 }, |
242 | /* 478 */ { "mediumpurple" , "#9370DB" , 147, 112, 219 }, |
243 | /* 376 */ { "lawngreen" , "#7CFC00" , 124, 252, 0 }, |
244 | /* 401 */ { "peru" , "#CD853F" , 205, 133, 63 }, |
245 | /* 695 */ { "blanchedalmond" , "#FFEBCD" , 255, 235, 205 }, |
246 | /* 256 */ { "teal" , "#008080" , 0, 128, 128 }, |
247 | /* 594 */ { "lightsteelblue" , "#B0C4DE" , 176, 196, 222 }, |
248 | /* 474 */ { "darkseagreen" , "#8FBC8F" , 143, 188, 143 }, |
249 | /* 287 */ { "sienna" , "#A0522D" , 160, 82, 45 }, |
250 | /* 401 */ { "slateblue" , "#6A5ACD" , 106, 90, 205 }, |
251 | /* 646 */ { "wheat" , "#F5DEB3" , 245, 222, 179 }, |
252 | /* 738 */ { "seashell" , "#FFF5EE" , 255, 245, 238 }, |
253 | /* 530 */ { "tan" , "#D2B48C" , 210, 180, 140 }, |
254 | /* 510 */ { "yellow" , "#FFFF00" , 255, 255, 0 }, |
255 | /* 496 */ { "lightcoral" , "#F08080" , 240, 128, 128 }, |
256 | /* 630 */ { "powderblue" , "#B0E0E6" , 176, 224, 230 }, |
257 | /* 541 */ { "burlywood" , "#DEB887" , 222, 184, 135 }, |
258 | /* 594 */ { "aquamarine" , "#7FFFD4" , 127, 255, 212 }, |
259 | /* 619 */ { "lightblue" , "#ADD8E6" , 173, 216, 230 }, |
260 | /* 537 */ { "lightsalmon" , "#FFA07A" , 255, 160, 122 }, |
261 | /* 482 */ { "mediumorchid" , "#BA55D3" , 186, 85, 211 }, |
262 | }; |
263 | |
264 | /* initial mod.fcn list for adaptive colormap, this ensures ease of comparison */ |
265 | Color |
266 | base_colors[NUM_COLORS] = { |
267 | /* reserve (base_)colors[0] for generic "*.*" */ |
268 | /* 99999 { 0, 0, "*", "*", 0 },*/ |
269 | /* arbitrarily ordered by descending frequency in TPCH SF-100 with 32 threads */ |
270 | /* 11054 */ { 0, 0, "algebra" , "projection" , 0 }, |
271 | /* 10355 */ { 0, 0, "language" , "pass" , 0 }, |
272 | /* 5941 */ { 0, 0, "sql" , "bind" , 0 }, |
273 | /* 5664 */ { 0, 0, "mat" , "packIncrement" , 0 }, |
274 | /* 4796 */ { 0, 0, "algebra" , "select" , 0 }, |
275 | /* 4789 */ { 0, 0, "algebra" , "subjoin" , 0 }, |
276 | /* 2664 */ { 0, 0, "sql" , "projectdelta" , 0 }, |
277 | /* 2112 */ { 0, 0, "batcalc" , "!=" , 0 }, |
278 | /* 1886 */ { 0, 0, "sql" , "bind_idxbat" , 0 }, |
279 | /* 1881 */ { 0, 0, "algebra" , "projectionPath" , 0 }, |
280 | /* */ { 0, 0, "algebra" , "tinter" , 0 }, |
281 | /* */ { 0, 0, "algebra" , "tdiff" , 0 }, |
282 | /* 1013 */ { 0, 0, "sql" , "tid" , 0 }, |
283 | /* 832 */ { 0, 0, "bat" , "mergecand" , 0 }, |
284 | /* 813 */ { 0, 0, "sql" , "delta" , 0 }, |
285 | /* 766 */ { 0, 0, "aggr" , "subsum" , 0 }, |
286 | /* 610 */ { 0, 0, "batcalc" , "*" , 0 }, |
287 | /* 577 */ { 0, 0, "group" , "subgroupdone" , 0 }, |
288 | /* 577 */ { 0, 0, "group" , "groupdone" , 0 }, |
289 | /* 481 */ { 0, 0, "sql" , "subdelta" , 0 }, |
290 | /* 481 */ { 0, 0, "sql" , "subsort" , 0 }, |
291 | /* 448 */ { 0, 0, "batcalc" , "-" , 0 }, |
292 | /* 334 */ { 0, 0, "bat" , "mirror" , 0 }, |
293 | /* 300 */ { 0, 0, "group" , "subgroup" , 0 }, |
294 | /* 300 */ { 0, 0, "group" , "group" , 0 }, |
295 | /* 264 */ { 0, 0, "batcalc" , "==" , 0 }, |
296 | /* 260 */ { 0, 0, "batcalc" , "ifthenelse" , 0 }, |
297 | /* 209 */ { 0, 0, "batcalc" , "hge" , 0 }, |
298 | /* 209 */ { 0, 0, "calc" , "str" , 0 }, |
299 | /* 207 */ { 0, 0, "aggr" , "sum" , 0 }, |
300 | /* 200 */ { 0, 0, "algebra" , "thetaselect" , 0 }, |
301 | /* 200 */ { 0, 0, "algebra" , "selectNotNil" , 0 }, |
302 | /* 197 */ { 0, 0, "aggr" , "subcount" , 0 }, |
303 | /* 166 */ { 0, 0, "batcalc" , "dbl" , 0 }, |
304 | /* 166 */ { 0, 0, "algebra" , "tinter" , 0 }, |
305 | /* 131 */ { 0, 0, "algebra" , "leftjoin" , 0 }, |
306 | /* 128 */ { 0, 0, "batcalc" , "isnil" , 0 }, |
307 | /* 98 */ { 0, 0, "aggr" , "count" , 0 }, |
308 | /* 97 */ { 0, 0, "batcalc" , ">" , 0 }, |
309 | /* */ { 0, 0, "bat" , "mergecand" , 0 }, |
310 | /* 96 */ { 0, 0, "batmtime" , "year" , 0 }, |
311 | /* 96 */ { 0, 0, "batcalc" , "<" , 0 }, |
312 | /* 79 */ { 0, 0, "sql" , "assert" , 0 }, |
313 | /* 72 */ { 0, 0, "sql" , "rsColumn" , 0 }, |
314 | /* 72 */ { 0, 0, "sql" , "mvc" , 0 }, |
315 | /* 69 */ { 0, 0, "mkey" , "bulk_rotate_xor_hash" , 0 }, |
316 | /* 69 */ { 0, 0, "calc" , "lng" , 0 }, |
317 | /* 69 */ { 0, 0, "batcalc" , "hash" , 0 }, |
318 | /* 66 */ { 0, 0, "pqueue" , "utopn_max" , 0 }, |
319 | /* 66 */ { 0, 0, "algebra" , "tdiff" , 0 }, |
320 | /* 53 */ { 0, 0, "calc" , "int" , 0 }, |
321 | /* 47 */ { 0, 0, "algebra" , "likeselect" , 0 }, |
322 | /* 44 */ { 0, 0, "sql" , "exportOperation" , 0 }, |
323 | /* 42 */ { 0, 0, "algebra" , "subslice" , 0 }, |
324 | /* 36 */ { 0, 0, "pqueue" , "utopn_min" , 0 }, |
325 | /* 36 */ { 0, 0, "pqueue" , "topn_max" , 0 }, |
326 | /* 33 */ { 0, 0, "aggr" , "submin" , 0 }, |
327 | /* 32 */ { 0, 0, "batalgebra" , "like" , 0 }, |
328 | /* 32 */ { 0, 0, "batcalc" , "or" , 0 }, |
329 | /* 32 */ { 0, 0, "batcalc" , "and" , 0 }, |
330 | /* 32 */ { 0, 0, "batcalc" , "+" , 0 }, |
331 | /* 24 */ { 0, 0, "sql" , "setVariable" , 0 }, |
332 | /* 23 */ { 0, 0, "language" , "dataflow" , 0 }, |
333 | /* 21 */ { 0, 0, "algebra" , "subsort" , 0 }, |
334 | /* 20 */ { 0, 0, "sql" , "catalog" , 0 }, |
335 | /* 19 */ { 0, 0, "calc" , "ptr" , 0 }, |
336 | /* 18 */ { 0, 0, "sql" , "resultSet" , 0 }, |
337 | /* 18 */ { 0, 0, "sql" , "exportResult" , 0 }, |
338 | /* 18 */ { 0, 0, "io" , "stdout" , 0 }, |
339 | /* 18 */ { 0, 0, "calc" , "!=" , 0 }, |
340 | /* 10 */ { 0, 0, "sql" , "update" , 0 }, |
341 | /* 9 */ { 0, 0, "mtime" , "addmonths" , 0 }, |
342 | /* 9 */ { 0, 0, "calc" , "ifthenelse" , 0 }, |
343 | /* 8 */ { 0, 0, "sql" , "copy_from" , 0 }, |
344 | /* 8 */ { 0, 0, "sql" , "affectedRows" , 0 }, |
345 | /* 8 */ { 0, 0, "calc" , "isnil" , 0 }, |
346 | /* 7 */ { 0, 0, "bat" , "append" , 0 }, |
347 | /* 6 */ { 0, 0, "mat" , "pack" , 0 }, |
348 | /* 6 */ { 0, 0, "bat" , "new" , 0 }, |
349 | /* 5 */ { 0, 0, "batcalc" , "/" , 0 }, |
350 | /* 4 */ { 0, 0, "sql" , "exportValue" , 0 }, |
351 | /* 4 */ { 0, 0, "calc" , "date" , 0 }, |
352 | /* 4 */ { 0, 0, "calc" , "+" , 0 }, |
353 | /* 3 */ { 0, 0, "calc" , "/" , 0 }, |
354 | /* 3 */ { 0, 0, "batstr" , "substring" , 0 }, |
355 | /* 3 */ { 0, 0, "batcalc" , "lng" , 0 }, |
356 | /* 2 */ { 0, 0, "calc" , "min" , 0 }, |
357 | /* 2 */ { 0, 0, "calc" , "max" , 0 }, |
358 | /* 2 */ { 0, 0, "calc" , "bit" , 0 }, |
359 | /* 2 */ { 0, 0, "calc" , "*" , 0 }, |
360 | /* 2 */ { 0, 0, "algebra" , "subthetajoin" , 0 }, |
361 | /* 1 */ { 0, 0, "sql" , "dec_round" , 0 }, |
362 | /* 1 */ { 0, 0, "pqueue" , "topn_min" , 0 }, |
363 | /* 1 */ { 0, 0, "mtime" , "date_sub_msec_interval" , 0 }, |
364 | /* 1 */ { 0, 0, "iterator" , "next" , 0 }, |
365 | /* 1 */ { 0, 0, "iterator" , "new" , 0 }, |
366 | /* 1 */ { 0, 0, "calc" , "dbl" , 0 }, |
367 | /* 1 */ { 0, 0, "calc" , "-" , 0 }, |
368 | /* 1 */ { 0, 0, "calc" , "==" , 0 }, |
369 | /* 1 */ { 0, 0, "bat" , "reverse" , 0 }, |
370 | /* 1 */ { 0, 0, "bat" , "insert" , 0 }, |
371 | /* 1 */ { 0, 0, "algebra" , "project" , 0 }, |
372 | /* 1 */ { 0, 0, "algebra" , "fetch" , 0 }, |
373 | /* 1 */ { 0, 0, "aggr" , "max" , 0 }, |
374 | /* ? */ { 0, 0, "aggr" , "avg" , 0 }, |
375 | /* ? */ { 0, 0, "aggr" , "group_concat" , 0 }, |
376 | /* ? */ { 0, 0, "aggr" , "subavg" , 0 }, |
377 | /* ? */ { 0, 0, "aggr" , "submedian" , 0 }, |
378 | /* ? */ { 0, 0, "aggr" , "subquantile" , 0 }, |
379 | /* ? */ { 0, 0, "aggr" , "substdev" , 0 }, |
380 | /* ? */ { 0, 0, "batcalc" , "floor" , 0 }, |
381 | /* ? */ { 0, 0, "batcalc" , "identity" , 0 }, |
382 | /* ? */ { 0, 0, "batmkey" , "hash" , 0 }, |
383 | /* ? */ { 0, 0, "calc" , "hge" , 0 }, |
384 | /* ? */ { 0, 0, "batcalc" , "hge" , 0 }, |
385 | /* ? */ { 0, 0, "algebra" , "firstn" , 0 }, |
386 | /* ? */ { 0, 0, "sql" , "single" , 0 }, |
387 | /* ? */ { 0, 0, "algebra" , "crossproduct" , 0 }, |
388 | /* ? */ { 0, 0, "profiler" , "wait" , 0 }, |
389 | /* ? */ { 0, 0, "querylog" , "define" , 0 }, |
390 | /* ? */ { 0, 0, "*" , "*" , 0 }, |
391 | /* 0 */ { 0, 0, 0, 0, 0 } |
392 | }; |
393 | |
394 | |
395 | Color |
396 | colors[NUM_COLORS] = {{0,0,0,0,0}}; |
397 | |
398 | #ifdef NUMAprofiling |
399 | static void |
400 | showNumaHeatmap(void){ |
401 | int i,j =0; |
402 | int max= 0; |
403 | FILE *f; |
404 | char buf[BUFSIZ]; |
405 | |
406 | |
407 | snprintf(buf,BUFSIZ,"%s_heatmap.csv" ,basefile); |
408 | f= fopen(buf,"a" ); |
409 | if( f == NULL){ |
410 | fprintf(stderr,"Can not create %s\n" ,buf); |
411 | exit(-1); |
412 | } |
413 | for( i=0; i< MAXTHREADS; i++){ |
414 | if( target[i]) |
415 | for(j=MAXTHREADS-1; j>0 && crossings[i][j]; j--) |
416 | ; |
417 | if (j > max) max =j; |
418 | } |
419 | for( i=0; i< max; i++) |
420 | if( target[i] && source[i] ){ |
421 | for(j=0; j< max; j++) |
422 | if( target[j] && source[j]) |
423 | fprintf(stderr,"%d\t" , crossings[i][j]); |
424 | fprintf(stderr,"\n" ); |
425 | } |
426 | |
427 | for( i=0; i< MAXTHREADS; i++){ |
428 | for(j=0; j< MAXTHREADS; j++) |
429 | crossings[i][j]=0; |
430 | target[i]=0; |
431 | source[i]=0; |
432 | } |
433 | } |
434 | #endif |
435 | |
436 | static void |
437 | usageTomograph(void) |
438 | { |
439 | fprintf(stderr, "tomograph [options]\n" ); |
440 | fprintf(stderr, " -d | --dbname=<database_name>\n" ); |
441 | fprintf(stderr, " -u | --user=<user>\n" ); |
442 | fprintf(stderr, " -P | --password=<password>\n" ); |
443 | fprintf(stderr, " -p | --port=<portnr>\n" ); |
444 | fprintf(stderr, " -h | --host=<hostname>\n" ); |
445 | fprintf(stderr, " -T | --title=<plot title>\n" ); |
446 | fprintf(stderr, " -r | --range=<starttime>-<endtime>[ms,s]\n" ); |
447 | fprintf(stderr, " -i | --input=<profiler event file >\n" ); |
448 | fprintf(stderr, " -o | --output=<dir/file prefix > (default 'cache/<dbname>'\n" ); |
449 | fprintf(stderr, " -b | --beat=<delay> in milliseconds (default 5000)\n" ); |
450 | fprintf(stderr, " -A | --atlas=<number> maximum number of queries (default 1)\n" ); |
451 | fprintf(stderr, " -D | --debug\n" ); |
452 | fprintf(stderr, " -? | --help\n" ); |
453 | exit(-1); |
454 | } |
455 | |
456 | /* Any signal should be captured and turned into a graceful |
457 | * termination of the profiling session. */ |
458 | static void createTomogram(void); |
459 | |
460 | static void |
461 | stopListening(int i) |
462 | { |
463 | #define BSIZE 64*1024 |
464 | char buf[BSIZE + BUFSIZ]={0}; |
465 | char pages[BSIZE]={0}; |
466 | int j, error =0, plen =0; |
467 | if( i) |
468 | fprintf(stderr,"signal %d received\n" ,i); |
469 | if( dbh) |
470 | doQ("profiler.stop();" ); |
471 | stop_disconnect: |
472 | if (atlas != atlaspage ) |
473 | createTomogram(); |
474 | // show follow up action only once |
475 | if(atlaspage >= 1){ |
476 | for (i = 0; i < atlaspage; i++){ |
477 | snprintf(buf, BUFSIZ, "gnuplot %s_%02d.gpl;" , basefile, i); |
478 | if( error == 0){ |
479 | fprintf(stderr,"-- exec:%s\n" ,buf); |
480 | error = system(buf); |
481 | if( error){ |
482 | fprintf(stderr, "To finish the atlas make sure gnuplot is available and run:\n" ); |
483 | for (j=i; j< atlaspage; j++) |
484 | fprintf(stderr, "gnuplot %s_%02d.gpl\n" , basefile, j); |
485 | } |
486 | } |
487 | |
488 | snprintf(buf, BUFSIZ, "%s_%02d.pdf " , basefile, i); |
489 | plen += snprintf(pages + plen, BSIZE -plen,"%s" ,buf); |
490 | if ( plen >= BSIZE-1){ |
491 | error = -1; |
492 | break; |
493 | } |
494 | } |
495 | |
496 | |
497 | if( error == 0) { |
498 | snprintf(buf, BSIZE, "gs -q -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=%s.pdf -dBATCH %s" ,basefile,pages); |
499 | fprintf(stderr,"-- exec:%s\n" ,buf); |
500 | error = system(buf); |
501 | } |
502 | if( error == 0) |
503 | fprintf(stderr,"-- done: %s.pdf\n" , basefile); |
504 | else |
505 | fprintf(stderr, "gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=%s.pdf -dBATCH %s\n" , basefile, pages); |
506 | } |
507 | |
508 | if(dbh) |
509 | mapi_disconnect(dbh); |
510 | exit(0); |
511 | } |
512 | |
513 | typedef struct BOX { |
514 | int row; |
515 | int color; |
516 | int thread; |
517 | int64_t clkstart, clkend; |
518 | int64_t ticks; |
519 | int64_t memstart, memend; |
520 | int64_t , tmpspace; |
521 | int64_t inblock, oublock; |
522 | int64_t majflt, nswap, csw; |
523 | char *stmt; |
524 | char *fcn; |
525 | char *numa; |
526 | int state; |
527 | } Box; |
528 | |
529 | int threads[MAXTHREADS]; |
530 | int64_t lastclk[MAXTHREADS]; |
531 | Box *box= 0; |
532 | int topbox = 0; |
533 | int maxbox = 0; |
534 | int events = 0; |
535 | |
536 | int64_t totalclkticks = 0; /* number of clock ticks reported */ |
537 | int64_t totalexecticks = 0; /* number of ticks reported for processing */ |
538 | int64_t lastclktick = 0; |
539 | int64_t totalticks = 0; |
540 | int64_t starttime = 0; |
541 | int figures = 0; |
542 | char *currentfunction= 0; |
543 | int object = 1; |
544 | |
545 | static void resetTomograph(void){ |
546 | static char buf[BUFSIZ]; |
547 | int i; |
548 | |
549 | snprintf(buf,BUFSIZ,"%s_%02d.trace" , basefile, atlaspage); |
550 | |
551 | if( inputfile && strcmp(inputfile,buf) == 0 ){ |
552 | fprintf(stderr,"Should not overwrite existing trace file '%s'\n" ,buf); |
553 | exit(-1); |
554 | } |
555 | if( inputfile == 0 ){ |
556 | // don't create another tracefile when input is given |
557 | tracefd = fopen(buf,"w" ); |
558 | if( tracefd == NULL){ |
559 | fprintf(stderr,"Could not create trace file '%s'\n" ,buf); |
560 | exit(-1); |
561 | } |
562 | } |
563 | if (debug) |
564 | fprintf(stderr, "RESET tomograph %d\n" , atlaspage); |
565 | for( i =0; i < NUM_COLORS; i++){ |
566 | colors[i].freq = 0; |
567 | colors[i].timeused = 0; |
568 | } |
569 | for(i=0; i< MAXTHREADS; i++) |
570 | lastclk[i]=0; |
571 | topbox =0; |
572 | events = 0; |
573 | for (i = 0; i < MAXTHREADS; i++) |
574 | threads[i] = topbox++; |
575 | for ( i=MAXTHREADS; i< maxbox; i++){ |
576 | if( box[i].fcn ){ |
577 | free(box[i].fcn); |
578 | box[i].fcn=0; |
579 | } |
580 | } |
581 | memset((char*) box, 0, sizeof(Box) * maxbox); |
582 | |
583 | totalclkticks = 0; |
584 | totalexecticks = 0; |
585 | lastclktick = 0; |
586 | figures = 0; |
587 | currentfunction = 0; |
588 | currentquery = 0; |
589 | object = 1; |
590 | } |
591 | |
592 | static int64_t |
593 | gnuXtics(int withlabels) |
594 | { |
595 | const char * scalename = "MB" ; |
596 | int digits; |
597 | int64_t scale =1, tw, w = lastclktick - starttime; |
598 | int i; |
599 | |
600 | if (w >= 10 * US_DD) { |
601 | scale = US_DD; |
602 | scalename = "d\0\0days" ; |
603 | } else if (w >= 10 * US_HH) { |
604 | scale = US_HH; |
605 | scalename = "h\0\0hours" ; |
606 | } else if (w >= 10 * US_MM) { |
607 | scale = US_MM; |
608 | scalename = "m\0\0minutes" ; |
609 | } else if (w >= US_SS) { |
610 | scale = US_SS; |
611 | scalename = "s\0\0seconds" ; |
612 | } else if (w >= US_MS) { |
613 | scale = US_MS; |
614 | scalename = "ms\0milliseconds" ; |
615 | } else { |
616 | scale = 1; |
617 | scalename = "us\0microseconds" ; |
618 | } |
619 | for (tw = (scale / 10 > 1)? scale/10:1; 15 * tw < w; tw *= 10) |
620 | ; |
621 | if (3 * tw > w) |
622 | tw /= 4; |
623 | else if (6 * tw > w) |
624 | tw /= 2; |
625 | if (w / scale >= 1000) |
626 | digits = 0; |
627 | else if (w / scale >= 100) |
628 | digits = 1; |
629 | else if (w / scale >= 10) |
630 | digits = 2; |
631 | else |
632 | digits = 3; |
633 | |
634 | if(withlabels) |
635 | fprintf(gnudata, "set xtics (\"0\" 0.0," ); |
636 | else |
637 | fprintf(gnudata, "set xtics ( \"\" 0.0," ); |
638 | for (i = 1; i * tw < w - 2 * tw / 3; i++){ |
639 | if( withlabels) |
640 | fprintf(gnudata, "\"%g\" %" PRId64".0," , ((double) i) * tw / scale, i * tw); |
641 | else |
642 | fprintf(gnudata, "\"\" %" PRId64".0," , i * tw); |
643 | } |
644 | if( withlabels) |
645 | fprintf(gnudata, "\"%.*f %s\" %" PRId64".0" , digits, ((double) w) / scale, scalename, w); |
646 | else |
647 | fprintf(gnudata, "\"\" %" PRId64".0" , w); |
648 | fprintf(gnudata, ")\n" ); |
649 | w /= 10; |
650 | fprintf(gnudata, "set grid xtics\n" ); |
651 | return w; |
652 | } |
653 | |
654 | static void dumpbox(int i) |
655 | { |
656 | fprintf(stderr,"object %d thread %d[%4d] row %d color %d " , object, box[i].thread,i, box[i].row, box[i].color); |
657 | if (box[i].fcn) |
658 | fprintf(stderr,"%s " , box[i].fcn); |
659 | fprintf(stderr,"clk %" PRId64" - %" PRId64" " , box[i].clkstart, box[i].clkend); |
660 | fprintf(stderr,"mem %" PRId64" - %" PRId64" " , box[i].memstart, box[i].memend); |
661 | fprintf(stderr,"foot %" PRId64" - %" PRId64" " , box[i].footstart, box[i].tmpspace); |
662 | fprintf(stderr,"ticks %" PRId64" " , box[i].ticks); |
663 | if (box[i].stmt) |
664 | fprintf(stderr,"\"%s\"" , box[i].stmt); |
665 | else |
666 | fprintf(stderr,"MISSING" ); |
667 | fprintf(stderr,"\n" ); |
668 | } |
669 | |
670 | static int |
671 | cmp_clr(const void * _one , const void * _two) |
672 | { |
673 | Color *one = (Color*) _one, *two = (Color*) _two; |
674 | |
675 | /* "*.*" should always be smallest / first */ |
676 | if (one->mod && one->fcn && |
677 | one->mod[0] == '*' && one->mod[1] == '\0' && |
678 | one->fcn[0] == '*' && one->fcn[1] == '\0') |
679 | return -1; |
680 | if (two->mod && two->fcn && |
681 | two->mod[0] == '*' && two->mod[1] == '\0' && |
682 | two->fcn[0] == '*' && two->fcn[1] == '\0') |
683 | return 1; |
684 | |
685 | /* order on timeused; use freq as tiebreaker */ |
686 | return ((one->timeused < two->timeused) ? -1 : |
687 | ((one->timeused > two->timeused) ? 1 : |
688 | ((one->freq < two->freq) ? -1 : |
689 | ((one->freq > two->freq) ? 1 : |
690 | 0)))); |
691 | } |
692 | |
693 | static void |
694 | initcolors(void) |
695 | { |
696 | int i = 0; |
697 | |
698 | for (i = 0; i < NUM_COLORS && base_colors[i].mod; i++) { |
699 | colors[i].mod = base_colors[i].mod; |
700 | colors[i].fcn = base_colors[i].fcn; |
701 | colors[i].freq = 0; |
702 | colors[i].timeused = 0; |
703 | colors[i].col = dictionary[i].hsv; |
704 | } |
705 | for (; i < NUM_COLORS; i++) { |
706 | colors[i].mod = 0; |
707 | colors[i].fcn = 0; |
708 | colors[i].freq = 0; |
709 | colors[i].timeused = 0; |
710 | colors[i].col = dictionary[i].hsv; |
711 | } |
712 | } |
713 | |
714 | static void |
715 | dumpboxes(void) |
716 | { |
717 | FILE *f = 0; |
718 | char buf[BUFSIZ]; |
719 | int i; |
720 | int written = 0; |
721 | |
722 | snprintf(buf, BUFSIZ, "%s_%02d.dat" , basefile,atlaspage); |
723 | f = fopen(buf, "w" ); |
724 | if(f == NULL){ |
725 | fprintf(stderr,"Could not create file '%s'\n" ,buf); |
726 | exit(-1); |
727 | } |
728 | |
729 | for (i = 0; i < topbox; i++) |
730 | if (box[i].clkend && box[i].fcn) { |
731 | fprintf(f, "%" PRId64" %f %f %" PRId64" %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64"\n" , box[i].clkstart, (box[i].memend / 1024.0), box[i].tmpspace/1024.0, box[i].inblock, box[i].oublock, box[i].majflt, box[i].nswap,box[i].csw); |
732 | written++; |
733 | } |
734 | if( written == 0){ |
735 | topbox = 0; |
736 | fprintf(stderr,"Insufficient data for %s\n" ,buf); |
737 | } |
738 | |
739 | if (f) |
740 | (void) fclose(f); |
741 | } |
742 | |
743 | /* produce memory thread trace */ |
744 | static void |
745 | showmemory(void) |
746 | { |
747 | int i; |
748 | int64_t max = 0, min = INT64_MAX; |
749 | double mx, mn, mm; |
750 | double scale = 1.0; |
751 | const char * scalename = "MB" ; |
752 | int digits; |
753 | |
754 | for (i = 0; i < topbox; i++) |
755 | if (box[i].clkend && box[i].fcn) { |
756 | if (box[i].memstart > max) |
757 | max = box[i].memstart; |
758 | if (box[i].memend > max) |
759 | max = box[i].memend; |
760 | if (box[i].memstart < min) |
761 | min = box[i].memstart; |
762 | if (box[i].memend < min) |
763 | min = box[i].memend; |
764 | } |
765 | if (min == max) { |
766 | min -= 1; |
767 | max += 1; |
768 | } |
769 | |
770 | if (max >= 1024) { |
771 | scale = 1024.0; |
772 | scalename = "GB" ; |
773 | } |
774 | if (max / scale >= 100) |
775 | digits = 0; |
776 | else if (max / scale >= 10) |
777 | digits = 1; |
778 | else |
779 | digits = 2; |
780 | |
781 | fprintf(gnudata, "\nset tmarg 1\n" ); |
782 | fprintf(gnudata, "set bmarg 1\n" ); |
783 | fprintf(gnudata, "set lmarg 10\n" ); |
784 | fprintf(gnudata, "set rmarg 10\n" ); |
785 | fprintf(gnudata, "set size 1,%s\n" , "0.1" ); |
786 | fprintf(gnudata, "set origin 0.0,0.87\n" ); |
787 | |
788 | fprintf(gnudata, "set xrange [%" PRId64".0:%" PRId64".0]\n" , startrange, lastclktick - starttime); |
789 | fprintf(gnudata, "set ylabel \"memory in %s\"\n" , scalename); |
790 | fprintf(gnudata, "unset xtics\n" ); |
791 | fprintf(gnudata, "set border\n" ); |
792 | gnuXtics(0); |
793 | mn = min / 1024.0; |
794 | mx = max / 1024.0; |
795 | mm = (mx - mn) / 50.0; /* 2% top & bottom margin */ |
796 | fprintf(gnudata, "set yrange [%f:%f]\n" , mn - mm, mx + mm); |
797 | fprintf(gnudata, "set ytics (\"%.*f\" %f, \"%.*f\" %f) nomirror\n" , digits, min / scale, mn, digits, max / scale, mx); |
798 | fprintf(gnudata, "plot \"%s_%02d.dat\" using 1:2 notitle with dots linecolor rgb \"blue\"\n" , basefile, atlaspage); |
799 | fprintf(gnudata, "unset yrange\n" ); |
800 | } |
801 | |
802 | static char * |
803 | getHeatColor(double load) |
804 | { |
805 | if ( load > 0.9 ) return "red" ; |
806 | if ( load > 0.75 ) return "orangered" ; |
807 | if ( load > 0.5 ) return "orange" ; |
808 | if ( load > 0.25 ) return "gold" ; |
809 | if ( load > 0.02 ) return "yellow" ; |
810 | return "white" ; |
811 | } |
812 | |
813 | /* produce memory thread trace */ |
814 | static void |
815 | showcpu(void) |
816 | { |
817 | int prev= -1, i, j = 0; |
818 | double cpuload[MAXTHREADS]; |
819 | char *s; |
820 | |
821 | for (i = 0; i < MAXTHREADS; i++) |
822 | cpuload[i] = 0; |
823 | fprintf(gnudata, "\nset tmarg 1\n" ); |
824 | fprintf(gnudata, "set bmarg 0\n" ); |
825 | fprintf(gnudata, "set lmarg 10\n" ); |
826 | fprintf(gnudata, "set rmarg 10\n" ); |
827 | fprintf(gnudata, "set size 1,0.084\n" ); |
828 | fprintf(gnudata, "set origin 0.0, 0.8\n" ); |
829 | fprintf(gnudata, "set ylabel \"%d cores\"\n" ,cpus); |
830 | fprintf(gnudata, "unset xtics\n" ); |
831 | fprintf(gnudata, "unset ytics\n" ); |
832 | fprintf(gnudata, "set ytics 0, %d\n" ,cpus <48?(cpus <=8 ?4:8):12); |
833 | fprintf(gnudata, "set grid ytics\n" ); |
834 | |
835 | fprintf(gnudata, "set border\n" ); |
836 | gnuXtics(0); |
837 | |
838 | fprintf(gnudata, "set xrange [%" PRId64".0:%" PRId64".0]\n" , startrange, (endrange? endrange:lastclktick - starttime)); |
839 | fprintf(gnudata, "set yrange [0:%d]\n" , cpus); |
840 | for (i = 0; i < topbox; i++) |
841 | j+=(box[i].state == MDB_PING); |
842 | if( debug) |
843 | fprintf(stderr,"Pings for cpu heat:%d\n" ,j); |
844 | for (i = 0; i < topbox; i++) |
845 | if (box[i].state == MDB_PING) { |
846 | // decode the cpu heat |
847 | j = 0; |
848 | s = box[i].stmt +1; |
849 | while (s) { |
850 | s = strchr(s + 1, ' '); |
851 | while (s && isspace((unsigned char) *s)) |
852 | s++; |
853 | if( s){ |
854 | cpuload[j++] = atof(s); |
855 | } |
856 | } |
857 | // paint the heatmap, the load refers the previous time slot |
858 | if( prev >= 0) |
859 | for(j=0; j < cpus; j++) |
860 | fprintf(gnudata,"set object %d rectangle from %" PRId64".0, %d.0 to %" PRId64".0, %d fillcolor rgb \"%s\" fillstyle solid 1.0 noborder\n" , |
861 | object++, box[prev].clkend, j , box[i].clkstart, (j+1) , getHeatColor(cpuload[j]) ); |
862 | prev = i; |
863 | } |
864 | if( cpus) |
865 | fprintf(gnudata," plot 0 notitle with lines\n unset for[i=1:%d] object i\n" ,object); |
866 | fprintf(gnudata, "set border\n" ); |
867 | fprintf(gnudata, "unset yrange\n" ); |
868 | fprintf(gnudata, "unset ytics\n" ); |
869 | fprintf(gnudata, "unset grid\n" ); |
870 | } |
871 | |
872 | /* produce memory thread trace */ |
873 | /* The IO statistics are not provided anymore in LINUX unless you have root permissions */ |
874 | static void |
875 | showio(void) |
876 | { |
877 | int i,b = (beat? beat:1); |
878 | int64_t max = 0; |
879 | char *c, ch; |
880 | |
881 | for (i = 0; i < topbox; i++) |
882 | if (box[i].clkend && box[i].state >= MDB_PING) { |
883 | if (box[i].inblock > max) |
884 | max = box[i].inblock; |
885 | if (box[i].oublock > max) |
886 | max = box[i].oublock; |
887 | //if (box[i].majflt > max) |
888 | //max = box[i].majflt; |
889 | //if (box[i].nswap > max) |
890 | //max = box[i].nswap; |
891 | } |
892 | max += b; |
893 | |
894 | |
895 | fprintf(gnudata, "\nset tmarg 1\n" ); |
896 | fprintf(gnudata, "set bmarg 1\n" ); |
897 | fprintf(gnudata, "set lmarg 10\n" ); |
898 | fprintf(gnudata, "set rmarg 10\n" ); |
899 | fprintf(gnudata, "set size 1,%s\n" , "0.1" ); |
900 | fprintf(gnudata, "set origin 0.0,0.87\n" ); |
901 | fprintf(gnudata, "set xrange [%" PRId64".0:%" PRId64".0]\n" , startrange, lastclktick - starttime); |
902 | fprintf(gnudata, "set yrange [0:%" PRId64".0]\n" , max / b); |
903 | fprintf(gnudata, "unset xtics\n" ); |
904 | fprintf(gnudata, "unset ytics\n" ); |
905 | fprintf(gnudata, "unset ylabel\n" ); |
906 | fprintf(gnudata, "set y2tics in (0, %" PRId64".0) nomirror\n" , max / b); |
907 | fprintf(gnudata, "set y2label \"in/oublock\"\n" ); |
908 | fprintf(gnudata, "set key font \",8\"\n" ); |
909 | fprintf(gnudata, "set key bottom right horizontal\n" ); |
910 | if( title){ |
911 | for (c = title; c && *c && i <100; c++, i++) |
912 | if (*c == '_')// for gnuplot |
913 | *c = '-'; |
914 | ch= *c; *c =0; |
915 | fprintf(gnudata, "set title \"%s%s\"\n" , title, (*c? "..." :"" )); |
916 | *c =ch; |
917 | } else |
918 | if( dbname) |
919 | fprintf(gnudata, "set title \"Database %s\"\n" , dbname); |
920 | #ifdef GNUPLOT_463_BUG_ON_FEDORA_20 |
921 | /* this is the original version, but on Fedora 20 with |
922 | * gnuplot-4.6.3-6.fc20.x86_64 it produces a red background on most of |
923 | * the page */ |
924 | fprintf(gnudata, "plot \"%s_%02d.dat\" using 1:($4/%d.0) notitle with dots fs solid linecolor rgb \"gray\" ,\\\n" , basefile, atlaspage, b); |
925 | fprintf(gnudata, "\"%s_%02d.dat\" using ($1+4):($5/%d.0) notitle with dots solid linecolor rgb \"red\"\n" , basefile, atlaspage, b); |
926 | //fprintf(gnudata, "\"%s_%02d.dat\" using ($1+8):($6/%d.0) notitle with dots linecolor rgb \"green\", \\\n", basefile, atlaspage, b); |
927 | //fprintf(gnudata, "\"%s_%02d.dat\" using ($1+12):($7/%d.0) notitle with dots linecolor rgb \"purple\"\n", basefile, atlaspage, b); |
928 | #else |
929 | /* this is a slightly modified version that produces decent results on |
930 | * all platforms */ |
931 | fprintf(gnudata, "plot \"%s_%02d.dat\" using 1:($4/%d.0) notitle with dots linecolor rgb \"gray\" ,\\\n" , basefile, atlaspage, b); |
932 | fprintf(gnudata, "\"%s_%02d.dat\" using ($1+4):($5/%d.0) notitle with dots linecolor rgb \"red\"\n" , basefile, atlaspage, b); |
933 | //fprintf(gnudata, "\"%s_%02d.dat\" using ($1+8):($6/%d.0) notitle with dots linecolor rgb \"green\", \\\n", basefile, atlaspage, b); |
934 | //fprintf(gnudata, "\"%s_%02d.dat\" using ($1+12):($7/%d.0) notitle with dots linecolor rgb \"purple\"\n", basefile, atlaspage, b); |
935 | #endif |
936 | fprintf(gnudata, "unset y2label\n" ); |
937 | fprintf(gnudata, "unset y2tics\n" ); |
938 | fprintf(gnudata, "unset y2range\n" ); |
939 | fprintf(gnudata, "unset title\n" ); |
940 | } |
941 | |
942 | |
943 | /* print time (given in microseconds) in human-readable form |
944 | * showing the highest two relevant units */ |
945 | static void |
946 | fprintf_time(FILE *f, int64_t time) |
947 | { |
948 | if (time >= US_DD) |
949 | fprintf(f, "%" PRId64 " d %02d h " , time / US_DD, |
950 | (int) ((time % US_DD) / US_HH)); |
951 | else if (time >= US_HH) |
952 | fprintf(f, "%d h %02d m " , (int) (time / US_HH), |
953 | (int) ((time % US_HH) / US_MM)); |
954 | else if (time >= US_MM) |
955 | fprintf(f, "%d m %02d s " , (int) (time / US_MM), |
956 | (int) ((time % US_MM) / US_SS)); |
957 | else if (time >= US_SS) |
958 | fprintf(f, "%d.%03d s " , (int) (time / US_SS), |
959 | (int) ((time % US_SS) / US_MS)); |
960 | else if (time >= US_MS) |
961 | fprintf(f, "%d.%03d ms " , (int) (time / US_MS), |
962 | (int) ((time % US_MS))); |
963 | else |
964 | fprintf(f, "%d us " , (int) time); |
965 | } |
966 | |
967 | /* produce a legenda image for the color map */ |
968 | #define COLUMNS 3 |
969 | |
970 | static void |
971 | showcolormap(char *filename, int all) |
972 | { |
973 | FILE *f = 0; |
974 | char buf[BUFSIZ]; |
975 | int i, k = 0,nl; |
976 | int w = 380; // 600 for 3 columns |
977 | int h = 590, h1=h; |
978 | int64_t totfreq = 0, tottime = 0; |
979 | Color *clrs = colors, *_clrs_ = NULL; |
980 | char *c; |
981 | time_t tm; |
982 | char *date; |
983 | int64_t longest = lastclktick > starttime? lastclktick - starttime: 0; |
984 | double perc; |
985 | |
986 | tm = time(0); |
987 | date = ctime(&tm); |
988 | if (strchr(date, '\n')) |
989 | *strchr(date, '\n') = 0; |
990 | |
991 | |
992 | // count size of query text |
993 | for (nl=0, c= currentquery; c && *c; c++) |
994 | nl += *c == '\n'; |
995 | |
996 | snprintf(buf, BUFSIZ, "%s_%02d.gpl" , basefile, atlaspage); |
997 | if (all) { |
998 | f = fopen(buf, "w" ); |
999 | if (f == NULL) { |
1000 | fprintf(stderr, "Could not create file '%s'\n" , buf); |
1001 | exit(-1); |
1002 | } |
1003 | fprintf(f, "set terminal pdfcairo noenhanced color solid size 8.3, 11.7\n" ); |
1004 | fprintf(f, "set output \"%s.pdf\"\n" , filename); |
1005 | fprintf(f, "set size 1,1\n" ); |
1006 | fprintf(f, "set xrange [0:1800]\n" ); |
1007 | fprintf(f, "set yrange [0:600]\n" ); |
1008 | fprintf(f, "unset xtics\n" ); |
1009 | fprintf(f, "unset ytics\n" ); |
1010 | fprintf(f, "unset colorbox\n" ); |
1011 | fprintf(f, "unset border\n" ); |
1012 | fprintf(f, "unset title\n" ); |
1013 | fprintf(f, "unset ylabel\n" ); |
1014 | fprintf(f, "unset xlabel\n" ); |
1015 | fprintf(f, "set origin 0.0,0.0\n" ); |
1016 | } else { |
1017 | f = gnudata; |
1018 | fprintf(f, "\nset tmarg 0\n" ); |
1019 | fprintf(f, "set bmarg 0\n" ); |
1020 | fprintf(f, "set lmarg 10\n" ); |
1021 | fprintf(f, "set rmarg 10\n" ); |
1022 | fprintf(f, "set size 1,0.4\n" ); |
1023 | fprintf(f, "set origin 0.0,%s\n" , "-0.04" ); |
1024 | fprintf(f, "set xrange [0:1800]\n" ); |
1025 | fprintf(f, "set yrange [0:600]\n" ); |
1026 | fprintf(f, "unset xtics\n" ); |
1027 | fprintf(f, "unset ytics\n" ); |
1028 | fprintf(f, "unset colorbox\n" ); |
1029 | fprintf(f, "unset border\n" ); |
1030 | fprintf(f, "unset title\n" ); |
1031 | fprintf(f, "unset ylabel\n" ); |
1032 | } |
1033 | /* create copy of colormap and sort in ascending order of timeused; |
1034 | * "*.*" stays first (colors[0]) */ |
1035 | _clrs_ = (Color*) malloc(sizeof(colors)); |
1036 | if (_clrs_) { |
1037 | memcpy(_clrs_, colors, sizeof(colors)); |
1038 | qsort(_clrs_, NUM_COLORS, sizeof(Color), cmp_clr); |
1039 | clrs = _clrs_; |
1040 | } |
1041 | /* show colormap / legend in descending order of timeused; |
1042 | * show max. the MAX_LEGEND_SHORT-1 most expensive function calls; |
1043 | * show all remaining aggregated as "*.*" */ |
1044 | for (i = NUM_COLORS - 1; i >= 0; i--) |
1045 | if (clrs[i].mod && (clrs[i].freq > 0 || all)) { |
1046 | if (all || k < MAX_LEGEND_SHORT - 1 || i == 0) { |
1047 | tottime += clrs[i].timeused; |
1048 | totfreq += clrs[i].freq; |
1049 | |
1050 | if (k % COLUMNS == 0 && i) |
1051 | h -= 35; |
1052 | fprintf(f, "set object %d rectangle from %.2f, %.2f to %.2f, %.2f fillcolor rgb \"%s\" fillstyle solid 1.0\n" , |
1053 | object++, (double) (k % COLUMNS) * w, (double) h - 40, (double) ((k % COLUMNS) * w + 0.09 * w), (double) h - 15, clrs[i].col); |
1054 | fprintf(f, "set label %d \"%s.%s \" at %d,%d\n" , |
1055 | object++, (clrs[i].mod?clrs[i].mod:"" ), clrs[i].fcn, (int) ((k % COLUMNS) * w + 0.1 * w), h - 20); |
1056 | fprintf(f, "set label %d \"%d call%s: " , object++, clrs[i].freq, clrs[i].freq>1?"s" :"" ); |
1057 | fprintf_time(f, clrs[i].timeused); |
1058 | fprintf(f, "\" at %f,%f\n" , (double) ((k % COLUMNS) * w + 0.1 * w), (double) h - 35); |
1059 | k++; |
1060 | } else { |
1061 | clrs[0].timeused += clrs[i].timeused; |
1062 | clrs[0].freq += clrs[i].freq; |
1063 | } |
1064 | } |
1065 | if (_clrs_) { |
1066 | clrs = colors; |
1067 | free(_clrs_); |
1068 | _clrs_ = NULL; |
1069 | } |
1070 | |
1071 | h -= 30; |
1072 | fprintf(f, "set label %d \"MAL instructions executed: %" PRId64, object++, totfreq); |
1073 | fprintf(f, "\" at 0.0,120\n" ); |
1074 | |
1075 | fprintf(f, "set label %d \"Total CPU core time: " , object++); |
1076 | fprintf_time(f, tottime); |
1077 | fprintf(f, "\" at 750.0,120.0\n" ); |
1078 | |
1079 | perc = (totalclkticks-longest) / ((cpus * longest) / 100.0); |
1080 | perc = perc <0?0.0:perc; |
1081 | fprintf(f, "set label %d \"Parallelism %.2f %%" , object++, perc>100.0 ? 100.0:perc); |
1082 | fprintf(f, "\" at 1550.0,120.0\n" ); |
1083 | // show complete query text |
1084 | if( currentquery ){ |
1085 | h = h1-40; |
1086 | //fprintf(gnudata, "set object %d rectangle from %d.0, 250.0 to %d.0,%d.0\n", object++, 3 * w, 5 *w , h -5); |
1087 | fprintf(f, "set label %d \"" , object++); |
1088 | k=0; |
1089 | for (c= currentquery; *c; c++){ |
1090 | if (*c == '"') fprintf(f,"\\" ); else |
1091 | if (*c == '\t') fprintf(f," " ); else |
1092 | if (*c == '\n') { fprintf(f,"\\n" ); k=0; continue;} else |
1093 | if( ++k >60 && (*c == ' ' || *c =='\t')){ |
1094 | fprintf(f,"\\n" ); |
1095 | k = 1; |
1096 | } |
1097 | fputc(*c,f); |
1098 | } |
1099 | fprintf(f, "\" at %d,%d\n" , (int) ( 3 * w ), h - 17); |
1100 | h-= 17; |
1101 | } |
1102 | fprintf(f, "set label %d \"%d\" at 1750.0, 100.00\n" , object++, atlaspage + 1); |
1103 | fprintf(f, "set label %d \"%s\" at 750.0, 100.00\n" , object++, buf); |
1104 | fprintf(f, "set label %d \"%s\" at 0.0, 100.00\n" , object++, date); |
1105 | fprintf(f, "plot 0 notitle with lines linecolor rgb \"white\"\n" ); |
1106 | if (all) { |
1107 | fclose(f); |
1108 | } |
1109 | } |
1110 | |
1111 | static void |
1112 | updatecolormap(int idx) |
1113 | { |
1114 | char *mod, *fcn, buf[BUFSIZ], *call = buf; |
1115 | int i, fnd = 0; |
1116 | |
1117 | if( box[idx].fcn == 0) |
1118 | return; |
1119 | |
1120 | snprintf(buf, sizeof(buf), "%s" , box[idx].fcn); |
1121 | mod = call; |
1122 | fcn = strchr(call, '.'); |
1123 | if (fcn) { |
1124 | *fcn = 0; |
1125 | fcn++; |
1126 | } else{ |
1127 | fcn = "*" ; |
1128 | } |
1129 | if( strncmp(call,"end" ,3) == 0) |
1130 | mod ="*" ; |
1131 | /* find "mod.fcn" */ |
1132 | for (i = 1; i < NUM_COLORS && colors[i].mod; i++) |
1133 | if (strcmp(mod, colors[i].mod) == 0 && |
1134 | strcmp(fcn, colors[i].fcn) == 0) { |
1135 | fnd = i; |
1136 | break; |
1137 | } |
1138 | if (fnd == 0 && i < NUM_COLORS) { |
1139 | /* not found, but still free slot: add new one */ |
1140 | fnd = i; |
1141 | colors[fnd].mod = strdup(mod); |
1142 | colors[fnd].fcn = strdup(fcn); |
1143 | if( debug) |
1144 | fprintf(stderr,"-- Added function #%d: %s.%s\n" , fnd, mod, fcn); |
1145 | } |
1146 | |
1147 | colors[fnd].freq++; |
1148 | colors[fnd].timeused += box[idx].clkend - box[idx].clkstart; |
1149 | box[idx].color = fnd; |
1150 | } |
1151 | |
1152 | /* gnuplot defaults */ |
1153 | static int height = 160; |
1154 | |
1155 | #define LOGOFILE DATA_DIR "/doc/MonetDB/monetdblogo.png" |
1156 | |
1157 | static char * |
1158 | findlogo(void) |
1159 | { |
1160 | #ifdef _MSC_VER |
1161 | /* on Windows, convert \ to / path separators since this path |
1162 | * is added to gnuplot input */ |
1163 | static char buf[sizeof(LOGOFILE)]; |
1164 | int i; |
1165 | |
1166 | snprintf(buf, sizeof(buf), "%s" , LOGOFILE); |
1167 | for (i = 0; buf[i]; i++) |
1168 | if (buf[i] == '\\') |
1169 | buf[i] = '/'; |
1170 | return buf; |
1171 | #else |
1172 | return LOGOFILE; |
1173 | #endif |
1174 | } |
1175 | |
1176 | static void |
1177 | (char *filename) |
1178 | { |
1179 | fprintf(gnudata, "set terminal pdfcairo noenhanced font 'verdana,10' color solid size 8.3,11.7\n" ); |
1180 | fprintf(gnudata, "set output \"%s.pdf\"\n" , filename); |
1181 | fprintf(gnudata, "set size 1,1\n" ); |
1182 | fprintf(gnudata, "set tics front\n" ); |
1183 | fprintf(gnudata, "set multiplot\n" ); |
1184 | // try to inject the MonetDB logo and documentation |
1185 | fprintf(gnudata,"set tmarg 1\n" ); |
1186 | fprintf(gnudata,"set bmarg 1\n" ); |
1187 | fprintf(gnudata,"set lmarg 10\n" ); |
1188 | fprintf(gnudata,"set rmarg 10\n" ); |
1189 | fprintf(gnudata,"set size 0.450,0.11\n" ); |
1190 | fprintf(gnudata,"set origin 0.0,0.945\n" ); |
1191 | fprintf(gnudata,"set xrange [0.0:1125.0]\n" ); |
1192 | fprintf(gnudata,"set yrange [0:581.0]\n" ); |
1193 | fprintf(gnudata,"unset border\n" ); |
1194 | fprintf(gnudata,"unset xtics\n" ); |
1195 | fprintf(gnudata,"unset ytics\n" ); |
1196 | fprintf(gnudata,"plot \"%s\" binary filetype=png dx=0.5 dy=0.5 notitle with rgbimage\n" , findlogo()); |
1197 | fprintf(gnudata,"unset title\n" ); |
1198 | |
1199 | } |
1200 | |
1201 | static void |
1202 | createTomogram(void) |
1203 | { |
1204 | char buf[BUFSIZ]; |
1205 | int rows[MAXTHREADS] = {0}; |
1206 | int top = 0, rowoffset = 0; |
1207 | int i, j; |
1208 | int h, prevobject = 1; |
1209 | int64_t w = lastclktick - starttime; |
1210 | |
1211 | if( debug) |
1212 | fprintf(stderr,"create tomogram\n" ); |
1213 | if( events == 0){ |
1214 | if( debug) |
1215 | fprintf(stderr,"No further events found\n" ); |
1216 | return; |
1217 | } |
1218 | snprintf(buf, BUFSIZ, "%s_%02d.gpl" , basefile, atlaspage); |
1219 | gnudata = fopen(buf, "w" ); |
1220 | if (gnudata == 0) { |
1221 | printf("Could not create file '%s'\n" , buf); |
1222 | exit(-1); |
1223 | } |
1224 | if( strrchr(buf,'.')) |
1225 | *strrchr(buf, '.') = 0; |
1226 | gnuplotheader(buf); |
1227 | object=1; |
1228 | dumpboxes(); |
1229 | showio(); //DISABLED due to access permissions |
1230 | showmemory(); |
1231 | showcpu(); |
1232 | |
1233 | fprintf(gnudata, "\nset tmarg 1\n" ); |
1234 | fprintf(gnudata, "set bmarg 3\n" ); |
1235 | fprintf(gnudata, "set lmarg 10\n" ); |
1236 | fprintf(gnudata, "set rmarg 10\n" ); |
1237 | fprintf(gnudata, "set size 1,0.48\n" ); |
1238 | fprintf(gnudata, "set origin 0.0,%s\n" , "0.33" ); |
1239 | fprintf(gnudata, "set xrange [%" PRId64".0:%" PRId64".0]\n" , startrange, lastclktick - starttime); |
1240 | |
1241 | /* detect all different threads and assign them a row */ |
1242 | for (i = 0; i < topbox; i++){ |
1243 | if (box[i].clkend && box[i].state != MDB_PING) { |
1244 | for (j = 0; j < top; j++) |
1245 | if (rows[j] == box[i].thread) |
1246 | break; |
1247 | box[i].row = j; |
1248 | if (j == top){ |
1249 | if( debug) |
1250 | fprintf(stderr,"Assign thread %d to %d\n" , box[i].thread, top); |
1251 | rows[top++] = box[i].thread; |
1252 | } |
1253 | if( box[i].state != MDB_WAIT) |
1254 | updatecolormap(i); |
1255 | } |
1256 | } |
1257 | |
1258 | |
1259 | h = 10; /* unit height of bars */ |
1260 | height = (cpus+1) * 2 * h; |
1261 | fprintf(gnudata, "set yrange [0:%d]\n" , height); |
1262 | fprintf(gnudata, "set ylabel \"worker threads\"\n" ); |
1263 | fprintf(gnudata, "set key right\n" ); |
1264 | fprintf(gnudata, "unset colorbox\n" ); |
1265 | fprintf(gnudata, "unset title\n" ); |
1266 | |
1267 | w = gnuXtics(1); |
1268 | |
1269 | /* calculate the effective use of parallelism */ |
1270 | totalticks = 0; |
1271 | for (i = 0; i < top; i++) |
1272 | totalticks += lastclk[rows[i]]; |
1273 | |
1274 | /* fill the page from top to bottom */ |
1275 | if( top <= cpus +1){ |
1276 | rowoffset = cpus+1 - top; |
1277 | if ( top <= cpus/2+1){ |
1278 | h *= 2; |
1279 | rowoffset = (cpus+2)/2 - top; |
1280 | } |
1281 | |
1282 | } |
1283 | |
1284 | fprintf(gnudata, "set ytics (" ); |
1285 | for (i = 0; i < top; i++) |
1286 | fprintf(gnudata, "\"%d\" %d%c" , rows[i], (rowoffset + i) * 2 * h + h / 2, (i < top - 1 ? ',' : ' ')); |
1287 | fprintf(gnudata, ")\n" ); |
1288 | |
1289 | /* mark duration of each thread */ |
1290 | for (i = 0; i < top; i++) |
1291 | fprintf(gnudata, "set object %d rectangle from %d, %d to %" PRId64".0, %d\n" , |
1292 | object++, 0, (rowoffset +i) * 2 * h + h/3, lastclk[rows[i]], (rowoffset +i) * 2 * h + h - h/3); |
1293 | |
1294 | /* fill the duration of each instruction encountered that fit our range constraint */ |
1295 | for (i = 0; i < topbox; i++) |
1296 | if (box[i].clkend) |
1297 | switch (box[i].state) { |
1298 | default: |
1299 | if (debug) |
1300 | dumpbox(i); |
1301 | // always show a start line |
1302 | if ( box[i].clkend - box[i].clkstart < w/200.0) |
1303 | fprintf(gnudata, "set object %d rectangle from %" PRId64".0, %d.0 to %" PRId64".0, %d.0 fillcolor rgb \"%s\" fillstyle solid 1.0\n" , |
1304 | object++, box[i].clkstart, (rowoffset + box[i].row) * 2 * h, box[i].clkstart+2, (rowoffset + box[i].row) * 2 * h + h, colors[box[i].color].col); |
1305 | else |
1306 | fprintf(gnudata, "set object %d rectangle from %" PRId64".0, %d.0 to %" PRId64".0, %d.0 fillcolor rgb \"%s\" fillstyle solid 1.0\n" , |
1307 | object++, box[i].clkstart, (rowoffset + box[i].row) * 2 * h, box[i].clkend, (rowoffset + box[i].row) * 2 * h + h, colors[box[i].color].col); |
1308 | break; |
1309 | case MDB_PING: |
1310 | break; |
1311 | case MDB_WAIT: |
1312 | fprintf(gnudata, "set object %d rectangle from %" PRId64".0, %d.0 to %.2f,%.2f front fillcolor rgb \"red\" fillstyle solid 1.0\n" , |
1313 | object++, box[i].clkstart, (rowoffset + box[i].row) * 2 * h+h/3, box[i].clkstart+ w /25.0, (rowoffset + box[i].row) *2 *h + h - 0.3 * h); |
1314 | break; |
1315 | } |
1316 | |
1317 | |
1318 | |
1319 | fprintf(gnudata, "plot 0 notitle with lines\n" ); |
1320 | fprintf(gnudata, "unset for[i=%d:%d] object i\n" , prevobject, object - 1); |
1321 | prevobject = object - 1; |
1322 | showcolormap(0, 0); |
1323 | fprintf(gnudata, "unset multiplot\n" ); |
1324 | (void) fclose(gnudata); |
1325 | gnudata = 0; |
1326 | atlaspage++; |
1327 | if (atlas == atlaspage ) { |
1328 | stopListening(0); |
1329 | } |
1330 | #ifdef NUMAprofiling |
1331 | showNumaHeatmap(); |
1332 | #endif |
1333 | } |
1334 | |
1335 | /* The intra-thread flow is collected for later presentation */ |
1336 | |
1337 | |
1338 | static void |
1339 | updateNumaHeatmap(int thread, char *numa){ |
1340 | char *c; |
1341 | int t; |
1342 | for( c= numa; *c && *c == '@';){ |
1343 | c++; |
1344 | t =atoi(c); |
1345 | crossings[thread][t]++; |
1346 | target[thread]++; |
1347 | source[t]++; |
1348 | while(*c && *c !='@') c++; |
1349 | } |
1350 | } |
1351 | |
1352 | |
1353 | /* the main issue to deal with in the analysis is |
1354 | * that the tomograph start can appear while the |
1355 | * system is already processing. |
1356 | * receiving 'done' events without matching 'start' |
1357 | * |
1358 | * A secondary issue is to properly count the functions |
1359 | * being monitored. |
1360 | */ |
1361 | |
1362 | static int ping = -1; |
1363 | |
1364 | static void |
1365 | update(char *line, EventRecord *ev) |
1366 | { |
1367 | int idx, i; |
1368 | Box b; |
1369 | int uid = 0,qid = 0; |
1370 | |
1371 | if (topbox == maxbox || maxbox < topbox) { |
1372 | if( box == 0){ |
1373 | box = calloc(MAXBOX, sizeof(Box)); |
1374 | } else |
1375 | box = realloc(box, (maxbox + MAXBOX) * sizeof(Box)); |
1376 | if( box == NULL){ |
1377 | fprintf(stderr, "Out of space for trace, exceeds max entries %d\n" , maxbox); |
1378 | fprintf(stderr, "Restart with a slower beat might help, e.g. --beat=5000 or --beat=0\n" ); |
1379 | exit(-1); |
1380 | } |
1381 | maxbox += MAXBOX; |
1382 | } |
1383 | /* handle a ping event, keep the current instruction in focus */ |
1384 | if (ev->state >= MDB_PING ) { |
1385 | if (cpus == 0 && ev->state == MDB_PING) { |
1386 | char *s; |
1387 | if( (s= strchr(ev->stmt,'[')) != NULL) |
1388 | s++; |
1389 | else s = ev->stmt; |
1390 | while (s && isspace((unsigned char) *s)) |
1391 | s++; |
1392 | while (s) { |
1393 | s = strchr(s + 1, ' '); |
1394 | while (s && isspace((unsigned char) *s)) |
1395 | s++; |
1396 | if (s) |
1397 | cpus++; |
1398 | } |
1399 | } |
1400 | if( startrange && starttime && ev->clkticks-starttime < startrange) |
1401 | return; |
1402 | if( endrange && starttime && ev->clkticks-starttime > endrange) |
1403 | return; |
1404 | idx = threads[ev->thread]; |
1405 | b = box[idx]; |
1406 | box[idx].state = ev->state; |
1407 | box[idx].thread = ev->thread; |
1408 | //lastclk[thread] = clkticks-starttime; |
1409 | box[idx].clkend = box[idx].clkstart = ev->clkticks-starttime; |
1410 | box[idx].memend = box[idx].memstart = ev->rss; |
1411 | box[idx].footstart = box[idx].tmpspace = ev->size; |
1412 | box[idx].inblock = ev->inblock; |
1413 | box[idx].oublock = ev->oublock; |
1414 | box[idx].majflt = ev->majflt; |
1415 | box[idx].nswap = ev->swaps; |
1416 | box[idx].csw = ev->csw; |
1417 | box[idx].stmt = strdup(ev->stmt); |
1418 | |
1419 | if ( !capturing){ |
1420 | ping = idx; |
1421 | return; |
1422 | } |
1423 | box[idx].fcn = ev->state == MDB_PING? strdup("profiler.ping" ):strdup("profiler.wait" ); |
1424 | if( box[idx].fcn == NULL){ |
1425 | fprintf(stderr,"Could not allocate blk->fcn\n" ); |
1426 | exit(-1); |
1427 | } |
1428 | threads[ev->thread] = ++topbox; |
1429 | idx = threads[ev->thread]; |
1430 | box[idx] = b; |
1431 | if( tracefd) |
1432 | fprintf(tracefd,"%s\n" ,line); |
1433 | return; |
1434 | } |
1435 | |
1436 | if (debug) |
1437 | fprintf(stderr, "Update %s input %s stmt %s time %" PRId64" %s\n" ,(ev->state>=0?statenames[ev->state]:"unknown" ),(ev->fcn?ev->fcn:"(null)" ),(currentfunction?currentfunction:"" ),ev->clkticks -starttime,(ev->numa?ev->numa:"" )); |
1438 | |
1439 | if (starttime == 0) { |
1440 | if (ev->fcn == 0 ) { |
1441 | if (debug) |
1442 | fprintf(stderr, "Skip %s input\n" ,(ev->state>=0?statenames[ev->state]:"unknown" )); |
1443 | return; |
1444 | } |
1445 | if (debug) |
1446 | fprintf(stderr, "Start capturing updates %s\n" ,ev->fcn); |
1447 | } |
1448 | if (ev->clkticks < 0) { |
1449 | /* HACK: *TRY TO* compensate for the fact that the MAL |
1450 | * profiler chops-off day information, and assume that |
1451 | * clkticks is < starttime because the tomograph run |
1452 | * crossed a day boundary (midnight); |
1453 | * we simply add 1 day (24 hours) worth of microseconds. |
1454 | * NOTE: this surely does NOT work correctly if the |
1455 | * tomograph run takes 24 hours or more ... |
1456 | */ |
1457 | ev->clkticks += US_DD; |
1458 | } |
1459 | |
1460 | /* monitor top level function brackets, we restrict ourselves to SQL queries */ |
1461 | if (!capturing && ev->state == MDB_START && ev->fcn && strncmp(ev->fcn, "function" , 8) == 0) { |
1462 | if( (i = sscanf(ev->fcn + 9,"user.s%d_%d" ,&uid,&qid)) != 2){ |
1463 | if( debug) |
1464 | fprintf(stderr,"Start phase parsing %d, uid %d qid %d\n" ,i,uid,qid); |
1465 | return; |
1466 | } |
1467 | if (capturing++ == 0) |
1468 | starttime = ev->clkticks; |
1469 | if( ping >= 0){ |
1470 | box[ping].clkend = box[ping].clkstart = 0; |
1471 | ping = -1; |
1472 | } |
1473 | if (currentfunction == 0) |
1474 | currentfunction = strdup(ev->fcn+9); |
1475 | if (debug) |
1476 | fprintf(stderr, "Enter function %s capture %d\n" , currentfunction, capturing); |
1477 | if( tracefd) |
1478 | fprintf(tracefd,"%s\n" ,line); |
1479 | return; |
1480 | } |
1481 | ev->clkticks -= starttime; |
1482 | |
1483 | if ( !capturing || ev->thread >= MAXTHREADS) |
1484 | return; |
1485 | idx = threads[ev->thread]; |
1486 | lastclk[ev->thread] = endrange? endrange: ev->clkticks; |
1487 | |
1488 | /* track the input in the trace file */ |
1489 | if( tracefd) |
1490 | fprintf(tracefd,"%s\n" ,line); |
1491 | /* start of instruction box */ |
1492 | if (ev->state == MDB_START ) { |
1493 | if(debug) |
1494 | fprintf(stderr, "Start box %s clicks %" PRId64" stmt %s thread %d idx %d box %d\n" , (ev->fcn?ev->fcn:"" ), ev->clkticks,currentfunction, ev->thread,idx,topbox); |
1495 | box[idx].state = ev->state; |
1496 | box[idx].thread = ev->thread; |
1497 | box[idx].clkstart = ev->clkticks? ev->clkticks:1; |
1498 | box[idx].clkend = ev->clkticks; |
1499 | box[idx].memstart = ev->rss; |
1500 | box[idx].memend = ev->rss; |
1501 | box[idx].numa = ev->numa; |
1502 | if(ev->numa) updateNumaHeatmap(ev->thread, ev->numa); |
1503 | box[idx].footstart = ev->size; |
1504 | box[idx].stmt = ev->beauty; |
1505 | box[idx].fcn = ev->fcn ? strdup(ev->fcn) : strdup("" ); |
1506 | if(ev->fcn && strstr(ev->fcn,"querylog.define" ) ) |
1507 | fprintf(stderr,"-- page %d :%s\n" ,atlaspage, currentquery); |
1508 | return; |
1509 | } |
1510 | /* end the instruction box */ |
1511 | if (ev->state == MDB_DONE && ev->fcn && strncmp(ev->fcn, "function" , 8) == 0) { |
1512 | if (currentfunction && strcmp(currentfunction, ev->fcn+9) == 0) { |
1513 | if( capturing == 0){ |
1514 | free(currentfunction); |
1515 | currentfunction = 0; |
1516 | } |
1517 | capturing--; |
1518 | if(debug) |
1519 | fprintf(stderr, "Leave function %s capture %d\n" , currentfunction, capturing); |
1520 | } |
1521 | if( capturing == 0){ |
1522 | if( tracefd){ |
1523 | fflush(tracefd); |
1524 | fclose(tracefd); |
1525 | tracefd = NULL; |
1526 | } |
1527 | |
1528 | createTomogram(); |
1529 | resetTomograph(); |
1530 | return; |
1531 | } |
1532 | } |
1533 | if (ev->state == MDB_DONE ){ |
1534 | if( box[idx].clkstart == 0){ |
1535 | // ignore incorrect pairs |
1536 | if(debug) fprintf(stderr, "INCORRECT START\n" ); |
1537 | return; |
1538 | } |
1539 | if( debug) |
1540 | fprintf(stderr, "End box [%d] %s clicks %" PRId64" : %s thread %d idx %d box %d\n" , idx, (ev->fcn?ev->fcn:"" ), ev->clkticks, (currentfunction?currentfunction:"" ), ev->thread,idx,topbox); |
1541 | events++; |
1542 | box[idx].clkend = ev->clkticks; |
1543 | box[idx].memend = ev->rss; |
1544 | box[idx].tmpspace = ev->size; |
1545 | box[idx].ticks = ev->ticks; |
1546 | box[idx].state = MDB_DONE; |
1547 | box[idx].inblock = ev->inblock; |
1548 | box[idx].oublock = ev->oublock; |
1549 | box[idx].majflt = ev->majflt; |
1550 | box[idx].nswap = ev->swaps; |
1551 | box[idx].csw = ev->csw; |
1552 | /* focus on part of the time frame */ |
1553 | if (endrange) { |
1554 | if( debug){ |
1555 | fprintf(stderr,"range filter %" PRId64" %" PRId64"\n" ,startrange,endrange); |
1556 | fprintf(stderr,"expression %" PRId64" %" PRId64"\n" , box[idx].clkstart, box[idx].clkend); |
1557 | } |
1558 | if (box[idx].clkend < startrange || box[idx].clkstart >endrange){ |
1559 | fprintf(stderr,"reject %" PRId64":%" PRId64" out %" PRId64":%" PRId64"\n" ,box[idx].clkstart , box[idx].clkend, startrange, endrange); |
1560 | return; |
1561 | } |
1562 | if (box[idx].clkstart < startrange) |
1563 | box[idx].clkstart = startrange; |
1564 | if (box[idx].clkend > endrange) |
1565 | box[idx].clkend = endrange; |
1566 | } |
1567 | threads[ev->thread] = ++topbox; |
1568 | lastclktick = box[idx].clkend + starttime; |
1569 | totalclkticks += box[idx].clkend - box[idx].clkstart; |
1570 | totalexecticks += box[idx].ticks - box[idx].ticks; |
1571 | } |
1572 | } |
1573 | |
1574 | |
1575 | int |
1576 | main(int argc, char **argv) |
1577 | { |
1578 | int i; |
1579 | ssize_t m; |
1580 | size_t n, len, buflen; |
1581 | char *host = NULL; |
1582 | int portnr = 0; |
1583 | char *uri = NULL; |
1584 | char *user = NULL; |
1585 | char *password = NULL; |
1586 | char buf[BUFSIZ], *buffer, *e, *response; |
1587 | FILE *inpfd; |
1588 | int colormap=0; |
1589 | EventRecord event; |
1590 | char *s; |
1591 | |
1592 | static struct option long_options[18] = { |
1593 | { "dbname" , 1, 0, 'd' }, |
1594 | { "user" , 1, 0, 'u' }, |
1595 | { "port" , 1, 0, 'p' }, |
1596 | { "password" , 1, 0, 'P' }, |
1597 | { "host" , 1, 0, 'h' }, |
1598 | { "help" , 0, 0, '?' }, |
1599 | { "title" , 1, 0, 'T' }, |
1600 | { "input" , 1, 0, 'i' }, |
1601 | { "range" , 1, 0, 'r' }, |
1602 | { "output" , 1, 0, 'o' }, |
1603 | { "debug" , 0, 0, 'D' }, |
1604 | { "beat" , 1, 0, 'b' }, |
1605 | { "atlas" , 1, 0, 'A' }, |
1606 | { "map" , 1, 0, 'm' }, |
1607 | { 0, 0, 0, 0 } |
1608 | }; |
1609 | |
1610 | /* parse config file first, command line options override */ |
1611 | parse_dotmonetdb(&user, &password, &dbname, NULL, NULL, NULL, NULL); |
1612 | |
1613 | if( argc == 1){ |
1614 | usageTomograph(); |
1615 | exit(-1); |
1616 | } |
1617 | while (1) { |
1618 | int option_index = 0; |
1619 | int c = getopt_long(argc, argv, "d:u:p:P:h:?T:i:r:s:o:c:Db:A:m" , |
1620 | long_options, &option_index); |
1621 | if (c == -1) |
1622 | break; |
1623 | switch (c) { |
1624 | case 'm': |
1625 | colormap =1; |
1626 | break; |
1627 | case 'A': |
1628 | atlas = atoi(optarg ? optarg : "1" ); |
1629 | break; |
1630 | case 'b': |
1631 | beat = atoi(optarg ? optarg : "5000" ); |
1632 | break; |
1633 | case 'D': |
1634 | debug = 1; |
1635 | break; |
1636 | case 'd': |
1637 | if (dbname) |
1638 | free(dbname); |
1639 | prefix = dbname = strdup(optarg); |
1640 | break; |
1641 | case 'i': |
1642 | inputfile = optarg; |
1643 | prefix = strdup("" ); |
1644 | break; |
1645 | case 'u': |
1646 | if (user) |
1647 | free(user); |
1648 | user = strdup(optarg); |
1649 | /* force password prompt */ |
1650 | if (password) |
1651 | free(password); |
1652 | password = NULL; |
1653 | break; |
1654 | case 'P': |
1655 | if (password) |
1656 | free(password); |
1657 | password = strdup(optarg); |
1658 | break; |
1659 | case 'p': |
1660 | if (optarg) |
1661 | portnr = atoi(optarg); |
1662 | break; |
1663 | case 'h': |
1664 | host = optarg; |
1665 | break; |
1666 | case 'T': |
1667 | title = optarg; |
1668 | break; |
1669 | case 'o': |
1670 | //store the output files in a specific place |
1671 | prefix = strdup(optarg); |
1672 | #ifdef NATIVE_WIN32 |
1673 | s= strrchr(prefix, (int) '\\'); |
1674 | #else |
1675 | s= strrchr(prefix, (int) '/'); |
1676 | #endif |
1677 | if( s ){ |
1678 | dirpath= prefix; |
1679 | prefix = strdup(prefix); |
1680 | *(s+1) = 0; |
1681 | prefix += s-dirpath; |
1682 | } |
1683 | break; |
1684 | case 'r': |
1685 | { |
1686 | int cnt; |
1687 | if (optarg == 0) |
1688 | break; |
1689 | if( *optarg == '=') |
1690 | optarg++; |
1691 | cnt = sscanf(optarg,"%" SCNd64"-%" SCNd64, &startrange,&endrange); |
1692 | if( cnt != 2) |
1693 | usageTomograph(); |
1694 | |
1695 | if( strstr(optarg,"ms" ) ){ |
1696 | startrange *= 1000; |
1697 | endrange *= 1000; |
1698 | } else if( strchr(optarg,'s')){ |
1699 | startrange *= 1000000; |
1700 | endrange *= 1000000; |
1701 | } else |
1702 | usageTomograph(); |
1703 | if( debug ) |
1704 | fprintf(stderr,"Cut out slice %" PRId64" -%" PRId64"\n" ,startrange,endrange); |
1705 | break; |
1706 | } |
1707 | case '?': |
1708 | usageTomograph(); |
1709 | /* a bit of a hack: look at the option that the |
1710 | current `c' is based on and see if we recognize |
1711 | it: if -? or --help, exit with 0, else with -1 */ |
1712 | exit(strcmp(argv[optind - 1], "-?" ) == 0 || strcmp(argv[optind - 1], "--help" ) == 0 ? 0 : -1); |
1713 | default: |
1714 | usageTomograph(); |
1715 | exit(-1); |
1716 | } |
1717 | } |
1718 | |
1719 | if ( dbname == NULL && inputfile == NULL){ |
1720 | fprintf(stderr,"Database name and inputfile missing\n" ); |
1721 | usageTomograph(); |
1722 | exit(-1); |
1723 | } |
1724 | if (dbname != NULL && strncmp(dbname, "mapi:monetdb://" , 15) == 0) { |
1725 | uri = dbname; |
1726 | dbname = NULL; |
1727 | } |
1728 | |
1729 | /* reprocess an existing profiler trace, possibly producing the trace split */ |
1730 | if( debug ) |
1731 | printf("-- Output directed towards %s%s_*\n" , dirpath, prefix); |
1732 | if ( |
1733 | #ifdef NATIVE_WIN32 |
1734 | _mkdir(dirpath) < 0 |
1735 | #else |
1736 | mkdir(dirpath,0755) < 0 |
1737 | #endif |
1738 | && errno != EEXIST) { |
1739 | fprintf(stderr,"Failed to create dirpath '%s'\n" ,dirpath); |
1740 | exit(-1); |
1741 | } |
1742 | |
1743 | initcolors(); |
1744 | |
1745 | if (colormap) { |
1746 | showcolormap(prefix, 1); |
1747 | printf("Color map file generated\n" ); |
1748 | exit(0); |
1749 | } |
1750 | |
1751 | #ifdef SIGPIPE |
1752 | signal(SIGPIPE, stopListening); |
1753 | #endif |
1754 | #ifdef SIGHUP |
1755 | signal(SIGHUP, stopListening); |
1756 | #endif |
1757 | #ifdef SIGQUIT |
1758 | signal(SIGQUIT, stopListening); |
1759 | #endif |
1760 | signal(SIGINT, stopListening); |
1761 | signal(SIGTERM, stopListening); |
1762 | close(0); |
1763 | |
1764 | if (inputfile) { |
1765 | inpfd = fopen(inputfile,"r" ); |
1766 | if (inpfd == NULL ){ |
1767 | fprintf(stderr,"Can not access '%s'\n" ,inputfile); |
1768 | exit(-1); |
1769 | } |
1770 | if( strstr(inputfile,".trace" )) |
1771 | *strstr(inputfile,".trace" ) = 0; |
1772 | snprintf(basefile,BUFSIZ,"%s" ,inputfile); |
1773 | len = 0; |
1774 | resetTomograph(); |
1775 | while ((n = fread(buf + len, 1, BUFSIZ - len, inpfd)) > 0) { |
1776 | buf[len + n] = 0; |
1777 | response = buf; |
1778 | while ((e = strchr(response, '\n')) != NULL) { |
1779 | *e = 0; |
1780 | i = keyvalueparser(response, &event); |
1781 | if( i == 1) |
1782 | update(response, &event); |
1783 | if (debug ) |
1784 | fprintf(stderr, "PARSE %d:%s\n" , i, response); |
1785 | response = e + 1; |
1786 | } |
1787 | /* handle last line in buffer */ |
1788 | if (*response) { |
1789 | if (debug) |
1790 | fprintf(stderr,"LASTLINE:%s" , response); |
1791 | len = strlen(response); |
1792 | snprintf(buf, len + 1, "%s" , response); |
1793 | } else |
1794 | len = 0; |
1795 | } |
1796 | createTomogram(); |
1797 | stopListening(0); |
1798 | } else { |
1799 | if (user == NULL) |
1800 | user = simple_prompt("user" , BUFSIZ, 1, prompt_getlogin()); |
1801 | if (password == NULL) |
1802 | password = simple_prompt("password" , BUFSIZ, 0, NULL); |
1803 | |
1804 | /* our hostname, how remote servers have to contact us */ |
1805 | gethostname(hostname, sizeof(hostname)); |
1806 | |
1807 | /* set up the profiler */ |
1808 | if (uri) |
1809 | dbh = mapi_mapiuri(uri, user, password, "mal" ); |
1810 | else |
1811 | dbh = mapi_mapi(host, portnr, user, password, "mal" , dbname); |
1812 | if (dbh == NULL || mapi_error(dbh)) |
1813 | die(dbh, hdl); |
1814 | mapi_reconnect(dbh); |
1815 | if (mapi_error(dbh)) |
1816 | die(dbh, hdl); |
1817 | host = strdup(mapi_get_host(dbh)); |
1818 | if(debug) |
1819 | fprintf(stderr,"-- connection with server %s\n" , uri ? uri : host); |
1820 | |
1821 | snprintf(buf,BUFSIZ-1,"profiler.setheartbeat(%d);" ,beat); |
1822 | if( debug) |
1823 | fprintf(stderr,"-- %s\n" ,buf); |
1824 | doQ(buf); |
1825 | |
1826 | snprintf(buf,BUFSIZ,"profiler.openstream(0);" ); |
1827 | if( debug) |
1828 | fprintf(stderr,"-- %s\n" ,buf); |
1829 | doQ(buf); |
1830 | |
1831 | snprintf(basefile,BUFSIZ,"%s%s" ,dirpath, prefix); |
1832 | snprintf(buf,BUFSIZ,"%s_%02d.trace" ,basefile, atlaspage); |
1833 | tracefd = fopen(buf,"w" ); |
1834 | if( tracefd == NULL) |
1835 | fprintf(stderr,"Could not create file '%s'\n" ,buf); |
1836 | |
1837 | len = 0; |
1838 | buflen = BUFSIZ; |
1839 | buffer = malloc(buflen); |
1840 | if( buffer == NULL){ |
1841 | fprintf(stderr,"Could not create input buffer\n" ); |
1842 | exit(-1); |
1843 | } |
1844 | resetTomograph(); |
1845 | conn = mapi_get_from(dbh); |
1846 | while ((m = mnstr_read(conn, buffer + len, 1, buflen - len-1)) >= 0) { |
1847 | if (m == 0 && |
1848 | (m = mnstr_read(conn, buffer + len, 1, buflen - len-1)) <= 0) |
1849 | break; |
1850 | buffer[len + m] = 0; |
1851 | response = buffer; |
1852 | while ((e = strchr(response, '\n')) != NULL) { |
1853 | *e = 0; |
1854 | i = keyvalueparser(response,&event); |
1855 | if( i == 1) |
1856 | update(response, &event); |
1857 | if (debug ) |
1858 | fprintf(stderr, "PARSE %d:%s\n" , i, response); |
1859 | response = e + 1; |
1860 | } |
1861 | /* handle the case that the line is too long to |
1862 | * fit in the buffer */ |
1863 | if( response == buffer){ |
1864 | char *new = realloc(buffer, buflen + BUFSIZ); |
1865 | if( new == NULL){ |
1866 | fprintf(stderr,"Could not extend input buffer\n" ); |
1867 | assert(0); |
1868 | } |
1869 | new[buflen] = 0; |
1870 | buffer = new; |
1871 | buflen += BUFSIZ; |
1872 | len += m; |
1873 | } |
1874 | /* handle the case the buffer contains more than one |
1875 | * line, and the last line is not completely read yet. |
1876 | * Copy the first part of the incomplete line to the |
1877 | * beginning of the buffer */ |
1878 | else if (*response) { |
1879 | if (debug) |
1880 | fprintf(stderr,"LASTLINE:%s" , response); |
1881 | len = strlen(response); |
1882 | snprintf(buffer, len + 1, "%s" , response); |
1883 | } else /* reset this line of buffer */ |
1884 | len = 0; |
1885 | } |
1886 | } |
1887 | |
1888 | if( !inputfile) |
1889 | doQ("profiler.stop();" ); |
1890 | stop_disconnect: |
1891 | if( !inputfile) { |
1892 | mapi_disconnect(dbh); |
1893 | printf("-- connection with server %s closed\n" , uri ? uri : host); |
1894 | } |
1895 | return 0; |
1896 | } |
1897 | |