2008-10-24 Mark Probst <mark.probst@gmail.com>
[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 PLATFORM_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/types.h>
18 #include <sys/sysctl.h>
19 #include <sys/proc.h>
20 #define USE_SYSCTL 1
21 #endif
22
23 /**
24  * mono_process_list:
25  * @size: a pointer to a location where the size of the returned array is stored
26  *
27  * Return an array of pid values for the processes currently running on the system.
28  * The size of the array is stored in @size.
29  */
30 gpointer*
31 mono_process_list (int *size)
32 {
33 #if USE_SYSCTL
34         int mib [4];
35         int res, i;
36         size_t data_len = sizeof (struct kinfo_proc) * 400;
37         struct kinfo_proc *processes = malloc (data_len);
38         void **buf = NULL;
39
40         if (size)
41                 *size = 0;
42         if (!processes)
43                 return NULL;
44
45         mib [0] = CTL_KERN;
46         mib [1] = KERN_PROC;
47         mib [2] = KERN_PROC_ALL;
48         mib [3] = 0;
49         
50         res = sysctl (mib, 4, processes, &data_len, NULL, 0);
51         if (res < 0) {
52                 free (processes);
53                 return NULL;
54         }
55         res = data_len/sizeof (struct kinfo_proc);
56         buf = g_realloc (buf, res * sizeof (void*));
57         for (i = 0; i < res; ++i)
58                 buf [i] = GINT_TO_POINTER (processes [i].kp_proc.p_pid);
59         free (processes);
60         if (size)
61                 *size = res;
62         return buf;
63 #else
64         const char *name;
65         void **buf = NULL;
66         int count = 0;
67         int i = 0;
68         GDir *dir = g_dir_open ("/proc/", 0, NULL);
69         if (!dir) {
70                 if (size)
71                         *size = 0;
72                 return NULL;
73         }
74         while ((name = g_dir_read_name (dir))) {
75                 int pid;
76                 char *nend;
77                 pid = strtol (name, &nend, 10);
78                 if (pid <= 0 || nend == name || *nend)
79                         continue;
80                 if (i >= count) {
81                         if (!count)
82                                 count = 16;
83                         else
84                                 count *= 2;
85                         buf = g_realloc (buf, count * sizeof (void*));
86                 }
87                 buf [i++] = GINT_TO_POINTER (pid);
88         }
89         g_dir_close (dir);
90         if (size)
91                 *size = i;
92         return buf;
93 #endif
94 }
95
96 static char*
97 get_pid_status_item_buf (int pid, const char *item, char *rbuf, int blen, MonoProcessError *error)
98 {
99         char buf [256];
100         char *s;
101         FILE *f;
102         int len = strlen (item);
103
104         g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
105         f = fopen (buf, "r");
106         if (!f) {
107                 if (error)
108                         *error = MONO_PROCESS_ERROR_NOT_FOUND;
109                 return NULL;
110         }
111         while ((s = fgets (buf, blen, f))) {
112                 if (*item != *buf)
113                         continue;
114                 if (strncmp (buf, item, len))
115                         continue;
116                 s = buf + len;
117                 while (g_ascii_isspace (*s)) s++;
118                 if (*s++ != ':')
119                         continue;
120                 while (g_ascii_isspace (*s)) s++;
121                 fclose (f);
122                 len = strlen (s);
123                 strncpy (rbuf, s, MIN (len, blen));
124                 rbuf [blen - 1] = 0;
125                 if (error)
126                         *error = MONO_PROCESS_ERROR_NONE;
127                 return rbuf;
128         }
129         fclose (f);
130         if (error)
131                 *error = MONO_PROCESS_ERROR_OTHER;
132         return NULL;
133 }
134
135 /**
136  * mono_process_get_name:
137  * @pid: pid of the process
138  * @buf: byte buffer where to store the name of the prcoess
139  * @len: size of the buffer @buf
140  *
141  * Return the name of the process identified by @pid, storing it
142  * inside @buf for a maximum of len bytes (including the terminating 0).
143  */
144 char*
145 mono_process_get_name (gpointer pid, char *buf, int len)
146 {
147 #if USE_SYSCTL
148         int mib [4];
149         int res;
150         char *p;
151         size_t data_len = sizeof (struct kinfo_proc);
152         struct kinfo_proc processi;
153
154         memset (buf, 0, len);
155
156         mib [0] = CTL_KERN;
157         mib [1] = KERN_PROC;
158         mib [2] = KERN_PROC_PID;
159         mib [3] = GPOINTER_TO_UINT (pid);
160         
161         res = sysctl (mib, 4, &processi, &data_len, NULL, 0);
162         if (res < 0 || data_len != sizeof (struct kinfo_proc)) {
163                 return buf;
164         }
165         strncpy (buf, processi.kp_proc.p_comm, len - 1);
166         return buf;
167 #else
168         char fname [128];
169         FILE *file;
170         char *p;
171         int r;
172         sprintf (fname, "/proc/%d/cmdline", GPOINTER_TO_INT (pid));
173         buf [0] = 0;
174         file = fopen (fname, "r");
175         if (!file)
176                 return buf;
177         r = fread (buf, 1, len - 1, file);
178         fclose (file);
179         buf [r] = 0;
180         p = strrchr (buf, '/');
181         if (p)
182                 return p + 1;
183         if (r == 0) {
184                 return get_pid_status_item_buf (GPOINTER_TO_INT (pid), "Name", buf, len, NULL);
185         }
186         return buf;
187 #endif
188 }
189
190 /*
191  * /proc/pid/stat format:
192  * pid (cmdname) S 
193  *      [0] ppid pgid sid tty_nr tty_pgrp flags min_flt cmin_flt maj_flt cmaj_flt
194  *      [10] utime stime cutime cstime prio nice threads start_time vsize rss
195  *      [20] rsslim start_code end_code start_stack esp eip pending blocked sigign sigcatch
196  *      [30] wchan 0 0 exit_signal cpu rt_prio policy
197  */
198
199 #define RET_ERROR(err) do {     \
200                 if (error) *error = (err);      \
201                 return 0;                       \
202         } while (0)
203
204 static gint64
205 get_process_stat_item (int pid, int pos, int sum, MonoProcessError *error)
206 {
207         char buf [512];
208         char *s, *end;
209         FILE *f;
210         int len, i;
211         gint64 value;
212
213         g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
214         f = fopen (buf, "r");
215         if (!f)
216                 RET_ERROR (MONO_PROCESS_ERROR_NOT_FOUND);
217         len = fread (buf, 1, sizeof (buf), f);
218         fclose (f);
219         if (len <= 0)
220                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
221         s = strchr (buf, ')');
222         if (!s)
223                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
224         s++;
225         while (g_ascii_isspace (*s)) s++;
226         if (!*s)
227                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
228         /* skip the status char */
229         while (*s && !g_ascii_isspace (*s)) s++;
230         if (!*s)
231                 RET_ERROR (MONO_PROCESS_ERROR_OTHER);
232         for (i = 0; i < pos; ++i) {
233                 while (g_ascii_isspace (*s)) s++;
234                 if (!*s)
235                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
236                 while (*s && !g_ascii_isspace (*s)) s++;
237                 if (!*s)
238                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
239         }
240         /* we are finally at the needed item */
241         value = strtoul (s, &end, 0);
242         /* add also the following value */
243         if (sum) {
244                 while (g_ascii_isspace (*s)) s++;
245                 if (!*s)
246                         RET_ERROR (MONO_PROCESS_ERROR_OTHER);
247                 value += strtoul (s, &end, 0);
248         }
249         if (error)
250                 *error = MONO_PROCESS_ERROR_NONE;
251         return value;
252 }
253
254 static int
255 get_user_hz (void)
256 {
257         static int user_hz = 0;
258         if (user_hz == 0) {
259 #ifdef _SC_CLK_TCK
260                 user_hz = sysconf (_SC_CLK_TCK);
261 #endif
262                 if (user_hz == 0)
263                         user_hz = 100;
264         }
265         return user_hz;
266 }
267
268 static gint64
269 get_process_stat_time (int pid, int pos, int sum, MonoProcessError *error)
270 {
271         gint64 val = get_process_stat_item (pid, pos, sum, error);
272         /* return milliseconds */
273         return (val * 1000) / get_user_hz ();
274 }
275
276 static gint64
277 get_pid_status_item (int pid, const char *item, MonoProcessError *error)
278 {
279         char buf [64];
280         char *s;
281
282         s = get_pid_status_item_buf (pid, item, buf, sizeof (buf), error);
283         if (s)
284                 return atoi (s);
285         return 0;
286 }
287
288 /**
289  * mono_process_get_data:
290  * @pid: pid of the process
291  * @data: description of data to return
292  *
293  * Return a data item of a process like user time, memory use etc,
294  * according to the @data argumet.
295  */
296 gint64
297 mono_process_get_data_with_error (gpointer pid, MonoProcessData data, MonoProcessError *error)
298 {
299         gint64 val;
300         int rpid = GPOINTER_TO_INT (pid);
301
302         if (error)
303                 *error = MONO_PROCESS_ERROR_OTHER;
304
305         switch (data) {
306         case MONO_PROCESS_NUM_THREADS:
307                 return get_pid_status_item (rpid, "Threads", error);
308         case MONO_PROCESS_USER_TIME:
309                 return get_process_stat_time (rpid, 12, FALSE, error);
310         case MONO_PROCESS_SYSTEM_TIME:
311                 return get_process_stat_time (rpid, 13, FALSE, error);
312         case MONO_PROCESS_TOTAL_TIME:
313                 return get_process_stat_time (rpid, 12, TRUE, error);
314         case MONO_PROCESS_WORKING_SET:
315                 return get_pid_status_item (rpid, "VmRSS", error) * 1024;
316         case MONO_PROCESS_WORKING_SET_PEAK:
317                 val = get_pid_status_item (rpid, "VmHWM", error) * 1024;
318                 if (val == 0)
319                         val = get_pid_status_item (rpid, "VmRSS", error) * 1024;
320                 return val;
321         case MONO_PROCESS_PRIVATE_BYTES:
322                 return get_pid_status_item (rpid, "VmData", error) * 1024;
323         case MONO_PROCESS_VIRTUAL_BYTES:
324                 return get_pid_status_item (rpid, "VmSize", error) * 1024;
325         case MONO_PROCESS_VIRTUAL_BYTES_PEAK:
326                 val = get_pid_status_item (rpid, "VmPeak", error) * 1024;
327                 if (val == 0)
328                         val = get_pid_status_item (rpid, "VmSize", error) * 1024;
329                 return val;
330         case MONO_PROCESS_FAULTS:
331                 return get_process_stat_item (rpid, 6, TRUE, error);
332         }
333         return 0;
334 }
335
336 gint64
337 mono_process_get_data (gpointer pid, MonoProcessData data)
338 {
339         MonoProcessError error;
340         return mono_process_get_data_with_error (pid, data, &error);
341 }
342
343 /**
344  * mono_cpu_count:
345  *
346  * Return the number of processors on the system.
347  */
348 int
349 mono_cpu_count (void)
350 {
351         int count;
352 #ifdef _SC_NPROCESSORS_ONLN
353         count = sysconf (_SC_NPROCESSORS_ONLN);
354         if (count > 0)
355                 return count;
356 #endif
357 #ifdef USE_SYSCTL
358         {
359                 int mib [2];
360                 size_t len = sizeof (int);
361                 mib [0] = CTL_HW;
362                 mib [1] = HW_NCPU;
363                 if (sysctl (mib, 2, &count, &len, NULL, 0) == 0)
364                         return count;
365         }
366 #endif
367 #ifdef PLATFORM_WIN32
368         {
369                 SYSTEM_INFO info;
370                 GetSystemInfo (&info);
371                 return info.dwNumberOfProcessors;
372         }
373 #endif
374         /* FIXME: warn */
375         return 1;
376 }
377
378 static void
379 get_cpu_times (int cpu_id, gint64 *user, gint64 *systemt, gint64 *irq, gint64 *sirq, gint64 *idle)
380 {
381         char buf [256];
382         char *s;
383         int hz = get_user_hz ();
384         long long unsigned int user_ticks, nice_ticks, system_ticks, idle_ticks, iowait_ticks, irq_ticks, sirq_ticks;
385         FILE *f = fopen ("/proc/stat", "r");
386         if (!f)
387                 return;
388         hz *= mono_cpu_count ();
389         while ((s = fgets (buf, sizeof (buf), f))) {
390                 char *data = NULL;
391                 if (cpu_id < 0 && strncmp (s, "cpu", 3) == 0 && g_ascii_isspace (s [3])) {
392                         data = s + 4;
393                 } else if (cpu_id >= 0 && strncmp (s, "cpu", 3) == 0 && strtol (s + 3, &data, 10) == cpu_id) {
394                         if (data == s + 3)
395                                 continue;
396                         data++;
397                 } else {
398                         continue;
399                 }
400                 sscanf (data, "%Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_ticks, &nice_ticks, &system_ticks, &idle_ticks, &iowait_ticks, &irq_ticks, &sirq_ticks);
401         }
402         fclose (f);
403
404         if (user)
405                 *user = (user_ticks + nice_ticks) * 10000000 / hz;
406         if (systemt)
407                 *systemt = (system_ticks) * 10000000 / hz;
408         if (irq)
409                 *irq = (irq_ticks) * 10000000 / hz;
410         if (sirq)
411                 *sirq = (sirq_ticks) * 10000000 / hz;
412         if (idle)
413                 *idle = (idle_ticks) * 10000000 / hz;
414 }
415
416 /**
417  * mono_cpu_get_data:
418  * @cpu_id: processor number or -1 to get a summary of all the processors
419  * @data: type of data to retrieve
420  *
421  * Get data about a processor on the system, like time spent in user space or idle time.
422  */
423 gint64
424 mono_cpu_get_data (int cpu_id, MonoCpuData data, MonoProcessError *error)
425 {
426         gint64 value = 0;
427
428         if (error)
429                 *error = MONO_PROCESS_ERROR_NONE;
430         switch (data) {
431         case MONO_CPU_USER_TIME:
432                 get_cpu_times (cpu_id, &value, NULL, NULL, NULL, NULL);
433                 break;
434         case MONO_CPU_PRIV_TIME:
435                 get_cpu_times (cpu_id, NULL, &value, NULL, NULL, NULL);
436                 break;
437         case MONO_CPU_INTR_TIME:
438                 get_cpu_times (cpu_id, NULL, NULL, &value, NULL, NULL);
439                 break;
440         case MONO_CPU_DCP_TIME:
441                 get_cpu_times (cpu_id, NULL, NULL, NULL, &value, NULL);
442                 break;
443         case MONO_CPU_IDLE_TIME:
444                 get_cpu_times (cpu_id, NULL, NULL, NULL, NULL, &value);
445                 break;
446         }
447         return value;
448 }
449