1 /*
2  * nsfProfile.c --
3  *
4  *      Provides profiling on Next Scripting Framework internals.
5  *      For turning on profiling, NSF_PROFILE must be configured.
6  *
7  * Copyright (C) 2010-2017 Gustaf Neumann
8  *
9  * Vienna University of Economics and Business
10  * Institute of Information Systems and New Media
11  * A-1020, Welthandelsplatz 1
12  * Vienna, Austria
13  *
14  * This work is licensed under the MIT License https://www.opensource.org/licenses/MIT
15  *
16  * Copyright:
17  *
18  * Permission is hereby granted, free of charge, to any person obtaining a
19  * copy of this software and associated documentation files (the "Software"),
20  * to deal in the Software without restriction, including without limitation
21  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
22  * and/or sell copies of the Software, and to permit persons to whom the
23  * Software is furnished to do so, subject to the following conditions:
24  *
25  * The above copyright notice and this permission notice shall be included in
26  * all copies or substantial portions of the Software.
27  *
28  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
33  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
34  * DEALINGS IN THE SOFTWARE.
35  *
36  */
37 
38 #include "nsfInt.h"
39 
40 #if defined(NSF_PROFILE)
41 typedef struct NsfProfileData {
42   long microSec;
43   long count;
44 } NsfProfileData;
45 #endif
46 
47 /*
48  *----------------------------------------------------------------------
49  * NsfProfileObjectLabel, NsfProfileMethodLabel --
50  *
51  *    Produce a string label for an object or method using in profiling.
52  *    NsfProfileMethodLabel() is available also when compiled without
53  *    NSF_PROFILE.
54  *
55  * Results:
56  *    None
57  *
58  * Side effects:
59  *    Initializes and fills the passed Tcl_DString,
60  *
61  *----------------------------------------------------------------------
62  */
63 static void NsfProfileObjectLabel(Tcl_DString *dsPtr, NsfObject *object)
64   nonnull(1) nonnull(2);
65 
66 static void
NsfProfileObjectLabel(Tcl_DString * dsPtr,NsfObject * object)67 NsfProfileObjectLabel(Tcl_DString *dsPtr, NsfObject *object) {
68 
69   nonnull_assert(dsPtr != NULL);
70   nonnull_assert(object != NULL);
71 
72   Tcl_DStringAppend(dsPtr, ObjectName_(object), -1);
73   Tcl_DStringAppend(dsPtr, " ", 1);
74   Tcl_DStringAppend(dsPtr, ClassName(object->cl), -1);
75 }
76 
77 static void NsfProfileMethodLabel(Tcl_DString *dsPtr, NsfClass *class, const char *methodName)
78   nonnull(1) nonnull(3);
79 
80 static void
NsfProfileMethodLabel(Tcl_DString * dsPtr,NsfClass * class,const char * methodName)81 NsfProfileMethodLabel(Tcl_DString *dsPtr, NsfClass *class, const char *methodName) {
82 
83   nonnull_assert(dsPtr != NULL);
84   nonnull_assert(methodName != NULL);
85 
86   Tcl_DStringAppendElement(dsPtr, methodName);
87   if (class != NULL) {
88     Tcl_DStringAppend(dsPtr, " ", 1);
89     Tcl_DStringAppend(dsPtr, ObjStr(class->object.cmdName), -1);
90   }
91 
92 }
93 
94 
95 /*
96  *----------------------------------------------------------------------
97  * NsfProfileDeprecatedCall --
98  *
99  *    Output a line in case a deprecated function/method is called using
100  *    the low-level NsfDeprecatedCmd() function.
101  *
102  * Results:
103  *    None
104  *
105  * Side effects:
106  *    logging
107  *
108  *----------------------------------------------------------------------
109  */
110 void
NsfProfileDeprecatedCall(Tcl_Interp * interp,NsfObject * UNUSED (object),NsfClass * class,const char * methodName,const char * altMethod)111 NsfProfileDeprecatedCall(Tcl_Interp *interp, NsfObject *UNUSED(object), NsfClass *class,
112                          const char *methodName, const char *altMethod) {
113   Tcl_DString ds;
114 
115   nonnull_assert(interp != NULL);
116   //nonnull_assert(object != NULL);
117   nonnull_assert(methodName != NULL);
118   nonnull_assert(altMethod != NULL);
119 
120   Tcl_DStringInit(&ds);
121   Tcl_DStringAppend(&ds, "{", 1);
122   NsfProfileMethodLabel(&ds, class, methodName);
123   Tcl_DStringAppend(&ds, "}", 1);
124 
125   NsfDeprecatedCmd(interp, "method", ds.string, altMethod);
126   Tcl_DStringFree(&ds);
127 }
128 
129 /*
130  *----------------------------------------------------------------------
131  * NsfProfileDebugCall, NsfProfileDebugExit --
132  *
133  *    Output a line in case a function/method is called/exited having the
134  *    debug flag set.  These two functions use the Tcl commands ::nsf::debug::call
135  *    and ::nsf::debug::exit for reporting.
136  *
137  * Results:
138  *    None
139  *
140  * Side effects:
141  *    logging
142  *
143  *----------------------------------------------------------------------
144  */
145 void
NsfProfileDebugCall(Tcl_Interp * interp,NsfObject * object,NsfClass * class,const char * methodName,int objc,Tcl_Obj ** objv)146 NsfProfileDebugCall(Tcl_Interp *interp, NsfObject *object, NsfClass *class, const char *methodName,
147                     int objc, Tcl_Obj **objv) {
148   NsfRuntimeState *rst;
149   Tcl_Obj         *listObj;
150   Tcl_DString      ds;
151 
152   nonnull_assert(interp != NULL);
153   nonnull_assert(methodName != NULL);
154 
155   rst = RUNTIME_STATE(interp);
156   rst->debugCallingDepth++;
157 
158   Tcl_DStringInit(&ds);
159   Nsf_DStringPrintf(&ds, "::nsf::debug::call %d {", rst->debugCallingDepth);
160   if (object != NULL) {
161     NsfProfileObjectLabel(&ds, object);
162   }
163   Tcl_DStringAppend(&ds, "} {", 3);
164   NsfProfileMethodLabel(&ds, class, methodName);
165   Tcl_DStringAppend(&ds, "}", 1);
166 
167   listObj = Tcl_NewListObj(objc, objv);
168   INCR_REF_COUNT(listObj);
169   Nsf_DStringPrintf(&ds, " {%s}", ObjStr(listObj));
170   DECR_REF_COUNT(listObj);
171 
172   NsfDStringEval(interp, &ds, "debug call", (NSF_EVAL_DEBUG|NSF_EVAL_SAVE|NSF_EVAL_NOPROFILE));
173 
174   Tcl_DStringFree(&ds);
175 
176 }
177 
178 void
NsfProfileDebugExit(Tcl_Interp * interp,NsfObject * object,NsfClass * class,const char * methodName,long startSec,long startUsec)179 NsfProfileDebugExit(Tcl_Interp *interp, NsfObject *object, NsfClass *class, const char *methodName,
180                     long startSec, long startUsec) {
181   Tcl_DString      ds, *dsPtr = &ds;
182   NsfRuntimeState *rst;
183 
184   nonnull_assert(interp != NULL);
185   nonnull_assert(methodName != NULL);
186 
187   rst = RUNTIME_STATE(interp);
188   Tcl_DStringInit(dsPtr);
189   Nsf_DStringPrintf(dsPtr, "::nsf::debug::exit %d {", rst->debugCallingDepth);
190 
191   if (object != NULL) {
192     NsfProfileObjectLabel(dsPtr, object);
193   }
194   Tcl_DStringAppend(dsPtr, "} {", 3);
195   NsfProfileMethodLabel(dsPtr, class, methodName);
196   Tcl_DStringAppend(dsPtr, "} ", 1);
197   Tcl_DStringAppendElement(dsPtr, ObjStr(Tcl_GetObjResult(interp)));
198 
199   if (startSec != 0 || startUsec != 0) {
200     struct Tcl_Time trt;
201 
202     Tcl_GetTime(&trt);
203     Nsf_DStringPrintf(dsPtr, " %ld ", (trt.sec - startSec) * 1000000 + (trt.usec - startUsec));
204   } else {
205     Tcl_DStringAppend(dsPtr, " {}", 4);
206   }
207 
208   NsfDStringEval(interp, &ds, "debug exit", (NSF_EVAL_DEBUG|NSF_EVAL_SAVE|NSF_EVAL_NOPROFILE));
209 
210   Tcl_DStringFree(dsPtr);
211   rst->debugCallingDepth--;
212 }
213 
214 
215 #if defined(NSF_PROFILE)
216 /*
217  *----------------------------------------------------------------------
218  * ReportLine --
219  *
220  *    Report a profile line via NsfLog(). Since NsfLog() uses a Tcl function,
221  *    ReportLine has to turn off profiling to avoid recursive profile
222  *    invocation. It is as well necessary to save the interp result.
223  *
224  * Results:
225  *    None
226  *
227  * Side effects:
228  *    logging
229  *
230  *----------------------------------------------------------------------
231  */
232 static void ReportLine(Tcl_Interp *interp, int level, NsfRuntimeState *rst, const char *line)
233   nonnull(1) nonnull(3) nonnull(4);
234 
235 static void
ReportLine(Tcl_Interp * interp,int level,NsfRuntimeState * rst,const char * line)236 ReportLine(Tcl_Interp *interp, int level, NsfRuntimeState *rst, const char *line) {
237   Tcl_Obj *savedResultObj;
238   int      prevProfileSetting;
239 
240   nonnull_assert(interp != NULL);
241   nonnull_assert(rst != NULL);
242   nonnull_assert(line != NULL);
243 
244   prevProfileSetting = rst->doProfile;
245   rst->doProfile = 0;
246 
247   savedResultObj = Tcl_GetObjResult(interp);
248   INCR_REF_COUNT(savedResultObj);
249 
250   NsfLog(interp, level, "%s", line);
251 
252   Tcl_SetObjResult(interp, savedResultObj);
253   DECR_REF_COUNT(savedResultObj);
254 
255   rst->doProfile = prevProfileSetting;
256 }
257 
258 /*
259  *----------------------------------------------------------------------
260  * NsfProfileFillTable --
261  *
262  *    Insert or Update a keyed entry with provided microseconds and
263  *    update the counts for this entry.
264  *
265  * Results:
266  *    None
267  *
268  * Side effects:
269  *    Updated or created profile data entry
270  *
271  *----------------------------------------------------------------------
272  */
273 static void NsfProfileFillTable(Tcl_HashTable *table, const char *keyStr, double totalMicroSec)
274   nonnull(1) nonnull(2);
275 
276 static void
NsfProfileFillTable(Tcl_HashTable * table,const char * keyStr,double totalMicroSec)277 NsfProfileFillTable(Tcl_HashTable *table, const char *keyStr, double totalMicroSec) {
278   NsfProfileData *value;
279   Tcl_HashEntry  *hPtr;
280   int             isNew;
281 
282   nonnull_assert(table != NULL);
283   nonnull_assert(keyStr != NULL);
284 
285   hPtr = Tcl_CreateHashEntry(table, keyStr, &isNew);
286   if (isNew != 0) {
287     value = (NsfProfileData *)ckalloc(sizeof(NsfProfileData));
288     value->microSec = 0;
289     value->count = 0;
290     Tcl_SetHashValue(hPtr, (ClientData) value);
291   } else {
292     value = (NsfProfileData *)Tcl_GetHashValue (hPtr);
293   }
294   value->microSec += (long)totalMicroSec;
295   value->count ++;
296 }
297 
298 /*
299  *----------------------------------------------------------------------
300  * Nsf_ProfileFilterObjCmd --
301  *
302  *    Stub command to include C-level commands in profile traces.
303  *
304  * Results:
305  *    Tcl result code
306  *
307  * Side effects:
308  *    Perform tracing
309  *
310  *----------------------------------------------------------------------
311  */
312 static int
Nsf_ProfileFilterObjCmd(ClientData cd,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])313 Nsf_ProfileFilterObjCmd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
314   NsfShadowTclCommandInfo *ti;
315   int             result;
316   struct Tcl_Time start;
317   const char     *fullMethodName, *label;
318   Tcl_DString     ds;
319 
320   assert(cd != NULL);
321 
322   fullMethodName = ObjStr(objv[0]);
323   ti = (NsfShadowTclCommandInfo *)cd;
324 
325   if (ti->nrArgs == 0 || objc < 2) {
326     label = fullMethodName;
327   } else {
328     int i, nrArgs = objc;
329 
330     if (nrArgs > ti->nrArgs) {
331       nrArgs = ti->nrArgs;
332     }
333 
334     Tcl_DStringInit(&ds);
335     Tcl_DStringAppend(&ds, fullMethodName, -1);
336     for (i = 1; i<=nrArgs; i++) {
337       Tcl_DStringAppend(&ds, " ", 1);
338       Tcl_DStringAppend(&ds, ObjStr(objv[i]), -1);
339     }
340     label = ds.string;
341   }
342 
343   NsfProfileTraceCallAppend(interp, label);
344 
345   Tcl_GetTime(&start);
346   result = Tcl_NRCallObjProc(interp, ti->proc, ti->clientData, objc, objv);
347   NsfProfileRecordProcData(interp, label, start.sec, start.usec);
348 
349   if (label != fullMethodName) {
350     Tcl_DStringFree(&ds);
351   }
352   return result;
353 }
354 
355 /*
356  *----------------------------------------------------------------------
357  * GetPair --
358  *
359  *    Split a Tcl_Obj into a nameObj and an integer value, if possible
360  *
361  * Results:
362  *    Tcl result
363  *
364  * Side effects:
365  *    Produce warnings for error cases, when "verbose" is on.
366  *
367  *----------------------------------------------------------------------
368  */
369 static int
370 GetPair(Tcl_Interp *interp, Tcl_Obj *objPtr, int verbose, Tcl_Obj **nameObjPtr, int *nrArgsPtr)
371   nonnull(1) nonnull(2) nonnull(4) nonnull(5);
372 
373 static int
GetPair(Tcl_Interp * interp,Tcl_Obj * objPtr,int verbose,Tcl_Obj ** nameObjPtr,int * nrArgsPtr)374 GetPair(Tcl_Interp *interp, Tcl_Obj *objPtr, int verbose, Tcl_Obj **nameObjPtr, int *nrArgsPtr) {
375   int result = TCL_OK, oc;
376   Tcl_Obj **ov;
377 
378   if (Tcl_ListObjGetElements(interp, objPtr, &oc, &ov) != TCL_OK) {
379     if (verbose) {
380       NsfLog(interp, NSF_LOG_WARN, "nsfprofile: invalid list element '%s'", ObjStr(objPtr));
381       result = TCL_ERROR;
382     }
383   } else {
384     if (oc == 1) {
385       *nameObjPtr = ov[0];
386     } else if (oc == 2) {
387       if (Tcl_GetIntFromObj(interp, ov[1], nrArgsPtr) == TCL_OK) {
388         *nameObjPtr = ov[0];
389       } else {
390         if (verbose) {
391           NsfLog(interp, NSF_LOG_WARN, "nsfprofile: second element of '%s' must be an integer", ObjStr(objPtr));
392           result = TCL_ERROR;
393         }
394       }
395     } else {
396       if (verbose) {
397         NsfLog(interp, NSF_LOG_WARN, "nsfprofile: list element '%s' not a valid pair", ObjStr(objPtr));
398         result = TCL_ERROR;
399       }
400     }
401   }
402 
403   return result;
404 }
405 
406 /*
407  *----------------------------------------------------------------------
408  * NsfProfileTrace --
409  *
410  *    Function to control trace behavior callable via Tcl.
411  *
412  * Results:
413  *    OK
414  *
415  * Side effects:
416  *    update RUNTIME_STATE(interp)->doTrace
417  *    and    profilePtr->verbose
418  *    and    profilePtr->inmemory
419  *
420  *----------------------------------------------------------------------
421  */
422 int
NsfProfileTrace(Tcl_Interp * interp,int withEnable,int withVerbose,int withDontsave,Tcl_Obj * builtinObjs)423 NsfProfileTrace(Tcl_Interp *interp, int withEnable, int withVerbose, int withDontsave, Tcl_Obj *builtinObjs) {
424   NsfRuntimeState *rst;
425   NsfProfile      *profilePtr;
426   int              oldProfileState, oc;
427   Tcl_Obj        **ov;
428 
429   nonnull_assert(interp != NULL);
430 
431   rst = RUNTIME_STATE(interp);
432   profilePtr = &rst->profile;
433 
434   oldProfileState = rst->doTrace;
435   rst->doTrace = withEnable;
436 
437   /*
438    * Turn automatically profiling on&off, when trace is turned on/off
439    */
440   if (withEnable == 1) {
441     if (rst->doProfile == 1) {
442       NsfLog(interp, NSF_LOG_WARN, "nsfprofile: tracing is already active");
443     } else {
444       /*
445        * Activate profile trace.
446        */
447       if (builtinObjs != NULL) {
448         /*
449          * A list of commands was provided
450          */
451         if (Tcl_ListObjGetElements(interp, builtinObjs, &oc, &ov) != TCL_OK) {
452           NsfLog(interp, NSF_LOG_WARN, "nsfprofile: argument '%s' is not a list of commands", ObjStr(builtinObjs));
453         } else {
454           int i;
455           NsfShadowTclCommandInfo *ti = NEW_ARRAY(NsfShadowTclCommandInfo, oc);
456 
457           for (i = 0; i < oc; i++) {
458             int      nrArgs = 0;
459             Tcl_Obj *nameObj = NULL;
460 
461             if (GetPair(interp, ov[i], 1, &nameObj, &nrArgs) == TCL_OK) {
462               assert(nameObj != NULL);
463               ti[i].nrArgs = nrArgs;
464 
465               if (NsfReplaceCommand(interp, nameObj, Nsf_ProfileFilterObjCmd, &ti[i], &ti[i]) != TCL_OK) {
466                 NsfLog(interp, NSF_LOG_WARN, "nsfprofile: list element '%s' is not a command", ObjStr(nameObj));
467               }
468             }
469           }
470           INCR_REF_COUNT(builtinObjs);
471           profilePtr->shadowedObjs = builtinObjs;
472           profilePtr->shadowedTi = ti;
473         }
474       }
475     }
476   } else {
477     /*
478      * Deactivate profile trace.
479      */
480     if (profilePtr->shadowedObjs != NULL) {
481 
482       if (Tcl_ListObjGetElements(interp, profilePtr->shadowedObjs, &oc, &ov) != TCL_OK) {
483         NsfLog(interp, NSF_LOG_WARN, "nsfprofile: shadowed objects are apparently not a list");
484       } else {
485         int i;
486 
487         for (i = 0; i < oc; i++) {
488           int      nrArgs = 0;
489           Tcl_Obj *nameObj = NULL;
490 
491           if (GetPair(interp, ov[i], 0, &nameObj, &nrArgs) == TCL_OK) {
492             assert(nameObj != NULL);
493             NsfReplaceCommandCleanup(interp, nameObj, &profilePtr->shadowedTi[i]);
494           }
495         }
496       }
497       INCR_REF_COUNT(profilePtr->shadowedObjs);
498 
499       FREE(NsfShadowTclCommandInfo*, profilePtr->shadowedTi);
500       profilePtr->shadowedTi = NULL;
501       profilePtr->shadowedObjs = NULL;
502       /*fprintf(stderr, "freed profile information\n");*/
503     }
504 
505   }
506 
507   rst->doProfile = withEnable;
508 
509   profilePtr->verbose = withVerbose;
510   profilePtr->inmemory = (withDontsave == 1) ? 0 : 1;
511   Tcl_SetObjResult(interp, Tcl_NewBooleanObj(oldProfileState));
512 
513   return TCL_OK;
514 }
515 
516 
517 
518 /*
519  *----------------------------------------------------------------------
520  * NsfProfileTraceCallAppend, NsfProfileTraceExitAppend --
521  *
522  *    Low level function to add entries to the trace Tcl_DString when functions ar
523  *    called or exited.
524  *
525  * Results:
526  *    None
527  *
528  * Side effects:
529  *    update profilePtr->depth and profilePtr->traceDs
530  *
531  *----------------------------------------------------------------------
532  */
533 void
NsfProfileTraceCallAppend(Tcl_Interp * interp,const char * label)534 NsfProfileTraceCallAppend(Tcl_Interp *interp, const char *label) {
535   NsfRuntimeState *rst = RUNTIME_STATE(interp);
536   NsfProfile *profilePtr = &rst->profile;
537   Tcl_DString ds;
538 
539   profilePtr->depth ++;
540 
541   Tcl_DStringInit(&ds);
542   Nsf_DStringPrintf(&ds, "call(%d): %s", profilePtr->depth, label);
543   if (profilePtr->verbose) {
544     ReportLine(interp, NSF_LOG_NOTICE, rst, ds.string);
545   }
546   if (profilePtr->inmemory) {
547     Tcl_DStringAppend(&ds, "\n", 1);
548     Tcl_DStringAppend(&profilePtr->traceDs, ds.string, ds.length);
549   }
550   Tcl_DStringFree(&ds);
551 }
552 
553 void
NsfProfileTraceExitAppend(Tcl_Interp * interp,const char * label,double duration)554 NsfProfileTraceExitAppend(Tcl_Interp *interp, const char *label, double duration) {
555   NsfRuntimeState *rst = RUNTIME_STATE(interp);
556   NsfProfile *profilePtr = &rst->profile;
557   Tcl_DString ds;
558 
559   Tcl_DStringInit(&ds);
560   Nsf_DStringPrintf(&ds, "exit(%d): %s %.0f", profilePtr->depth, label, duration);
561   if (profilePtr->verbose) {
562     ReportLine(interp, NSF_LOG_NOTICE, rst, ds.string);
563   }
564   if (profilePtr->inmemory) {
565     Tcl_DStringAppend(&ds, "\n", 1);
566     Tcl_DStringAppend(&profilePtr->traceDs, ds.string, ds.length);
567   }
568   Tcl_DStringFree(&ds);
569 
570   profilePtr->depth --;
571 }
572 
573 
574 /*
575  *----------------------------------------------------------------------
576  * NsfProfileTraceCall, NsfProfileTraceExit --
577  *
578  *    Add entries to the trace Tcl_DString when methods/procs are called or
579  *    exited.  This function builds the labels for invocation strings in the
580  *    same way as for profiling and calls the lower-level function, which does
581  *    the recording.
582  *
583  * Results:
584  *    None
585  *
586  * Side effects:
587  *    update profilePtr->depth and profilePtr->traceDs
588  *
589  *----------------------------------------------------------------------
590  */
591 
592 void
NsfProfileTraceCall(Tcl_Interp * interp,NsfObject * object,NsfClass * class,const char * methodName)593 NsfProfileTraceCall(Tcl_Interp *interp, NsfObject *object, NsfClass *class, const char *methodName) {
594   NsfRuntimeState *rst = RUNTIME_STATE(interp);
595 
596   if (rst->doTrace) {
597     Tcl_DString ds, traceLabel;
598 
599     Tcl_DStringInit(&ds);
600     NsfProfileObjectLabel(&ds, object);
601 
602     Tcl_DStringInit(&traceLabel);
603     Tcl_DStringAppendElement(&traceLabel, Tcl_DStringValue(&ds));
604     Tcl_DStringAppend(&traceLabel, " ", 1);
605 
606     Tcl_DStringSetLength(&ds, 0);
607     NsfProfileMethodLabel(&ds, class, methodName);
608     Tcl_DStringAppendElement(&traceLabel, Tcl_DStringValue(&ds));
609 
610     NsfProfileTraceCallAppend(interp, Tcl_DStringValue(&traceLabel));
611     Tcl_DStringFree(&traceLabel);
612     Tcl_DStringFree(&ds);
613   }
614 }
615 
616 
617 void
NsfProfileTraceExit(Tcl_Interp * interp,NsfObject * object,NsfClass * class,const char * methodName,struct Tcl_Time * callTime)618 NsfProfileTraceExit(Tcl_Interp *interp, NsfObject *object, NsfClass *class, const char *methodName,
619                     struct Tcl_Time *callTime) {
620   NsfRuntimeState *rst = RUNTIME_STATE(interp);
621 
622   if (rst->doTrace) {
623     Tcl_DString ds, traceLabel;
624     double totalMicroSec;
625     struct Tcl_Time trt;
626 
627     Tcl_GetTime(&trt);
628     totalMicroSec = (double)((trt.sec - callTime->sec) * 1000000 + (trt.usec - callTime->usec));
629 
630     Tcl_DStringInit(&ds);
631     NsfProfileObjectLabel(&ds, object);
632 
633     Tcl_DStringInit(&traceLabel);
634     Tcl_DStringAppendElement(&traceLabel, Tcl_DStringValue(&ds));
635     Tcl_DStringAppend(&traceLabel, " ", 1);
636 
637     Tcl_DStringSetLength(&ds, 0);
638     NsfProfileMethodLabel(&ds, class, methodName);
639     Tcl_DStringAppendElement(&traceLabel, Tcl_DStringValue(&ds));
640 
641     NsfProfileTraceExitAppend(interp, Tcl_DStringValue(&traceLabel), totalMicroSec);
642     Tcl_DStringFree(&traceLabel);
643     Tcl_DStringFree(&ds);
644   }
645 }
646 
647 /*
648  *----------------------------------------------------------------------
649  * NsfProfileRecordMethodData --
650  *
651  *    This function is invoked, when a call of a method ends. It
652  *    records profiling information based on the provided call stack
653  *    content and the caller. In particular, it records the time spent
654  *    in an object (identified with an objectKey) and the time spent
655  *    in the method (using methodKey).
656  *
657  * Results:
658  *    None
659  *
660  * Side effects:
661  *    Updated or created profile data entries
662  *
663  *----------------------------------------------------------------------
664  */
665 void
NsfProfileRecordMethodData(Tcl_Interp * interp,NsfCallStackContent * cscPtr)666 NsfProfileRecordMethodData(Tcl_Interp *interp, NsfCallStackContent *cscPtr) {
667   NsfRuntimeState *rst;
668   double           totalMicroSec;
669   NsfObject       *obj;
670   NsfClass        *cl;
671   Tcl_DString      methodKey, objectKey, methodInfo;
672   NsfProfile      *profilePtr;
673   struct Tcl_Time  trt;
674 
675   nonnull_assert(interp != NULL);
676   nonnull_assert(cscPtr != NULL);
677 
678   Tcl_GetTime(&trt);
679   rst = RUNTIME_STATE(interp);
680   profilePtr = &rst->profile;
681 
682   totalMicroSec = (double)((trt.sec - cscPtr->startSec) * 1000000 + (trt.usec - cscPtr->startUsec));
683   profilePtr->overallTime += (long)totalMicroSec;
684 
685   obj = cscPtr->self;
686   if (obj->teardown == 0 || !obj->id) {
687     return;
688   }
689 
690   Tcl_DStringInit(&objectKey);
691   NsfProfileObjectLabel(&objectKey, obj);
692 
693   Tcl_DStringInit(&methodInfo);
694   Tcl_DStringInit(&methodKey);
695   cl = cscPtr->cl;
696   NsfProfileMethodLabel(&methodInfo, cl, cscPtr->methodName);
697 
698   if (rst->doTrace) {
699     Tcl_DString traceKey;
700 
701     Tcl_DStringInit(&traceKey);
702     Tcl_DStringAppendElement(&traceKey, Tcl_DStringValue(&objectKey));
703     Tcl_DStringAppend(&traceKey, " ", 1);
704     Tcl_DStringAppendElement(&traceKey, Tcl_DStringValue(&methodInfo));
705     NsfProfileTraceExitAppend(interp, Tcl_DStringValue(&traceKey), totalMicroSec);
706     Tcl_DStringFree(&traceKey);
707   }
708 
709   /*
710    * Append method to object key as needed by statistics (but not by trace)
711    */
712   Tcl_DStringAppendElement(&objectKey, cscPtr->methodName);
713 
714   /*
715    * Build method key, containing actual method info and caller method info.
716    */
717   Tcl_DStringInit(&methodKey);
718   Tcl_DStringAppend(&methodKey, "{", 1);
719   Tcl_DStringAppend(&methodKey, Tcl_DStringValue(&methodInfo), Tcl_DStringLength(&methodInfo));
720   Tcl_DStringAppend(&methodKey, "}", 1);
721 
722   {
723     NsfCallStackContent *cscPtrTop = NsfCallStackGetTopFrame(interp, NULL);
724     if (cscPtrTop != NULL) {
725       Tcl_DStringAppend(&methodKey, " {", 2);
726       NsfProfileMethodLabel(&methodKey, cscPtrTop->cl, cscPtrTop->methodName);
727       Tcl_DStringAppend(&methodKey, "}", 1);
728     } else {
729       Tcl_DStringAppend(&methodKey, " {}", 3);
730     }
731   }
732 
733   NsfProfileFillTable(&profilePtr->objectData, Tcl_DStringValue(&objectKey), totalMicroSec);
734   NsfProfileFillTable(&profilePtr->methodData, Tcl_DStringValue(&methodKey), totalMicroSec);
735   Tcl_DStringFree(&objectKey);
736   Tcl_DStringFree(&methodKey);
737   Tcl_DStringFree(&methodInfo);
738 }
739 
740 
741 
742 /*
743  *----------------------------------------------------------------------
744  * NsfProfileRecordProcData --
745  *
746  *    This function is invoked, when a call of a nsf::proc. It records
747  *    time spent and count per nsf::proc.
748  *
749  * Results:
750  *    None
751  *
752  * Side effects:
753  *    Updated or created profile data entries
754  *
755  *----------------------------------------------------------------------
756  */
757 void
NsfProfileRecordProcData(Tcl_Interp * interp,const char * methodName,long startSec,long startUsec)758 NsfProfileRecordProcData(Tcl_Interp *interp, const char *methodName, long startSec, long startUsec) {
759   NsfRuntimeState *rst;
760   NsfProfile      *profilePtr;
761   double           totalMicroSec;
762   struct Tcl_Time  trt;
763 
764   nonnull_assert(interp != NULL);
765   nonnull_assert(methodName != NULL);
766 
767   rst = RUNTIME_STATE(interp);
768   profilePtr = &rst->profile;
769 
770   Tcl_GetTime(&trt);
771 
772   totalMicroSec = (double)((trt.sec - startSec) * 1000000 + (trt.usec - startUsec));
773   profilePtr->overallTime += (long)totalMicroSec;
774 
775   if (rst->doTrace) {
776     NsfProfileTraceExitAppend(interp, methodName, totalMicroSec);
777   }
778 
779   NsfProfileFillTable(&profilePtr->procData, methodName, totalMicroSec);
780 }
781 
782 /*
783  *----------------------------------------------------------------------
784  * NsfProfileClearTable --
785  *
786  *    Clear all data in a profile table.
787  *
788  * Results:
789  *    None
790  *
791  * Side effects:
792  *    freed profile information.
793  *
794  *----------------------------------------------------------------------
795  */
796 
797 
798 static void NsfProfileClearTable(Tcl_HashTable *table) nonnull(1);
799 
800 static void
NsfProfileClearTable(Tcl_HashTable * table)801 NsfProfileClearTable(Tcl_HashTable *table) {
802   Tcl_HashSearch hSrch;
803   Tcl_HashEntry *hPtr;
804 
805   nonnull_assert(table != NULL);
806 
807   for (hPtr = Tcl_FirstHashEntry(table, &hSrch); hPtr != NULL;
808        hPtr = Tcl_NextHashEntry(&hSrch)) {
809     NsfProfileData *value = (NsfProfileData *) Tcl_GetHashValue(hPtr);
810     ckfree((char *) value);
811     Tcl_DeleteHashEntry(hPtr);
812   }
813 }
814 
815 /*
816  *----------------------------------------------------------------------
817  * NsfProfileClearData --
818  *
819  *    Flush all data in all profile tables and reset the time
820  *    counters.
821  *
822  * Results:
823  *    None
824  *
825  * Side effects:
826  *    freed profile information.
827  *
828  *----------------------------------------------------------------------
829  */
830 
831 void
NsfProfileClearData(Tcl_Interp * interp)832 NsfProfileClearData(Tcl_Interp *interp) {
833   NsfProfile      *profilePtr;
834   struct Tcl_Time  trt;
835 
836   nonnull_assert(interp != NULL);
837 
838   profilePtr = &RUNTIME_STATE(interp)->profile;
839   NsfProfileClearTable(&profilePtr->objectData);
840   NsfProfileClearTable(&profilePtr->methodData);
841   NsfProfileClearTable(&profilePtr->procData);
842 
843   Tcl_GetTime(&trt);
844   profilePtr->startSec = trt.sec;
845   profilePtr->startUSec = trt.usec;
846   profilePtr->overallTime = 0;
847   profilePtr->depth = 0;
848 
849   Tcl_DStringSetLength(&profilePtr->traceDs, 0);
850 }
851 
852 /*
853  *----------------------------------------------------------------------
854  * NsfProfileGetTable --
855  *
856  *    Return the profiling information for the specified profile table
857  *    in form of a Tcl list.
858  *
859  * Results:
860  *    Tcl List
861  *
862  * Side effects:
863  *    None.
864  *
865  *----------------------------------------------------------------------
866  */
867 static Tcl_Obj* NsfProfileGetTable(Tcl_Interp *interp, Tcl_HashTable *table) nonnull(1) nonnull(2);
868 
869 static Tcl_Obj*
NsfProfileGetTable(Tcl_Interp * interp,Tcl_HashTable * table)870 NsfProfileGetTable(Tcl_Interp *interp, Tcl_HashTable *table) {
871   Tcl_Obj        *list = Tcl_NewListObj(0, NULL);
872   Tcl_HashSearch  hSrch;
873   Tcl_HashEntry  *hPtr;
874 
875   nonnull_assert(interp != NULL);
876   nonnull_assert(table != NULL);
877 
878   for (hPtr = Tcl_FirstHashEntry(table, &hSrch); hPtr != NULL;
879        hPtr = Tcl_NextHashEntry(&hSrch)) {
880     NsfProfileData *value = (NsfProfileData *) Tcl_GetHashValue(hPtr);
881     char           *key = Tcl_GetHashKey(table, hPtr);
882     Tcl_Obj        *subList = Tcl_NewListObj(0, NULL);
883 
884     Tcl_ListObjAppendElement(interp, subList, Tcl_NewStringObj(key, -1));
885     Tcl_ListObjAppendElement(interp, subList, Tcl_NewLongObj(value->microSec));
886     Tcl_ListObjAppendElement(interp, subList, Tcl_NewLongObj(value->count));
887     Tcl_ListObjAppendElement(interp, list, subList);
888   }
889   return list;
890 }
891 
892 /*
893  *----------------------------------------------------------------------
894  * NsfProfileGetData --
895  *
896  *    Return recorded profiling information. This function returns a
897  *    list containing (a) the elapsed time since the last clear (or
898  *    init), (b) the cumulative time, (c) the list with the per-object
899  *    data and (d) the list with the method invocation data.
900  *
901  * Results:
902  *    Tcl List
903  *
904  * Side effects:
905  *    None.
906  *
907  *----------------------------------------------------------------------
908  */
909 
910 void
NsfProfileGetData(Tcl_Interp * interp)911 NsfProfileGetData(Tcl_Interp *interp) {
912   Tcl_Obj        *list = Tcl_NewListObj(0, NULL);
913   NsfProfile     *profilePtr;
914   long            totalMicroSec;
915   struct Tcl_Time trt;
916 
917   nonnull_assert(interp != NULL);
918 
919   profilePtr = &RUNTIME_STATE(interp)->profile;
920   Tcl_GetTime(&trt);
921   totalMicroSec = (trt.sec - profilePtr->startSec) * 1000000 + (trt.usec - profilePtr->startUSec);
922 
923   Tcl_ListObjAppendElement(interp, list, Tcl_NewLongObj((long)totalMicroSec));
924   Tcl_ListObjAppendElement(interp, list, Tcl_NewLongObj(profilePtr->overallTime));
925   Tcl_ListObjAppendElement(interp, list, NsfProfileGetTable(interp, &profilePtr->objectData));
926   Tcl_ListObjAppendElement(interp, list, NsfProfileGetTable(interp, &profilePtr->methodData));
927   Tcl_ListObjAppendElement(interp, list, NsfProfileGetTable(interp, &profilePtr->procData));
928   Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(profilePtr->traceDs.string, profilePtr->traceDs.length));
929 
930   Tcl_SetObjResult(interp, list);
931 }
932 
933 /*
934  *----------------------------------------------------------------------
935  * NsfProfileInit --
936  *
937  *    Initialize the profiling information. This is a one-time only
938  *    operation and initializes the hash table and the timing
939  *    results. The inverse operation is NsfProfileFree()
940  *
941  * Results:
942  *    None.
943  *
944  * Side effects:
945  *    None.
946  *
947  *----------------------------------------------------------------------
948  */
949 void
NsfProfileInit(Tcl_Interp * interp)950 NsfProfileInit(Tcl_Interp *interp) {
951   NsfProfile     *profilePtr;
952   struct Tcl_Time trt;
953 
954   nonnull_assert(interp != NULL);
955 
956   profilePtr = &RUNTIME_STATE(interp)->profile;
957   Tcl_InitHashTable(&profilePtr->objectData, TCL_STRING_KEYS);
958   Tcl_InitHashTable(&profilePtr->methodData, TCL_STRING_KEYS);
959   Tcl_InitHashTable(&profilePtr->procData, TCL_STRING_KEYS);
960 
961   Tcl_GetTime(&trt);
962   profilePtr->startSec = trt.sec;
963   profilePtr->startUSec = trt.usec;
964   profilePtr->overallTime = 0;
965   profilePtr->depth = 0;
966   Tcl_DStringInit(&profilePtr->traceDs);
967 }
968 
969 /*
970  *----------------------------------------------------------------------
971  * NsfProfileFree --
972  *
973  *    Free all profiling information. This is a one-time only
974  *    operation only. The inverse operation is NsfProfileInit().
975  *
976  * Results:
977  *    None.
978  *
979  * Side effects:
980  *    None.
981  *
982  *----------------------------------------------------------------------
983  */
984 void
NsfProfileFree(Tcl_Interp * interp)985 NsfProfileFree(Tcl_Interp *interp) {
986   NsfProfile *profilePtr;
987 
988   nonnull_assert(interp != NULL);
989 
990   profilePtr = &RUNTIME_STATE(interp)->profile;
991   NsfProfileClearData(interp);
992   Tcl_DeleteHashTable(&profilePtr->objectData);
993   Tcl_DeleteHashTable(&profilePtr->methodData);
994   Tcl_DeleteHashTable(&profilePtr->procData);
995   Tcl_DStringFree(&profilePtr->traceDs);
996 }
997 #endif
998 
999 /*
1000  * Local Variables:
1001  * mode: c
1002  * c-basic-offset: 2
1003  * fill-column: 78
1004  * indent-tabs-mode: nil
1005  * End:
1006  */
1007