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