2008-07-22 Zoltan Varga <vargaz@gmail.com>
[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 <mono/io-layer/io-layer.h>
24
25 /* map of CounterSample.cs */
26 struct _MonoCounterSample {
27         gint64 rawValue;
28         gint64 baseValue;
29         gint64 counterFrequency;
30         gint64 systemFrequency;
31         gint64 timeStamp;
32         gint64 timeStamp100nSec;
33         gint64 counterTimeStamp;
34         int counterType;
35 };
36
37 /* map of PerformanceCounterType.cs */
38 enum {
39         NumberOfItemsHEX32=0x00000000,
40         NumberOfItemsHEX64=0x00000100,
41         NumberOfItems32=0x00010000,
42         NumberOfItems64=0x00010100,
43         CounterDelta32=0x00400400,
44         CounterDelta64=0x00400500,
45         SampleCounter=0x00410400,
46         CountPerTimeInterval32=0x00450400,
47         CountPerTimeInterval64=0x00450500,
48         RateOfCountsPerSecond32=0x10410400,
49         RateOfCountsPerSecond64=0x10410500,
50         RawFraction=0x20020400,
51         CounterTimer=0x20410500,
52         Timer100Ns=0x20510500,
53         SampleFraction=0x20C20400,
54         CounterTimerInverse=0x21410500,
55         Timer100NsInverse=0x21510500,
56         CounterMultiTimer=0x22410500,
57         CounterMultiTimer100Ns=0x22510500,
58         CounterMultiTimerInverse=0x23410500,
59         CounterMultiTimer100NsInverse=0x23510500,
60         AverageTimer32=0x30020400,
61         ElapsedTime=0x30240500,
62         AverageCount64=0x40020500,
63         SampleBase=0x40030401,
64         AverageBase=0x40030402,
65         RawBase=0x40030403,
66         CounterMultiBase=0x42030500
67 };
68
69 enum {
70         SingleInstance,
71         MultiInstance,
72         CatTypeUnknown = -1
73 };
74
75 #define PERFCTR_CAT(id,name,help,type,first_counter) CATEGORY_ ## id,
76 #define PERFCTR_COUNTER(id,name,help,type)
77 enum {
78 #include "mono-perfcounters-def.h"
79         NUM_CATEGORIES
80 };
81
82 #undef PERFCTR_CAT
83 #undef PERFCTR_COUNTER
84 #define PERFCTR_CAT(id,name,help,type,first_counter) CATEGORY_START_ ## id = -1,
85 #define PERFCTR_COUNTER(id,name,help,type) COUNTER_ ## id,
86 /* each counter is assigned an id starting from 0 inside the category */
87 enum {
88 #include "mono-perfcounters-def.h"
89         END_COUNTERS
90 };
91
92 #undef PERFCTR_CAT
93 #undef PERFCTR_COUNTER
94 #define PERFCTR_CAT(id,name,help,type,first_counter)
95 #define PERFCTR_COUNTER(id,name,help,type) CCOUNTER_ ## id,
96 /* this is used just to count the number of counters */
97 enum {
98 #include "mono-perfcounters-def.h"
99         NUM_COUNTERS
100 };
101
102 typedef struct {
103         const char *name;
104         const char *help;
105         unsigned char id;
106         signed char type;
107         short first_counter;
108 } CategoryDesc;
109
110 typedef struct {
111         const char *name;
112         const char *help;
113         int id;
114         int type;
115 } CounterDesc;
116
117 #undef PERFCTR_CAT
118 #undef PERFCTR_COUNTER
119 #define PERFCTR_CAT(id,name,help,type,first_counter) {name, help, CATEGORY_ ## id, type, CCOUNTER_ ## first_counter},
120 #define PERFCTR_COUNTER(id,name,help,type)
121 static const CategoryDesc
122 predef_categories [] = {
123 #include "mono-perfcounters-def.h"
124         {NULL, NULL, NUM_CATEGORIES, -1, NUM_COUNTERS}
125 };
126
127 #undef PERFCTR_CAT
128 #undef PERFCTR_COUNTER
129 #define PERFCTR_CAT(id,name,help,type,first_counter)
130 #define PERFCTR_COUNTER(id,name,help,type) {name, help, COUNTER_ ## id, type},
131 static const CounterDesc
132 predef_counters [] = {
133 #include "mono-perfcounters-def.h"
134         {NULL, NULL, -1, 0}
135 };
136
137 /*
138  * We have several different classes of counters:
139  * *) system counters
140  * *) runtime counters
141  * *) remote counters
142  * *) user-defined counters
143  * *) windows counters (the implementation on windows will use this)
144  *
145  * To easily handle the differences we create a vtable for each class that contains the
146  * function pointers with the actual implementation to access the counters.
147  */
148 typedef struct _ImplVtable ImplVtable;
149
150 typedef MonoBoolean (*SampleFunc) (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample* sample);
151 typedef gint64 (*UpdateFunc) (ImplVtable *vtable, MonoBoolean do_incr, gint64 value);
152
153 struct _ImplVtable {
154         void *arg;
155         SampleFunc sample;
156         UpdateFunc update;
157 };
158
159 static ImplVtable*
160 create_vtable (void *arg, SampleFunc sample, UpdateFunc update)
161 {
162         ImplVtable *vtable = g_new (ImplVtable, 1);
163         vtable->arg = arg;
164         vtable->sample = sample;
165         vtable->update = update;
166         return vtable;
167 }
168
169 MonoPerfCounters *mono_perfcounters = NULL;
170
171 void
172 mono_perfcounters_init (void)
173 {
174         /* later allocate in the shared memory area */
175         mono_perfcounters = g_new0 (MonoPerfCounters, 1);
176 }
177
178 static int
179 mono_string_compare_ascii (MonoString *str, const char *ascii_str)
180 {
181         guint16 *strc = mono_string_chars (str);
182         while (*strc == *ascii_str++) {
183                 if (*strc == 0)
184                         return 0;
185                 strc++;
186         }
187         return *strc - *(const unsigned char *)(ascii_str - 1);
188 }
189
190 /* fill the info in sample (except the raw value) */
191 static void
192 fill_sample (MonoCounterSample *sample)
193 {
194         sample->timeStamp = mono_100ns_ticks ();
195         sample->timeStamp100nSec = sample->timeStamp;
196         sample->counterTimeStamp = sample->timeStamp;
197         sample->counterFrequency = 10000000;
198         sample->systemFrequency = 10000000;
199         // the real basevalue needs to be get from a different counter...
200         sample->baseValue = 0;
201 }
202
203 static int
204 id_from_string (MonoString *instance)
205 {
206         int id = -1;
207         if (mono_string_length (instance)) {
208                 char *id_str = mono_string_to_utf8 (instance);
209                 char *end;
210                 id = strtol (id_str, &end, 0);
211                 g_free (id_str);
212                 if (end == id_str)
213                         return -1;
214         }
215         return id;
216 }
217
218 static void
219 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
220 {
221         char buf [256];
222         char *s;
223         int hz = 100 * 2; // 2 numprocs
224         long long unsigned int user_ticks, nice_ticks, system_ticks, idle_ticks, iowait_ticks, irq_ticks, sirq_ticks;
225         FILE *f = fopen ("/proc/stat", "r");
226         if (!f)
227                 return;
228         while ((s = fgets (buf, sizeof (buf), f))) {
229                 char *data = NULL;
230                 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
231                         data = s + 4;
232                 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
233                         if (data == s + 3)
234                                 continue;
235                         data++;
236                 } else {
237                         continue;
238                 }
239                 sscanf (data, "%Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_ticks, &nice_ticks, &system_ticks, &idle_ticks, &iowait_ticks, &irq_ticks, &sirq_ticks);
240         }
241         fclose (f);
242
243         if (user)
244                 *user = (user_ticks + nice_ticks) * 10000000 / hz;
245         if (systemt)
246                 *systemt = (system_ticks) * 10000000 / hz;
247         if (irq)
248                 *irq = (irq_ticks) * 10000000 / hz;
249         if (sirq)
250                 *sirq = (sirq_ticks) * 10000000 / hz;
251         if (idle)
252                 *idle = (idle_ticks) * 10000000 / hz;
253 }
254
255 static MonoBoolean
256 get_cpu_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
257 {
258         gint64 value = 0;
259         int id = GPOINTER_TO_INT (vtable->arg);
260         int pid = id >> 5;
261         id &= 0x1f;
262         if (!only_value) {
263                 fill_sample (sample);
264                 sample->baseValue = 1;
265         }
266         sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
267         switch (id) {
268         case COUNTER_CPU_USER_TIME:
269                 get_cpu_times (pid, &value, NULL, NULL, NULL, NULL);
270                 sample->rawValue = value;
271                 return TRUE;
272         case COUNTER_CPU_PRIV_TIME:
273                 get_cpu_times (pid, NULL, &value, NULL, NULL, NULL);
274                 sample->rawValue = value;
275                 return TRUE;
276         case COUNTER_CPU_INTR_TIME:
277                 get_cpu_times (pid, NULL, NULL, &value, NULL, NULL);
278                 sample->rawValue = value;
279                 return TRUE;
280         case COUNTER_CPU_DCP_TIME:
281                 get_cpu_times (pid, NULL, NULL, NULL, &value, NULL);
282                 sample->rawValue = value;
283                 return TRUE;
284         case COUNTER_CPU_PROC_TIME:
285                 get_cpu_times (pid, NULL, NULL, NULL, NULL, &value);
286                 sample->rawValue = value;
287                 return TRUE;
288         }
289         return FALSE;
290 }
291
292 static void*
293 cpu_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
294 {
295         int id = id_from_string (instance) << 5;
296         int i;
297         const CategoryDesc *desc = &predef_categories [CATEGORY_CPU];
298         *custom = FALSE;
299         /* increase the shift above and the mask also in the implementation functions */
300         g_assert (32 > desc [1].first_counter - desc->first_counter);
301         for (i = desc->first_counter; i < desc [1].first_counter; ++i) {
302                 const CounterDesc *cdesc = &predef_counters [i];
303                 if (mono_string_compare_ascii (counter, cdesc->name) == 0) {
304                         *type = cdesc->type;
305                         return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_cpu_counter, NULL);
306                 }
307         }
308         return NULL;
309 }
310
311 /*
312  * /proc/pid/stat format:
313  * pid (cmdname) S 
314  *      [0] ppid pgid sid tty_nr tty_pgrp flags min_flt cmin_flt maj_flt cmaj_flt
315  *      [10] utime stime cutime cstime prio nice threads start_time vsize rss
316  *      [20] rsslim start_code end_code start_stack esp eip pending blocked sigign sigcatch
317  *      [30] wchan 0 0 exit_signal cpu rt_prio policy
318  */
319
320 static gint64
321 get_process_time (int pid, int pos, int sum)
322 {
323         char buf [512];
324         char *s, *end;
325         FILE *f;
326         int len, i;
327         gint64 value;
328
329         g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
330         f = fopen (buf, "r");
331         if (!f)
332                 return 0;
333         len = fread (buf, 1, sizeof (buf), f);
334         fclose (f);
335         if (len <= 0)
336                 return 0;
337         s = strchr (buf, ')');
338         if (!s)
339                 return 0;
340         s++;
341         while (g_ascii_isspace (*s)) s++;
342         if (!*s)
343                 return 0;
344         /* skip the status char */
345         while (*s && !g_ascii_isspace (*s)) s++;
346         if (!*s)
347                 return 0;
348         for (i = 0; i < pos; ++i) {
349                 while (g_ascii_isspace (*s)) s++;
350                 if (!*s)
351                         return 0;
352                 while (*s && !g_ascii_isspace (*s)) s++;
353                 if (!*s)
354                         return 0;
355         }
356         /* we are finally at the needed item */
357         value = strtoul (s, &end, 0);
358         /* add also the following value */
359         if (sum) {
360                 while (g_ascii_isspace (*s)) s++;
361                 if (!*s)
362                         return 0;
363                 value += strtoul (s, &end, 0);
364         }
365         return value;
366 }
367
368 static gint64
369 get_pid_stat_item (int pid, const char *item)
370 {
371         char buf [256];
372         char *s;
373         FILE *f;
374         int len = strlen (item);
375
376         g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
377         f = fopen (buf, "r");
378         if (!f)
379                 return 0;
380         while ((s = fgets (buf, sizeof (buf), f))) {
381                 if (*item != *buf)
382                         continue;
383                 if (strncmp (buf, item, len))
384                         continue;
385                 if (buf [len] != ':')
386                         continue;
387                 fclose (f);
388                 return atoi (buf + len + 1);
389         }
390         fclose (f);
391         return 0;
392 }
393
394 static MonoBoolean
395 get_process_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
396 {
397         int id = GPOINTER_TO_INT (vtable->arg);
398         int pid = id >> 5;
399         if (pid < 0)
400                 return FALSE;
401         id &= 0x1f;
402         if (!only_value) {
403                 fill_sample (sample);
404                 sample->baseValue = 1;
405         }
406         sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
407         switch (id) {
408         case COUNTER_PROC_USER_TIME:
409                 sample->rawValue = get_process_time (pid, 12, FALSE);
410                 return TRUE;
411         case COUNTER_PROC_PRIV_TIME:
412                 sample->rawValue = get_process_time (pid, 13, FALSE);
413                 return TRUE;
414         case COUNTER_PROC_PROC_TIME:
415                 sample->rawValue = get_process_time (pid, 12, TRUE);
416                 return TRUE;
417         case COUNTER_PROC_THREADS:
418                 sample->rawValue = get_pid_stat_item (pid, "Threads");
419                 return TRUE;
420         case COUNTER_PROC_VBYTES:
421                 sample->rawValue = get_pid_stat_item (pid, "VmSize") * 1024;
422                 return TRUE;
423         case COUNTER_PROC_WSET:
424                 sample->rawValue = get_pid_stat_item (pid, "VmRSS") * 1024;
425                 return TRUE;
426         case COUNTER_PROC_PBYTES:
427                 sample->rawValue = get_pid_stat_item (pid, "VmData") * 1024;
428                 return TRUE;
429         }
430         return FALSE;
431 }
432
433 static void*
434 process_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
435 {
436         int id = id_from_string (instance) << 5;
437         int i;
438         const CategoryDesc *desc = &predef_categories [CATEGORY_PROC];
439         *custom = FALSE;
440         /* increase the shift above and the mask also in the implementation functions */
441         g_assert (32 > desc [1].first_counter - desc->first_counter);
442         for (i = desc->first_counter; i < desc [1].first_counter; ++i) {
443                 const CounterDesc *cdesc = &predef_counters [i];
444                 if (mono_string_compare_ascii (counter, cdesc->name) == 0) {
445                         *type = cdesc->type;
446                         return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_process_counter, NULL);
447                 }
448         }
449         return NULL;
450 }
451
452 static MonoBoolean
453 mono_mem_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
454 {
455         int id = GPOINTER_TO_INT (vtable->arg);
456         if (!only_value) {
457                 fill_sample (sample);
458                 sample->baseValue = 1;
459         }
460         sample->counterType = predef_counters [predef_categories [CATEGORY_MONO_MEM].first_counter + id].type;
461         switch (id) {
462         case COUNTER_MEM_NUM_OBJECTS:
463                 sample->rawValue = mono_stats.new_object_count;
464                 return TRUE;
465         }
466         return FALSE;
467 }
468
469 static void*
470 mono_mem_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
471 {
472         int i;
473         const CategoryDesc *desc = &predef_categories [CATEGORY_MONO_MEM];
474         *custom = FALSE;
475         for (i = desc->first_counter; i < desc [1].first_counter; ++i) {
476                 const CounterDesc *cdesc = &predef_counters [i];
477                 if (mono_string_compare_ascii (counter, cdesc->name) == 0) {
478                         *type = cdesc->type;
479                         return create_vtable (GINT_TO_POINTER (cdesc->id), mono_mem_counter, NULL);
480                 }
481         }
482         return NULL;
483 }
484
485 /* consider storing the pointer directly in vtable->arg, so the runtime overhead is lower:
486  * this needs some way to set sample->counterType as well, though.
487  */
488 static MonoBoolean
489 predef_writable_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
490 {
491         int cat_id = GPOINTER_TO_INT (vtable->arg);
492         int id = cat_id >> 16;
493         cat_id &= 0xffff;
494         if (!only_value) {
495                 fill_sample (sample);
496                 sample->baseValue = 1;
497         }
498         sample->counterType = predef_counters [predef_categories [cat_id].first_counter + id].type;
499         switch (id) {
500         case COUNTER_ASPNET_REQ_Q:
501                 sample->rawValue = mono_perfcounters->aspnet_requests_queued;
502                 return TRUE;
503         }
504         return FALSE;
505 }
506
507 static gint64
508 predef_writable_update (ImplVtable *vtable, MonoBoolean do_incr, gint64 value)
509 {
510         glong *ptr = NULL;
511         int cat_id = GPOINTER_TO_INT (vtable->arg);
512         int id = cat_id >> 16;
513         cat_id &= 0xffff;
514         switch (cat_id) {
515         case CATEGORY_ASPNET:
516                 switch (id) {
517                 case COUNTER_ASPNET_REQ_Q: ptr = (glong*)&mono_perfcounters->aspnet_requests_queued; break;
518                 }
519                 break;
520         }
521         if (ptr) {
522                 if (do_incr) {
523                         /* FIXME: we need to do this atomically */
524                         *ptr += value;
525                         return *ptr;
526                 }
527                 /* this can be non-atomic */
528                 *ptr = value;
529                 return value;
530         }
531         return 0;
532 }
533
534 static void*
535 predef_writable_get_impl (int cat, MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
536 {
537         int i;
538         const CategoryDesc *desc = &predef_categories [cat];
539         *custom = TRUE;
540         for (i = desc->first_counter; i < desc [1].first_counter; ++i) {
541                 const CounterDesc *cdesc = &predef_counters [i];
542                 if (mono_string_compare_ascii (counter, cdesc->name) == 0) {
543                         *type = cdesc->type;
544                         return create_vtable (GINT_TO_POINTER ((cdesc->id << 16) | cat), predef_writable_counter, predef_writable_update);
545                 }
546         }
547         return NULL;
548 }
549
550 static const CategoryDesc*
551 find_category (MonoString *category)
552 {
553         int i;
554         for (i = 0; i < NUM_CATEGORIES; ++i) {
555                 if (mono_string_compare_ascii (category, predef_categories [i].name) == 0)
556                         return &predef_categories [i];
557         }
558         return NULL;
559 }
560
561 void*
562 mono_perfcounter_get_impl (MonoString* category, MonoString* counter, MonoString* instance,
563                 MonoString* machine, int *type, MonoBoolean *custom)
564 {
565         const CategoryDesc *cdesc;
566         /* no support for counters on other machines */
567         if (mono_string_compare_ascii (machine, "."))
568                 return NULL;
569         cdesc = find_category (category);
570         if (!cdesc)
571                 return NULL;
572         switch (cdesc->id) {
573         case CATEGORY_CPU:
574                 return cpu_get_impl (counter, instance, type, custom);
575         case CATEGORY_PROC:
576                 return process_get_impl (counter, instance, type, custom);
577         case CATEGORY_MONO_MEM:
578                 return mono_mem_get_impl (counter, instance, type, custom);
579         case CATEGORY_ASPNET:
580                 return predef_writable_get_impl (cdesc->id, counter, instance, type, custom);
581         }
582         return NULL;
583 }
584
585 MonoBoolean
586 mono_perfcounter_get_sample (void *impl, MonoBoolean only_value, MonoCounterSample *sample)
587 {
588         ImplVtable *vtable = impl;
589         if (vtable && vtable->sample)
590                 return vtable->sample (vtable, only_value, sample);
591         return FALSE;
592 }
593
594 gint64
595 mono_perfcounter_update_value (void *impl, MonoBoolean do_incr, gint64 value)
596 {
597         ImplVtable *vtable = impl;
598         if (vtable && vtable->update)
599                 return vtable->update (vtable, do_incr, value);
600         return 0;
601 }
602
603 void
604 mono_perfcounter_free_data (void *impl)
605 {
606         g_free (impl);
607 }
608
609 /* Category icalls */
610 MonoBoolean
611 mono_perfcounter_category_del (MonoString *name)
612 {
613         return FALSE;
614 }
615
616 MonoString*
617 mono_perfcounter_category_help (MonoString *category, MonoString *machine)
618 {
619         const CategoryDesc *cdesc;
620         /* no support for counters on other machines */
621         if (mono_string_compare_ascii (machine, "."))
622                 return NULL;
623         cdesc = find_category (category);
624         if (!cdesc)
625                 return NULL;
626         return mono_string_new (mono_domain_get (), cdesc->help);
627 }
628
629 MonoBoolean
630 mono_perfcounter_category_exists (MonoString *counter, MonoString *category, MonoString *machine)
631 {
632         int i;
633         const CategoryDesc *cdesc;
634         /* no support for counters on other machines */
635         if (mono_string_compare_ascii (machine, "."))
636                 return FALSE;
637         cdesc = find_category (category);
638         if (!cdesc)
639                 return FALSE;
640         /* counter is allowed to be null */
641         if (!counter)
642                 return TRUE;
643         for (i = cdesc->first_counter; i < cdesc [1].first_counter; ++i) {
644                 const CounterDesc *desc = &predef_counters [i];
645                 if (mono_string_compare_ascii (counter, desc->name) == 0)
646                         return TRUE;
647         }
648         return FALSE;
649 }
650
651 /*
652  * Since we'll keep a copy of the category per-process, we should also make sure
653  * categories with the same name are compatible.
654  */
655 MonoBoolean
656 mono_perfcounter_create (MonoString *category, MonoString *help, int type, MonoArray *items)
657 {
658         return FALSE;
659 }
660
661 int
662 mono_perfcounter_instance_exists (MonoString *instance, MonoString *category, MonoString *machine)
663 {
664         const CategoryDesc *cdesc;
665         /* no support for counters on other machines */
666         if (mono_string_compare_ascii (machine, "."))
667                 return FALSE;
668         cdesc = find_category (category);
669         if (!cdesc)
670                 return FALSE;
671         return FALSE;
672 }
673
674 MonoArray*
675 mono_perfcounter_category_names (MonoString *machine)
676 {
677         int i;
678         MonoArray *res;
679         MonoDomain *domain = mono_domain_get ();
680         /* no support for counters on other machines */
681         if (mono_string_compare_ascii (machine, "."))
682                 return mono_array_new (domain, mono_get_string_class (), 0);
683         res = mono_array_new (domain, mono_get_string_class (), NUM_CATEGORIES);
684         for (i = 0; i < NUM_CATEGORIES; ++i) {
685                 const CategoryDesc *cdesc = &predef_categories [i];
686                 mono_array_setref (res, i, mono_string_new (domain, cdesc->name));
687         }
688         return res;
689 }
690
691 MonoArray*
692 mono_perfcounter_counter_names (MonoString *category, MonoString *machine)
693 {
694         int i;
695         const CategoryDesc *cdesc;
696         MonoArray *res;
697         MonoDomain *domain = mono_domain_get ();
698         /* no support for counters on other machines */
699         if (mono_string_compare_ascii (machine, ".") || !(cdesc = find_category (category)))
700                 return mono_array_new (domain, mono_get_string_class (), 0);
701         res = mono_array_new (domain, mono_get_string_class (), cdesc [1].first_counter - cdesc->first_counter);
702         for (i = cdesc->first_counter; i < cdesc [1].first_counter; ++i) {
703                 const CounterDesc *desc = &predef_counters [i];
704                 mono_array_setref (res, i - cdesc->first_counter, mono_string_new (domain, desc->name));
705         }
706         return res;
707 }
708
709 MonoArray*
710 mono_perfcounter_instance_names (MonoString *category, MonoString *machine)
711 {
712         return mono_array_new (mono_domain_get (), mono_get_string_class (), 0);
713 }
714