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