LCOV - code coverage report
Current view: top level - monetdb5/mal - mal_runtime.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 170 217 78.3 %
Date: 2024-12-20 21:24:02 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       24733 : clearUSRstats(size_t idx)
      43             : {
      44       24733 :         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      266080 : getUSRstatsIdx(MalBlkPtr mb, oid user)
      54             : {
      55      266080 :         size_t i = 0;
      56      266080 :         UserStats tmp = NULL;
      57             : 
      58      268204 :         for (i = 0; i < usrstatscnt; i++)
      59             :                 /* The array is dense, so we either find the user or an empty slot. */
      60      268203 :                 if (USRstats[i].user == user || USRstats[i].username == NULL)
      61      266079 :                         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      585135 : 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      585135 :         if (cntxt->username == NULL)
      85             :                 return;
      86             : 
      87      266080 :         size_t idx = getUSRstatsIdx(mb, cntxt->user);
      88             : 
      89      266080 :         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      266080 :         if (USRstats[idx].username == NULL || USRstats[idx].user != cntxt->user
      97      265709 :                 || strcmp(USRstats[idx].username, cntxt->username) != 0) {
      98         401 :                 GDKfree(USRstats[idx].username);
      99         401 :                 GDKfree(USRstats[idx].maxquery);
     100         401 :                 clearUSRstats(idx);
     101         401 :                 USRstats[idx].user = cntxt->user;
     102         401 :                 USRstats[idx].username = GDKstrdup(cntxt->username);
     103             :         }
     104      266080 :         USRstats[idx].querycount++;
     105      266080 :         USRstats[idx].totalticks += ticks;
     106      266080 :         if (ticks >= USRstats[idx].maxticks && query) {
     107        1380 :                 USRstats[idx].started = started;
     108        1380 :                 USRstats[idx].finished = finished;
     109        1380 :                 USRstats[idx].maxticks = ticks;
     110        1380 :                 GDKfree(USRstats[idx].maxquery);
     111        1380 :                 USRstats[idx].maxquery = GDKstrdup(query);
     112             :         }
     113             : }
     114             : 
     115             : /*
     116             :  * Free up the whole USRstats before mserver5 exits.
     117             :  */
     118             : static void
     119         350 : dropUSRstats(void)
     120             : {
     121         350 :         size_t i;
     122         350 :         MT_lock_set(&mal_delayLock);
     123       25028 :         for (i = 0; i < usrstatscnt; i++) {
     124       24328 :                 GDKfree(USRstats[i].username);
     125       24328 :                 GDKfree(USRstats[i].maxquery);
     126       24328 :                 clearUSRstats(i);
     127             :         }
     128         350 :         GDKfree(USRstats);
     129         350 :         USRstats = NULL;
     130         350 :         usrstatscnt = 0;
     131         350 :         MT_lock_unset(&mal_delayLock);
     132         350 : }
     133             : 
     134             : static str
     135      585135 : isaSQLquery(MalBlkPtr mb)
     136             : {
     137      585135 :         if (mb) {
     138    12239690 :                 for (int i = 1; i < mb->stop; i++) {
     139    11910628 :                         InstrPtr p = getInstrPtr(mb, i);
     140    11910628 :                         if (getModuleId(p) && idcmp(getModuleId(p), "querylog") == 0
     141      256073 :                                 && idcmp(getFunctionId(p), "define") == 0)
     142      256073 :                                 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       26636 : clearQRYqueue(size_t idx)
     158             : {
     159       26636 :         QRYqueue[idx] = (struct QRYQUEUE) { 0 };
     160             : }
     161             : 
     162             : static void
     163         350 : dropQRYqueue(void)
     164             : {
     165         350 :         MT_lock_set(&mal_delayLock);
     166       25830 :         for (size_t i = 0; i < qsize; i++) {
     167       25480 :                 GDKfree(QRYqueue[i].query);
     168       25480 :                 GDKfree(QRYqueue[i].username);
     169       25480 :                 clearQRYqueue(i);
     170             :         }
     171         350 :         GDKfree(QRYqueue);
     172         350 :         QRYqueue = NULL;
     173         350 :         qsize = 0;
     174         350 :         qtag = 1;
     175         350 :         MT_lock_unset(&mal_delayLock);
     176         350 : }
     177             : 
     178             : oid
     179      255817 : runtimeProfileSetTag(Client cntxt)
     180             : {
     181      255817 :         MT_lock_set(&mal_delayLock);
     182      256471 :         cntxt->curprg->def->tag = qtag++;
     183      256471 :         MT_lock_unset(&mal_delayLock);
     184             : 
     185      256471 :         return cntxt->curprg->def->tag;
     186             : }
     187             : 
     188             : /* At the start of every MAL block or SQL query */
     189             : void
     190      756025 : runtimeProfileInit(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
     191             : {
     192      756025 :         static size_t qlast = 0;
     193      756025 :         size_t i, paused = 0;
     194      756025 :         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      756025 :         if (stk->up)
     199             :                 return;
     200      585085 :         MT_lock_set(&mal_delayLock);
     201             : 
     202      585135 :         if (USRstats == NULL) {
     203         351 :                 usrstatscnt = MAL_MAXCLIENTS;
     204         351 :                 USRstats = (UserStats) GDKzalloc(sizeof(struct USERSTAT) * usrstatscnt);
     205         351 :                 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      585135 :         if (QRYqueue == NULL) {
     213         351 :                 QRYqueue = (QueryQueue) GDKzalloc(sizeof(struct QRYQUEUE) *
     214             :                                                                                   (qsize = MAL_MAXCLIENTS));
     215             : 
     216         351 :                 if (QRYqueue == NULL) {
     217           0 :                         addMalException(mb, "runtimeProfileInit" MAL_MALLOC_FAIL);
     218           0 :                         MT_lock_unset(&mal_delayLock);
     219           0 :                         return;
     220             :                 }
     221             :         }
     222    52203723 :         for (i = 0; i < qsize; i++) {
     223   102912005 :                 paused += QRYqueue[i].status && (QRYqueue[i].status[0] == 'p' || QRYqueue[i].status[0] == 'r'); /* running, prepared or paused */
     224             :         }
     225      585135 :         if (qsize - paused < (size_t) MAL_MAXCLIENTS) {
     226          19 :                 qsize += MAL_MAXCLIENTS;
     227          19 :                 QueryQueue tmp;
     228          19 :                 tmp = (QueryQueue) GDKrealloc(QRYqueue,
     229             :                                                                           sizeof(struct QRYQUEUE) * qsize);
     230          19 :                 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          19 :                 QRYqueue = tmp;
     237        1175 :                 for (i = qsize - MAL_MAXCLIENTS; i < qsize; i++)
     238        1156 :                         clearQRYqueue(i);
     239             :         }
     240             :         // add new invocation
     241      585153 :         for (i = 0; i < qsize; i++) {
     242      585153 :                 size_t j = qlast;
     243      585153 :                 if (++qlast >= qsize)
     244        8119 :                         qlast = 0;
     245      585153 :                 if (QRYqueue[j].stk == NULL ||
     246          18 :                         QRYqueue[j].status == NULL ||
     247          18 :                         (QRYqueue[j].status[0] != 'r' && QRYqueue[j].status[0] != 'p')) {
     248      585135 :                         QRYqueue[j].mb = mb;
     249      585135 :                         QRYqueue[j].tag = stk->tag = mb->tag;
     250      585135 :                         QRYqueue[j].stk = stk;  // for status pause 'p'/running '0'/ quitting 'q'
     251      585135 :                         QRYqueue[j].finished = 0;
     252      585135 :                         QRYqueue[j].start = time(0);
     253      585135 :                         q = isaSQLquery(mb);
     254      585135 :                         GDKfree(QRYqueue[j].query);
     255      585135 :                         QRYqueue[j].query = GDKstrdup(q);       /* NULL in, NULL out */
     256      585135 :                         GDKfree(QRYqueue[j].username);
     257      585135 :                         if (!GDKembedded())
     258      583300 :                                 QRYqueue[j].username = GDKstrdup(cntxt->username);
     259      585135 :                         QRYqueue[j].idx = cntxt->idx;
     260             :                         /* give the MB upperbound by addition of 1 MB */
     261      585135 :                         QRYqueue[j].memory = 1 + (int) (stk->memory / LL_CONSTANT(1048576)); /* Convert to MB */
     262      585135 :                         QRYqueue[j].workers = (int) 1;  /* this is the first one */
     263      585135 :                         QRYqueue[j].status = "running";
     264      585135 :                         QRYqueue[j].cntxt = cntxt;
     265      585135 :                         QRYqueue[j].ticks = GDKusec();
     266      585135 :                         break;
     267             :                 }
     268             :         }
     269      585135 :         MT_lock_unset(&mal_delayLock);
     270      585135 :         MT_lock_set(&mal_contextLock);
     271      585135 :         cntxt->idle = 0;
     272      585135 :         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      755247 : runtimeProfileFinish(Client cntxt, MalBlkPtr mb, MalStkPtr stk)
     282             : {
     283      755247 :         size_t i;
     284      755247 :         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      755247 :         if (stk->up)
     289             :                 return;
     290      584567 :         MT_lock_set(&mal_delayLock);
     291    26176035 :         for (i = 0; i < qsize; i++) {
     292    25590900 :                 if (QRYqueue[i].stk == stk) {
     293      585135 :                         if (QRYqueue[i].status[0] == 's')
     294           1 :                                 QRYqueue[i].status = "aborted";
     295             :                         else
     296      585134 :                                 QRYqueue[i].status = "finished";
     297      585135 :                         QRYqueue[i].finished = time(0);
     298      585135 :                         QRYqueue[i].workers = (int) ATOMIC_GET(&mb->workers);
     299             :                         /* give the MB upperbound by addition of 1 MB */
     300      585135 :                         QRYqueue[i].memory = 1 + (int) (mb->memory / LL_CONSTANT(1048576));
     301      585135 :                         QRYqueue[i].cntxt = NULL;
     302      585135 :                         QRYqueue[i].stk = NULL;
     303      585135 :                         QRYqueue[i].mb = NULL;
     304      585135 :                         QRYqueue[i].ticks = GDKusec() - QRYqueue[i].ticks;
     305      585135 :                         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      585135 :                         MT_lock_unset(&mal_delayLock);
     309      585135 :                         MT_lock_set(&mal_contextLock);
     310      585135 :                         cntxt->idle = time(0);
     311      585135 :                         MT_lock_unset(&mal_contextLock);
     312      585135 :                         found = true;
     313      585135 :                         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      585135 :         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             :                                         struct tm tmp;
     330             :                                         char tbuf[64];
     331             :                                         (void) localtime_r(&QRYqueue[i].start, &tmp);
     332             :                                         strftime(tbuf, sizeof(tbuf), "%F %T", &tmp);
     333             :                                         TRC_INFO_ENDIF(MAL_SERVER,
     334             :                                                                    "QRYqueue[%zu]: stk(%p), tag(" OIDFMT
     335             :                                                                    "), username(%s), start(%s), status(%s), query(%s)",
     336             :                                                                    i, QRYqueue[i].stk, QRYqueue[i].tag,
     337             :                                                                    QRYqueue[i].username, tbuf,
     338             :                                                                    QRYqueue[i].status, QRYqueue[i].query);
     339             :                                 }
     340             :                         }
     341             :                 }
     342      755815 :                 MT_lock_unset(&mal_delayLock);
     343             :         }
     344             : 
     345             : }
     346             : 
     347             : /* Used by mal_reset to do the grand final clean up of this area before MonetDB exits */
     348             : void
     349         350 : mal_runtime_reset(void)
     350             : {
     351         350 :         dropQRYqueue();
     352         350 :         dropUSRstats();
     353         350 : }
     354             : 
     355             : /* At the start of each MAL stmt */
     356             : void
     357    67956071 : runtimeProfileBegin(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
     358             :                                         RuntimeProfile prof)
     359             : {
     360    67956071 :         (void) cntxt;
     361    67956071 :         (void) mb;
     362    67956071 :         (void) stk;
     363    67956071 :         (void) pci;
     364             : 
     365    67956071 :         assert(pci);
     366             :         /* always collect the MAL instruction execution time */
     367    67956071 :         prof->ticks = GDKusec();
     368    67974757 : }
     369             : 
     370             : /* At the end of each MAL stmt */
     371             : void
     372    67995043 : runtimeProfileExit(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
     373             :                                    RuntimeProfile prof)
     374             : {
     375    67995043 :         lng ticks = GDKusec();
     376             : 
     377    68036280 :         if (profilerStatus > 0)
     378          45 :                 profilerEvent(&(struct MalEvent) { cntxt, mb, stk, pci, ticks,
     379          45 :                                           ticks - prof->ticks },
     380             :                                           NULL);
     381    68036280 :         if (cntxt->sqlprofiler)
     382         942 :                 sqlProfilerEvent(cntxt, mb, stk, pci, ticks, ticks - prof->ticks);
     383    68036281 :         if (profilerStatus < 0) {
     384             :                 /* delay profiling until you encounter start of MAL function */
     385           0 :                 if (getInstrPtr(mb, 0) == pci)
     386           0 :                         profilerStatus = 1;
     387             :         }
     388    68036281 : }
     389             : 
     390             : /*
     391             :  * For performance evaluation it is handy to estimate the
     392             :  * amount of bytes produced by an instruction.
     393             :  * The actual amount is harder to guess, because an instruction
     394             :  * may trigger a side effect, such as creating a hash-index.
     395             :  * Side effects are ignored.
     396             :  */
     397             : 
     398             : lng
     399           0 : getBatSpace(BAT *b)
     400             : {
     401           0 :         lng space = 0;
     402           0 :         if (b == NULL)
     403             :                 return 0;
     404           0 :         space += BATcount(b) << b->tshift;
     405           0 :         if (space) {
     406           0 :                 MT_lock_set(&b->theaplock);
     407           0 :                 if (b->tvheap)
     408           0 :                         space += heapinfo(b->tvheap, b->batCacheid);
     409           0 :                 MT_lock_unset(&b->theaplock);
     410           0 :                 MT_rwlock_rdlock(&b->thashlock);
     411           0 :                 space += hashinfo(b->thash, b->batCacheid);
     412           0 :                 MT_rwlock_rdunlock(&b->thashlock);
     413             :         }
     414             :         return space;
     415             : }
     416             : 
     417             : lng
     418           0 : getVolume(MalStkPtr stk, InstrPtr pci, int rd)
     419             : {
     420           0 :         int i, limit;
     421           0 :         lng vol = 0;
     422           0 :         BAT *b;
     423             : 
     424           0 :         if (stk == NULL)
     425             :                 return 0;
     426           0 :         limit = rd ? pci->argc : pci->retc;
     427           0 :         i = rd ? pci->retc : 0;
     428             : 
     429           0 :         for (; i < limit; i++) {
     430           0 :                 if (stk->stk[getArg(pci, i)].bat) {
     431           0 :                         oid cnt = 0;
     432             : 
     433           0 :                         b = BBPquickdesc(stk->stk[getArg(pci, i)].val.bval);
     434           0 :                         if (b == NULL)
     435           0 :                                 continue;
     436           0 :                         cnt = BATcount(b);
     437             :                         /* Usually reading views cost as much as full bats.
     438             :                            But when we output a slice that is not the case. */
     439           0 :                         if (rd)
     440           0 :                                 vol += (!isVIEW(b) && !VIEWtparent(b)) ? tailsize(b, cnt) : 0;
     441           0 :                         else if (!isVIEW(b))
     442           0 :                                 vol += tailsize(b, cnt);
     443             :                 }
     444             :         }
     445             :         return vol;
     446             : }

Generated by: LCOV version 1.14