2008-08-22 Robert Jordan <robertj@gmx.net>
[mono.git] / mono / metadata / mono-perfcounters.c
1 /*
2  * mono-perfcounters.c
3  *
4  * Performance counters support.
5  *
6  * Author: Paolo Molaro (lupus@ximian.com)
7  *
8  * (C) 2008 Novell, Inc
9  */
10
11 #include "config.h"
12 #include <time.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #ifdef HAVE_SYS_TIME_H
16 #include <sys/time.h>
17 #endif
18 #include "metadata/mono-perfcounters.h"
19 #include "metadata/appdomain.h"
20 /* for mono_stats */
21 #include "metadata/class-internals.h"
22 #include "utils/mono-time.h"
23 #include "utils/mono-mmap.h"
24 #include <mono/io-layer/io-layer.h>
25
26 /* map of CounterSample.cs */
27 struct _MonoCounterSample {
28         gint64 rawValue;
29         gint64 baseValue;
30         gint64 counterFrequency;
31         gint64 systemFrequency;
32         gint64 timeStamp;
33         gint64 timeStamp100nSec;
34         gint64 counterTimeStamp;
35         int counterType;
36 };
37
38 /* map of PerformanceCounterType.cs */
39 enum {
40         NumberOfItemsHEX32=0x00000000,
41         NumberOfItemsHEX64=0x00000100,
42         NumberOfItems32=0x00010000,
43         NumberOfItems64=0x00010100,
44         CounterDelta32=0x00400400,
45         CounterDelta64=0x00400500,
46         SampleCounter=0x00410400,
47         CountPerTimeInterval32=0x00450400,
48         CountPerTimeInterval64=0x00450500,
49         RateOfCountsPerSecond32=0x10410400,
50         RateOfCountsPerSecond64=0x10410500,
51         RawFraction=0x20020400,
52         CounterTimer=0x20410500,
53         Timer100Ns=0x20510500,
54         SampleFraction=0x20C20400,
55         CounterTimerInverse=0x21410500,
56         Timer100NsInverse=0x21510500,
57         CounterMultiTimer=0x22410500,
58         CounterMultiTimer100Ns=0x22510500,
59         CounterMultiTimerInverse=0x23410500,
60         CounterMultiTimer100NsInverse=0x23510500,
61         AverageTimer32=0x30020400,
62         ElapsedTime=0x30240500,
63         AverageCount64=0x40020500,
64         SampleBase=0x40030401,
65         AverageBase=0x40030402,
66         RawBase=0x40030403,
67         CounterMultiBase=0x42030500
68 };
69
70 /* maps a small integer type to the counter types above */
71 static const int
72 simple_type_to_type [] = {
73         NumberOfItemsHEX32, NumberOfItemsHEX64,
74         NumberOfItems32, NumberOfItems64,
75         CounterDelta32, CounterDelta64,
76         SampleCounter, CountPerTimeInterval32,
77         CountPerTimeInterval64, RateOfCountsPerSecond32,
78         RateOfCountsPerSecond64, RawFraction,
79         CounterTimer, Timer100Ns,
80         SampleFraction, CounterTimerInverse,
81         Timer100NsInverse, CounterMultiTimer,
82         CounterMultiTimer100Ns, CounterMultiTimerInverse,
83         CounterMultiTimer100NsInverse, AverageTimer32,
84         ElapsedTime, AverageCount64,
85         SampleBase, AverageBase,
86         RawBase, CounterMultiBase
87 };
88
89 enum {
90         SingleInstance,
91         MultiInstance,
92         CatTypeUnknown = -1
93 };
94
95 enum {
96         ProcessInstance,
97         ThreadInstance,
98         CPUInstance,
99         MonoInstance,
100         CustomInstance
101 };
102
103 #define PERFCTR_CAT(id,name,help,type,inst,first_counter) CATEGORY_ ## id,
104 #define PERFCTR_COUNTER(id,name,help,type)
105 enum {
106 #include "mono-perfcounters-def.h"
107         NUM_CATEGORIES
108 };
109
110 #undef PERFCTR_CAT
111 #undef PERFCTR_COUNTER
112 #define PERFCTR_CAT(id,name,help,type,inst,first_counter) CATEGORY_START_ ## id = -1,
113 #define PERFCTR_COUNTER(id,name,help,type) COUNTER_ ## id,
114 /* each counter is assigned an id starting from 0 inside the category */
115 enum {
116 #include "mono-perfcounters-def.h"
117         END_COUNTERS
118 };
119
120 #undef PERFCTR_CAT
121 #undef PERFCTR_COUNTER
122 #define PERFCTR_CAT(id,name,help,type,inst,first_counter)
123 #define PERFCTR_COUNTER(id,name,help,type) CCOUNTER_ ## id,
124 /* this is used just to count the number of counters */
125 enum {
126 #include "mono-perfcounters-def.h"
127         NUM_COUNTERS
128 };
129
130 static CRITICAL_SECTION perfctr_mutex;
131 #define perfctr_lock() EnterCriticalSection (&perfctr_mutex)
132 #define perfctr_unlock() LeaveCriticalSection (&perfctr_mutex)
133
134 typedef struct {
135         char reserved [16];
136         int size;
137         unsigned short counters_start;
138         unsigned short counters_size;
139         unsigned short data_start;
140         MonoPerfCounters counters;
141         char data [1];
142 } MonoSharedArea;
143
144 /*
145   binary format of custom counters in shared memory, starting from MonoSharedArea* + data_start;
146   basic stanza:
147   struct stanza_header {
148         byte stanza_type; // FTYPE_*
149         byte other_info;
150         ushort stanza_length; // includeas header
151         ... data ...
152   }
153
154 // strings are utf8
155 // perfcat and perfinstance are 4-bytes aligned
156 struct perfcat {
157         byte typeidx;
158         byte categorytype;
159         ushort length; // includes the counters
160         ushort num_counters;
161         ushort counters_data_size;
162         int num_instances;
163         char name[]; // null terminated
164         char help[]; // null terminated
165         // perfcounters follow
166         {
167                 byte countertype;
168                 char name[]; // null terminated
169                 char help[]; // null terminated
170         }
171         0-byte
172 };
173
174 struct perfinstance {
175         byte typeidx;
176         byte data_offset; // offset of counters from beginning of struct
177         ushort length;
178         uint category_offset; // offset of category in the shared area
179         char name[]; // null terminated
180         // data follows: this is always 8-byte aligned
181 };
182
183 */
184
185 enum {
186         FTYPE_CATEGORY = 'C',
187         FTYPE_DELETED = 'D',
188         FTYPE_PREDEF_INSTANCE = 'P', // an instance of a predef counter
189         FTYPE_INSTANCE = 'I',
190         FTYPE_DIRTY = 'd',
191         FTYPE_END = 0
192 };
193
194 typedef struct {
195         unsigned char ftype;
196         unsigned char extra;
197         unsigned short size;
198 } SharedHeader;
199
200 typedef struct {
201         SharedHeader header;
202         unsigned short num_counters;
203         unsigned short counters_data_size;
204         int num_instances;
205         /* variable length data follows */
206         char name [1];
207 } SharedCategory;
208
209 typedef struct {
210         SharedHeader header;
211         unsigned int category_offset;
212         /* variable length data follows */
213         char name [1];
214 } SharedInstance;
215
216 typedef struct {
217         unsigned char type;
218         /* variable length data follows */
219         char name [1];
220 } SharedCounter;
221
222 typedef struct {
223         const char *name;
224         const char *help;
225         unsigned char id;
226         signed int type : 2;
227         unsigned int instance_type : 6;
228         short first_counter;
229 } CategoryDesc;
230
231 typedef struct {
232         const char *name;
233         const char *help;
234         int id;
235         int type;
236 } CounterDesc;
237
238 #undef PERFCTR_CAT
239 #undef PERFCTR_COUNTER
240 #define PERFCTR_CAT(id,name,help,type,inst,first_counter) {name, help, CATEGORY_ ## id, type, inst ## Instance, CCOUNTER_ ## first_counter},
241 #define PERFCTR_COUNTER(id,name,help,type)
242 static const CategoryDesc
243 predef_categories [] = {
244 #include "mono-perfcounters-def.h"
245         {NULL, NULL, NUM_CATEGORIES, -1, 0, NUM_COUNTERS}
246 };
247
248 #undef PERFCTR_CAT
249 #undef PERFCTR_COUNTER
250 #define PERFCTR_CAT(id,name,help,type,inst,first_counter)
251 #define PERFCTR_COUNTER(id,name,help,type) {name, help, COUNTER_ ## id, type},
252 static const CounterDesc
253 predef_counters [] = {
254 #include "mono-perfcounters-def.h"
255         {NULL, NULL, -1, 0}
256 };
257
258 /*
259  * We have several different classes of counters:
260  * *) system counters
261  * *) runtime counters
262  * *) remote counters
263  * *) user-defined counters
264  * *) windows counters (the implementation on windows will use this)
265  *
266  * To easily handle the differences we create a vtable for each class that contains the
267  * function pointers with the actual implementation to access the counters.
268  */
269 typedef struct _ImplVtable ImplVtable;
270
271 typedef MonoBoolean (*SampleFunc) (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample* sample);
272 typedef gint64 (*UpdateFunc) (ImplVtable *vtable, MonoBoolean do_incr, gint64 value);
273 typedef void (*CleanupFunc) (ImplVtable *vtable);
274
275 struct _ImplVtable {
276         void *arg;
277         SampleFunc sample;
278         UpdateFunc update;
279         CleanupFunc cleanup;
280 };
281
282 typedef struct {
283         ImplVtable vtable;
284         MonoPerfCounters *counters;
285         int pid;
286 } PredefVtable;
287
288 static ImplVtable*
289 create_vtable (void *arg, SampleFunc sample, UpdateFunc update)
290 {
291         ImplVtable *vtable = g_new0 (ImplVtable, 1);
292         vtable->arg = arg;
293         vtable->sample = sample;
294         vtable->update = update;
295         return vtable;
296 }
297
298 MonoPerfCounters *mono_perfcounters = NULL;
299 static MonoSharedArea *shared_area = NULL;
300
301 typedef struct {
302         void *sarea;
303         int refcount;
304 } ExternalSArea;
305
306 /* maps a pid to a ExternalSArea pointer */
307 static GHashTable *pid_to_shared_area = NULL;
308
309 static MonoSharedArea *
310 load_sarea_for_pid (int pid)
311 {
312         ExternalSArea *data;
313         MonoSharedArea *area = NULL;
314
315         perfctr_lock ();
316         if (pid_to_shared_area == NULL)
317                 pid_to_shared_area = g_hash_table_new (NULL, NULL);
318         data = g_hash_table_lookup (pid_to_shared_area, GINT_TO_POINTER (pid));
319         if (!data) {
320                 area = mono_shared_area_for_pid (GINT_TO_POINTER (pid));
321                 if (area) {
322                         data = g_new (ExternalSArea, 1);
323                         data->sarea = area;
324                         data->refcount = 1;
325                         g_hash_table_insert (pid_to_shared_area, GINT_TO_POINTER (pid), data);
326                 }
327         } else {
328                 area = data->sarea;
329                 data->refcount ++;
330         }
331         perfctr_unlock ();
332         return area;
333 }
334
335 static void
336 predef_cleanup (ImplVtable *vtable)
337 {
338         PredefVtable *vt = (PredefVtable*)vtable;
339         ExternalSArea *data;
340         perfctr_lock ();
341         if (!pid_to_shared_area) {
342                 perfctr_unlock ();
343                 return;
344         }
345         data = g_hash_table_lookup (pid_to_shared_area, GINT_TO_POINTER (vt->pid));
346         if (data) {
347                 data->refcount--;
348                 if (!data->refcount) {
349                         g_hash_table_remove (pid_to_shared_area, GINT_TO_POINTER (vt->pid));
350                         mono_shared_area_unload (data->sarea);
351                         g_free (data);
352                 }
353         }
354         perfctr_unlock ();
355 }
356
357 void
358 mono_perfcounters_init (void)
359 {
360         int d_offset = G_STRUCT_OFFSET (MonoSharedArea, data);
361         d_offset += 7;
362         d_offset &= ~7;
363
364         InitializeCriticalSection (&perfctr_mutex);
365
366         shared_area = mono_shared_area ();
367         shared_area->counters_start = G_STRUCT_OFFSET (MonoSharedArea, counters);
368         shared_area->counters_size = sizeof (MonoPerfCounters);
369         shared_area->data_start = d_offset;
370         shared_area->size = 4096;
371         mono_perfcounters = &shared_area->counters;
372 }
373
374 static int
375 perfctr_type_compress (int type)
376 {
377         int i;
378         for (i = 0; i < G_N_ELEMENTS (simple_type_to_type); ++i) {
379                 if (simple_type_to_type [i] == type)
380                         return i;
381         }
382         /* NumberOfItems32 */
383         return 2;
384 }
385
386 static unsigned char*
387 shared_data_find_room (int size)
388 {
389         unsigned char *p = (unsigned char *)shared_area + shared_area->data_start;
390         unsigned char *end = (unsigned char *)shared_area + shared_area->size;
391
392         size += 3;
393         size &= ~3;
394         while (p < end) {
395                 unsigned short *next;
396                 if (*p == FTYPE_END) {
397                         if (size < (end - p))
398                                 return p;
399                         return NULL;
400                 }
401                 if (p + 4 > end)
402                         return NULL;
403                 next = (unsigned short*)(p + 2);
404                 if (*p == FTYPE_DELETED) {
405                         /* we reuse only if it's the same size */
406                         if (*next == size) {
407                                 return p;
408                         }
409                 }
410                 p += *next;
411         }
412         return NULL;
413 }
414
415 typedef gboolean (*SharedFunc) (SharedHeader *header, void *data);
416
417 static void
418 foreach_shared_item (SharedFunc func, void *data)
419 {
420         unsigned char *p = (unsigned char *)shared_area + shared_area->data_start;
421         unsigned char *end = (unsigned char *)shared_area + shared_area->size;
422
423         while (p < end) {
424                 unsigned short *next;
425                 if (p + 4 > end)
426                         return;
427                 next = (unsigned short*)(p + 2);
428                 if (!func ((SharedHeader*)p, data))
429                         return;
430                 if (*p == FTYPE_END)
431                         return;
432                 p += *next;
433         }
434 }
435
436 static int
437 mono_string_compare_ascii (MonoString *str, const char *ascii_str)
438 {
439         /* FIXME: make this case insensitive */
440         guint16 *strc = mono_string_chars (str);
441         while (*strc == *ascii_str++) {
442                 if (*strc == 0)
443                         return 0;
444                 strc++;
445         }
446         return *strc - *(const unsigned char *)(ascii_str - 1);
447 }
448
449 typedef struct {
450         MonoString *name;
451         SharedCategory *cat;
452 } CatSearch;
453
454 static gboolean
455 category_search (SharedHeader *header, void *data)
456 {
457         CatSearch *search = data;
458         if (header->ftype == FTYPE_CATEGORY) {
459                 SharedCategory *cat = (SharedCategory*)header;
460                 if (mono_string_compare_ascii (search->name, cat->name) == 0) {
461                         search->cat = cat;
462                         return FALSE;
463                 }
464         }
465         return TRUE;
466 }
467
468 static SharedCategory*
469 find_custom_category (MonoString *name)
470 {
471         CatSearch search;
472         search.name = name;
473         search.cat = NULL;
474         foreach_shared_item (category_search, &search);
475         return search.cat;
476 }
477
478 static gboolean
479 category_collect (SharedHeader *header, void *data)
480 {
481         GSList **list = data;
482         if (header->ftype == FTYPE_CATEGORY) {
483                 *list = g_slist_prepend (*list, header);
484         }
485         return TRUE;
486 }
487
488 static GSList*
489 get_custom_categories (void) {
490         GSList *list = NULL;
491         foreach_shared_item (category_collect, &list);
492         return list;
493 }
494
495 static char*
496 custom_category_counters (SharedCategory* cat)
497 {
498         char *p = cat->name + strlen (cat->name) + 1;
499         p += strlen (p) + 1; /* skip category help */
500         return p;
501 }
502
503 static SharedCounter*
504 find_custom_counter (SharedCategory* cat, MonoString *name)
505 {
506         int i;
507         char *p = custom_category_counters (cat);
508         for (i = 0; i < cat->num_counters; ++i) {
509                 SharedCounter *counter = (SharedCounter*)p;
510                 if (mono_string_compare_ascii (name, counter->name) == 0)
511                         return counter;
512                 p += 1 + strlen (p + 1) + 1; /* skip counter type and name */
513                 p += strlen (p) + 1; /* skip counter help */
514         }
515         return NULL;
516 }
517
518 static char*
519 custom_category_help (SharedCategory* cat)
520 {
521         return cat->name + strlen (cat->name) + 1;
522 }
523
524 static const CounterDesc*
525 get_counter_in_category (const CategoryDesc *desc, MonoString *counter)
526 {
527         const CounterDesc *cdesc = &predef_counters [desc->first_counter];
528         const CounterDesc *end = &predef_counters [desc [1].first_counter];
529         for (; cdesc < end; ++cdesc) {
530                 if (mono_string_compare_ascii (counter, cdesc->name) == 0)
531                         return cdesc;
532         }
533         return NULL;
534 }
535
536 /* fill the info in sample (except the raw value) */
537 static void
538 fill_sample (MonoCounterSample *sample)
539 {
540         sample->timeStamp = mono_100ns_ticks ();
541         sample->timeStamp100nSec = sample->timeStamp;
542         sample->counterTimeStamp = sample->timeStamp;
543         sample->counterFrequency = 10000000;
544         sample->systemFrequency = 10000000;
545         // the real basevalue needs to be get from a different counter...
546         sample->baseValue = 0;
547 }
548
549 static int
550 id_from_string (MonoString *instance)
551 {
552         int id = -1;
553         if (mono_string_length (instance)) {
554                 char *id_str = mono_string_to_utf8 (instance);
555                 char *end;
556                 id = strtol (id_str, &end, 0);
557                 g_free (id_str);
558                 if (end == id_str)
559                         return -1;
560         }
561         return id;
562 }
563
564 static void
565 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
566 {
567         char buf [256];
568         char *s;
569         int hz = 100 * 2; // 2 numprocs
570         long long unsigned int user_ticks, nice_ticks, system_ticks, idle_ticks, iowait_ticks, irq_ticks, sirq_ticks;
571         FILE *f = fopen ("/proc/stat", "r");
572         if (!f)
573                 return;
574         while ((s = fgets (buf, sizeof (buf), f))) {
575                 char *data = NULL;
576                 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
577                         data = s + 4;
578                 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
579                         if (data == s + 3)
580                                 continue;
581                         data++;
582                 } else {
583                         continue;
584                 }
585                 sscanf (data, "%Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_ticks, &nice_ticks, &system_ticks, &idle_ticks, &iowait_ticks, &irq_ticks, &sirq_ticks);
586         }
587         fclose (f);
588
589         if (user)
590                 *user = (user_ticks + nice_ticks) * 10000000 / hz;
591         if (systemt)
592                 *systemt = (system_ticks) * 10000000 / hz;
593         if (irq)
594                 *irq = (irq_ticks) * 10000000 / hz;
595         if (sirq)
596                 *sirq = (sirq_ticks) * 10000000 / hz;
597         if (idle)
598                 *idle = (idle_ticks) * 10000000 / hz;
599 }
600
601 static MonoBoolean
602 get_cpu_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
603 {
604         gint64 value = 0;
605         int id = GPOINTER_TO_INT (vtable->arg);
606         int pid = id >> 5;
607         id &= 0x1f;
608         if (!only_value) {
609                 fill_sample (sample);
610                 sample->baseValue = 1;
611         }
612         sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
613         switch (id) {
614         case COUNTER_CPU_USER_TIME:
615                 get_cpu_times (pid, &value, NULL, NULL, NULL, NULL);
616                 sample->rawValue = value;
617                 return TRUE;
618         case COUNTER_CPU_PRIV_TIME:
619                 get_cpu_times (pid, NULL, &value, NULL, NULL, NULL);
620                 sample->rawValue = value;
621                 return TRUE;
622         case COUNTER_CPU_INTR_TIME:
623                 get_cpu_times (pid, NULL, NULL, &value, NULL, NULL);
624                 sample->rawValue = value;
625                 return TRUE;
626         case COUNTER_CPU_DCP_TIME:
627                 get_cpu_times (pid, NULL, NULL, NULL, &value, NULL);
628                 sample->rawValue = value;
629                 return TRUE;
630         case COUNTER_CPU_PROC_TIME:
631                 get_cpu_times (pid, NULL, NULL, NULL, NULL, &value);
632                 sample->rawValue = value;
633                 return TRUE;
634         }
635         return FALSE;
636 }
637
638 static void*
639 cpu_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
640 {
641         int id = id_from_string (instance) << 5;
642         const CounterDesc *cdesc;
643         *custom = FALSE;
644         /* increase the shift above and the mask also in the implementation functions */
645         //g_assert (32 > desc [1].first_counter - desc->first_counter);
646         if ((cdesc = get_counter_in_category (&predef_categories [CATEGORY_CPU], counter))) {
647                 *type = cdesc->type;
648                 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_cpu_counter, NULL);
649         }
650         return NULL;
651 }
652
653 /*
654  * /proc/pid/stat format:
655  * pid (cmdname) S 
656  *      [0] ppid pgid sid tty_nr tty_pgrp flags min_flt cmin_flt maj_flt cmaj_flt
657  *      [10] utime stime cutime cstime prio nice threads start_time vsize rss
658  *      [20] rsslim start_code end_code start_stack esp eip pending blocked sigign sigcatch
659  *      [30] wchan 0 0 exit_signal cpu rt_prio policy
660  */
661
662 static gint64
663 get_process_time (int pid, int pos, int sum)
664 {
665         char buf [512];
666         char *s, *end;
667         FILE *f;
668         int len, i;
669         gint64 value;
670
671         g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
672         f = fopen (buf, "r");
673         if (!f)
674                 return 0;
675         len = fread (buf, 1, sizeof (buf), f);
676         fclose (f);
677         if (len <= 0)
678                 return 0;
679         s = strchr (buf, ')');
680         if (!s)
681                 return 0;
682         s++;
683         while (g_ascii_isspace (*s)) s++;
684         if (!*s)
685                 return 0;
686         /* skip the status char */
687         while (*s && !g_ascii_isspace (*s)) s++;
688         if (!*s)
689                 return 0;
690         for (i = 0; i < pos; ++i) {
691                 while (g_ascii_isspace (*s)) s++;
692                 if (!*s)
693                         return 0;
694                 while (*s && !g_ascii_isspace (*s)) s++;
695                 if (!*s)
696                         return 0;
697         }
698         /* we are finally at the needed item */
699         value = strtoul (s, &end, 0);
700         /* add also the following value */
701         if (sum) {
702                 while (g_ascii_isspace (*s)) s++;
703                 if (!*s)
704                         return 0;
705                 value += strtoul (s, &end, 0);
706         }
707         return value;
708 }
709
710 static gint64
711 get_pid_stat_item (int pid, const char *item)
712 {
713         char buf [256];
714         char *s;
715         FILE *f;
716         int len = strlen (item);
717
718         g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
719         f = fopen (buf, "r");
720         if (!f)
721                 return 0;
722         while ((s = fgets (buf, sizeof (buf), f))) {
723                 if (*item != *buf)
724                         continue;
725                 if (strncmp (buf, item, len))
726                         continue;
727                 if (buf [len] != ':')
728                         continue;
729                 fclose (f);
730                 return atoi (buf + len + 1);
731         }
732         fclose (f);
733         return 0;
734 }
735
736 static MonoBoolean
737 get_process_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
738 {
739         int id = GPOINTER_TO_INT (vtable->arg);
740         int pid = id >> 5;
741         if (pid < 0)
742                 return FALSE;
743         id &= 0x1f;
744         if (!only_value) {
745                 fill_sample (sample);
746                 sample->baseValue = 1;
747         }
748         sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
749         switch (id) {
750         case COUNTER_PROC_USER_TIME:
751                 sample->rawValue = get_process_time (pid, 12, FALSE);
752                 return TRUE;
753         case COUNTER_PROC_PRIV_TIME:
754                 sample->rawValue = get_process_time (pid, 13, FALSE);
755                 return TRUE;
756         case COUNTER_PROC_PROC_TIME:
757                 sample->rawValue = get_process_time (pid, 12, TRUE);
758                 return TRUE;
759         case COUNTER_PROC_THREADS:
760                 sample->rawValue = get_pid_stat_item (pid, "Threads");
761                 return TRUE;
762         case COUNTER_PROC_VBYTES:
763                 sample->rawValue = get_pid_stat_item (pid, "VmSize") * 1024;
764                 return TRUE;
765         case COUNTER_PROC_WSET:
766                 sample->rawValue = get_pid_stat_item (pid, "VmRSS") * 1024;
767                 return TRUE;
768         case COUNTER_PROC_PBYTES:
769                 sample->rawValue = get_pid_stat_item (pid, "VmData") * 1024;
770                 return TRUE;
771         }
772         return FALSE;
773 }
774
775 static void*
776 process_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
777 {
778         int id = id_from_string (instance) << 5;
779         const CounterDesc *cdesc;
780         *custom = FALSE;
781         /* increase the shift above and the mask also in the implementation functions */
782         //g_assert (32 > desc [1].first_counter - desc->first_counter);
783         if ((cdesc = get_counter_in_category (&predef_categories [CATEGORY_PROC], counter))) {
784                 *type = cdesc->type;
785                 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_process_counter, NULL);
786         }
787         return NULL;
788 }
789
790 static MonoBoolean
791 mono_mem_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
792 {
793         int id = GPOINTER_TO_INT (vtable->arg);
794         if (!only_value) {
795                 fill_sample (sample);
796                 sample->baseValue = 1;
797         }
798         sample->counterType = predef_counters [predef_categories [CATEGORY_MONO_MEM].first_counter + id].type;
799         switch (id) {
800         case COUNTER_MEM_NUM_OBJECTS:
801                 sample->rawValue = mono_stats.new_object_count;
802                 return TRUE;
803         }
804         return FALSE;
805 }
806
807 static void*
808 mono_mem_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
809 {
810         const CounterDesc *cdesc;
811         *custom = FALSE;
812         if ((cdesc = get_counter_in_category (&predef_categories [CATEGORY_MONO_MEM], counter))) {
813                 *type = cdesc->type;
814                 return create_vtable (GINT_TO_POINTER (cdesc->id), mono_mem_counter, NULL);
815         }
816         return NULL;
817 }
818
819 static MonoBoolean
820 predef_readonly_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
821 {
822         PredefVtable *vt = (PredefVtable *)vtable;
823         int cat_id = GPOINTER_TO_INT (vtable->arg);
824         int id = cat_id >> 16;
825         cat_id &= 0xffff;
826         if (!only_value) {
827                 fill_sample (sample);
828                 sample->baseValue = 1;
829         }
830         sample->counterType = predef_counters [predef_categories [cat_id].first_counter + id].type;
831         switch (cat_id) {
832         case CATEGORY_JIT:
833                 switch (id) {
834                 case COUNTER_JIT_BYTES:
835                 case COUNTER_JIT_BYTES_PSEC:
836                         sample->rawValue = vt->counters->jit_bytes;
837                         return TRUE;
838                 case COUNTER_JIT_METHODS:
839                         sample->rawValue = vt->counters->jit_methods;
840                         return TRUE;
841                 }
842                 break;
843         case CATEGORY_EXC:
844                 switch (id) {
845                 case COUNTER_EXC_THROWN:
846                         sample->rawValue = vt->counters->exceptions_thrown;
847                         return TRUE;
848                 }
849                 break;
850
851         case CATEGORY_ASPNET:
852                 switch (id) {
853                 case COUNTER_ASPNET_REQ_Q:
854                         sample->rawValue = vt->counters->aspnet_requests_queued;
855                         return TRUE;
856                 }
857         }
858         return FALSE;
859 }
860
861 static ImplVtable*
862 predef_vtable (void *arg, MonoString *instance)
863 {
864         MonoSharedArea *area;
865         PredefVtable *vtable;
866         char *pids = mono_string_to_utf8 (instance);
867         int pid;
868
869         pid = atoi (pids);
870         g_free (pids);
871         area = load_sarea_for_pid (pid);
872         if (!area)
873                 return NULL;
874
875         vtable = g_new (PredefVtable, 1);
876         vtable->vtable.arg = arg;
877         vtable->vtable.sample = predef_readonly_counter;
878         vtable->vtable.cleanup = predef_cleanup;
879         vtable->counters = &area->counters;
880         vtable->pid = pid;
881
882         return (ImplVtable*)vtable;
883 }
884
885 /* consider storing the pointer directly in vtable->arg, so the runtime overhead is lower:
886  * this needs some way to set sample->counterType as well, though.
887  */
888 static MonoBoolean
889 predef_writable_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
890 {
891         int cat_id = GPOINTER_TO_INT (vtable->arg);
892         int id = cat_id >> 16;
893         cat_id &= 0xffff;
894         if (!only_value) {
895                 fill_sample (sample);
896                 sample->baseValue = 1;
897         }
898         sample->counterType = predef_counters [predef_categories [cat_id].first_counter + id].type;
899         switch (cat_id) {
900         case CATEGORY_ASPNET:
901                 switch (id) {
902                 case COUNTER_ASPNET_REQ_Q:
903                         sample->rawValue = mono_perfcounters->aspnet_requests_queued;
904                         return TRUE;
905                 }
906                 break;
907         }
908         return FALSE;
909 }
910
911 static gint64
912 predef_writable_update (ImplVtable *vtable, MonoBoolean do_incr, gint64 value)
913 {
914         guint32 *ptr = NULL;
915         int cat_id = GPOINTER_TO_INT (vtable->arg);
916         int id = cat_id >> 16;
917         cat_id &= 0xffff;
918         switch (cat_id) {
919         case CATEGORY_ASPNET:
920                 switch (id) {
921                 case COUNTER_ASPNET_REQ_Q: ptr = &mono_perfcounters->aspnet_requests_queued; break;
922                 }
923                 break;
924         }
925         if (ptr) {
926                 if (do_incr) {
927                         /* FIXME: we need to do this atomically */
928                         *ptr += value;
929                         return *ptr;
930                 }
931                 /* this can be non-atomic */
932                 *ptr = value;
933                 return value;
934         }
935         return 0;
936 }
937
938 static void*
939 predef_writable_get_impl (int cat, MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
940 {
941         const CounterDesc *cdesc;
942         *custom = TRUE;
943         if ((cdesc = get_counter_in_category (&predef_categories [cat], counter))) {
944                 *type = cdesc->type;
945                 if (instance == NULL || mono_string_compare_ascii (instance, "") == 0)
946                         return create_vtable (GINT_TO_POINTER ((cdesc->id << 16) | cat), predef_writable_counter, predef_writable_update);
947                 else
948                         return predef_vtable (GINT_TO_POINTER ((cdesc->id << 16) | cat), instance);
949         }
950         return NULL;
951 }
952
953 static MonoBoolean
954 custom_writable_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
955 {
956         SharedCounter *scounter = vtable->arg;
957         if (!only_value) {
958                 fill_sample (sample);
959                 sample->baseValue = 1;
960         }
961         sample->counterType = simple_type_to_type [scounter->type];
962         /* FIXME */
963         sample->rawValue = 0;
964         return TRUE;
965 }
966
967 static gint64
968 custom_writable_update (ImplVtable *vtable, MonoBoolean do_incr, gint64 value)
969 {
970         /* FIXME */
971         guint32 *ptr = NULL;
972         if (ptr) {
973                 if (do_incr) {
974                         /* FIXME: we need to do this atomically */
975                         *ptr += value;
976                         return *ptr;
977                 }
978                 /* this can be non-atomic */
979                 *ptr = value;
980                 return value;
981         }
982         return 0;
983 }
984
985 static void*
986 custom_get_impl (SharedCategory *cat, MonoString* counter, MonoString* instance, int *type)
987 {
988         SharedCounter *scounter;
989
990         scounter = find_custom_counter (cat, counter);
991         if (!scounter)
992                 return NULL;
993         *type = simple_type_to_type [scounter->type];
994         /* FIXME: use instance */
995         return create_vtable (scounter, custom_writable_counter, custom_writable_update);
996 }
997
998 static const CategoryDesc*
999 find_category (MonoString *category)
1000 {
1001         int i;
1002         for (i = 0; i < NUM_CATEGORIES; ++i) {
1003                 if (mono_string_compare_ascii (category, predef_categories [i].name) == 0)
1004                         return &predef_categories [i];
1005         }
1006         return NULL;
1007 }
1008
1009 void*
1010 mono_perfcounter_get_impl (MonoString* category, MonoString* counter, MonoString* instance,
1011                 MonoString* machine, int *type, MonoBoolean *custom)
1012 {
1013         const CategoryDesc *cdesc;
1014         /* no support for counters on other machines */
1015         if (mono_string_compare_ascii (machine, "."))
1016                 return NULL;
1017         cdesc = find_category (category);
1018         if (!cdesc) {
1019                 SharedCategory *scat = find_custom_category (category);
1020                 if (!scat)
1021                         return NULL;
1022                 *custom = TRUE;
1023                 return custom_get_impl (scat, counter, instance, type);
1024         }
1025         switch (cdesc->id) {
1026         case CATEGORY_CPU:
1027                 return cpu_get_impl (counter, instance, type, custom);
1028         case CATEGORY_PROC:
1029                 return process_get_impl (counter, instance, type, custom);
1030         case CATEGORY_MONO_MEM:
1031                 return mono_mem_get_impl (counter, instance, type, custom);
1032         case CATEGORY_JIT:
1033         case CATEGORY_EXC:
1034         case CATEGORY_ASPNET:
1035                 return predef_writable_get_impl (cdesc->id, counter, instance, type, custom);
1036         }
1037         return NULL;
1038 }
1039
1040 MonoBoolean
1041 mono_perfcounter_get_sample (void *impl, MonoBoolean only_value, MonoCounterSample *sample)
1042 {
1043         ImplVtable *vtable = impl;
1044         if (vtable && vtable->sample)
1045                 return vtable->sample (vtable, only_value, sample);
1046         return FALSE;
1047 }
1048
1049 gint64
1050 mono_perfcounter_update_value (void *impl, MonoBoolean do_incr, gint64 value)
1051 {
1052         ImplVtable *vtable = impl;
1053         if (vtable && vtable->update)
1054                 return vtable->update (vtable, do_incr, value);
1055         return 0;
1056 }
1057
1058 void
1059 mono_perfcounter_free_data (void *impl)
1060 {
1061         ImplVtable *vtable = impl;
1062         if (vtable && vtable->cleanup)
1063                 vtable->cleanup (vtable);
1064         g_free (impl);
1065 }
1066
1067 /* Category icalls */
1068 MonoBoolean
1069 mono_perfcounter_category_del (MonoString *name)
1070 {
1071         const CategoryDesc *cdesc;
1072         SharedCategory *cat;
1073         cdesc = find_category (name);
1074         /* can't delete a predefined category */
1075         if (cdesc)
1076                 return FALSE;
1077         perfctr_lock ();
1078         cat = find_custom_category (name);
1079         /* FIXME: check the semantics, if deleting a category means also deleting the instances */
1080         if (!cat || cat->num_instances) {
1081                 perfctr_unlock ();
1082                 return FALSE;
1083         }
1084         cat->header.ftype = FTYPE_DELETED;
1085         perfctr_unlock ();
1086         return TRUE;
1087 }
1088
1089 MonoString*
1090 mono_perfcounter_category_help (MonoString *category, MonoString *machine)
1091 {
1092         const CategoryDesc *cdesc;
1093         /* no support for counters on other machines */
1094         if (mono_string_compare_ascii (machine, "."))
1095                 return NULL;
1096         cdesc = find_category (category);
1097         if (!cdesc) {
1098                 SharedCategory *scat = find_custom_category (category);
1099                 if (!scat)
1100                         return NULL;
1101                 return mono_string_new (mono_domain_get (), custom_category_help (scat));
1102         }
1103         return mono_string_new (mono_domain_get (), cdesc->help);
1104 }
1105
1106 /*
1107  * Check if the category named @category exists on @machine. If @counter is not NULL, return
1108  * TRUE only if a counter with that name exists in the category.
1109  */
1110 MonoBoolean
1111 mono_perfcounter_category_exists (MonoString *counter, MonoString *category, MonoString *machine)
1112 {
1113         const CategoryDesc *cdesc;
1114         /* no support for counters on other machines */
1115         if (mono_string_compare_ascii (machine, "."))
1116                 return FALSE;
1117         cdesc = find_category (category);
1118         if (!cdesc) {
1119                 SharedCategory *scat = find_custom_category (category);
1120                 if (!scat)
1121                         return FALSE;
1122                 /* counter is allowed to be null */
1123                 if (!counter)
1124                         return TRUE;
1125                 /* search through the custom category */
1126                 return find_custom_counter (scat, counter) != NULL;
1127         }
1128         /* counter is allowed to be null */
1129         if (!counter)
1130                 return TRUE;
1131         if (get_counter_in_category (cdesc, counter))
1132                 return TRUE;
1133         return FALSE;
1134 }
1135
1136 /* C map of the type with the same name */
1137 typedef struct {
1138         MonoObject object;
1139         MonoString *help;
1140         MonoString *name;
1141         int type;
1142 } CounterCreationData;
1143
1144 /*
1145  * Since we'll keep a copy of the category per-process, we should also make sure
1146  * categories with the same name are compatible.
1147  */
1148 MonoBoolean
1149 mono_perfcounter_create (MonoString *category, MonoString *help, int type, MonoArray *items)
1150 {
1151         int result = FALSE;
1152         int i, size;
1153         int num_counters = mono_array_length (items);
1154         int counters_data_size;
1155         char *name = mono_string_to_utf8 (category);
1156         char *chelp = mono_string_to_utf8 (help);
1157         char **counter_info;
1158         unsigned char *ptr;
1159         char *p;
1160         SharedCategory *cat;
1161
1162         counter_info = g_new0 (char*, num_counters * 2);
1163         /* calculate the size we need structure size + name/help + 2 0 string terminators */
1164         size = G_STRUCT_OFFSET (SharedCategory, name) + strlen (name) + strlen (chelp) + 2;
1165         for (i = 0; i < num_counters; ++i) {
1166                 CounterCreationData *data = mono_array_get (items, CounterCreationData*, i);
1167                 counter_info [i * 2] = mono_string_to_utf8 (data->name);
1168                 counter_info [i * 2 + 1] = mono_string_to_utf8 (data->help);
1169                 size += 3; /* type and two 0 string terminators */
1170         }
1171         for (i = 0; i < num_counters * 2; ++i) {
1172                 if (!counter_info [i])
1173                         goto failure;
1174                 size += strlen (counter_info [i]);
1175         }
1176         counters_data_size = num_counters * 8; /* optimize for size later */
1177         if (size > 65535)
1178                 goto failure;
1179         perfctr_lock ();
1180         ptr = shared_data_find_room (size);
1181         if (!ptr) {
1182                 perfctr_unlock ();
1183                 goto failure;
1184         }
1185         cat = (SharedCategory*)ptr;
1186         cat->header.extra = type;
1187         cat->header.size = size;
1188         cat->num_counters = num_counters;
1189         cat->counters_data_size = counters_data_size;
1190         /* now copy the vaiable data */
1191         p = cat->name;
1192         strcpy (p, name);
1193         p += strlen (name) + 1;
1194         strcpy (p, chelp);
1195         p += strlen (chelp) + 1;
1196         for (i = 0; i < num_counters; ++i) {
1197                 CounterCreationData *data = mono_array_get (items, CounterCreationData*, i);
1198                 *p++ = perfctr_type_compress (data->type);
1199                 strcpy (p, counter_info [i * 2]);
1200                 p += strlen (counter_info [i * 2]) + 1;
1201                 strcpy (p, counter_info [i * 2 + 1]);
1202                 p += strlen (counter_info [i * 2 + 1]) + 1;
1203         }
1204         cat->header.ftype = FTYPE_CATEGORY;
1205
1206         perfctr_unlock ();
1207         result = TRUE;
1208 failure:
1209         for (i = 0; i < num_counters * 2; ++i) {
1210                 g_free (counter_info [i]);
1211         }
1212         g_free (counter_info);
1213         g_free (name);
1214         g_free (chelp);
1215         return result;
1216 }
1217
1218 int
1219 mono_perfcounter_instance_exists (MonoString *instance, MonoString *category, MonoString *machine)
1220 {
1221         const CategoryDesc *cdesc;
1222         /* no support for counters on other machines */
1223         if (mono_string_compare_ascii (machine, "."))
1224                 return FALSE;
1225         cdesc = find_category (category);
1226         if (!cdesc)
1227                 return FALSE;
1228         return FALSE;
1229 }
1230
1231 MonoArray*
1232 mono_perfcounter_category_names (MonoString *machine)
1233 {
1234         int i;
1235         MonoArray *res;
1236         MonoDomain *domain = mono_domain_get ();
1237         GSList *custom_categories, *tmp;
1238         /* no support for counters on other machines */
1239         if (mono_string_compare_ascii (machine, "."))
1240                 return mono_array_new (domain, mono_get_string_class (), 0);
1241         perfctr_lock ();
1242         custom_categories = get_custom_categories ();
1243         res = mono_array_new (domain, mono_get_string_class (), NUM_CATEGORIES + g_slist_length (custom_categories));
1244         for (i = 0; i < NUM_CATEGORIES; ++i) {
1245                 const CategoryDesc *cdesc = &predef_categories [i];
1246                 mono_array_setref (res, i, mono_string_new (domain, cdesc->name));
1247         }
1248         for (tmp = custom_categories; tmp; tmp = tmp->next) {
1249                 SharedCategory *scat = tmp->data;
1250                 mono_array_setref (res, i, mono_string_new (domain, scat->name));
1251                 i++;
1252         }
1253         perfctr_unlock ();
1254         g_slist_free (custom_categories);
1255         return res;
1256 }
1257
1258 MonoArray*
1259 mono_perfcounter_counter_names (MonoString *category, MonoString *machine)
1260 {
1261         int i;
1262         SharedCategory *scat;
1263         const CategoryDesc *cdesc;
1264         MonoArray *res;
1265         MonoDomain *domain = mono_domain_get ();
1266         /* no support for counters on other machines */
1267         if (mono_string_compare_ascii (machine, "."))
1268                 return mono_array_new (domain, mono_get_string_class (), 0);
1269         cdesc = find_category (category);
1270         if (cdesc) {
1271                 res = mono_array_new (domain, mono_get_string_class (), cdesc [1].first_counter - cdesc->first_counter);
1272                 for (i = cdesc->first_counter; i < cdesc [1].first_counter; ++i) {
1273                         const CounterDesc *desc = &predef_counters [i];
1274                         mono_array_setref (res, i - cdesc->first_counter, mono_string_new (domain, desc->name));
1275                 }
1276                 return res;
1277         }
1278         perfctr_lock ();
1279         scat = find_custom_category (category);
1280         if (scat) {
1281                 char *p = custom_category_counters (scat);
1282                 int i;
1283                 res = mono_array_new (domain, mono_get_string_class (), scat->num_counters);
1284                 for (i = 0; i < scat->num_counters; ++i) {
1285                         mono_array_setref (res, i, mono_string_new (domain, p + 1));
1286                         p += 1 + strlen (p + 1) + 1; /* skip counter type and name */
1287                         p += strlen (p) + 1; /* skip counter help */
1288                 }
1289                 perfctr_unlock ();
1290                 return res;
1291         }
1292         perfctr_unlock ();
1293         return mono_array_new (domain, mono_get_string_class (), 0);
1294 }
1295
1296 MonoArray*
1297 mono_perfcounter_instance_names (MonoString *category, MonoString *machine)
1298 {
1299         if (mono_string_compare_ascii (machine, "."))
1300                 return mono_array_new (mono_domain_get (), mono_get_string_class (), 0);
1301         return mono_array_new (mono_domain_get (), mono_get_string_class (), 0);
1302 }
1303