Merge pull request #1041 from mono/staged-cyclic-builds
[mono.git] / mono / utils / mono-proclib.c
1 /*
2  * Copyright 2008-2011 Novell Inc
3  * Copyright 2011 Xamarin Inc
4  */
5
6 #include "config.h"
7 #include "utils/mono-proclib.h"
8
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <fcntl.h>
13 #ifdef HAVE_UNISTD_H
14 #include <unistd.h>
15 #endif
16
17 #ifdef HOST_WIN32
18 #include <windows.h>
19 #include <process.h>
20 #endif
21
22 #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
23 #include <sys/param.h>
24 #include <sys/types.h>
25 #include <sys/sysctl.h>
26 #include <sys/proc.h>
27 #if defined(__APPLE__)
28 #include <mach/mach.h>
29 #endif
30 #ifdef HAVE_SYS_USER_H
31 #include <sys/user.h>
32 #endif
33 #ifdef HAVE_STRUCT_KINFO_PROC_KP_PROC
34 #    define kinfo_pid_member kp_proc.p_pid
35 #    define kinfo_name_member kp_proc.p_comm
36 #elif defined(__OpenBSD__)
37 #    define kinfo_pid_member p_pid
38 #    define kinfo_name_member p_comm
39 #else
40 #define kinfo_pid_member ki_pid
41 #define kinfo_name_member ki_comm
42 #endif
43 #define USE_SYSCTL 1
44 #endif
45
46 /**
47  * mono_process_list:
48  * @size: a pointer to a location where the size of the returned array is stored
49  *
50  * Return an array of pid values for the processes currently running on the system.
51  * The size of the array is stored in @size.
52  */
53 gpointer*
54 mono_process_list (int *size)
55 {
56 #if USE_SYSCTL
57         int res, i;
58 #ifdef KERN_PROC2
59         int mib [6];
60         size_t data_len = sizeof (struct kinfo_proc2) * 400;
61         struct kinfo_proc2 *processes = malloc (data_len);
62 #else
63         int mib [4];
64         size_t data_len = sizeof (struct kinfo_proc) * 400;
65         struct kinfo_proc *processes = malloc (data_len);
66 #endif /* KERN_PROC2 */
67         void **buf = NULL;
68
69         if (size)
70                 *size = 0;
71         if (!processes)
72                 return NULL;
73
74 #ifdef KERN_PROC2
75         mib [0] = CTL_KERN;
76         mib [1] = KERN_PROC2;
77         mib [2] = KERN_PROC_ALL;
78         mib [3] = 0;
79         mib [4] = sizeof(struct kinfo_proc2);
80         mib [5] = 400; /* XXX */
81
82         res = sysctl (mib, 6, processes, &data_len, NULL, 0);
83 #else
84         mib [0] = CTL_KERN;
85         mib [1] = KERN_PROC;
86         mib [2] = KERN_PROC_ALL;
87         mib [3] = 0;
88         
89         res = sysctl (mib, 4, processes, &data_len, NULL, 0);
90 #endif /* KERN_PROC2 */
91
92         if (res < 0) {
93                 free (processes);
94                 return NULL;
95         }
96 #ifdef KERN_PROC2
97         res = data_len/sizeof (struct kinfo_proc2);
98 #else
99         res = data_len/sizeof (struct kinfo_proc);
100 #endif /* KERN_PROC2 */
101         buf = g_realloc (buf, res * sizeof (void*));
102         for (i = 0; i < res; ++i)
103                 buf [i] = GINT_TO_POINTER (processes [i].kinfo_pid_member);
104         free (processes);
105         if (size)
106                 *size = res;
107         return buf;
108 #else
109         const char *name;
110         void **buf = NULL;
111         int count = 0;
112         int i = 0;
113         GDir *dir = g_dir_open ("/proc/", 0, NULL);
114         if (!dir) {
115                 if (size)
116                         *size = 0;
117                 return NULL;
118         }
119         while ((name = g_dir_read_name (dir))) {
120                 int pid;
121                 char *nend;
122                 pid = strtol (name, &nend, 10);
123                 if (pid <= 0 || nend == name || *nend)
124                         continue;
125                 if (i >= count) {
126                         if (!count)
127                                 count = 16;
128                         else
129                                 count *= 2;
130                         buf = g_realloc (buf, count * sizeof (void*));
131                 }
132                 buf [i++] = GINT_TO_POINTER (pid);
133         }
134         g_dir_close (dir);
135         if (size)
136                 *size = i;
137         return buf;
138 #endif
139 }
140
141 static G_GNUC_UNUSED char*
142 get_pid_status_item_buf (int pid, const char *item, char *rbuf, int blen, MonoProcessError *error)
143 {
144         char buf [256];
145         char *s;
146         FILE *f;
147         int len = strlen (item);
148
149         g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
150         f = fopen (buf, "r");
151         if (!f) {
152                 if (error)
153                         *error = MONO_PROCESS_ERROR_NOT_FOUND;
154                 return NULL;
155         }
156         while ((s = fgets (buf, blen, f))) {
157                 if (*item != *buf)
158                         continue;
159                 if (strncmp (buf, item, len))
160                         continue;
161                 s = buf + len;
162                 while (g_ascii_isspace (*s)) s++;
163                 if (*s++ != ':')
164                         continue;
165                 while (g_ascii_isspace (*s)) s++;
166                 fclose (f);
167                 len = strlen (s);
168                 strncpy (rbuf, s, MIN (len, blen));
169                 rbuf [MIN (len, blen) - 1] = 0;
170                 if (error)
171                         *error = MONO_PROCESS_ERROR_NONE;
172                 return rbuf;
173         }
174         fclose (f);
175         if (error)
176                 *error = MONO_PROCESS_ERROR_OTHER;
177         return NULL;
178 }
179
180 /**
181  * mono_process_get_name:
182  * @pid: pid of the process
183  * @buf: byte buffer where to store the name of the prcoess
184  * @len: size of the buffer @buf
185  *
186  * Return the name of the process identified by @pid, storing it
187  * inside @buf for a maximum of len bytes (including the terminating 0).
188  */
189 char*
190 mono_process_get_name (gpointer pid, char *buf, int len)
191 {
192 #if USE_SYSCTL
193         int res;
194 #ifdef KERN_PROC2
195         int mib [6];
196         size_t data_len = sizeof (struct kinfo_proc2);
197         struct kinfo_proc2 processi;
198 #else
199         int mib [4];
200         size_t data_len = sizeof (struct kinfo_proc);
201         struct kinfo_proc processi;
202 #endif /* KERN_PROC2 */
203
204         memset (buf, 0, len);
205
206 #ifdef KERN_PROC2
207         mib [0] = CTL_KERN;
208         mib [1] = KERN_PROC2;
209         mib [2] = KERN_PROC_PID;
210         mib [3] = GPOINTER_TO_UINT (pid);
211         mib [4] = sizeof(struct kinfo_proc2);
212         mib [5] = 400; /* XXX */
213
214         res = sysctl (mib, 6, &processi, &data_len, NULL, 0);
215
216         if (res < 0 || data_len != sizeof (struct kinfo_proc2)) {
217                 return buf;
218         }
219 #else
220         mib [0] = CTL_KERN;
221         mib [1] = KERN_PROC;
222         mib [2] = KERN_PROC_PID;
223         mib [3] = GPOINTER_TO_UINT (pid);
224         
225         res = sysctl (mib, 4, &processi, &data_len, NULL, 0);
226         if (res < 0 || data_len != sizeof (struct kinfo_proc)) {
227                 return buf;
228         }
229 #endif /* KERN_PROC2 */
230         strncpy (buf, processi.kinfo_name_member, len - 1);
231         return buf;
232 #else
233         char fname [128];
234         FILE *file;
235         char *p;
236         int r;
237         sprintf (fname, "/proc/%d/cmdline", GPOINTER_TO_INT (pid));
238         buf [0] = 0;
239         file = fopen (fname, "r");
240         if (!file)
241                 return buf;
242         r = fread (buf, 1, len - 1, file);
243         fclose (file);
244         buf [r] = 0;
245         p = strrchr (buf, '/');
246         if (p)
247                 return p + 1;
248         if (r == 0) {
249                 return get_pid_status_item_buf (GPOINTER_TO_INT (pid), "Name", buf, len, NULL);
250         }
251         return buf;
252 #endif
253 }
254
255 /*
256  * /proc/pid/stat format:
257  * pid (cmdname) S 
258  *      [0] ppid pgid sid tty_nr tty_pgrp flags min_flt cmin_flt maj_flt cmaj_flt
259  *      [10] utime stime cutime cstime prio nice threads 0 start_time vsize rss
260  *      [20] rss rsslim start_code end_code start_stack esp eip pending blocked sigign
261  *      [30] sigcatch wchan 0 0 exit_signal cpu rt_prio policy
262  */
263
264 #define RET_ERROR(err) do {     \
265                 if (error) *error = (err);      \
266                 return 0;                       \
267         } while (0)
268
269 static gint64
270 get_process_stat_item (int pid, int pos, int sum, MonoProcessError *error)
271 {
272 #if defined(__APPLE__) 
273         double process_user_time = 0, process_system_time = 0;//, process_percent = 0;
274         task_t task;
275         struct task_basic_info t_info;
276         mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT, th_count;
277         thread_array_t th_array;
278         size_t i;
279
280         if (task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS)
281                 RET_ERROR (MONO_PROCESS_ERROR_NOT_FOUND);
282
283         if (task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count) != KERN_SUCCESS) {
284                 mach_port_deallocate (mach_task_self (), task);
285                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
286         }
287         
288         if (task_threads(task, &th_array, &th_count) != KERN_SUCCESS) {
289                 mach_port_deallocate (mach_task_self (), task);
290                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
291         }
292                 
293         for (i = 0; i < th_count; i++) {
294                 double thread_user_time, thread_system_time;//, thread_percent;
295                 
296                 struct thread_basic_info th_info;
297                 mach_msg_type_number_t th_info_count = THREAD_BASIC_INFO_COUNT;
298                 if (thread_info(th_array[i], THREAD_BASIC_INFO, (thread_info_t)&th_info, &th_info_count) == KERN_SUCCESS) {
299                         thread_user_time = th_info.user_time.seconds + th_info.user_time.microseconds / 1e6;
300                         thread_system_time = th_info.system_time.seconds + th_info.system_time.microseconds / 1e6;
301                         //thread_percent = (double)th_info.cpu_usage / TH_USAGE_SCALE;
302                         
303                         process_user_time += thread_user_time;
304                         process_system_time += thread_system_time;
305                         //process_percent += th_percent;
306                 }
307         }
308         
309         for (i = 0; i < th_count; i++)
310                 mach_port_deallocate(task, th_array[i]);
311
312         mach_port_deallocate (mach_task_self (), task);
313
314         process_user_time += t_info.user_time.seconds + t_info.user_time.microseconds / 1e6;
315         process_system_time += t_info.system_time.seconds + t_info.system_time.microseconds / 1e6;
316     
317         if (pos == 10 && sum == TRUE)
318                 return (gint64)((process_user_time + process_system_time) * 10000000);
319         else if (pos == 10)
320                 return (gint64)(process_user_time * 10000000);
321         else if (pos == 11)
322                 return (gint64)(process_system_time * 10000000);
323                 
324         return 0;
325 #else
326         char buf [512];
327         char *s, *end;
328         FILE *f;
329         int len, i;
330         gint64 value;
331
332         g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
333         f = fopen (buf, "r");
334         if (!f)
335                 RET_ERROR (MONO_PROCESS_ERROR_NOT_FOUND);
336         len = fread (buf, 1, sizeof (buf), f);
337         fclose (f);
338         if (len <= 0)
339                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
340         s = strchr (buf, ')');
341         if (!s)
342                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
343         s++;
344         while (g_ascii_isspace (*s)) s++;
345         if (!*s)
346                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
347         /* skip the status char */
348         while (*s && !g_ascii_isspace (*s)) s++;
349         if (!*s)
350                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
351         for (i = 0; i < pos; ++i) {
352                 while (g_ascii_isspace (*s)) s++;
353                 if (!*s)
354                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
355                 while (*s && !g_ascii_isspace (*s)) s++;
356                 if (!*s)
357                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
358         }
359         /* we are finally at the needed item */
360         value = strtoul (s, &end, 0);
361         /* add also the following value */
362         if (sum) {
363                 while (g_ascii_isspace (*s)) s++;
364                 if (!*s)
365                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
366                 value += strtoul (s, &end, 0);
367         }
368         if (error)
369                 *error = MONO_PROCESS_ERROR_NONE;
370         return value;
371 #endif
372 }
373
374 static int
375 get_user_hz (void)
376 {
377         static int user_hz = 0;
378         if (user_hz == 0) {
379 #ifdef _SC_CLK_TCK
380                 user_hz = sysconf (_SC_CLK_TCK);
381 #endif
382                 if (user_hz == 0)
383                         user_hz = 100;
384         }
385         return user_hz;
386 }
387
388 static gint64
389 get_process_stat_time (int pid, int pos, int sum, MonoProcessError *error)
390 {
391         gint64 val = get_process_stat_item (pid, pos, sum, error);
392 #if defined(__APPLE__)
393         return val;
394 #else
395         /* return 100ns ticks */
396         return (val * 10000000) / get_user_hz ();
397 #endif
398 }
399
400 static gint64
401 get_pid_status_item (int pid, const char *item, MonoProcessError *error, int multiplier)
402 {
403 #if defined(__APPLE__)
404         // ignore the multiplier
405         
406         gint64 ret;
407         task_t task;
408         struct task_basic_info t_info;
409         mach_msg_type_number_t th_count = TASK_BASIC_INFO_COUNT;
410
411         if (task_for_pid (mach_task_self (), pid, &task) != KERN_SUCCESS)
412                 RET_ERROR (MONO_PROCESS_ERROR_NOT_FOUND);
413         
414         if (task_info (task, TASK_BASIC_INFO, (task_info_t)&t_info, &th_count) != KERN_SUCCESS) {
415                 mach_port_deallocate (mach_task_self (), task);
416                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
417         }
418
419         if (strcmp (item, "VmRSS") == 0 || strcmp (item, "VmHWM") == 0 || strcmp (item, "VmData") == 0)
420                 ret = t_info.resident_size;
421         else if (strcmp (item, "VmSize") == 0 || strcmp (item, "VmPeak") == 0)
422                 ret = t_info.virtual_size;
423         else if (strcmp (item, "Threads") == 0)
424                 ret = th_count;
425         else
426                 ret = 0;
427
428         mach_port_deallocate (mach_task_self (), task);
429         
430         return ret;
431 #else
432         char buf [64];
433         char *s;
434
435         s = get_pid_status_item_buf (pid, item, buf, sizeof (buf), error);
436         if (s)
437                 return ((gint64) atol (s)) * multiplier;
438         return 0;
439 #endif
440 }
441
442 /**
443  * mono_process_get_data:
444  * @pid: pid of the process
445  * @data: description of data to return
446  *
447  * Return a data item of a process like user time, memory use etc,
448  * according to the @data argumet.
449  */
450 gint64
451 mono_process_get_data_with_error (gpointer pid, MonoProcessData data, MonoProcessError *error)
452 {
453         gint64 val;
454         int rpid = GPOINTER_TO_INT (pid);
455
456         if (error)
457                 *error = MONO_PROCESS_ERROR_OTHER;
458
459         switch (data) {
460         case MONO_PROCESS_NUM_THREADS:
461                 return get_pid_status_item (rpid, "Threads", error, 1);
462         case MONO_PROCESS_USER_TIME:
463                 return get_process_stat_time (rpid, 10, FALSE, error);
464         case MONO_PROCESS_SYSTEM_TIME:
465                 return get_process_stat_time (rpid, 11, FALSE, error);
466         case MONO_PROCESS_TOTAL_TIME:
467                 return get_process_stat_time (rpid, 10, TRUE, error);
468         case MONO_PROCESS_WORKING_SET:
469                 return get_pid_status_item (rpid, "VmRSS", error, 1024);
470         case MONO_PROCESS_WORKING_SET_PEAK:
471                 val = get_pid_status_item (rpid, "VmHWM", error, 1024);
472                 if (val == 0)
473                         val = get_pid_status_item (rpid, "VmRSS", error, 1024);
474                 return val;
475         case MONO_PROCESS_PRIVATE_BYTES:
476                 return get_pid_status_item (rpid, "VmData", error, 1024);
477         case MONO_PROCESS_VIRTUAL_BYTES:
478                 return get_pid_status_item (rpid, "VmSize", error, 1024);
479         case MONO_PROCESS_VIRTUAL_BYTES_PEAK:
480                 val = get_pid_status_item (rpid, "VmPeak", error, 1024);
481                 if (val == 0)
482                         val = get_pid_status_item (rpid, "VmSize", error, 1024);
483                 return val;
484         case MONO_PROCESS_FAULTS:
485                 return get_process_stat_item (rpid, 6, TRUE, error);
486         case MONO_PROCESS_ELAPSED:
487                 return get_process_stat_item (rpid, 18, FALSE, error) / get_user_hz ();
488         case MONO_PROCESS_PPID:
489                 return get_process_stat_time (rpid, 0, FALSE, error);
490
491                 /* Nothing yet */
492         case MONO_PROCESS_END:
493                 return 0;
494         }
495         return 0;
496 }
497
498 gint64
499 mono_process_get_data (gpointer pid, MonoProcessData data)
500 {
501         MonoProcessError error;
502         return mono_process_get_data_with_error (pid, data, &error);
503 }
504
505 int
506 mono_process_current_pid ()
507 {
508 #if defined(HAVE_UNISTD_H)
509         return (int) getpid ();
510 #elif defined(HOST_WIN32)
511         return (int) GetCurrentProcessId ();
512 #else
513 #error getpid
514 #endif
515 }
516
517 /**
518  * mono_cpu_count:
519  *
520  * Return the number of processors on the system.
521  */
522 int
523 mono_cpu_count (void)
524 {
525         int count = 0;
526 #ifdef PLATFORM_ANDROID
527         /* Android tries really hard to save power by powering off CPUs on SMP phones which
528          * means the normal way to query cpu count returns a wrong value with userspace API.
529          * Instead we use /sys entries to query the actual hardware CPU count.
530          */
531         char buffer[8] = {'\0'};
532         int present = open ("/sys/devices/system/cpu/present", O_RDONLY);
533         /* Format of the /sys entry is a cpulist of indexes which in the case
534          * of present is always of the form "0-(n-1)" when there is more than
535          * 1 core, n being the number of CPU cores in the system. Otherwise
536          * the value is simply 0
537          */
538         if (present != -1 && read (present, (char*)buffer, sizeof (buffer)) > 3)
539                 count = strtol (((char*)buffer) + 2, NULL, 10);
540         if (present != -1)
541                 close (present);
542         if (count > 0)
543                 return count + 1;
544 #endif
545 #ifdef _SC_NPROCESSORS_ONLN
546         count = sysconf (_SC_NPROCESSORS_ONLN);
547         if (count > 0)
548                 return count;
549 #endif
550 #ifdef USE_SYSCTL
551         {
552                 int mib [2];
553                 size_t len = sizeof (int);
554                 mib [0] = CTL_HW;
555                 mib [1] = HW_NCPU;
556                 if (sysctl (mib, 2, &count, &len, NULL, 0) == 0)
557                         return count;
558         }
559 #endif
560 #ifdef HOST_WIN32
561         {
562                 SYSTEM_INFO info;
563                 GetSystemInfo (&info);
564                 return info.dwNumberOfProcessors;
565         }
566 #endif
567         /* FIXME: warn */
568         return 1;
569 }
570
571 static void
572 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
573 {
574         char buf [256];
575         char *s;
576         int hz = get_user_hz ();
577         guint64 user_ticks = 0, nice_ticks = 0, system_ticks = 0, idle_ticks = 0, irq_ticks = 0, sirq_ticks = 0;
578         FILE *f = fopen ("/proc/stat", "r");
579         if (!f)
580                 return;
581         if (cpu_id < 0)
582                 hz *= mono_cpu_count ();
583         while ((s = fgets (buf, sizeof (buf), f))) {
584                 char *data = NULL;
585                 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
586                         data = s + 4;
587                 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
588                         if (data == s + 3)
589                                 continue;
590                         data++;
591                 } else {
592                         continue;
593                 }
594                 
595                 user_ticks = strtoull (data, &data, 10);
596                 nice_ticks = strtoull (data, &data, 10);
597                 system_ticks = strtoull (data, &data, 10);
598                 idle_ticks = strtoull (data, &data, 10);
599                 /* iowait_ticks = strtoull (data, &data, 10); */
600                 irq_ticks = strtoull (data, &data, 10);
601                 sirq_ticks = strtoull (data, &data, 10);
602                 break;
603         }
604         fclose (f);
605
606         if (user)
607                 *user = (user_ticks + nice_ticks) * 10000000 / hz;
608         if (systemt)
609                 *systemt = (system_ticks) * 10000000 / hz;
610         if (irq)
611                 *irq = (irq_ticks) * 10000000 / hz;
612         if (sirq)
613                 *sirq = (sirq_ticks) * 10000000 / hz;
614         if (idle)
615                 *idle = (idle_ticks) * 10000000 / hz;
616 }
617
618 /**
619  * mono_cpu_get_data:
620  * @cpu_id: processor number or -1 to get a summary of all the processors
621  * @data: type of data to retrieve
622  *
623  * Get data about a processor on the system, like time spent in user space or idle time.
624  */
625 gint64
626 mono_cpu_get_data (int cpu_id, MonoCpuData data, MonoProcessError *error)
627 {
628         gint64 value = 0;
629
630         if (error)
631                 *error = MONO_PROCESS_ERROR_NONE;
632         switch (data) {
633         case MONO_CPU_USER_TIME:
634                 get_cpu_times (cpu_id, &value, NULL, NULL, NULL, NULL);
635                 break;
636         case MONO_CPU_PRIV_TIME:
637                 get_cpu_times (cpu_id, NULL, &value, NULL, NULL, NULL);
638                 break;
639         case MONO_CPU_INTR_TIME:
640                 get_cpu_times (cpu_id, NULL, NULL, &value, NULL, NULL);
641                 break;
642         case MONO_CPU_DCP_TIME:
643                 get_cpu_times (cpu_id, NULL, NULL, NULL, &value, NULL);
644                 break;
645         case MONO_CPU_IDLE_TIME:
646                 get_cpu_times (cpu_id, NULL, NULL, NULL, NULL, &value);
647                 break;
648
649         case MONO_CPU_END:
650                 /* Nothing yet */
651                 return 0;
652         }
653         return value;
654 }
655