4 * Performance counters support.
6 * Author: Paolo Molaro (lupus@ximian.com)
15 #ifdef HAVE_SYS_TIME_H
18 #include "metadata/mono-perfcounters.h"
19 #include "metadata/appdomain.h"
21 #include "metadata/class-internals.h"
22 #include "utils/mono-time.h"
23 #include <mono/io-layer/io-layer.h>
25 /* map of CounterSample.cs */
26 struct _MonoCounterSample {
29 gint64 counterFrequency;
30 gint64 systemFrequency;
32 gint64 timeStamp100nSec;
33 gint64 counterTimeStamp;
37 /* map of PerformanceCounterType.cs */
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,
66 CounterMultiBase=0x42030500
75 #define PERFCTR_CAT(id,name,help,type,first_counter) CATEGORY_ ## id,
76 #define PERFCTR_COUNTER(id,name,help,type)
78 #include "mono-perfcounters-def.h"
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 */
88 #include "mono-perfcounters-def.h"
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 */
98 #include "mono-perfcounters-def.h"
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}
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"
138 * We have several different classes of counters:
140 * *) runtime counters
142 * *) user-defined counters
143 * *) windows counters (the implementation on windows will use this)
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.
148 typedef struct _ImplVtable ImplVtable;
150 typedef MonoBoolean (*SampleFunc) (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample* sample);
151 typedef gint64 (*UpdateFunc) (ImplVtable *vtable, MonoBoolean do_incr, gint64 value);
160 create_vtable (void *arg, SampleFunc sample, UpdateFunc update)
162 ImplVtable *vtable = g_new (ImplVtable, 1);
164 vtable->sample = sample;
165 vtable->update = update;
169 MonoPerfCounters *mono_perfcounters = NULL;
172 mono_perfcounters_init (void)
174 /* later allocate in the shared memory area */
175 mono_perfcounters = g_new0 (MonoPerfCounters, 1);
179 mono_string_compare_ascii (MonoString *str, const char *ascii_str)
181 guint16 *strc = mono_string_chars (str);
182 while (*strc == *ascii_str++) {
187 return *strc - *(const unsigned char *)(ascii_str - 1);
190 /* fill the info in sample (except the raw value) */
192 fill_sample (MonoCounterSample *sample)
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;
204 id_from_string (MonoString *instance)
207 if (mono_string_length (instance)) {
208 char *id_str = mono_string_to_utf8 (instance);
210 id = strtol (id_str, &end, 0);
219 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
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");
228 while ((s = fgets (buf, sizeof (buf), f))) {
230 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
232 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
239 sscanf (data, "%Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_ticks, &nice_ticks, &system_ticks, &idle_ticks, &iowait_ticks, &irq_ticks, &sirq_ticks);
244 *user = (user_ticks + nice_ticks) * 10000000 / hz;
246 *systemt = (system_ticks) * 10000000 / hz;
248 *irq = (irq_ticks) * 10000000 / hz;
250 *sirq = (sirq_ticks) * 10000000 / hz;
252 *idle = (idle_ticks) * 10000000 / hz;
256 get_cpu_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
259 int id = GPOINTER_TO_INT (vtable->arg);
263 fill_sample (sample);
264 sample->baseValue = 1;
266 sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
268 case COUNTER_CPU_USER_TIME:
269 get_cpu_times (pid, &value, NULL, NULL, NULL, NULL);
270 sample->rawValue = value;
272 case COUNTER_CPU_PRIV_TIME:
273 get_cpu_times (pid, NULL, &value, NULL, NULL, NULL);
274 sample->rawValue = value;
276 case COUNTER_CPU_INTR_TIME:
277 get_cpu_times (pid, NULL, NULL, &value, NULL, NULL);
278 sample->rawValue = value;
280 case COUNTER_CPU_DCP_TIME:
281 get_cpu_times (pid, NULL, NULL, NULL, &value, NULL);
282 sample->rawValue = value;
284 case COUNTER_CPU_PROC_TIME:
285 get_cpu_times (pid, NULL, NULL, NULL, NULL, &value);
286 sample->rawValue = value;
293 cpu_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
295 int id = id_from_string (instance) << 5;
297 const CategoryDesc *desc = &predef_categories [CATEGORY_CPU];
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) {
305 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_cpu_counter, NULL);
312 * /proc/pid/stat format:
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
321 get_process_time (int pid, int pos, int sum)
329 g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
330 f = fopen (buf, "r");
333 len = fread (buf, 1, sizeof (buf), f);
337 s = strchr (buf, ')');
341 while (g_ascii_isspace (*s)) s++;
344 /* skip the status char */
345 while (*s && !g_ascii_isspace (*s)) s++;
348 for (i = 0; i < pos; ++i) {
349 while (g_ascii_isspace (*s)) s++;
352 while (*s && !g_ascii_isspace (*s)) s++;
356 /* we are finally at the needed item */
357 value = strtoul (s, &end, 0);
358 /* add also the following value */
360 while (g_ascii_isspace (*s)) s++;
363 value += strtoul (s, &end, 0);
369 get_pid_stat_item (int pid, const char *item)
374 int len = strlen (item);
376 g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
377 f = fopen (buf, "r");
380 while ((s = fgets (buf, sizeof (buf), f))) {
383 if (strncmp (buf, item, len))
385 if (buf [len] != ':')
388 return atoi (buf + len + 1);
395 get_process_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
397 int id = GPOINTER_TO_INT (vtable->arg);
403 fill_sample (sample);
404 sample->baseValue = 1;
406 sample->counterType = predef_counters [predef_categories [CATEGORY_PROC].first_counter + id].type;
408 case COUNTER_PROC_USER_TIME:
409 sample->rawValue = get_process_time (pid, 12, FALSE);
411 case COUNTER_PROC_PRIV_TIME:
412 sample->rawValue = get_process_time (pid, 13, FALSE);
414 case COUNTER_PROC_PROC_TIME:
415 sample->rawValue = get_process_time (pid, 12, TRUE);
417 case COUNTER_PROC_THREADS:
418 sample->rawValue = get_pid_stat_item (pid, "Threads");
420 case COUNTER_PROC_VBYTES:
421 sample->rawValue = get_pid_stat_item (pid, "VmSize") * 1024;
423 case COUNTER_PROC_WSET:
424 sample->rawValue = get_pid_stat_item (pid, "VmRSS") * 1024;
426 case COUNTER_PROC_PBYTES:
427 sample->rawValue = get_pid_stat_item (pid, "VmData") * 1024;
434 process_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
436 int id = id_from_string (instance) << 5;
438 const CategoryDesc *desc = &predef_categories [CATEGORY_PROC];
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) {
446 return create_vtable (GINT_TO_POINTER (id | cdesc->id), get_process_counter, NULL);
453 mono_mem_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
455 int id = GPOINTER_TO_INT (vtable->arg);
457 fill_sample (sample);
458 sample->baseValue = 1;
460 sample->counterType = predef_counters [predef_categories [CATEGORY_MONO_MEM].first_counter + id].type;
462 case COUNTER_MEM_NUM_OBJECTS:
463 sample->rawValue = mono_stats.new_object_count;
470 mono_mem_get_impl (MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
473 const CategoryDesc *desc = &predef_categories [CATEGORY_MONO_MEM];
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) {
479 return create_vtable (GINT_TO_POINTER (cdesc->id), mono_mem_counter, NULL);
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.
489 predef_writable_counter (ImplVtable *vtable, MonoBoolean only_value, MonoCounterSample *sample)
491 int cat_id = GPOINTER_TO_INT (vtable->arg);
492 int id = cat_id >> 16;
495 fill_sample (sample);
496 sample->baseValue = 1;
498 sample->counterType = predef_counters [predef_categories [cat_id].first_counter + id].type;
500 case COUNTER_ASPNET_REQ_Q:
501 sample->rawValue = mono_perfcounters->aspnet_requests_queued;
508 predef_writable_update (ImplVtable *vtable, MonoBoolean do_incr, gint64 value)
511 int cat_id = GPOINTER_TO_INT (vtable->arg);
512 int id = cat_id >> 16;
515 case CATEGORY_ASPNET:
517 case COUNTER_ASPNET_REQ_Q: ptr = (glong*)&mono_perfcounters->aspnet_requests_queued; break;
523 /* FIXME: we need to do this atomically */
527 /* this can be non-atomic */
535 predef_writable_get_impl (int cat, MonoString* counter, MonoString* instance, int *type, MonoBoolean *custom)
538 const CategoryDesc *desc = &predef_categories [cat];
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) {
544 return create_vtable (GINT_TO_POINTER ((cdesc->id << 16) | cat), predef_writable_counter, predef_writable_update);
550 static const CategoryDesc*
551 find_category (MonoString *category)
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];
562 mono_perfcounter_get_impl (MonoString* category, MonoString* counter, MonoString* instance,
563 MonoString* machine, int *type, MonoBoolean *custom)
565 const CategoryDesc *cdesc;
566 /* no support for counters on other machines */
567 if (mono_string_compare_ascii (machine, "."))
569 cdesc = find_category (category);
574 return cpu_get_impl (counter, instance, type, custom);
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);
586 mono_perfcounter_get_sample (void *impl, MonoBoolean only_value, MonoCounterSample *sample)
588 ImplVtable *vtable = impl;
589 if (vtable && vtable->sample)
590 return vtable->sample (vtable, only_value, sample);
595 mono_perfcounter_update_value (void *impl, MonoBoolean do_incr, gint64 value)
597 ImplVtable *vtable = impl;
598 if (vtable && vtable->update)
599 return vtable->update (vtable, do_incr, value);
604 mono_perfcounter_free_data (void *impl)
609 /* Category icalls */
611 mono_perfcounter_category_del (MonoString *name)
617 mono_perfcounter_category_help (MonoString *category, MonoString *machine)
619 const CategoryDesc *cdesc;
620 /* no support for counters on other machines */
621 if (mono_string_compare_ascii (machine, "."))
623 cdesc = find_category (category);
626 return mono_string_new (mono_domain_get (), cdesc->help);
630 mono_perfcounter_category_exists (MonoString *counter, MonoString *category, MonoString *machine)
633 const CategoryDesc *cdesc;
634 /* no support for counters on other machines */
635 if (mono_string_compare_ascii (machine, "."))
637 cdesc = find_category (category);
640 /* counter is allowed to be null */
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)
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.
656 mono_perfcounter_create (MonoString *category, MonoString *help, int type, MonoArray *items)
662 mono_perfcounter_instance_exists (MonoString *instance, MonoString *category, MonoString *machine)
664 const CategoryDesc *cdesc;
665 /* no support for counters on other machines */
666 if (mono_string_compare_ascii (machine, "."))
668 cdesc = find_category (category);
675 mono_perfcounter_category_names (MonoString *machine)
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));
692 mono_perfcounter_counter_names (MonoString *category, MonoString *machine)
695 const CategoryDesc *cdesc;
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));
710 mono_perfcounter_instance_names (MonoString *category, MonoString *machine)
712 return mono_array_new (mono_domain_get (), mono_get_string_class (), 0);