4fb70ade91636a2b62db708b25c5e5424c3041d5
[collectd.git] / src / processes.c
1 /**
2  * collectd - src/processes.c
3  * Copyright (C) 2005  Lyonel Vincent
4  * Copyright (C) 2006-2008  Florian Forster (Mach code)
5  * Copyright (C) 2008  Oleg King
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; either version 2 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20  *
21  * Authors:
22  *   Lyonel Vincent <lyonel at ezix.org>
23  *   Florian octo Forster <octo at verplant.org>
24  *   Oleg King <king2 at kaluga.ru>
25  *   Sebastian Harl <sh at tokkee.org>
26  **/
27
28 #include "collectd.h"
29 #include "common.h"
30 #include "plugin.h"
31 #include "configfile.h"
32
33 /* Include header files for the mach system, if they exist.. */
34 #if HAVE_THREAD_INFO
35 #  if HAVE_MACH_MACH_INIT_H
36 #    include <mach/mach_init.h>
37 #  endif
38 #  if HAVE_MACH_HOST_PRIV_H
39 #    include <mach/host_priv.h>
40 #  endif
41 #  if HAVE_MACH_MACH_ERROR_H
42 #    include <mach/mach_error.h>
43 #  endif
44 #  if HAVE_MACH_MACH_HOST_H
45 #    include <mach/mach_host.h>
46 #  endif
47 #  if HAVE_MACH_MACH_PORT_H
48 #    include <mach/mach_port.h>
49 #  endif
50 #  if HAVE_MACH_MACH_TYPES_H
51 #    include <mach/mach_types.h>
52 #  endif
53 #  if HAVE_MACH_MESSAGE_H
54 #    include <mach/message.h>
55 #  endif
56 #  if HAVE_MACH_PROCESSOR_SET_H
57 #    include <mach/processor_set.h>
58 #  endif
59 #  if HAVE_MACH_TASK_H
60 #    include <mach/task.h>
61 #  endif
62 #  if HAVE_MACH_THREAD_ACT_H
63 #    include <mach/thread_act.h>
64 #  endif
65 #  if HAVE_MACH_VM_REGION_H
66 #    include <mach/vm_region.h>
67 #  endif
68 #  if HAVE_MACH_VM_MAP_H
69 #    include <mach/vm_map.h>
70 #  endif
71 #  if HAVE_MACH_VM_PROT_H
72 #    include <mach/vm_prot.h>
73 #  endif
74 #  if HAVE_SYS_SYSCTL_H
75 #    include <sys/sysctl.h>
76 #  endif
77 /* #endif HAVE_THREAD_INFO */
78
79 #elif KERNEL_LINUX
80 #  if HAVE_LINUX_CONFIG_H
81 #    include <linux/config.h>
82 #  endif
83 #  ifndef CONFIG_HZ
84 #    define CONFIG_HZ 100
85 #  endif
86 /* #endif KERNEL_LINUX */
87
88 #elif HAVE_LIBKVM_GETPROCS
89 #  include <kvm.h>
90 #  include <sys/user.h>
91 #  include <sys/proc.h>
92 #  if HAVE_SYS_SYSCTL_H
93 #    include <sys/sysctl.h>
94 #  endif
95 /* #endif HAVE_LIBKVM_GETPROCS */
96
97 #else
98 # error "No applicable input method."
99 #endif
100
101 #if HAVE_REGEX_H
102 # include <regex.h>
103 #endif
104
105 #define BUFSIZE 256
106
107 static const char *config_keys[] =
108 {
109         "Process",
110         "ProcessMatch",
111         NULL
112 };
113 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
114
115 typedef struct procstat_entry_s
116 {
117         unsigned long id;
118         unsigned long age;
119
120         unsigned long num_proc;
121         unsigned long num_lwp;
122         unsigned long vmem_rss;
123
124         unsigned long vmem_minflt;
125         unsigned long vmem_majflt;
126         unsigned long vmem_minflt_counter;
127         unsigned long vmem_majflt_counter;
128
129         unsigned long cpu_user;
130         unsigned long cpu_system;
131         unsigned long cpu_user_counter;
132         unsigned long cpu_system_counter;
133
134         struct procstat_entry_s *next;
135 } procstat_entry_t;
136
137 #define PROCSTAT_NAME_LEN 256
138 typedef struct procstat
139 {
140         char          name[PROCSTAT_NAME_LEN];
141 #if HAVE_REGEX_H
142         regex_t *re;
143 #endif
144
145         unsigned long num_proc;
146         unsigned long num_lwp;
147         unsigned long vmem_rss;
148
149         unsigned long vmem_minflt_counter;
150         unsigned long vmem_majflt_counter;
151
152         unsigned long cpu_user_counter;
153         unsigned long cpu_system_counter;
154
155         struct procstat   *next;
156         struct procstat_entry_s *instances;
157 } procstat_t;
158
159 static procstat_t *list_head_g = NULL;
160
161 #if HAVE_THREAD_INFO
162 static mach_port_t port_host_self;
163 static mach_port_t port_task_self;
164
165 static processor_set_name_array_t pset_list;
166 static mach_msg_type_number_t     pset_list_len;
167 /* #endif HAVE_THREAD_INFO */
168
169 #elif KERNEL_LINUX
170 static long pagesize_g;
171 /* #endif KERNEL_LINUX */
172
173 #elif HAVE_LIBKVM_GETPROCS
174 /* no global variables */
175 #endif /* HAVE_LIBKVM_GETPROCS */
176
177 /* put name of process from config to list_head_g tree
178    list_head_g is a list of 'procstat_t' structs with
179    processes names we want to watch */
180 static void ps_list_register (const char *name, const char *regexp)
181 {
182         procstat_t *new;
183         procstat_t *ptr;
184         int status;
185
186         new = (procstat_t *) malloc (sizeof (procstat_t));
187         if (new == NULL)
188         {
189                 ERROR ("processes plugin: ps_list_register: malloc failed.");
190                 return;
191         }
192         memset (new, 0, sizeof (procstat_t));
193         sstrncpy (new->name, name, sizeof (new->name));
194
195 #if HAVE_REGEX_H
196         if (regexp != NULL)
197         {
198                 DEBUG ("ProcessMatch: adding \"%s\" as criteria to process %s.", regexp, name);
199                 new->re = (regex_t *) malloc (sizeof (regex_t));
200                 if (new->re == NULL)
201                 {
202                         ERROR ("processes plugin: ps_list_register: malloc failed.");
203                         sfree (new);
204                         return;
205                 }
206
207                 status = regcomp (new->re, regexp, REG_EXTENDED | REG_NOSUB);
208                 if (status != 0)
209                 {
210                         DEBUG ("ProcessMatch: compiling the regular expression \"%s\" failed.", regexp);
211                         sfree(new->re);
212                         return;
213                 }
214         }
215 #else
216         if (regexp != NULL)
217         {
218                 ERROR ("processes plugin: ps_list_register: "
219                                 "Regular expression \"%s\" found in config "
220                                 "file, but support for regular expressions "
221                                 "has been dispabled at compile time.",
222                                 regexp);
223                 sfree (new);
224                 return;
225         }
226 #endif
227         
228         for (ptr = list_head_g; ptr != NULL; ptr = ptr->next)
229         {
230                 if (strcmp (ptr->name, name) == 0)
231                 {
232                         WARNING ("processes plugin: You have configured more "
233                                         "than one `Process' or "
234                                         "`ProcessMatch' with the same name. "
235                                         "All but the first setting will be "
236                                         "ignored.");
237                         sfree (new->re);
238                         sfree (new);
239                         return;
240                 }
241
242                 if (ptr->next == NULL)
243                         break;
244         }
245
246         if (ptr == NULL)
247                 list_head_g = new;
248         else
249                 ptr->next = new;
250 } /* void ps_list_register */
251
252 /* try to match name against entry, returns 1 if success */
253 static int ps_list_match (const char *name, const char *cmdline, procstat_t *ps)
254 {
255 #if HAVE_REGEX_H
256         if (ps->re != NULL)
257         {
258                 int status;
259                 const char *str;
260
261                 str = cmdline;
262                 if ((str == NULL) || (str[0] == 0))
263                         str = name;
264
265                 assert (str != NULL);
266
267                 status = regexec (ps->re, str,
268                                 /* nmatch = */ 0,
269                                 /* pmatch = */ NULL,
270                                 /* eflags = */ 0);
271                 if (status == 0)
272                         return (1);
273         }
274         else
275 #endif
276         if (strcmp (ps->name, name) == 0)
277                 return (1);
278
279         return (0);
280 } /* int ps_list_match */
281
282 /* add process entry to 'instances' of process 'name' (or refresh it) */
283 static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t *entry)
284 {
285         procstat_t *ps;
286         procstat_entry_t *pse;
287
288         if (entry->id == 0)
289                 return;
290
291         for (ps = list_head_g; ps != NULL; ps = ps->next)
292         {
293                 if ((ps_list_match (name, cmdline, ps)) == 0)
294                         continue;
295
296                 for (pse = ps->instances; pse != NULL; pse = pse->next)
297                         if ((pse->id == entry->id) || (pse->next == NULL))
298                                 break;
299
300                 if ((pse == NULL) || (pse->id != entry->id))
301                 {
302                         procstat_entry_t *new;
303                         
304                         new = (procstat_entry_t *) malloc (sizeof (procstat_entry_t));
305                         if (new == NULL)
306                                 return;
307                         memset (new, 0, sizeof (procstat_entry_t));
308                         new->id = entry->id;
309                         
310                         if (pse == NULL)
311                                 ps->instances = new;
312                         else
313                                 pse->next = new;
314
315                         pse = new;
316                 }
317
318                 pse->age = 0;
319                 pse->num_proc = entry->num_proc;
320                 pse->num_lwp  = entry->num_lwp;
321                 pse->vmem_rss = entry->vmem_rss;
322
323                 ps->num_proc += pse->num_proc;
324                 ps->num_lwp  += pse->num_lwp;
325                 ps->vmem_rss += pse->vmem_rss;
326
327                 if ((entry->vmem_minflt_counter == 0)
328                                 && (entry->vmem_majflt_counter == 0))
329                 {
330                         pse->vmem_minflt_counter += entry->vmem_minflt;
331                         pse->vmem_minflt = entry->vmem_minflt;
332
333                         pse->vmem_majflt_counter += entry->vmem_majflt;
334                         pse->vmem_majflt = entry->vmem_majflt;
335                 }
336                 else
337                 {
338                         if (entry->vmem_minflt_counter < pse->vmem_minflt_counter)
339                         {
340                                 pse->vmem_minflt = entry->vmem_minflt_counter
341                                         + (ULONG_MAX - pse->vmem_minflt_counter);
342                         }
343                         else
344                         {
345                                 pse->vmem_minflt = entry->vmem_minflt_counter - pse->vmem_minflt_counter;
346                         }
347                         pse->vmem_minflt_counter = entry->vmem_minflt_counter;
348                         
349                         if (entry->vmem_majflt_counter < pse->vmem_majflt_counter)
350                         {
351                                 pse->vmem_majflt = entry->vmem_majflt_counter
352                                         + (ULONG_MAX - pse->vmem_majflt_counter);
353                         }
354                         else
355                         {
356                                 pse->vmem_majflt = entry->vmem_majflt_counter - pse->vmem_majflt_counter;
357                         }
358                         pse->vmem_majflt_counter = entry->vmem_majflt_counter;
359                 }
360
361                 ps->vmem_minflt_counter += pse->vmem_minflt;
362                 ps->vmem_majflt_counter += pse->vmem_majflt;
363
364                 if ((entry->cpu_user_counter == 0)
365                                 && (entry->cpu_system_counter == 0))
366                 {
367                         pse->cpu_user_counter += entry->cpu_user;
368                         pse->cpu_user = entry->cpu_user;
369
370                         pse->cpu_system_counter += entry->cpu_system;
371                         pse->cpu_system = entry->cpu_system;
372                 }
373                 else
374                 {
375                         if (entry->cpu_user_counter < pse->cpu_user_counter)
376                         {
377                                 pse->cpu_user = entry->cpu_user_counter
378                                         + (ULONG_MAX - pse->cpu_user_counter);
379                         }
380                         else
381                         {
382                                 pse->cpu_user = entry->cpu_user_counter - pse->cpu_user_counter;
383                         }
384                         pse->cpu_user_counter = entry->cpu_user_counter;
385                         
386                         if (entry->cpu_system_counter < pse->cpu_system_counter)
387                         {
388                                 pse->cpu_system = entry->cpu_system_counter
389                                         + (ULONG_MAX - pse->cpu_system_counter);
390                         }
391                         else
392                         {
393                                 pse->cpu_system = entry->cpu_system_counter - pse->cpu_system_counter;
394                         }
395                         pse->cpu_system_counter = entry->cpu_system_counter;
396                 }
397
398                 ps->cpu_user_counter   += pse->cpu_user;
399                 ps->cpu_system_counter += pse->cpu_system;
400         }
401 }
402
403 /* remove old entries from instances of processes in list_head_g */
404 static void ps_list_reset (void)
405 {
406         procstat_t *ps;
407         procstat_entry_t *pse;
408         procstat_entry_t *pse_prev;
409
410         for (ps = list_head_g; ps != NULL; ps = ps->next)
411         {
412                 ps->num_proc    = 0;
413                 ps->num_lwp     = 0;
414                 ps->vmem_rss    = 0;
415
416                 pse_prev = NULL;
417                 pse = ps->instances;
418                 while (pse != NULL)
419                 {
420                         if (pse->age > 10)
421                         {
422                                 DEBUG ("Removing this procstat entry cause it's too old: "
423                                                 "id = %lu; name = %s;",
424                                                 pse->id, ps->name);
425
426                                 if (pse_prev == NULL)
427                                 {
428                                         ps->instances = pse->next;
429                                         free (pse);
430                                         pse = ps->instances;
431                                 }
432                                 else
433                                 {
434                                         pse_prev->next = pse->next;
435                                         free (pse);
436                                         pse = pse_prev->next;
437                                 }
438                         }
439                         else
440                         {
441                                 pse->age++;
442                                 pse_prev = pse;
443                                 pse = pse->next;
444                         }
445                 } /* while (pse != NULL) */
446         } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */
447 }
448
449 /* put all pre-defined 'Process' names from config to list_head_g tree */
450 static int ps_config (const char *key, const char *value)
451 {
452         if (strcasecmp (key, "Process") == 0)
453         {
454                 ps_list_register (value, NULL);
455         }
456         else if (strcasecmp (key, "ProcessMatch") == 0)
457         {
458                 char *new_val;
459                 char *fields[3];
460                 int fields_num;
461
462                 new_val = strdup (value);
463                 if (new_val == NULL) {
464                         ERROR ("processes plugin: strdup failed when processing "
465                                         "`ProcessMatch %s'.", value);
466                         return (1);
467                 }
468
469                 fields_num = strsplit (new_val, fields,
470                                 STATIC_ARRAY_SIZE (fields));
471                 if (fields_num != 2)
472                 {
473                         ERROR ("processes plugin: `ProcessMatch' needs exactly "
474                                         "two string arguments.");
475                         sfree (new_val);
476                         return (1);
477                 }
478                 ps_list_register (fields[0], fields[1]);
479                 sfree (new_val);
480         }
481         else
482         {
483                 ERROR ("processes plugin: The `%s' configuration option is not "
484                                 "understood and will be ignored.", key);
485                 return (-1);
486         }
487
488         return (0);
489 }
490
491 static int ps_init (void)
492 {
493 #if HAVE_THREAD_INFO
494         kern_return_t status;
495
496         port_host_self = mach_host_self ();
497         port_task_self = mach_task_self ();
498
499         if (pset_list != NULL)
500         {
501                 vm_deallocate (port_task_self,
502                                 (vm_address_t) pset_list,
503                                 pset_list_len * sizeof (processor_set_t));
504                 pset_list = NULL;
505                 pset_list_len = 0;
506         }
507
508         if ((status = host_processor_sets (port_host_self,
509                                         &pset_list,
510                                         &pset_list_len)) != KERN_SUCCESS)
511         {
512                 ERROR ("host_processor_sets failed: %s\n",
513                                 mach_error_string (status));
514                 pset_list = NULL;
515                 pset_list_len = 0;
516                 return (-1);
517         }
518 /* #endif HAVE_THREAD_INFO */
519
520 #elif KERNEL_LINUX
521         pagesize_g = sysconf(_SC_PAGESIZE);
522         DEBUG ("pagesize_g = %li; CONFIG_HZ = %i;",
523                         pagesize_g, CONFIG_HZ);
524 /* #endif KERNEL_LINUX */
525
526 #elif HAVE_LIBKVM_GETPROCS
527 /* no initialization */
528 #endif /* HAVE_LIBKVM_GETPROCS */
529
530         return (0);
531 } /* int ps_init */
532
533 /* submit global state (e.g.: qty of zombies, running, etc..) */
534 static void ps_submit_state (const char *state, double value)
535 {
536         value_t values[1];
537         value_list_t vl = VALUE_LIST_INIT;
538
539         values[0].gauge = value;
540
541         vl.values = values;
542         vl.values_len = 1;
543         vl.time = time (NULL);
544         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
545         sstrncpy (vl.plugin, "processes", sizeof (vl.plugin));
546         sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
547         sstrncpy (vl.type, "ps_state", sizeof (vl.type));
548         sstrncpy (vl.type_instance, state, sizeof (vl.type_instance));
549
550         plugin_dispatch_values (&vl);
551 }
552
553 /* submit info about specific process (e.g.: memory taken, cpu usage, etc..) */
554 static void ps_submit_proc_list (procstat_t *ps)
555 {
556         value_t values[2];
557         value_list_t vl = VALUE_LIST_INIT;
558
559         vl.values = values;
560         vl.values_len = 2;
561         vl.time = time (NULL);
562         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
563         sstrncpy (vl.plugin, "processes", sizeof (vl.plugin));
564         sstrncpy (vl.plugin_instance, ps->name, sizeof (vl.plugin_instance));
565
566         sstrncpy (vl.type, "ps_rss", sizeof (vl.type));
567         vl.values[0].gauge = ps->vmem_rss;
568         vl.values_len = 1;
569         plugin_dispatch_values (&vl);
570
571         sstrncpy (vl.type, "ps_cputime", sizeof (vl.type));
572         vl.values[0].counter = ps->cpu_user_counter;
573         vl.values[1].counter = ps->cpu_system_counter;
574         vl.values_len = 2;
575         plugin_dispatch_values (&vl);
576
577         sstrncpy (vl.type, "ps_count", sizeof (vl.type));
578         vl.values[0].gauge = ps->num_proc;
579         vl.values[1].gauge = ps->num_lwp;
580         vl.values_len = 2;
581         plugin_dispatch_values (&vl);
582
583         sstrncpy (vl.type, "ps_pagefaults", sizeof (vl.type));
584         vl.values[0].counter = ps->vmem_minflt_counter;
585         vl.values[1].counter = ps->vmem_majflt_counter;
586         vl.values_len = 2;
587         plugin_dispatch_values (&vl);
588
589         DEBUG ("name = %s; num_proc = %lu; num_lwp = %lu; vmem_rss = %lu; "
590                         "vmem_minflt_counter = %lu; vmem_majflt_counter = %lu; "
591                         "cpu_user_counter = %lu; cpu_system_counter = %lu;",
592                         ps->name, ps->num_proc, ps->num_lwp, ps->vmem_rss,
593                         ps->vmem_minflt_counter, ps->vmem_majflt_counter,
594                         ps->cpu_user_counter, ps->cpu_system_counter);
595 } /* void ps_submit_proc_list */
596
597 /* ------- additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */
598 #if KERNEL_LINUX
599 static int *ps_read_tasks (int pid)
600 {
601         int *list = NULL;
602         int  list_size = 1; /* size of allocated space, in elements */
603         int  list_len = 0;  /* number of currently used elements */
604
605         char           dirname[64];
606         DIR           *dh;
607         struct dirent *ent;
608
609         ssnprintf (dirname, sizeof (dirname), "/proc/%i/task", pid);
610
611         if ((dh = opendir (dirname)) == NULL)
612         {
613                 DEBUG ("Failed to open directory `%s'", dirname);
614                 return (NULL);
615         }
616
617         while ((ent = readdir (dh)) != NULL)
618         {
619                 if (!isdigit (ent->d_name[0]))
620                         continue;
621
622                 if ((list_len + 1) >= list_size)
623                 {
624                         int *new_ptr;
625                         int  new_size = 2 * list_size;
626                         /* Comes in sizes: 2, 4, 8, 16, ... */
627
628                         new_ptr = (int *) realloc (list, (size_t) (sizeof (int) * new_size));
629                         if (new_ptr == NULL)
630                         {
631                                 if (list != NULL)
632                                         free (list);
633                                 ERROR ("processes plugin: "
634                                                 "Failed to allocate more memory.");
635                                 return (NULL);
636                         }
637
638                         list = new_ptr;
639                         list_size = new_size;
640
641                         memset (list + list_len, 0, sizeof (int) * (list_size - list_len));
642                 }
643
644                 list[list_len] = atoi (ent->d_name);
645                 if (list[list_len] != 0)
646                         list_len++;
647         }
648
649         closedir (dh);
650
651         if (list_len == 0)
652                 return (NULL);
653
654         assert (list_len < list_size);
655         assert (list[list_len] == 0);
656
657         return (list);
658 } /* int *ps_read_tasks */
659
660 int ps_read_process (int pid, procstat_t *ps, char *state)
661 {
662         char  filename[64];
663         char  buffer[1024];
664
665         char *fields[64];
666         char  fields_len;
667
668         int  *tasks;
669         int   i;
670
671         int   ppid;
672         int   name_len;
673
674         long long unsigned cpu_user_counter;
675         long long unsigned cpu_system_counter;
676         long long unsigned vmem_rss;
677
678         memset (ps, 0, sizeof (procstat_t));
679
680         ssnprintf (filename, sizeof (filename), "/proc/%i/stat", pid);
681
682         i = read_file_contents (filename, buffer, sizeof(buffer) - 1);
683         if (i <= 0)
684                 return (-1);
685         buffer[i] = 0;
686
687         fields_len = strsplit (buffer, fields, 64);
688         if (fields_len < 24)
689         {
690                 DEBUG ("processes plugin: ps_read_process (pid = %i):"
691                                 " `%s' has only %i fields..",
692                                 (int) pid, filename, fields_len);
693                 return (-1);
694         }
695
696         /* copy the name, strip brackets in the process */
697         name_len = strlen (fields[1]) - 2;
698         if ((fields[1][0] != '(') || (fields[1][name_len + 1] != ')'))
699         {
700                 DEBUG ("No brackets found in process name: `%s'", fields[1]);
701                 return (-1);
702         }
703         fields[1] = fields[1] + 1;
704         fields[1][name_len] = '\0';
705         strncpy (ps->name, fields[1], PROCSTAT_NAME_LEN);
706
707         ppid = atoi (fields[3]);
708
709         *state = fields[2][0];
710
711         if (*state == 'Z')
712         {
713                 ps->num_lwp  = 0;
714                 ps->num_proc = 0;
715         }
716         else if ((tasks = ps_read_tasks (pid)) == NULL)
717         {
718                 /* Kernel 2.4 or so */
719                 ps->num_lwp  = 1;
720                 ps->num_proc = 1;
721         }
722         else
723         {
724                 ps->num_lwp  = 0;
725                 ps->num_proc = 1;
726                 for (i = 0; tasks[i] != 0; i++)
727                         ps->num_lwp++;
728
729                 free (tasks);
730                 tasks = NULL;
731         }
732
733         /* Leave the rest at zero if this is only a zombi */
734         if (ps->num_proc == 0)
735         {
736                 DEBUG ("processes plugin: This is only a zombi: pid = %i; "
737                                 "name = %s;", pid, ps->name);
738                 return (0);
739         }
740
741         cpu_user_counter   = atoll (fields[13]);
742         cpu_system_counter = atoll (fields[14]);
743         vmem_rss = atoll (fields[23]);
744         ps->vmem_minflt_counter = atol (fields[9]);
745         ps->vmem_majflt_counter = atol (fields[11]);
746         
747         /* Convert jiffies to useconds */
748         cpu_user_counter   = cpu_user_counter   * 1000000 / CONFIG_HZ;
749         cpu_system_counter = cpu_system_counter * 1000000 / CONFIG_HZ;
750         vmem_rss = vmem_rss * pagesize_g;
751
752         ps->cpu_user_counter = (unsigned long) cpu_user_counter;
753         ps->cpu_system_counter = (unsigned long) cpu_system_counter;
754         ps->vmem_rss = (unsigned long) vmem_rss;
755
756         /* success */
757         return (0);
758 } /* int ps_read_process (...) */
759
760 static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len)
761 {
762         char  *buf_ptr;
763         size_t len;
764
765         char file[PATH_MAX];
766         int  fd;
767
768         size_t n;
769
770         if ((pid < 1) || (NULL == buf) || (buf_len < 2))
771                 return NULL;
772
773         ssnprintf (file, sizeof (file), "/proc/%u/cmdline", pid);
774
775         fd = open (file, O_RDONLY);
776         if (fd < 0) {
777                 char errbuf[4096];
778                 WARNING ("processes plugin: Failed to open `%s': %s.", file,
779                                 sstrerror (errno, errbuf, sizeof (errbuf)));
780                 return NULL;
781         }
782
783         buf_ptr = buf;
784         len     = buf_len;
785
786         n = 0;
787
788         while (42) {
789                 size_t status;
790
791                 status = read (fd, (void *)buf_ptr, len);
792
793                 if (status < 0) {
794                         char errbuf[4096];
795
796                         if ((EAGAIN == errno) || (EINTR == errno))
797                                 continue;
798
799                         WARNING ("processes plugin: Failed to read from `%s': %s.", file,
800                                         sstrerror (errno, errbuf, sizeof (errbuf)));
801                         close (fd);
802                         return NULL;
803                 }
804
805                 n += status;
806
807                 if (status == 0)
808                         break;
809
810                 buf_ptr += status;
811                 len     -= status;
812
813                 if (len <= 0)
814                         break;
815         }
816
817         close (fd);
818
819         if (0 == n) {
820                 /* cmdline not available; e.g. kernel thread, zombie */
821                 if (NULL == name)
822                         return NULL;
823
824                 ssnprintf (buf, buf_len, "[%s]", name);
825                 return buf;
826         }
827
828         assert (n <= buf_len);
829
830         if (n == buf_len)
831                 --n;
832         buf[n] = '\0';
833
834         --n;
835         /* remove trailing whitespace */
836         while ((n > 0) && (isspace (buf[n]) || ('\0' == buf[n]))) {
837                 buf[n] = '\0';
838                 --n;
839         }
840
841         /* arguments are separated by '\0' in /proc/<pid>/cmdline */
842         while (n > 0) {
843                 if ('\0' == buf[n])
844                         buf[n] = ' ';
845                 --n;
846         }
847         return buf;
848 } /* char *ps_get_cmdline (...) */
849 #endif /* KERNEL_LINUX */
850
851 #if HAVE_THREAD_INFO
852 static int mach_get_task_name (task_t t, int *pid, char *name, size_t name_max_len)
853 {
854         int mib[4];
855
856         struct kinfo_proc kp;
857         size_t            kp_size;
858
859         mib[0] = CTL_KERN;
860         mib[1] = KERN_PROC;
861         mib[2] = KERN_PROC_PID;
862
863         if (pid_for_task (t, pid) != KERN_SUCCESS)
864                 return (-1);
865         mib[3] = *pid;
866
867         kp_size = sizeof (kp);
868         if (sysctl (mib, 4, &kp, &kp_size, NULL, 0) != 0)
869                 return (-1);
870
871         if (name_max_len > (MAXCOMLEN + 1))
872                 name_max_len = MAXCOMLEN + 1;
873
874         strncpy (name, kp.kp_proc.p_comm, name_max_len - 1);
875         name[name_max_len - 1] = '\0';
876
877         DEBUG ("pid = %i; name = %s;", *pid, name);
878
879         /* We don't do the special handling for `p_comm == "LaunchCFMApp"' as
880          * `top' does it, because it is a lot of work and only used when
881          * debugging. -octo */
882
883         return (0);
884 }
885 #endif /* HAVE_THREAD_INFO */
886 /* ------- end of additional functions for KERNEL_LINUX/HAVE_THREAD_INFO ------- */
887
888 /* do actual readings from kernel */
889 static int ps_read (void)
890 {
891 #if HAVE_THREAD_INFO
892         kern_return_t            status;
893
894         int                      pset;
895         processor_set_t          port_pset_priv;
896
897         int                      task;
898         task_array_t             task_list;
899         mach_msg_type_number_t   task_list_len;
900
901         int                      task_pid;
902         char                     task_name[MAXCOMLEN + 1];
903
904         int                      thread;
905         thread_act_array_t       thread_list;
906         mach_msg_type_number_t   thread_list_len;
907         thread_basic_info_data_t thread_data;
908         mach_msg_type_number_t   thread_data_len;
909
910         int running  = 0;
911         int sleeping = 0;
912         int zombies  = 0;
913         int stopped  = 0;
914         int blocked  = 0;
915
916         procstat_t *ps;
917         procstat_entry_t pse;
918
919         ps_list_reset ();
920
921         /*
922          * The Mach-concept is a little different from the traditional UNIX
923          * concept: All the work is done in threads. Threads are contained in
924          * `tasks'. Therefore, `task status' doesn't make much sense, since
925          * it's actually a `thread status'.
926          * Tasks are assigned to sets of processors, so that's where you go to
927          * get a list.
928          */
929         for (pset = 0; pset < pset_list_len; pset++)
930         {
931                 if ((status = host_processor_set_priv (port_host_self,
932                                                 pset_list[pset],
933                                                 &port_pset_priv)) != KERN_SUCCESS)
934                 {
935                         ERROR ("host_processor_set_priv failed: %s\n",
936                                         mach_error_string (status));
937                         continue;
938                 }
939
940                 if ((status = processor_set_tasks (port_pset_priv,
941                                                 &task_list,
942                                                 &task_list_len)) != KERN_SUCCESS)
943                 {
944                         ERROR ("processor_set_tasks failed: %s\n",
945                                         mach_error_string (status));
946                         mach_port_deallocate (port_task_self, port_pset_priv);
947                         continue;
948                 }
949
950                 for (task = 0; task < task_list_len; task++)
951                 {
952                         ps = NULL;
953                         if (mach_get_task_name (task_list[task],
954                                                 &task_pid,
955                                                 task_name, PROCSTAT_NAME_LEN) == 0)
956                         {
957                                 /* search for at least one match */
958                                 for (ps = list_head_g; ps != NULL; ps = ps->next)
959                                         /* FIXME: cmdline should be here instead of NULL */
960                                         if (ps_list_match (task_name, NULL, ps) == 1)
961                                                 break;
962                         }
963
964                         /* Collect more detailed statistics for this process */
965                         if (ps != NULL)
966                         {
967                                 task_basic_info_data_t        task_basic_info;
968                                 mach_msg_type_number_t        task_basic_info_len;
969                                 task_events_info_data_t       task_events_info;
970                                 mach_msg_type_number_t        task_events_info_len;
971                                 task_absolutetime_info_data_t task_absolutetime_info;
972                                 mach_msg_type_number_t        task_absolutetime_info_len;
973
974                                 memset (&pse, '\0', sizeof (pse));
975                                 pse.id = task_pid;
976
977                                 task_basic_info_len = TASK_BASIC_INFO_COUNT;
978                                 status = task_info (task_list[task],
979                                                 TASK_BASIC_INFO,
980                                                 (task_info_t) &task_basic_info,
981                                                 &task_basic_info_len);
982                                 if (status != KERN_SUCCESS)
983                                 {
984                                         ERROR ("task_info failed: %s",
985                                                         mach_error_string (status));
986                                         continue; /* with next thread_list */
987                                 }
988
989                                 task_events_info_len = TASK_EVENTS_INFO_COUNT;
990                                 status = task_info (task_list[task],
991                                                 TASK_EVENTS_INFO,
992                                                 (task_info_t) &task_events_info,
993                                                 &task_events_info_len);
994                                 if (status != KERN_SUCCESS)
995                                 {
996                                         ERROR ("task_info failed: %s",
997                                                         mach_error_string (status));
998                                         continue; /* with next thread_list */
999                                 }
1000
1001                                 task_absolutetime_info_len = TASK_ABSOLUTETIME_INFO_COUNT;
1002                                 status = task_info (task_list[task],
1003                                                 TASK_ABSOLUTETIME_INFO,
1004                                                 (task_info_t) &task_absolutetime_info,
1005                                                 &task_absolutetime_info_len);
1006                                 if (status != KERN_SUCCESS)
1007                                 {
1008                                         ERROR ("task_info failed: %s",
1009                                                         mach_error_string (status));
1010                                         continue; /* with next thread_list */
1011                                 }
1012
1013                                 pse.num_proc++;
1014                                 pse.vmem_rss = task_basic_info.resident_size;
1015
1016                                 pse.vmem_minflt_counter = task_events_info.cow_faults;
1017                                 pse.vmem_majflt_counter = task_events_info.faults;
1018
1019                                 pse.cpu_user_counter = task_absolutetime_info.total_user;
1020                                 pse.cpu_system_counter = task_absolutetime_info.total_system;
1021                         }
1022
1023                         status = task_threads (task_list[task], &thread_list,
1024                                         &thread_list_len);
1025                         if (status != KERN_SUCCESS)
1026                         {
1027                                 /* Apple's `top' treats this case a zombie. It
1028                                  * makes sense to some extend: A `zombie'
1029                                  * thread is nonsense, since the task/process
1030                                  * is dead. */
1031                                 zombies++;
1032                                 DEBUG ("task_threads failed: %s",
1033                                                 mach_error_string (status));
1034                                 if (task_list[task] != port_task_self)
1035                                         mach_port_deallocate (port_task_self,
1036                                                         task_list[task]);
1037                                 continue; /* with next task_list */
1038                         }
1039
1040                         for (thread = 0; thread < thread_list_len; thread++)
1041                         {
1042                                 thread_data_len = THREAD_BASIC_INFO_COUNT;
1043                                 status = thread_info (thread_list[thread],
1044                                                 THREAD_BASIC_INFO,
1045                                                 (thread_info_t) &thread_data,
1046                                                 &thread_data_len);
1047                                 if (status != KERN_SUCCESS)
1048                                 {
1049                                         ERROR ("thread_info failed: %s",
1050                                                         mach_error_string (status));
1051                                         if (task_list[task] != port_task_self)
1052                                                 mach_port_deallocate (port_task_self,
1053                                                                 thread_list[thread]);
1054                                         continue; /* with next thread_list */
1055                                 }
1056
1057                                 if (ps != NULL)
1058                                         pse.num_lwp++;
1059
1060                                 switch (thread_data.run_state)
1061                                 {
1062                                         case TH_STATE_RUNNING:
1063                                                 running++;
1064                                                 break;
1065                                         case TH_STATE_STOPPED:
1066                                         /* What exactly is `halted'? */
1067                                         case TH_STATE_HALTED:
1068                                                 stopped++;
1069                                                 break;
1070                                         case TH_STATE_WAITING:
1071                                                 sleeping++;
1072                                                 break;
1073                                         case TH_STATE_UNINTERRUPTIBLE:
1074                                                 blocked++;
1075                                                 break;
1076                                         /* There is no `zombie' case here,
1077                                          * since there are no zombie-threads.
1078                                          * There's only zombie tasks, which are
1079                                          * handled above. */
1080                                         default:
1081                                                 WARNING ("Unknown thread status: %i",
1082                                                                 thread_data.run_state);
1083                                                 break;
1084                                 } /* switch (thread_data.run_state) */
1085
1086                                 if (task_list[task] != port_task_self)
1087                                 {
1088                                         status = mach_port_deallocate (port_task_self,
1089                                                         thread_list[thread]);
1090                                         if (status != KERN_SUCCESS)
1091                                                 ERROR ("mach_port_deallocate failed: %s",
1092                                                                 mach_error_string (status));
1093                                 }
1094                         } /* for (thread_list) */
1095
1096                         if ((status = vm_deallocate (port_task_self,
1097                                                         (vm_address_t) thread_list,
1098                                                         thread_list_len * sizeof (thread_act_t)))
1099                                         != KERN_SUCCESS)
1100                         {
1101                                 ERROR ("vm_deallocate failed: %s",
1102                                                 mach_error_string (status));
1103                         }
1104                         thread_list = NULL;
1105                         thread_list_len = 0;
1106
1107                         /* Only deallocate the task port, if it isn't our own.
1108                          * Don't know what would happen in that case, but this
1109                          * is what Apple's top does.. ;) */
1110                         if (task_list[task] != port_task_self)
1111                         {
1112                                 status = mach_port_deallocate (port_task_self,
1113                                                 task_list[task]);
1114                                 if (status != KERN_SUCCESS)
1115                                         ERROR ("mach_port_deallocate failed: %s",
1116                                                         mach_error_string (status));
1117                         }
1118
1119                         if (ps != NULL)
1120                                 /* FIXME: cmdline should be here instead of NULL */
1121                                 ps_list_add (task_name, NULL, &pse);
1122                 } /* for (task_list) */
1123
1124                 if ((status = vm_deallocate (port_task_self,
1125                                 (vm_address_t) task_list,
1126                                 task_list_len * sizeof (task_t))) != KERN_SUCCESS)
1127                 {
1128                         ERROR ("vm_deallocate failed: %s",
1129                                         mach_error_string (status));
1130                 }
1131                 task_list = NULL;
1132                 task_list_len = 0;
1133
1134                 if ((status = mach_port_deallocate (port_task_self, port_pset_priv))
1135                                 != KERN_SUCCESS)
1136                 {
1137                         ERROR ("mach_port_deallocate failed: %s",
1138                                         mach_error_string (status));
1139                 }
1140         } /* for (pset_list) */
1141
1142         ps_submit_state ("running", running);
1143         ps_submit_state ("sleeping", sleeping);
1144         ps_submit_state ("zombies", zombies);
1145         ps_submit_state ("stopped", stopped);
1146         ps_submit_state ("blocked", blocked);
1147
1148         for (ps = list_head_g; ps != NULL; ps = ps->next)
1149                 ps_submit_proc_list (ps);
1150 /* #endif HAVE_THREAD_INFO */
1151
1152 #elif KERNEL_LINUX
1153         int running  = 0;
1154         int sleeping = 0;
1155         int zombies  = 0;
1156         int stopped  = 0;
1157         int paging   = 0;
1158         int blocked  = 0;
1159
1160         struct dirent *ent;
1161         DIR           *proc;
1162         int            pid;
1163
1164         char cmdline[ARG_MAX];
1165
1166         int        status;
1167         procstat_t ps;
1168         procstat_entry_t pse;
1169         char       state;
1170
1171         procstat_t *ps_ptr;
1172
1173         running = sleeping = zombies = stopped = paging = blocked = 0;
1174         ps_list_reset ();
1175
1176         if ((proc = opendir ("/proc")) == NULL)
1177         {
1178                 char errbuf[1024];
1179                 ERROR ("Cannot open `/proc': %s",
1180                                 sstrerror (errno, errbuf, sizeof (errbuf)));
1181                 return (-1);
1182         }
1183
1184         while ((ent = readdir (proc)) != NULL)
1185         {
1186                 if (!isdigit (ent->d_name[0]))
1187                         continue;
1188
1189                 if ((pid = atoi (ent->d_name)) < 1)
1190                         continue;
1191
1192                 status = ps_read_process (pid, &ps, &state);
1193                 if (status != 0)
1194                 {
1195                         DEBUG ("ps_read_process failed: %i", status);
1196                         continue;
1197                 }
1198
1199                 pse.id       = pid;
1200                 pse.age      = 0;
1201
1202                 pse.num_proc = ps.num_proc;
1203                 pse.num_lwp  = ps.num_lwp;
1204                 pse.vmem_rss = ps.vmem_rss;
1205
1206                 pse.vmem_minflt = 0;
1207                 pse.vmem_minflt_counter = ps.vmem_minflt_counter;
1208                 pse.vmem_majflt = 0;
1209                 pse.vmem_majflt_counter = ps.vmem_majflt_counter;
1210
1211                 pse.cpu_user = 0;
1212                 pse.cpu_user_counter = ps.cpu_user_counter;
1213                 pse.cpu_system = 0;
1214                 pse.cpu_system_counter = ps.cpu_system_counter;
1215
1216                 switch (state)
1217                 {
1218                         case 'R': running++;  break;
1219                         case 'S': sleeping++; break;
1220                         case 'D': blocked++;  break;
1221                         case 'Z': zombies++;  break;
1222                         case 'T': stopped++;  break;
1223                         case 'W': paging++;   break;
1224                 }
1225
1226                 ps_list_add (ps.name,
1227                                 ps_get_cmdline (pid, ps.name, cmdline, sizeof (cmdline)),
1228                                 &pse);
1229         }
1230
1231         closedir (proc);
1232
1233         ps_submit_state ("running",  running);
1234         ps_submit_state ("sleeping", sleeping);
1235         ps_submit_state ("zombies",  zombies);
1236         ps_submit_state ("stopped",  stopped);
1237         ps_submit_state ("paging",   paging);
1238         ps_submit_state ("blocked",  blocked);
1239
1240         for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next)
1241                 ps_submit_proc_list (ps_ptr);
1242 /* #endif KERNEL_LINUX */
1243
1244 #elif HAVE_LIBKVM_GETPROCS
1245         int running  = 0;
1246         int sleeping = 0;
1247         int zombies  = 0;
1248         int stopped  = 0;
1249         int blocked  = 0;
1250         int idle     = 0;
1251         int wait     = 0;
1252
1253         kvm_t *kd;
1254         char errbuf[1024];
1255         char cmdline[ARG_MAX];
1256         char *cmdline_ptr;
1257         struct kinfo_proc *procs;          /* array of processes */
1258         char **argv;
1259         int count;                         /* returns number of processes */
1260         int i;
1261
1262         procstat_t *ps_ptr;
1263         procstat_entry_t pse;
1264
1265         ps_list_reset ();
1266
1267         /* Open the kvm interface, get a descriptor */
1268         kd = kvm_open (NULL, NULL, NULL, 0, errbuf);
1269         if (kd == NULL)
1270         {
1271                 ERROR ("processes plugin: Cannot open kvm interface: %s",
1272                                 errbuf);
1273                 return (0);
1274         }
1275
1276         /* Get the list of processes. */
1277         procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &count);
1278         if (procs == NULL)
1279         {
1280                 kvm_close (kd);
1281                 ERROR ("processes plugin: Cannot get kvm processes list: %s",
1282                                 kvm_geterr(kd));
1283                 return (0);
1284         }
1285
1286         /* Iterate through the processes in kinfo_proc */
1287         for (i = 0; i < count; i++)
1288         {
1289                 /* retrieve the arguments */
1290                 cmdline[0] = 0;
1291                 cmdline_ptr = NULL;
1292
1293                 argv = kvm_getargv (kd, (const struct kinfo_proc *) &(procs[i]), 0);
1294                 if (argv != NULL)
1295                 {
1296                         int status;
1297                         int argc;
1298
1299                         argc = 0;
1300                         while (argv[argc] != NULL)
1301                                 argc++;
1302
1303                         status = strjoin (cmdline, sizeof (cmdline),
1304                                         argv, argc, " ");
1305
1306                         if (status < 0)
1307                         {
1308                                 WARNING ("processes plugin: Command line did "
1309                                                 "not fit into buffer.");
1310                         }
1311                         else
1312                         {
1313                                 cmdline_ptr = &cmdline[0];
1314                         }
1315                 }
1316
1317                 pse.id       = procs[i].ki_pid;
1318                 pse.age      = 0;
1319
1320                 pse.num_proc = 1;
1321                 pse.num_lwp  = procs[i].ki_numthreads;
1322
1323                 pse.vmem_rss = procs[i].ki_rssize * getpagesize();
1324                 pse.vmem_minflt = 0;
1325                 pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
1326                 pse.vmem_majflt = 0;
1327                 pse.vmem_majflt_counter = procs[i].ki_rusage.ru_majflt;
1328
1329                 pse.cpu_user = 0;
1330                 pse.cpu_user_counter = procs[i].ki_rusage.ru_utime.tv_sec
1331                         * 1000
1332                         + procs[i].ki_rusage.ru_utime.tv_usec;
1333                 pse.cpu_system = 0;
1334                 pse.cpu_system_counter = procs[i].ki_rusage.ru_stime.tv_sec
1335                         * 1000
1336                         + procs[i].ki_rusage.ru_stime.tv_usec;
1337
1338                 switch (procs[i].ki_stat)
1339                 {
1340                         case SSTOP:     stopped++;      break;
1341                         case SSLEEP:    sleeping++;     break;
1342                         case SRUN:      running++;      break;
1343                         case SIDL:      idle++;         break;
1344                         case SWAIT:     wait++;         break;
1345                         case SLOCK:     blocked++;      break;
1346                         case SZOMB:     zombies++;      break;
1347                 }
1348
1349                 ps_list_add (procs[i].ki_comm, cmdline_ptr, &pse);
1350         }
1351
1352         kvm_close(kd);
1353
1354         ps_submit_state ("running",  running);
1355         ps_submit_state ("sleeping", sleeping);
1356         ps_submit_state ("zombies",  zombies);
1357         ps_submit_state ("stopped",  stopped);
1358         ps_submit_state ("blocked",  blocked);
1359         ps_submit_state ("idle",     idle);
1360         ps_submit_state ("wait",     wait);
1361
1362         for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next)
1363                 ps_submit_proc_list (ps_ptr);
1364 #endif /* HAVE_LIBKVM_GETPROCS */
1365
1366         return (0);
1367 } /* int ps_read */
1368
1369 void module_register (void)
1370 {
1371         plugin_register_config ("processes", ps_config,
1372                         config_keys, config_keys_num);
1373         plugin_register_init ("processes", ps_init);
1374         plugin_register_read ("processes", ps_read);
1375 } /* void module_register */