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