LCOV - code coverage report
Current view: top level - monetdb5/mal - mal_runtime.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 170 218 78.0 %
Date: 2024-04-25 23:25:41 Functions: 11 13 84.6 %

          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       22031 : clearUSRstats(size_t idx)
      43             : {
      44       22031 :         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      262183 : getUSRstatsIdx(MalBlkPtr mb, oid user)
      54             : {
      55      262183 :         size_t i = 0;
      56      262183 :         UserStats tmp = NULL;
      57             : 
      58      264258 :         for (i = 0; i < usrstatscnt; i++)
      59             :                 /* The array is dense, so we either find the user or an empty slot. */
      60      264257 :                 if (USRstats[i].user == user || USRstats[i].username == NULL)
      61      262182 :                         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      573592 : 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      573592 :         if (cntxt->username == NULL)
      85             :                 return;
      86             : 
      87      262183 :         size_t idx = getUSRstatsIdx(mb, cntxt->user);
      88             : 
      89      262183 :         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      262183 :         if (USRstats[idx].username == NULL || USRstats[idx].user != cntxt->user
      97      261826 :                 || strcmp(USRstats[idx].username, cntxt->username) != 0) {
      98         387 :                 GDKfree(USRstats[idx].username);
      99         387 :                 GDKfree(USRstats[idx].maxquery);
     100         387 :                 clearUSRstats(idx);
     101         387 :                 USRstats[idx].user = cntxt->user;
     102         387 :                 USRstats[idx].username = GDKstrdup(cntxt->username);
     103             :         }
     104      262183 :         USRstats[idx].querycount++;
     105      262183 :         USRstats[idx].totalticks += ticks;
     106      262183 :         if (ticks >= USRstats[idx].maxticks && query) {
     107        1370 :                 USRstats[idx].started = started;
     108        1370 :                 USRstats[idx].finished = finished;
     109        1370 :                 USRstats[idx].maxticks = ticks;
     110        1370 :                 GDKfree(USRstats[idx].maxquery);
     111        1370 :                 USRstats[idx].maxquery = GDKstrdup(query);
     112             :         }
     113             : }
     114             : 
     115             : /*
     116             :  * Free up the whole USRstats before mserver5 exits.
     117             :  */
     118             : static void
     119         339 : dropUSRstats(void)
     120             : {
     121         339 :         size_t i;
     122         339 :         MT_lock_set(&mal_delayLock);
     123       22318 :         for (i = 0; i < usrstatscnt; i++) {
     124       21640 :                 GDKfree(USRstats[i].username);
     125       21640 :                 GDKfree(USRstats[i].maxquery);
     126       21640 :                 clearUSRstats(i);
     127             :         }
     128         339 :         GDKfree(USRstats);
     129         339 :         USRstats = NULL;
     130         339 :         usrstatscnt = 0;
     131         339 :         MT_lock_unset(&mal_delayLock);
     132         339 : }
     133             : 
     134             : static str
     135      573592 : isaSQLquery(MalBlkPtr mb)
     136             : {
     137      573592 :         if (mb) {
     138    11724033 :                 for (int i = 1; i < mb->stop; i++) {
     139    11402616 :                         InstrPtr p = getInstrPtr(mb, i);
     140    11402616 :                         if (getModuleId(p) && idcmp(getModuleId(p), "querylog") == 0
     141      252175 :                                 && idcmp(getFunctionId(p), "define") == 0)
     142      252175 :                                 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       23692 : clearQRYqueue(size_t idx)
     158             : {
     159       23692 :         QRYqueue[idx] = (struct QRYQUEUE) { 0 };
     160             : }
     161             : 
     162             : static void
     163         339 : dropQRYqueue(void)
     164             : {
     165         339 :         MT_lock_set(&mal_delayLock);
     166       23003 :         for (size_t i = 0; i < qsize; i++) {
     167       22664 :                 GDKfree(QRYqueue[i].query);
     168       22664 :                 GDKfree(QRYqueue[i].username);
     169       22664 :                 clearQRYqueue(i);
     170             :         }
     171         339 :         GDKfree(QRYqueue);
     172         339 :         QRYqueue = NULL;
     173         339 :         qsize = 0;
     174         339 :         qtag = 1;
     175         339 :         MT_lock_unset(&mal_delayLock);
     176         339 : }
     177             : 
     178             : oid
     179      252593 : runtimeProfileSetTag(Client cntxt)
     180             : {
     181      252593 :         MT_lock_set(&mal_delayLock);
     182      252593 :         cntxt->curprg->def->tag = qtag++;
     183      252593 :         MT_lock_unset(&mal_delayLock);
     184             : 
     185      252593 :         return cntxt->curprg->def->tag;
     186             : }
     187             : 
     188             : /* At the start of every MAL block or SQL query */
     189             : void
     190      647388 : runtimeProfileInit(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
     191             : {
     192      647388 :         static size_t qlast = 0;
     193      647388 :         size_t i, paused = 0;
     194      647388 :         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      647388 :         if (stk->up)
     199             :                 return;
     200      573592 :         MT_lock_set(&mal_delayLock);
     201             : 
     202      573592 :         if (USRstats == NULL) {
     203         340 :                 usrstatscnt = MAL_MAXCLIENTS;
     204         340 :                 USRstats = (UserStats) GDKzalloc(sizeof(struct USERSTAT) * usrstatscnt);
     205         340 :                 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      573592 :         if (QRYqueue == NULL) {
     213         340 :                 QRYqueue = (QueryQueue) GDKzalloc(sizeof(struct QRYQUEUE) *
     214             :                                                                                   (qsize = MAL_MAXCLIENTS));
     215             : 
     216         340 :                 if (QRYqueue == NULL) {
     217           0 :                         addMalException(mb, "runtimeProfileInit" MAL_MALLOC_FAIL);
     218           0 :                         MT_lock_unset(&mal_delayLock);
     219           0 :                         return;
     220             :                 }
     221             :         }
     222    46345456 :         for (i = 0; i < qsize; i++) {
     223    91245970 :                 paused += QRYqueue[i].status && (QRYqueue[i].status[0] == 'p' || QRYqueue[i].status[0] == 'r'); /* running, prepared or paused */
     224             :         }
     225      573592 :         if (qsize - paused < (size_t) MAL_MAXCLIENTS) {
     226          17 :                 qsize += MAL_MAXCLIENTS;
     227          17 :                 QueryQueue tmp;
     228          17 :                 tmp = (QueryQueue) GDKrealloc(QRYqueue,
     229             :                                                                           sizeof(struct QRYQUEUE) * qsize);
     230          17 :                 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          17 :                 QRYqueue = tmp;
     237        1045 :                 for (i = qsize - MAL_MAXCLIENTS; i < qsize; i++)
     238        1028 :                         clearQRYqueue(i);
     239             :         }
     240             :         // add new invocation
     241      573610 :         for (i = 0; i < qsize; i++) {
     242      573610 :                 size_t j = qlast;
     243      573610 :                 if (++qlast >= qsize)
     244        7969 :                         qlast = 0;
     245      573610 :                 if (QRYqueue[j].stk == NULL ||
     246          18 :                         QRYqueue[j].status == NULL ||
     247          18 :                         (QRYqueue[j].status[0] != 'r' && QRYqueue[j].status[0] != 'p')) {
     248      573592 :                         QRYqueue[j].mb = mb;
     249      573592 :                         QRYqueue[j].tag = stk->tag = mb->tag;
     250      573592 :                         QRYqueue[j].stk = stk;  // for status pause 'p'/running '0'/ quiting 'q'
     251      573592 :                         QRYqueue[j].finished = 0;
     252      573592 :                         QRYqueue[j].start = time(0);
     253      573592 :                         q = isaSQLquery(mb);
     254      573592 :                         GDKfree(QRYqueue[j].query);
     255      573592 :                         QRYqueue[j].query = GDKstrdup(q);       /* NULL in, NULL out */
     256      573592 :                         GDKfree(QRYqueue[j].username);
     257      573592 :                         if (!GDKembedded())
     258      571793 :                                 QRYqueue[j].username = GDKstrdup(cntxt->username);
     259      573592 :                         QRYqueue[j].idx = cntxt->idx;
     260             :                         /* give the MB upperbound by addition of 1 MB */
     261      573592 :                         QRYqueue[j].memory = 1 + (int) (stk->memory / LL_CONSTANT(1048576)); /* Convert to MB */
     262      573592 :                         QRYqueue[j].workers = (int) 1;  /* this is the first one */
     263      573592 :                         QRYqueue[j].status = "running";
     264      573592 :                         QRYqueue[j].cntxt = cntxt;
     265      573592 :                         QRYqueue[j].ticks = GDKusec();
     266      573592 :                         break;
     267             :                 }
     268             :         }
     269      573592 :         MT_lock_unset(&mal_delayLock);
     270      573592 :         MT_lock_set(&mal_contextLock);
     271      573592 :         cntxt->idle = 0;
     272      573592 :         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      647389 : runtimeProfileFinish(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
     282             : {
     283      647389 :         size_t i;
     284      647389 :         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      647389 :         if (stk->up)
     289             :                 return;
     290      573592 :         MT_lock_set(&mal_delayLock);
     291    23608220 :         for (i = 0; i < qsize; i++) {
     292    23034628 :                 if (QRYqueue[i].stk == stk) {
     293      573592 :                         if (QRYqueue[i].status[0] == 's')
     294           1 :                                 QRYqueue[i].status = "aborted";
     295             :                         else
     296      573591 :                                 QRYqueue[i].status = "finished";
     297      573592 :                         QRYqueue[i].finished = time(0);
     298      573592 :                         QRYqueue[i].workers = (int) ATOMIC_GET(&mb->workers);
     299             :                         /* give the MB upperbound by addition of 1 MB */
     300      573592 :                         QRYqueue[i].memory = 1 + (int) (mb->memory / LL_CONSTANT(1048576));
     301      573592 :                         QRYqueue[i].cntxt = NULL;
     302      573592 :                         QRYqueue[i].stk = NULL;
     303      573592 :                         QRYqueue[i].mb = NULL;
     304      573592 :                         QRYqueue[i].ticks = GDKusec() - QRYqueue[i].ticks;
     305      573592 :                         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      573592 :                         MT_lock_unset(&mal_delayLock);
     309      573592 :                         MT_lock_set(&mal_contextLock);
     310      573592 :                         cntxt->idle = time(0);
     311      573592 :                         MT_lock_unset(&mal_contextLock);
     312      573592 :                         found = true;
     313      573592 :                         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      573592 :         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      647389 :                 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         339 : mal_runtime_reset(void)
     346             : {
     347         339 :         dropQRYqueue();
     348         339 :         dropUSRstats();
     349         339 : }
     350             : 
     351             : /* At the start of each MAL stmt */
     352             : void
     353    53822758 : runtimeProfileBegin(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
     354             :                                         RuntimeProfile prof)
     355             : {
     356    53822758 :         (void) cntxt;
     357    53822758 :         (void) mb;
     358    53822758 :         (void) stk;
     359    53822758 :         (void) pci;
     360             : 
     361    53822758 :         assert(pci);
     362             :         /* always collect the MAL instruction execution time */
     363    53822758 :         prof->ticks = GDKusec();
     364    53822645 : }
     365             : 
     366             : /* At the end of each MAL stmt */
     367             : void
     368    53872146 : runtimeProfileExit(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
     369             :                                    RuntimeProfile prof)
     370             : {
     371    53872146 :         lng ticks = GDKusec();
     372             : 
     373    53871880 :         if (profilerStatus > 0)
     374          45 :                 profilerEvent(&(struct MalEvent) { cntxt, mb, stk, pci, ticks,
     375          45 :                                           ticks - prof->ticks },
     376             :                                           NULL);
     377    53871880 :         if (cntxt->sqlprofiler)
     378         858 :                 sqlProfilerEvent(cntxt, mb, stk, pci, ticks, ticks - prof->ticks);
     379    53871880 :         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    53871880 : }
     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)].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             : }

Generated by: LCOV version 1.14