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