4 * Performance counters support.
6 * Author: Paolo Molaro (lupus@ximian.com)
16 #include "metadata/mono-perfcounters.h"
17 #include "metadata/appdomain.h"
19 #include "metadata/class-internals.h"
20 #include "utils/mono-time.h"
21 #include <mono/io-layer/io-layer.h>
23 /* map of CounterSample.cs */
24 struct _MonoCounterSample {
27 gint64 counterFrequency;
28 gint64 systemFrequency;
30 gint64 timeStamp100nSec;
31 gint64 counterTimeStamp;
35 /* map of PerformanceCounterType.cs */
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,
64 CounterMultiBase=0x42030500
73 #define PERFCTR_CAT(id,name,help,type,first_counter) CATEGORY_ ## id,
74 #define PERFCTR_COUNTER(id,name,help,type)
76 #include "mono-perfcounters-def.h"
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 */
86 #include "mono-perfcounters-def.h"
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 */
96 #include "mono-perfcounters-def.h"
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}
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"
136 * We have several different classes of counters:
138 * *) runtime counters
140 * *) user-defined counters
141 * *) windows counters (the implementation on windows will use this)
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.
146 typedef struct _ImplVtable ImplVtable;
148 typedef MonoBoolean (*SampleFunc) (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample* sample);
149 typedef gint64 (*UpdateFunc) (ImplVtable *vtable, MonoBoolean do_incr, gint64 value);
158 create_vtable (void *arg, SampleFunc sample, UpdateFunc update)
160 ImplVtable *vtable = g_new (ImplVtable, 1);
162 vtable->sample = sample;
163 vtable->update = update;
167 MonoPerfCounters *mono_perfcounters = NULL;
170 mono_perfcounters_init (void)
172 /* later allocate in the shared memory area */
173 mono_perfcounters = g_new0 (MonoPerfCounters, 1);
177 mono_string_compare_ascii (MonoString *str, const char *ascii_str)
179 guint16 *strc = mono_string_chars (str);
180 while (*strc == *ascii_str++) {
185 return *strc - *(const unsigned char *)(ascii_str - 1);
188 /* fill the info in sample (except the raw value) */
190 fill_sample (MonoCounterSample *sample)
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;
202 id_from_string (MonoString *instance)
205 if (mono_string_length (instance)) {
206 char *id_str = mono_string_to_utf8 (instance);
208 id = strtol (id_str, &end, 0);
217 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
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");
226 while ((s = fgets (buf, sizeof (buf), f))) {
228 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
230 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
237 sscanf (data, "%Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_ticks, &nice_ticks, &system_ticks, &idle_ticks, &iowait_ticks, &irq_ticks, &sirq_ticks);
242 *user = (user_ticks + nice_ticks) * 10000000 / hz;
244 *systemt = (system_ticks) * 10000000 / hz;
246 *irq = (irq_ticks) * 10000000 / hz;
248 *sirq = (sirq_ticks) * 10000000 / hz;
250 *idle = (idle_ticks) * 10000000 / hz;
254 get_cpu_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
257 int id = GPOINTER_TO_INT (vtable->arg);
261 fill_sample (sample);
262 sample->baseValue = 1;
264 sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
266 case COUNTER_CPU_USER_TIME:
267 get_cpu_times (pid, &value, NULL, NULL, NULL, NULL);
268 sample->rawValue = value;
270 case COUNTER_CPU_PRIV_TIME:
271 get_cpu_times (pid, NULL, &value, NULL, NULL, NULL);
272 sample->rawValue = value;
274 case COUNTER_CPU_INTR_TIME:
275 get_cpu_times (pid, NULL, NULL, &value, NULL, NULL);
276 sample->rawValue = value;
278 case COUNTER_CPU_DCP_TIME:
279 get_cpu_times (pid, NULL, NULL, NULL, &value, NULL);
280 sample->rawValue = value;
282 case COUNTER_CPU_PROC_TIME:
283 get_cpu_times (pid, NULL, NULL, NULL, NULL, &value);
284 sample->rawValue = value;
291 cpu_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
293 int id = id_from_string (instance) << 5;
295 const CategoryDesc *desc = &predef_categories [CATEGORY_CPU];
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) {
303 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_cpu_counter, NULL);
310 * /proc/pid/stat format:
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
319 get_process_time (int pid, int pos, int sum)
327 g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
328 f = fopen (buf, "r");
331 len = fread (buf, 1, sizeof (buf), f);
335 s = strchr (buf, ')');
339 while (g_ascii_isspace (*s)) s++;
342 /* skip the status char */
343 while (*s && !g_ascii_isspace (*s)) s++;
346 for (i = 0; i < pos; ++i) {
347 while (g_ascii_isspace (*s)) s++;
350 while (*s && !g_ascii_isspace (*s)) s++;
354 /* we are finally at the needed item */
355 value = strtoul (s, &end, 0);
356 /* add also the following value */
358 while (g_ascii_isspace (*s)) s++;
361 value += strtoul (s, &end, 0);
367 get_pid_stat_item (int pid, const char *item)
372 int len = strlen (item);
374 g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
375 f = fopen (buf, "r");
378 while ((s = fgets (buf, sizeof (buf), f))) {
381 if (strncmp (buf, item, len))
383 if (buf [len] != ':')
386 return atoi (buf + len + 1);
393 get_process_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
395 int id = GPOINTER_TO_INT (vtable->arg);
401 fill_sample (sample);
402 sample->baseValue = 1;
404 sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
406 case COUNTER_PROC_USER_TIME:
407 sample->rawValue = get_process_time (pid, 12, FALSE);
409 case COUNTER_PROC_PRIV_TIME:
410 sample->rawValue = get_process_time (pid, 13, FALSE);
412 case COUNTER_PROC_PROC_TIME:
413 sample->rawValue = get_process_time (pid, 12, TRUE);
415 case COUNTER_PROC_THREADS:
416 sample->rawValue = get_pid_stat_item (pid, "Threads");
418 case COUNTER_PROC_VBYTES:
419 sample->rawValue = get_pid_stat_item (pid, "VmSize") * 1024;
421 case COUNTER_PROC_WSET:
422 sample->rawValue = get_pid_stat_item (pid, "VmRSS") * 1024;
424 case COUNTER_PROC_PBYTES:
425 sample->rawValue = get_pid_stat_item (pid, "VmData") * 1024;
432 process_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
434 int id = id_from_string (instance) << 5;
436 const CategoryDesc *desc = &predef_categories [CATEGORY_PROC];
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) {
444 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_process_counter, NULL);
451 mono_mem_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
453 int id = GPOINTER_TO_INT (vtable->arg);
455 fill_sample (sample);
456 sample->baseValue = 1;
458 sample->counterType = predef_counters [predef_categories [CATEGORY_MONO_MEM].first_counter + id].type;
460 case COUNTER_MEM_NUM_OBJECTS:
461 sample->rawValue = mono_stats.new_object_count;
468 mono_mem_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
471 const CategoryDesc *desc = &predef_categories [CATEGORY_MONO_MEM];
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) {
477 return create_vtable (GINT_TO_POINTER (cdesc->id), mono_mem_counter, NULL);
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.
487 predef_writable_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
489 int cat_id = GPOINTER_TO_INT (vtable->arg);
490 int id = cat_id >> 16;
493 fill_sample (sample);
494 sample->baseValue = 1;
496 sample->counterType = predef_counters [predef_categories [cat_id].first_counter + id].type;
498 case COUNTER_ASPNET_REQ_Q:
499 sample->rawValue = mono_perfcounters->aspnet_requests_queued;
506 predef_writable_update (ImplVtable *vtable, MonoBoolean do_incr, gint64 value)
509 int cat_id = GPOINTER_TO_INT (vtable->arg);
510 int id = cat_id >> 16;
513 case CATEGORY_ASPNET:
515 case COUNTER_ASPNET_REQ_Q: ptr = (glong*)&mono_perfcounters->aspnet_requests_queued; break;
521 /* FIXME: we need to do this atomically */
525 /* this can be non-atomic */
533 predef_writable_get_impl (int cat, MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
536 const CategoryDesc *desc = &predef_categories [cat];
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) {
542 return create_vtable (GINT_TO_POINTER ((cdesc->id << 16) | cat), predef_writable_counter, predef_writable_update);
548 static const CategoryDesc*
549 find_category (MonoString *category)
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];
560 mono_perfcounter_get_impl (MonoString* category, MonoString* counter, MonoString* instance,
561 MonoString* machine, int *type, MonoBoolean *custom)
563 const CategoryDesc *cdesc;
564 /* no support for counters on other machines */
565 if (mono_string_compare_ascii (machine, "."))
567 cdesc = find_category (category);
572 return cpu_get_impl (counter, instance, type, custom);
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);
584 mono_perfcounter_get_sample (void *impl, MonoBoolean only_value, MonoCounterSample *sample)
586 ImplVtable *vtable = impl;
587 if (vtable && vtable->sample)
588 return vtable->sample (vtable, only_value, sample);
593 mono_perfcounter_update_value (void *impl, MonoBoolean do_incr, gint64 value)
595 ImplVtable *vtable = impl;
596 if (vtable && vtable->update)
597 return vtable->update (vtable, do_incr, value);
602 mono_perfcounter_free_data (void *impl)
607 /* Category icalls */
609 mono_perfcounter_category_del (MonoString *name)
615 mono_perfcounter_category_help (MonoString *category, MonoString *machine)
617 const CategoryDesc *cdesc;
618 /* no support for counters on other machines */
619 if (mono_string_compare_ascii (machine, "."))
621 cdesc = find_category (category);
624 return mono_string_new (mono_domain_get (), cdesc->help);
628 mono_perfcounter_category_exists (MonoString *counter, MonoString *category, MonoString *machine)
631 const CategoryDesc *cdesc;
632 /* no support for counters on other machines */
633 if (mono_string_compare_ascii (machine, "."))
635 cdesc = find_category (category);
638 /* counter is allowed to be null */
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)
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.
654 mono_perfcounter_create (MonoString *category, MonoString *help, int type, MonoArray *items)
660 mono_perfcounter_instance_exists (MonoString *instance, MonoString *category, MonoString *machine)
662 const CategoryDesc *cdesc;
663 /* no support for counters on other machines */
664 if (mono_string_compare_ascii (machine, "."))
666 cdesc = find_category (category);
673 mono_perfcounter_category_names (MonoString *machine)
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));
690 mono_perfcounter_counter_names (MonoString *category, MonoString *machine)
693 const CategoryDesc *cdesc;
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));
708 mono_perfcounter_instance_names (MonoString *category, MonoString *machine)
710 return mono_array_new (mono_domain_get (), mono_get_string_class (), 0);