1 /**
2  * collectd - src/irq.c
3  * Copyright (C) 2007  Peter Holik
4  * Copyright (C) 2011  Florian Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; either version 2 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Authors:
21  *   Peter Holik <peter at holik.at>
22  **/
23 
24 #include "collectd.h"
25 
26 #include "plugin.h"
27 #include "utils/common/common.h"
28 #include "utils/ignorelist/ignorelist.h"
29 
30 #if !KERNEL_LINUX && !KERNEL_NETBSD
31 #error "No applicable input method."
32 #endif
33 
34 #if KERNEL_NETBSD
35 #include <malloc.h>
36 #include <sys/evcnt.h>
37 #include <sys/param.h>
38 #include <sys/sysctl.h>
39 #include <sys/types.h>
40 #endif /* KERNEL_NETBSD */
41 
42 /*
43  * (Module-)Global variables
44  */
45 static const char *config_keys[] = {"Irq", "IgnoreSelected"};
46 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
47 
48 static ignorelist_t *ignorelist;
49 
50 /*
51  * Private functions
52  */
irq_config(const char * key,const char * value)53 static int irq_config(const char *key, const char *value) {
54   if (ignorelist == NULL)
55     ignorelist = ignorelist_create(/* invert = */ 1);
56 
57   if (strcasecmp(key, "Irq") == 0) {
58     ignorelist_add(ignorelist, value);
59   } else if (strcasecmp(key, "IgnoreSelected") == 0) {
60     int invert = 1;
61     if (IS_TRUE(value))
62       invert = 0;
63     ignorelist_set_invert(ignorelist, invert);
64   } else {
65     return -1;
66   }
67 
68   return 0;
69 }
70 
irq_submit(const char * irq_name,derive_t value)71 static void irq_submit(const char *irq_name, derive_t value) {
72   value_list_t vl = VALUE_LIST_INIT;
73 
74   if (ignorelist_match(ignorelist, irq_name) != 0)
75     return;
76 
77   vl.values = &(value_t){.derive = value};
78   vl.values_len = 1;
79   sstrncpy(vl.plugin, "irq", sizeof(vl.plugin));
80   sstrncpy(vl.type, "irq", sizeof(vl.type));
81   sstrncpy(vl.type_instance, irq_name, sizeof(vl.type_instance));
82 
83   plugin_dispatch_values(&vl);
84 } /* void irq_submit */
85 
86 #if KERNEL_LINUX
irq_read(void)87 static int irq_read(void) {
88   FILE *fh;
89   char buffer[1024];
90   int cpu_count;
91   char *fields[256];
92 
93   /*
94    * Example content:
95    *         CPU0       CPU1       CPU2       CPU3
96    * 0:       2574          1          3          2   IO-APIC-edge      timer
97    * 1:     102553     158669     218062      70587   IO-APIC-edge      i8042
98    * 8:          0          0          0          1   IO-APIC-edge      rtc0
99    */
100   fh = fopen("/proc/interrupts", "r");
101   if (fh == NULL) {
102     ERROR("irq plugin: fopen (/proc/interrupts): %s", STRERRNO);
103     return -1;
104   }
105 
106   /* Get CPU count from the first line */
107   if (fgets(buffer, sizeof(buffer), fh) != NULL) {
108     cpu_count = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
109   } else {
110     ERROR("irq plugin: unable to get CPU count from first line "
111           "of /proc/interrupts");
112     fclose(fh);
113     return -1;
114   }
115 
116   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
117     char *irq_name;
118     size_t irq_name_len;
119     derive_t irq_value;
120     int i;
121     int fields_num;
122     int irq_values_to_parse;
123 
124     fields_num = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
125     if (fields_num < 2)
126       continue;
127 
128     /* Parse this many numeric fields, skip the rest
129      * (+1 because first there is a name of irq in each line) */
130     if (fields_num >= cpu_count + 1)
131       irq_values_to_parse = cpu_count;
132     else
133       irq_values_to_parse = fields_num - 1;
134 
135     /* First field is irq name and colon */
136     irq_name = fields[0];
137     irq_name_len = strlen(irq_name);
138     if (irq_name_len < 2)
139       continue;
140 
141     /* Check if irq name ends with colon.
142      * Otherwise it's a header. */
143     if (irq_name[irq_name_len - 1] != ':')
144       continue;
145 
146     /* Is it the the ARM fast interrupt (FIQ)? */
147     if (irq_name_len == 4 && (strncmp(irq_name, "FIQ:", 4) == 0))
148       continue;
149 
150     irq_name[irq_name_len - 1] = '\0';
151     irq_name_len--;
152 
153     irq_value = 0;
154     for (i = 1; i <= irq_values_to_parse; i++) {
155       /* Per-CPU value */
156       value_t v;
157       int status;
158 
159       status = parse_value(fields[i], &v, DS_TYPE_DERIVE);
160       if (status != 0)
161         break;
162 
163       irq_value += v.derive;
164     } /* for (i) */
165 
166     /* No valid fields -> do not submit anything. */
167     if (i <= 1)
168       continue;
169 
170     irq_submit(irq_name, irq_value);
171   }
172 
173   fclose(fh);
174 
175   return 0;
176 } /* int irq_read */
177 #endif /* KERNEL_LINUX */
178 
179 #if KERNEL_NETBSD
irq_read(void)180 static int irq_read(void) {
181   const int mib[4] = {CTL_KERN, KERN_EVCNT, EVCNT_TYPE_INTR,
182                       KERN_EVCNT_COUNT_NONZERO};
183   size_t buflen = 0;
184   void *buf = NULL;
185   const struct evcnt_sysctl *evs, *last_evs;
186 
187   for (;;) {
188     size_t newlen;
189     int error;
190 
191     newlen = buflen;
192     if (buflen)
193       buf = malloc(buflen);
194     error = sysctl(mib, __arraycount(mib), buf, &newlen, NULL, 0);
195     if (error) {
196       ERROR("irq plugin: failed to get event count");
197       return -1;
198     }
199     if (newlen <= buflen) {
200       buflen = newlen;
201       break;
202     }
203     if (buf)
204       free(buf);
205     buflen = newlen;
206   }
207   evs = buf;
208   last_evs = (void *)((char *)buf + buflen);
209   buflen /= sizeof(uint64_t);
210   while (evs < last_evs && buflen > sizeof(*evs) / sizeof(uint64_t) &&
211          buflen >= evs->ev_len) {
212     char irqname[80];
213 
214     snprintf(irqname, 80, "%s-%s", evs->ev_strings,
215              evs->ev_strings + evs->ev_grouplen + 1);
216 
217     irq_submit(irqname, evs->ev_count);
218 
219     buflen -= evs->ev_len;
220     evs = (const void *)((const uint64_t *)evs + evs->ev_len);
221   }
222   free(buf);
223   return 0;
224 }
225 #endif /* KERNEL_NETBSD */
226 
module_register(void)227 void module_register(void) {
228   plugin_register_config("irq", irq_config, config_keys, config_keys_num);
229   plugin_register_read("irq", irq_read);
230 } /* void module_register */
231