1 /*---------------------------------------------------------------------------
2  * Gamedriver - Dump object statistics.
3  *
4  *---------------------------------------------------------------------------
5  * Function to compute the memory usage of objects and dump their
6  * statistics.
7  *---------------------------------------------------------------------------
8  */
9 
10 #include "driver.h"
11 #include "typedefs.h"
12 
13 #include <stdio.h>
14 #ifdef HAVE_SYS_TIME_H
15 #include <sys/time.h>
16 #endif
17 #include <time.h>
18 
19 #include "dumpstat.h"
20 #include "array.h"
21 #include "closure.h"
22 #include "exec.h"
23 #include "filestat.h"
24 #include "instrs.h"  /* F_RETURN, F_RETURN0 for overhead computation */
25 #include "mapping.h"
26 #include "mstrings.h"
27 #include "object.h"
28 #include "ptrtable.h"
29 #include "simulate.h"
30 #include "stdstrings.h"
31 #ifdef USE_STRUCTS
32 #include "structs.h"
33 #endif /* USE_STRUCTS */
34 #include "svalue.h"
35 #include "xalloc.h"
36 
37 /*-------------------------------------------------------------------------*/
38 
39 static size_t svalue_size(svalue_t *, mp_int *); /* forward */
40 
41 /* Auxiliary structure for counting a mapping */
42 
43 struct svalue_size_locals
44 {
45     mp_uint composite;   /* Return: total composite memory usage of all elmts */
46     mp_uint total;       /* Return: total memory usage of all elmts */
47     int     num_values;  /* Passed: width of the mapping */
48 };
49 
50 /*-------------------------------------------------------------------------*/
51 
52 static struct pointer_table *ptable;
53   /* The pointer_table to register all arrays and mappings.
54    */
55 
56 /*-------------------------------------------------------------------------*/
57 static void
svalue_size_map_filter(svalue_t * key,svalue_t * values,void * extra)58 svalue_size_map_filter (svalue_t *key, svalue_t *values, void *extra)
59 
60 /* Called for all keys in a mapping, it counts the size of the values
61  * and returns the total in extra->num_values.
62  */
63 
64 {
65     struct svalue_size_locals *locals;
66     mp_int total;
67     int i;
68 
69     locals = (struct svalue_size_locals*)extra;
70     locals->composite += svalue_size(key, &total);
71     locals->total += total;
72     for(i = locals->num_values; --i >= 0; )
73     {
74         locals->composite += svalue_size(values++, &total) + sizeof(svalue_t);
75         locals->total += total + sizeof(svalue_t);
76     }
77 }
78 
79 /*-------------------------------------------------------------------------*/
80 static size_t
svalue_size(svalue_t * v,mp_int * pTotal)81 svalue_size (svalue_t *v, mp_int * pTotal)
82 
83 /* Compute the memory usage of *<v> (modified to reflect data sharing),
84  * calling svalue_size() recursively if necessary, and return it.
85  * The size of *v itself is not included.
86  * *<pUnshared> and *<pShared> are set to the total unshared and shared
87  * datasize.
88  */
89 
90 {
91     mp_int i, composite, total, overhead;
92 
93     assert_stack_gap();
94 
95     *pTotal = 0;
96 
97     total = overhead = composite = 0;
98 
99     switch(v->type)
100     {
101     case T_OBJECT:
102     case T_NUMBER:
103     case T_FLOAT:
104         return 0;
105 
106     case T_STRING:
107     case T_SYMBOL:
108         // If ref==0 the string is probably shared a lot, but we can't estimate
109         // the correct number, so we return 0 as memory consumption for the
110         // string.
111         if (v->u.str->info.ref)
112         {
113             *pTotal = mstr_mem_size(v->u.str);
114             return *pTotal / v->u.str->info.ref;
115         }
116         else
117             return 0;
118 
119     case T_MAPPING:
120     {
121         struct svalue_size_locals locals;
122 
123         if (NULL == register_pointer(ptable, v->u.map) ) return 0;
124 
125         if (v->u.map->ref)
126         {
127             overhead = (mp_uint)mapping_overhead(v->u.map);
128             locals.total = 0;
129             locals.composite = 0;
130             locals.num_values = v->u.map->num_values;
131             walk_mapping(v->u.map, svalue_size_map_filter, &locals);
132 
133             *pTotal = locals.total + overhead;
134             return (overhead + locals.composite) / v->u.map->ref;
135         }
136         else
137             return 0;
138     }
139 
140     case T_POINTER:
141     case T_QUOTED_ARRAY:
142     {
143         if (v->u.vec == &null_vector) return 0;
144         if (NULL == register_pointer(ptable, v->u.vec) ) return 0;
145         if (v->u.vec->ref)
146         {
147             overhead = sizeof *v->u.vec - sizeof v->u.vec->item +
148               sizeof(svalue_t) * v->u.vec->size + sizeof(char *);
149             for (i=0; i < (mp_int)VEC_SIZE(v->u.vec); i++) {
150                 composite += svalue_size(&v->u.vec->item[i], &total);
151                 *pTotal += total;
152             }
153             *pTotal += overhead;
154 
155             return (overhead + composite) / v->u.vec->ref;
156         }
157         else
158             return 0;
159     }
160 
161 #ifdef USE_STRUCTS
162     case T_STRUCT:
163     {
164         struct_t *st = v->u.strct;
165         if (NULL == register_pointer(ptable, st) ) return 0;
166         if (st->ref)
167         {
168             overhead = sizeof *st - sizeof st->member
169                       + sizeof(svalue_t) * struct_size(st);
170             for (i=0; i < (mp_int)struct_size(st); i++) {
171                 composite += svalue_size(&st->member[i], &total);
172                 *pTotal += total;
173             }
174             *pTotal += overhead;
175 
176             return (overhead + composite) / st->ref;
177         }
178         else
179             return 0;
180     }
181 #endif /* USE_STRUCTS */
182 
183     case T_CLOSURE:
184     {
185         int num_values;
186         svalue_t *svp;
187         lambda_t *l;
188 
189         if (!CLOSURE_MALLOCED(v->x.closure_type)) return 0;
190         if (!CLOSURE_REFERENCES_CODE(v->x.closure_type))
191         {
192 #ifdef USE_NEW_INLINES
193             if (v->x.closure_type == CLOSURE_LFUN)
194                 composite = SIZEOF_LAMBDA(v->u.lambda->function.lfun.context_size);
195             else /* CLOSURE_IDENTIFIER || CLOSURE_PRELIMINARY */
196                 composite = sizeof *v->u.lambda;
197 
198             composite += sizeof(char *);
199 #else
200             /* CLOSURE_LFUN || CLOSURE_IDENTIFIER || CLOSURE_PRELIMINARY */
201             composite = sizeof *v->u.lambda + sizeof(char *);
202 #endif
203             *pTotal = composite;
204             return composite / v->u.lambda->ref;
205         }
206         /* CLOSURE_LAMBDA */
207         composite = overhead = 0;
208         l = v->u.lambda;
209         if (v->x.closure_type == CLOSURE_BOUND_LAMBDA)
210         {
211             total = sizeof *l - sizeof l->function + sizeof l->function.lambda;
212             *pTotal += total;
213             composite += total / l->ref;
214             l = l->function.lambda;
215         }
216         num_values = EXTRACT_UCHAR(&l->function.code[0]);
217         if (num_values == 0xff)
218             num_values = ((svalue_t *)l)[-0xff].u.number;
219         svp = (svalue_t *)l - num_values;
220         if (NULL == register_pointer(ptable, svp)) return 0;
221         overhead = sizeof(svalue_t) * num_values + sizeof (char *);
222         {
223             bytecode_p p = &l->function.code[2];
224             do {
225                 ++p;
226                 switch(GET_CODE(p)) {
227                   case F_RETURN:
228                   case F_RETURN0:
229                     break;
230                   default:
231                     continue;
232                 }
233                 break;
234             } while (1);
235             overhead +=   (p - (bytecode_p)l + (sizeof(bytecode_p) - 1))
236                         & ~(sizeof(bytecode_p) - 1);
237         }
238 
239         while (--num_values >= 0)
240         {
241             composite += svalue_size(svp++, &total);
242             *pTotal += total;
243         }
244 
245         *pTotal += overhead;
246         if (l->ref)
247             return (overhead + composite) / l->ref;
248         else
249             return 0;
250     }
251 
252     default:
253         fatal("Illegal type: %d\n", v->type);
254     }
255 
256     /*NOTREACHED*/
257     return 0;
258 }
259 
260 /*-------------------------------------------------------------------------*/
261 mp_int
data_size(object_t * ob,mp_int * pTotal)262 data_size (object_t *ob, mp_int * pTotal)
263 
264 /* Compute the memory usage (modified to reflect shared data use)
265  * of the data held by object <ob> and return it.
266  * If the object is swapped out or has no variables, return 0.
267  *
268  * If <pTotal> is given, *<pTotal> is set to the raw datasize.
269  */
270 
271 {
272     mp_int total = sizeof(p_int); /* smalloc overhead */
273     int i;
274     svalue_t *svp;
275 
276     if (pTotal != NULL)
277         *pTotal = 0;
278 
279     if (ob->flags & O_SWAPPED || !(i = ob->prog->num_variables) )
280         return 0;
281     ptable = new_pointer_table();
282     if (!ptable)
283         errorf("(dumpstat) Out of memory for new pointer table.\n");
284     for (svp = ob->variables; --i >= 0; svp++)
285     {
286         mp_int tmp;
287         total += svalue_size(svp, &tmp) + sizeof (svalue_t);
288         if (pTotal != NULL)
289             *pTotal += tmp + sizeof(svalue_t);
290     }
291     free_pointer_table(ptable);
292     return total;
293 } /* data_size() */
294 
295 /*-------------------------------------------------------------------------*/
296 mp_int
program_string_size(program_t * prog,mp_int * pOverhead,mp_int * pData)297 program_string_size (program_t *prog, mp_int * pOverhead, mp_int * pData)
298 
299 /* Compute the composite data size of all strings in program <prog>
300  * which must be swapped in.
301  * Set *<pOverhead> to the size of the overhead in the program structure,
302  * and *<pData> to the raw data size of the strings.
303  */
304 
305 {
306     int i;
307     mp_int rc, data;
308 
309     rc = data = 0;
310 
311     for (i = prog->num_strings; i--; )
312     {
313         string_t * str = prog->strings[i];
314         mp_int size;
315 
316         size = mstr_mem_size(str);
317         data += size;
318         rc += size / str->info.ref;
319     }
320 
321     *pOverhead = prog->num_strings * sizeof(char *);
322     *pData = data;
323     return rc;
324 } /* program_string_size() */
325 
326 /*-------------------------------------------------------------------------*/
327 Bool
dumpstat(string_t * fname)328 dumpstat (string_t *fname)
329 
330 /* This function dumps statistics about all listed objects into the file
331  * $MUDLIB/<fname>. It is called by the command parser or from debug_info.
332  * Return TRUE on success, FALSE if <fname> can't be written.
333  */
334 
335 {
336     FILE *f;
337     object_t *ob;
338 
339     static char *swapstrings[] =
340         {"", "PROG SWAPPED", "VAR SWAPPED", "SWAPPED", };
341 
342     fname = check_valid_path(fname, current_object, STR_OBJDUMP, MY_TRUE);
343     if (!fname)
344         return MY_FALSE;
345     f = fopen(get_txt(fname), "w");
346     if (!f)
347     {
348         free_mstring(fname);
349         return MY_FALSE;
350     }
351     FCOUNT_WRITE(get_txt(fname));
352 
353     for (ob = obj_list; ob; ob = ob->next_all)
354     {
355         mp_int compsize, totalsize, overhead;
356         char timest[21];
357         struct tm *tm;
358 
359 #ifdef DEBUG
360         if (ob->flags & O_DESTRUCTED) /* TODO: Can't happen */
361             continue;
362 #endif
363         compsize = data_size(ob, &totalsize);
364 
365         if (!O_PROG_SWAPPED(ob)
366          && (ob->prog->ref == 1 || !(ob->flags & (O_CLONE|O_REPLACED))))
367         {
368              overhead = ob->prog->total_size;
369         }
370         else
371         {
372             overhead = 0;
373         }
374 
375         overhead += sizeof (object_t);
376 
377         fprintf(f, "%-20s %5"PRIdMPINT" (%5"PRIdMPINT") ref %2"PRIdPINT" %s "
378                  , get_txt(ob->name)
379                  , compsize + overhead, totalsize + overhead
380                  , ob->ref
381                  , ob->flags & O_HEART_BEAT ? "HB" : "  "
382         );
383         if (ob->super)
384             fprintf(f, "%s ", get_txt(ob->super->name));
385         else
386             fprintf(f, "-- ");
387 
388         if (ob->gigaticks)
389             fprintf(f, " (%"PRIuMPINT"%09"PRIuMPINT")",
390                     (mp_uint)ob->gigaticks, (mp_uint)ob->ticks);
391         else
392             fprintf(f, " (%"PRIuMPINT")", (mp_uint)ob->ticks);
393         fprintf(f, " %s",
394                 swapstrings[(O_PROG_SWAPPED(ob)?1:0) | (O_VAR_SWAPPED(ob)?2:0)]
395         );
396         tm = localtime((time_t *)&ob->load_time);
397         strftime(timest, sizeof(timest), "%Y.%m.%d-%H:%M:%S", tm);
398         fprintf(f, " %s\n", timest);
399     }
400     fclose(f);
401     free_mstring(fname);
402 
403     return MY_TRUE;
404 } /* dumpstat() */
405 
406 /*-------------------------------------------------------------------------*/
407 Bool
dumpstat_dest(string_t * fname)408 dumpstat_dest(string_t *fname)
409 
410 /* this function dumps statistics about all destructed objects into the file
411  * $MUDLIB/<fname>. It is called by the commandparser and by debug_info().
412  * Return TRUE on success, FALSE if <fname> can't be written.
413  */
414 
415 {
416     FILE *f;
417     object_t *ob;
418 
419     fname = check_valid_path(fname, current_object, STR_OBJDUMP, MY_TRUE);
420     if (!fname)
421         return MY_FALSE;
422     f = fopen(get_txt(fname), "w");
423     if (!f)
424     {
425         free_mstring(fname);
426         return MY_FALSE;
427     }
428     FCOUNT_WRITE(get_txt(fname));
429 
430     for (ob = newly_destructed_objs; ob; ob = ob->next_all)
431     {
432 #ifdef DEBUG
433         if (!(ob->flags & O_DESTRUCTED)) /* TODO: Can't happen */
434             continue;
435 #endif
436         fprintf(f, "%-20s ref %2"PRIdPINT" NEW\n"
437                  , get_txt(ob->name)
438                  , ob->ref
439         );
440     }
441 
442     for (ob = destructed_objs; ob; ob = ob->next_all)
443     {
444 #ifdef DEBUG
445         if (!(ob->flags & O_DESTRUCTED)) /* TODO: Can't happen */
446             continue;
447 #endif
448         fprintf(f, "%-20s ref %2"PRIdPINT"\n"
449                  , get_txt(ob->name)
450                  , ob->ref
451         );
452     }
453     fclose(f);
454     free_mstring(fname);
455     return MY_TRUE;
456 } /* dumpstat_dest() */
457 
458 /***************************************************************************/
459 
460