source: trunk/lang/tcl/src/RpRusage.c @ 2079

Last change on this file since 2079 was 2079, checked in by mmc, 13 years ago

Misc fixes that have been pending in my local directory for a while.
Added "usage mark" and "usage measure" options to the usage command,
for better handling of simulation times.

File size: 12.2 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  Rappture::rusage
4 *
5 *  This is an interface to the system getrusage() routine.  It allows
6 *  you to query resource used by child executables.  We use this in
7 *  Rappture to track the usage during each click of the "Simulate"
8 *  button.
9 * ======================================================================
10 *  AUTHOR:  Michael McLennan, Purdue University
11 *  Copyright (c) 2004-2006  Purdue Research Foundation
12 *
13 *  See the file "license.terms" for information on usage and
14 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15 * ======================================================================
16 */
17#include <tcl.h>
18#include <string.h>
19#ifndef WIN32
20#include <sys/time.h>
21#include <sys/resource.h>
22#else
23#include "RpWinResource.h"
24#endif
25
26#include "RpOp.h"
27
28/*
29 * Store rusage info in this data structure:
30 */
31typedef struct RpRusageStats {
32    struct timeval times;
33    struct rusage resources;
34} RpRusageStats;
35
36static RpRusageStats RpRusage_Start;      /* time at start of program */
37
38static Tcl_ObjCmdProc RpRusageCmd;
39static Tcl_ObjCmdProc RpRusageForgetOp;
40static Tcl_ObjCmdProc RpRusageMarkOp;
41static Tcl_ObjCmdProc RpRusageMeasureOp;
42
43static int RpRusageCapture _ANSI_ARGS_((Tcl_Interp *interp,
44    RpRusageStats *rptr));
45static double RpRusageTimeDiff _ANSI_ARGS_((struct timeval *currptr,
46    struct timeval *prevptr));
47static void RpDestroyMarkNames _ANSI_ARGS_((ClientData cdata,
48    Tcl_Interp *interp));
49
50/*
51 * rusage subcommands:
52 */
53static Rp_OpSpec rusageOps[] = {
54    {"forget", 1, RpRusageForgetOp, 2, 0, "?name...?",},
55    {"mark",    2, RpRusageMarkOp, 2, 3, "?name?",},
56    {"measure", 2, RpRusageMeasureOp, 2, 3, "?name?",},
57};
58static int nRusageOps = sizeof(rusageOps) / sizeof(Rp_OpSpec);
59
60/*
61 * ------------------------------------------------------------------------
62 *  RpRusage_Init()
63 *
64 *  Called in Rappture_Init() to initialize the commands defined
65 *  in this file.
66 * ------------------------------------------------------------------------
67 */
68int
69RpRusage_Init(interp)
70    Tcl_Interp *interp;  /* interpreter being initialized */
71{
72    Tcl_HashTable *markNamesPtr;
73
74    Tcl_CreateObjCommand(interp, "::Rappture::rusage", RpRusageCmd,
75        NULL, NULL);
76
77    /* set up a hash table for different mark names */
78    markNamesPtr = (Tcl_HashTable*)ckalloc(sizeof(Tcl_HashTable));
79    Tcl_InitHashTable(markNamesPtr, TCL_STRING_KEYS);
80
81    Tcl_SetAssocData(interp, "RpRusageMarks",
82        RpDestroyMarkNames, (ClientData)markNamesPtr);
83
84    /* capture the starting time for this program */
85    if (RpRusageCapture(interp, &RpRusage_Start) != TCL_OK) {
86        return TCL_ERROR;
87    }
88
89    /* set an initial "global" mark automatically */
90    if (RpRusageMarkOp(NULL, interp, 0, (Tcl_Obj**)NULL) != TCL_OK) {
91        return TCL_ERROR;
92    }
93
94    return TCL_OK;
95}
96
97/*
98 * ------------------------------------------------------------------------
99 *  RpRusageCmd()
100 *
101 *  Invoked whenever someone uses the "rusage" command to get/set
102 *  limits for child processes.  Handles the following syntax:
103 *
104 *      rusage mark
105 *      rusage measure
106 *
107 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
108 *  message in the interpreter) if anything goes wrong.
109 * ------------------------------------------------------------------------
110 */
111static int
112RpRusageCmd(cdata, interp, objc, objv)
113    ClientData cdata;         /* not used */
114    Tcl_Interp *interp;       /* interpreter handling this request */
115    int objc;                 /* number of command line args */
116    Tcl_Obj *const *objv;     /* strings for command line args */
117{
118    Tcl_ObjCmdProc *proc;
119
120    proc = Rp_GetOpFromObj(interp, nRusageOps, rusageOps, RP_OP_ARG1,
121        objc, objv, 0);
122
123    if (proc == NULL) {
124        return TCL_ERROR;
125    }
126    return (*proc)(cdata, interp, objc, objv);
127}
128
129/*
130 * ------------------------------------------------------------------------
131 *  RpRusageMarkOp()
132 *
133 *  Invoked whenever someone uses the "rusage mark" command to mark
134 *  the start of an execution.  Handles the following syntax:
135 *
136 *      rusage mark ?name?
137 *
138 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
139 *  message in the interpreter) if anything goes wrong.
140 * ------------------------------------------------------------------------
141 */
142static int
143RpRusageMarkOp(cdata, interp, objc, objv)
144    ClientData cdata;         /* not used */
145    Tcl_Interp *interp;       /* interpreter handling this request */
146    int objc;                 /* number of command line args */
147    Tcl_Obj *const *objv;     /* strings for command line args */
148{
149    char *markName;
150    Tcl_HashTable *markNamesPtr;
151    Tcl_HashEntry *entryPtr;
152    int newEntry;
153    RpRusageStats *markPtr;
154
155    markNamesPtr = (Tcl_HashTable *)
156        Tcl_GetAssocData(interp, "RpRusageMarks", NULL);
157
158    markName = (objc > 2) ? Tcl_GetString(objv[2]): "global";
159    entryPtr = Tcl_CreateHashEntry(markNamesPtr, markName, &newEntry);
160    if (newEntry) {
161        markPtr = (RpRusageStats*)ckalloc(sizeof(RpRusageStats));
162        Tcl_SetHashValue(entryPtr, (ClientData)markPtr);
163    } else {
164        markPtr = (RpRusageStats*)Tcl_GetHashValue(entryPtr);
165    }
166
167    return RpRusageCapture(interp, markPtr);
168}
169
170/*
171 * ------------------------------------------------------------------------
172 *  RpRusageMeasureOp()
173 *
174 *  Invoked whenever someone uses the "rusage measure" command to
175 *  measure resource usage since the last "mark" operation.  Handles
176 *  the following syntax:
177 *
178 *      rusage measure ?name?
179 *
180 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
181 *  message in the interpreter) if anything goes wrong.
182 * ------------------------------------------------------------------------
183 */
184static int
185RpRusageMeasureOp(cdata, interp, objc, objv)
186    ClientData cdata;         /* not used */
187    Tcl_Interp *interp;       /* interpreter handling this request */
188    int objc;                 /* number of command line args */
189    Tcl_Obj *const *objv;     /* strings for command line args */
190{
191    char *markName;
192    Tcl_HashTable *markNamesPtr;
193    Tcl_HashEntry *entryPtr;
194    RpRusageStats *markPtr;
195    double tval;
196    RpRusageStats curstats;
197    char buffer[TCL_DOUBLE_SPACE];
198
199    if (RpRusageCapture(interp, &curstats) != TCL_OK) {
200        return TCL_ERROR;
201    }
202
203    markNamesPtr = (Tcl_HashTable *)
204        Tcl_GetAssocData(interp, "RpRusageMarks", NULL);
205
206    markName = (objc > 2) ? Tcl_GetString(objv[2]): "global";
207    entryPtr = Tcl_FindHashEntry(markNamesPtr, markName);
208    if (entryPtr == NULL) {
209        Tcl_AppendResult(interp, "mark \"", markName,
210            "\" doesn't exist", NULL);
211        return TCL_ERROR;
212    }
213    markPtr = (RpRusageStats*)Tcl_GetHashValue(entryPtr);
214
215    /*
216     * Compute: START TIME
217     */
218    Tcl_AppendElement(interp, "start");
219    tval = RpRusageTimeDiff(&markPtr->times, &RpRusage_Start.times);
220    Tcl_PrintDouble(interp, tval, buffer);
221    Tcl_AppendElement(interp, buffer);
222
223    /*
224     * Compute: WALL TIME
225     */
226    Tcl_AppendElement(interp, "walltime");
227    tval = RpRusageTimeDiff(&curstats.times, &markPtr->times);
228    Tcl_PrintDouble(interp, tval, buffer);
229    Tcl_AppendElement(interp, buffer);
230
231    /*
232     * Compute: CPU TIME = user time + system time
233     */
234    Tcl_AppendElement(interp, "cputime");
235    tval = RpRusageTimeDiff(&curstats.resources.ru_utime,
236             &markPtr->resources.ru_utime)
237         + RpRusageTimeDiff(&curstats.resources.ru_stime,
238             &markPtr->resources.ru_stime);
239    Tcl_PrintDouble(interp, tval, buffer);
240    Tcl_AppendElement(interp, buffer);
241
242    return TCL_OK;
243}
244
245/*
246 * ------------------------------------------------------------------------
247 *  RpRusageForgetOp()
248 *
249 *  Invoked whenever someone uses the "rusage forget" command to release
250 *  information previous set by "rusage mark".  With no args, it releases
251 *  all known marks; otherwise, it releases just the specified mark
252 *  names.  This isn't usually needed, but if a program creates thousands
253 *  of marks, this gives a way to avoid a huge memory leak.
254 *  Handles the following syntax:
255 *
256 *      rusage forget ?name name...?
257 *
258 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
259 *  message in the interpreter) if anything goes wrong.
260 * ------------------------------------------------------------------------
261 */
262static int
263RpRusageForgetOp(cdata, interp, objc, objv)
264    ClientData cdata;         /* not used */
265    Tcl_Interp *interp;       /* interpreter handling this request */
266    int objc;                 /* number of command line args */
267    Tcl_Obj *const *objv;     /* strings for command line args */
268{
269    int i;
270    char *markName;
271    Tcl_HashTable *markNamesPtr;
272    Tcl_HashEntry *entryPtr;
273    Tcl_HashSearch search;
274
275    markNamesPtr = (Tcl_HashTable *)
276        Tcl_GetAssocData(interp, "RpRusageMarks", NULL);
277
278    /*
279     * No args? Then clear all entries in the hash table.
280     */
281    if (objc == 2) {
282        for (entryPtr = Tcl_FirstHashEntry(markNamesPtr, &search);
283             entryPtr != NULL;
284             entryPtr = Tcl_NextHashEntry(&search)) {
285            ckfree((char *)Tcl_GetHashValue(entryPtr));
286        }
287        Tcl_DeleteHashTable(markNamesPtr);
288        Tcl_InitHashTable(markNamesPtr, TCL_STRING_KEYS);
289    }
290
291    /*
292     * Otherwise, delete only the specified marks.  Be forgiving.
293     * If a mark isn't recognized, ignore it.
294     */
295    else {
296        for (i=2; i < objc; i++) {
297            markName = Tcl_GetString(objv[i]);
298            entryPtr = Tcl_FindHashEntry(markNamesPtr, markName);
299            if (entryPtr) {
300                ckfree((char *)Tcl_GetHashValue(entryPtr));
301                Tcl_DeleteHashEntry(entryPtr);
302            }
303        }
304    }
305    return TCL_OK;
306}
307
308/*
309 * ------------------------------------------------------------------------
310 *  RpRusageCapture()
311 *
312 *  Used internally to capture a snapshot of current time and resource
313 *  usage.  Stores the stats in the given data structure.
314 *
315 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
316 *  message in the interpreter) if anything goes wrong.
317 * ------------------------------------------------------------------------
318 */
319static int
320RpRusageCapture(interp, rptr)
321    Tcl_Interp *interp;       /* interpreter handling this request */
322    RpRusageStats *rptr;      /* returns: snapshot of stats */
323{
324    int status;
325
326    status = getrusage(RUSAGE_CHILDREN, &rptr->resources);
327    if (status != 0) {
328        Tcl_AppendResult(interp, "unexpected error from getrusage()",
329            (char*)NULL);
330        return TCL_ERROR;
331    }
332
333    status = gettimeofday(&rptr->times, (struct timezone*)NULL);
334    if (status != 0) {
335        Tcl_AppendResult(interp, "unexpected error from gettimeofday()",
336            (char*)NULL);
337        return TCL_ERROR;
338    }
339
340    return TCL_OK;
341}
342
343/*
344 * ------------------------------------------------------------------------
345 *  RpRusageTimeDiff()
346 *
347 *  Used internally to compute the difference between two timeval
348 *  structures.  Returns a double precision value representing the
349 *  time difference.
350 * ------------------------------------------------------------------------
351 */
352static double
353RpRusageTimeDiff(currptr, prevptr)
354    struct timeval *currptr;  /* current time */
355    struct timeval *prevptr;  /*  - previous time */
356{
357    double tval;
358
359    if (prevptr) {
360        tval = (currptr->tv_sec - prevptr->tv_sec)
361                 + 1.0e-6*(currptr->tv_usec - prevptr->tv_usec);
362    } else {
363        tval = currptr->tv_sec + 1.0e-6*currptr->tv_usec;
364    }
365    return tval;
366}
367
368/*
369 * ------------------------------------------------------------------------
370 *  RpDestroyMarkNames()
371 *
372 *  Used internally to clean up the marker names when the interpreter
373 *  that owns them is being destroyed.
374 * ------------------------------------------------------------------------
375 */
376static void
377RpDestroyMarkNames(cdata, interp)
378    ClientData cdata;         /* data being destroyed */
379    Tcl_Interp *interp;       /* interpreter that owned the data */
380{
381    Tcl_HashTable *markNamesPtr;
382    Tcl_HashEntry *entryPtr;
383    Tcl_HashSearch search;
384
385    for (entryPtr=Tcl_FirstHashEntry(markNamesPtr, &search);
386         entryPtr != NULL;
387         entryPtr = Tcl_NextHashEntry(&search)) {
388        ckfree( (char*)Tcl_GetHashValue(entryPtr) );
389    }
390    Tcl_DeleteHashTable(markNamesPtr);
391    ckfree((char*)markNamesPtr);
392}
Note: See TracBrowser for help on using the repository browser.