Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /* Author(s) M.L. Kersten
14 : * The MAL Runtime Profiler and system queue
15 : * This little helper module is used to perform instruction based profiling.
16 : * The QRYqueue is only update at the start/finish of a query.
17 : * It is also the place to keep track on the number of workers
18 : * The current code relies on a scan rather than a hash.
19 : */
20 :
21 : #include "monetdb_config.h"
22 : #include "mal_utils.h"
23 : #include "mal_runtime.h"
24 : #include "mal_interpreter.h"
25 : #include "mal_function.h"
26 : #include "mal_profiler.h"
27 : #include "mal_listing.h"
28 : #include "mal_authorize.h"
29 : #include "mal_resource.h"
30 : #include "mal_internal.h"
31 : #include "mal_private.h"
32 :
33 :
34 : QueryQueue QRYqueue = NULL;
35 : size_t qsize = 0;
36 : static oid qtag = 1; // A unique query identifier
37 :
38 : UserStats USRstats = NULL;
39 : size_t usrstatscnt = 0;
40 :
41 : static inline void
42 21706 : clearUSRstats(size_t idx)
43 : {
44 21706 : USRstats[idx] = (struct USERSTAT) { 0 };
45 : }
46 :
47 : /*
48 : * Find the index of the given 'user' in USRstats.
49 : * For a new 'user' return a new free slot.
50 : * If USRstats is full, extend it.
51 : */
52 : static size_t
53 260120 : getUSRstatsIdx(MalBlkPtr mb, oid user)
54 : {
55 260120 : size_t i = 0;
56 260120 : UserStats tmp = NULL;
57 :
58 262195 : for (i = 0; i < usrstatscnt; i++)
59 : /* The array is dense, so we either find the user or an empty slot. */
60 262194 : if (USRstats[i].user == user || USRstats[i].username == NULL)
61 260119 : return i;
62 :
63 : /* expand USRstats */
64 1 : tmp = (UserStats) GDKrealloc(USRstats,
65 : sizeof(struct USERSTAT) *
66 : (size_t) (usrstatscnt += MAL_MAXCLIENTS));
67 1 : if (tmp == NULL) {
68 : /* It's not a fatal error if we can't extend USRstats.
69 : * We don't want to affect existing USRstats. */
70 0 : addMalException(mb, "getUSRstatsIdx" MAL_MALLOC_FAIL);
71 0 : return (size_t) -1;
72 : }
73 1 : USRstats = tmp;
74 5 : for (; i < usrstatscnt; i++)
75 4 : clearUSRstats(i);
76 1 : return usrstatscnt - MAL_MAXCLIENTS;
77 : }
78 :
79 : static void
80 553623 : updateUserStats(Client cntxt, MalBlkPtr mb, lng ticks, time_t started,
81 : time_t finished, str query)
82 : {
83 : // don't keep stats for context without username
84 553623 : if (cntxt->username == NULL)
85 : return;
86 :
87 260120 : size_t idx = getUSRstatsIdx(mb, cntxt->user);
88 :
89 260120 : if (idx == (size_t) -1) {
90 0 : addMalException(mb,
91 : "updateUserStats"
92 : "Failed to get an entry in user statistics");
93 0 : return;
94 : }
95 :
96 260120 : if (USRstats[idx].username == NULL || USRstats[idx].user != cntxt->user
97 259768 : || strcmp(USRstats[idx].username, cntxt->username) != 0) {
98 382 : GDKfree(USRstats[idx].username);
99 382 : GDKfree(USRstats[idx].maxquery);
100 382 : clearUSRstats(idx);
101 382 : USRstats[idx].user = cntxt->user;
102 382 : USRstats[idx].username = GDKstrdup(cntxt->username);
103 : }
104 260120 : USRstats[idx].querycount++;
105 260120 : USRstats[idx].totalticks += ticks;
106 260120 : if (ticks >= USRstats[idx].maxticks && query) {
107 1407 : USRstats[idx].started = started;
108 1407 : USRstats[idx].finished = finished;
109 1407 : USRstats[idx].maxticks = ticks;
110 1407 : GDKfree(USRstats[idx].maxquery);
111 1407 : USRstats[idx].maxquery = GDKstrdup(query);
112 : }
113 : }
114 :
115 : /*
116 : * Free up the whole USRstats before mserver5 exits.
117 : */
118 : static void
119 334 : dropUSRstats(void)
120 : {
121 334 : size_t i;
122 334 : MT_lock_set(&mal_delayLock);
123 21988 : for (i = 0; i < usrstatscnt; i++) {
124 21320 : GDKfree(USRstats[i].username);
125 21320 : GDKfree(USRstats[i].maxquery);
126 21320 : clearUSRstats(i);
127 : }
128 334 : GDKfree(USRstats);
129 334 : USRstats = NULL;
130 334 : usrstatscnt = 0;
131 334 : MT_lock_unset(&mal_delayLock);
132 334 : }
133 :
134 : static str
135 553623 : isaSQLquery(MalBlkPtr mb)
136 : {
137 553623 : if (mb) {
138 11026596 : for (int i = 1; i < mb->stop; i++) {
139 10723099 : InstrPtr p = getInstrPtr(mb, i);
140 10723099 : if (getModuleId(p) && idcmp(getModuleId(p), "querylog") == 0
141 250126 : && idcmp(getFunctionId(p), "define") == 0)
142 250126 : return getVarConstant(mb, getArg(p, 1)).val.sval;
143 : }
144 : }
145 : return NULL;
146 : }
147 :
148 : /*
149 : * Manage the runtime profiling information
150 : * It is organized as a circular buffer, head/tail.
151 : * Elements are removed from the buffer when it becomes full.
152 : * This way we keep the information a little longer for inspection.
153 : */
154 :
155 : /* clear the next entry for a new call unless it is a running query */
156 : static inline void
157 23500 : clearQRYqueue(size_t idx)
158 : {
159 23500 : QRYqueue[idx] = (struct QRYQUEUE) { 0 };
160 : }
161 :
162 : static void
163 334 : dropQRYqueue(void)
164 : {
165 334 : MT_lock_set(&mal_delayLock);
166 22742 : for (size_t i = 0; i < qsize; i++) {
167 22408 : GDKfree(QRYqueue[i].query);
168 22408 : GDKfree(QRYqueue[i].username);
169 22408 : clearQRYqueue(i);
170 : }
171 334 : GDKfree(QRYqueue);
172 334 : QRYqueue = NULL;
173 334 : qsize = 0;
174 334 : qtag = 1;
175 334 : MT_lock_unset(&mal_delayLock);
176 334 : }
177 :
178 : oid
179 250532 : runtimeProfileSetTag(Client cntxt)
180 : {
181 250532 : MT_lock_set(&mal_delayLock);
182 250533 : cntxt->curprg->def->tag = qtag++;
183 250533 : MT_lock_unset(&mal_delayLock);
184 :
185 250533 : return cntxt->curprg->def->tag;
186 : }
187 :
188 : /* At the start of every MAL block or SQL query */
189 : void
190 608237 : runtimeProfileInit(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
191 : {
192 608237 : static size_t qlast = 0;
193 608237 : size_t i, paused = 0;
194 608237 : str q;
195 :
196 : /* Recursive calls don't change the query queue, but later we have to check
197 : how to stop/pause/resume queries doing recursive calls from multiple workers */
198 608237 : if (stk->up)
199 : return;
200 553622 : MT_lock_set(&mal_delayLock);
201 :
202 553623 : if (USRstats == NULL) {
203 335 : usrstatscnt = MAL_MAXCLIENTS;
204 335 : USRstats = (UserStats) GDKzalloc(sizeof(struct USERSTAT) * usrstatscnt);
205 335 : if (USRstats == NULL) {
206 0 : addMalException(mb, "runtimeProfileInit" MAL_MALLOC_FAIL);
207 0 : MT_lock_unset(&mal_delayLock);
208 0 : return;
209 : }
210 : }
211 :
212 553623 : if (QRYqueue == NULL) {
213 335 : QRYqueue = (QueryQueue) GDKzalloc(sizeof(struct QRYQUEUE) *
214 : (qsize = MAL_MAXCLIENTS));
215 :
216 335 : if (QRYqueue == NULL) {
217 0 : addMalException(mb, "runtimeProfileInit" MAL_MALLOC_FAIL);
218 0 : MT_lock_unset(&mal_delayLock);
219 0 : return;
220 : }
221 : }
222 45138727 : for (i = 0; i < qsize; i++) {
223 88862388 : paused += QRYqueue[i].status && (QRYqueue[i].status[0] == 'p' || QRYqueue[i].status[0] == 'r'); /* running, prepared or paused */
224 : }
225 553623 : if (qsize - paused < (size_t) MAL_MAXCLIENTS) {
226 18 : qsize += MAL_MAXCLIENTS;
227 18 : QueryQueue tmp;
228 18 : tmp = (QueryQueue) GDKrealloc(QRYqueue,
229 : sizeof(struct QRYQUEUE) * qsize);
230 18 : if (tmp == NULL) {
231 0 : addMalException(mb, "runtimeProfileInit" MAL_MALLOC_FAIL);
232 0 : qsize -= MAL_MAXCLIENTS; /* undo increment */
233 0 : MT_lock_unset(&mal_delayLock);
234 0 : return;
235 : }
236 18 : QRYqueue = tmp;
237 1110 : for (i = qsize - MAL_MAXCLIENTS; i < qsize; i++)
238 1092 : clearQRYqueue(i);
239 : }
240 : // add new invocation
241 553641 : for (i = 0; i < qsize; i++) {
242 553641 : size_t j = qlast;
243 553641 : if (++qlast >= qsize)
244 7638 : qlast = 0;
245 553641 : if (QRYqueue[j].stk == NULL ||
246 18 : QRYqueue[j].status == NULL ||
247 18 : (QRYqueue[j].status[0] != 'r' && QRYqueue[j].status[0] != 'p')) {
248 553623 : QRYqueue[j].mb = mb;
249 553623 : QRYqueue[j].tag = stk->tag = mb->tag;
250 553623 : QRYqueue[j].stk = stk; // for status pause 'p'/running '0'/ quiting 'q'
251 553623 : QRYqueue[j].finished = 0;
252 553623 : QRYqueue[j].start = time(0);
253 553623 : q = isaSQLquery(mb);
254 553623 : GDKfree(QRYqueue[j].query);
255 553623 : QRYqueue[j].query = GDKstrdup(q); /* NULL in, NULL out */
256 553623 : GDKfree(QRYqueue[j].username);
257 553623 : if (!GDKembedded())
258 551892 : QRYqueue[j].username = GDKstrdup(cntxt->username);
259 553623 : QRYqueue[j].idx = cntxt->idx;
260 : /* give the MB upperbound by addition of 1 MB */
261 553623 : QRYqueue[j].memory = 1 + (int) (stk->memory / LL_CONSTANT(1048576)); /* Convert to MB */
262 553623 : QRYqueue[j].workers = (int) 1; /* this is the first one */
263 553623 : QRYqueue[j].status = "running";
264 553623 : QRYqueue[j].cntxt = cntxt;
265 553623 : QRYqueue[j].ticks = GDKusec();
266 553623 : break;
267 : }
268 : }
269 553623 : MT_lock_unset(&mal_delayLock);
270 553623 : MT_lock_set(&mal_contextLock);
271 553623 : cntxt->idle = 0;
272 553623 : MT_lock_unset(&mal_contextLock);
273 : }
274 :
275 : /*
276 : * At the end of every MAL block or SQL query.
277 : *
278 : * Returning from a recursive call does not change the number of workers.
279 : */
280 : void
281 608238 : runtimeProfileFinish(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
282 : {
283 608238 : size_t i;
284 608238 : bool found = false;
285 :
286 : /* Recursive calls don't change the query queue, but later we have to check
287 : how to stop/pause/resume queries doing recursive calls from multiple workers */
288 608238 : if (stk->up)
289 : return;
290 553623 : MT_lock_set(&mal_delayLock);
291 23010465 : for (i = 0; i < qsize; i++) {
292 22456842 : if (QRYqueue[i].stk == stk) {
293 553623 : if (QRYqueue[i].status[0] == 's')
294 1 : QRYqueue[i].status = "aborted";
295 : else
296 553622 : QRYqueue[i].status = "finished";
297 553623 : QRYqueue[i].finished = time(0);
298 553623 : QRYqueue[i].workers = (int) ATOMIC_GET(&mb->workers);
299 : /* give the MB upperbound by addition of 1 MB */
300 553623 : QRYqueue[i].memory = 1 + (int) (mb->memory / LL_CONSTANT(1048576));
301 553623 : QRYqueue[i].cntxt = NULL;
302 553623 : QRYqueue[i].stk = NULL;
303 553623 : QRYqueue[i].mb = NULL;
304 553623 : QRYqueue[i].ticks = GDKusec() - QRYqueue[i].ticks;
305 553623 : updateUserStats(cntxt, mb, QRYqueue[i].ticks, QRYqueue[i].start,
306 : QRYqueue[i].finished, QRYqueue[i].query);
307 : // assume that the user is now idle
308 553623 : MT_lock_unset(&mal_delayLock);
309 553623 : MT_lock_set(&mal_contextLock);
310 553623 : cntxt->idle = time(0);
311 553623 : MT_lock_unset(&mal_contextLock);
312 553623 : found = true;
313 553623 : break;
314 : }
315 : }
316 :
317 : // every query that has been started has an entry in QRYqueue. If this
318 : // finished query is not found, we want to print some informational
319 : // messages for debugging.
320 553623 : if (!found) {
321 0 : assert(0);
322 : TRC_INFO_IF(MAL_SERVER) {
323 : TRC_INFO_ENDIF(MAL_SERVER,
324 : "runtimeProfilerFinish: stk (%p) not found in QRYqueue",
325 : stk);
326 : for (i = 0; i < qsize; i++) {
327 : // print some info. of queries not "finished"
328 : if (strcmp(QRYqueue[i].status, "finished") != 0) {
329 : TRC_INFO_ENDIF(MAL_SERVER,
330 : "QRYqueue[%zu]: stk(%p), tag(" OIDFMT
331 : "), username(%s), start(%ld), status(%s), query(%s)",
332 : i, QRYqueue[i].stk, QRYqueue[i].tag,
333 : QRYqueue[i].username, QRYqueue[i].start,
334 : QRYqueue[i].status, QRYqueue[i].query);
335 : }
336 : }
337 : }
338 608238 : MT_lock_unset(&mal_delayLock);
339 : }
340 :
341 : }
342 :
343 : /* Used by mal_reset to do the grand final clean up of this area before MonetDB exits */
344 : void
345 334 : mal_runtime_reset(void)
346 : {
347 334 : dropQRYqueue();
348 334 : dropUSRstats();
349 334 : }
350 :
351 : /* At the start of each MAL stmt */
352 : void
353 53571393 : runtimeProfileBegin(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
354 : RuntimeProfile prof)
355 : {
356 53571393 : (void) cntxt;
357 53571393 : (void) mb;
358 53571393 : (void) stk;
359 53571393 : (void) pci;
360 :
361 53571393 : assert(pci);
362 : /* always collect the MAL instruction execution time */
363 53571393 : prof->ticks = GDKusec();
364 53571223 : }
365 :
366 : /* At the end of each MAL stmt */
367 : void
368 53601129 : runtimeProfileExit(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
369 : RuntimeProfile prof)
370 : {
371 53601129 : lng ticks = GDKusec();
372 :
373 53600936 : if (profilerStatus > 0)
374 45 : profilerEvent(&(struct MalEvent) { cntxt, mb, stk, pci, ticks,
375 45 : ticks - prof->ticks },
376 : NULL);
377 53600936 : if (cntxt->sqlprofiler)
378 858 : sqlProfilerEvent(cntxt, mb, stk, pci, ticks, ticks - prof->ticks);
379 53600936 : if (profilerStatus < 0) {
380 : /* delay profiling until you encounter start of MAL function */
381 0 : if (getInstrPtr(mb, 0) == pci)
382 0 : profilerStatus = 1;
383 : }
384 53600936 : }
385 :
386 : /*
387 : * For performance evaluation it is handy to estimate the
388 : * amount of bytes produced by an instruction.
389 : * The actual amount is harder to guess, because an instruction
390 : * may trigger a side effect, such as creating a hash-index.
391 : * Side effects are ignored.
392 : */
393 :
394 : lng
395 0 : getBatSpace(BAT *b)
396 : {
397 0 : lng space = 0;
398 0 : if (b == NULL)
399 : return 0;
400 0 : space += BATcount(b) << b->tshift;
401 0 : if (space) {
402 0 : MT_lock_set(&b->theaplock);
403 0 : if (b->tvheap)
404 0 : space += heapinfo(b->tvheap, b->batCacheid);
405 0 : MT_lock_unset(&b->theaplock);
406 0 : MT_rwlock_rdlock(&b->thashlock);
407 0 : space += hashinfo(b->thash, b->batCacheid);
408 0 : MT_rwlock_rdunlock(&b->thashlock);
409 0 : space += IMPSimprintsize(b);
410 : }
411 : return space;
412 : }
413 :
414 : lng
415 0 : getVolume(MalStkPtr stk, InstrPtr pci, int rd)
416 : {
417 0 : int i, limit;
418 0 : lng vol = 0;
419 0 : BAT *b;
420 :
421 0 : if (stk == NULL)
422 : return 0;
423 0 : limit = rd ? pci->argc : pci->retc;
424 0 : i = rd ? pci->retc : 0;
425 :
426 0 : for (; i < limit; i++) {
427 0 : if (stk->stk[getArg(pci, i)].vtype == TYPE_bat) {
428 0 : oid cnt = 0;
429 :
430 0 : b = BBPquickdesc(stk->stk[getArg(pci, i)].val.bval);
431 0 : if (b == NULL)
432 0 : continue;
433 0 : cnt = BATcount(b);
434 : /* Usually reading views cost as much as full bats.
435 : But when we output a slice that is not the case. */
436 0 : if (rd)
437 0 : vol += (!isVIEW(b) && !VIEWtparent(b)) ? tailsize(b, cnt) : 0;
438 0 : else if (!isVIEW(b))
439 0 : vol += tailsize(b, cnt);
440 : }
441 : }
442 : return vol;
443 : }
|