smart plugin: add IgnoreSleepMode option to ignore sleeping disks
[collectd.git] / src / smart.c
1 /**
2  * collectd - src/smart.c
3  * Copyright (C) 2014       Vincent Bernat
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Vincent Bernat <vbe at exoscale.ch>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "utils_ignorelist.h"
31
32 #include <atasmart.h>
33 #include <libudev.h>
34
35 static const char *config_keys[] =
36 {
37   "Disk",
38   "IgnoreSelected",
39   "IgnoreSleepMode"
40 };
41
42 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
43
44 static ignorelist_t *ignorelist = NULL;
45 static int ignore_sleep_mode = 0;
46
47 static int smart_config (const char *key, const char *value)
48 {
49   if (ignorelist == NULL)
50     ignorelist = ignorelist_create (/* invert = */ 1);
51   if (ignorelist == NULL)
52     return (1);
53
54   if (strcasecmp ("Disk", key) == 0)
55   {
56     ignorelist_add (ignorelist, value);
57   }
58   else if (strcasecmp ("IgnoreSelected", key) == 0)
59   {
60     int invert = 1;
61     if (IS_TRUE (value))
62       invert = 0;
63     ignorelist_set_invert (ignorelist, invert);
64   }
65   else if (strcasecmp ("IgnoreSleepMode", key) == 0)
66   {
67     if (IS_TRUE (value))
68       ignore_sleep_mode = 1;
69   }
70   else
71   {
72     return (-1);
73   }
74
75   return (0);
76 } /* int smart_config */
77
78 static void smart_submit (const char *dev, const char *type,
79                 const char *type_inst, double value)
80 {
81         value_t values[1];
82         value_list_t vl = VALUE_LIST_INIT;
83
84         values[0].gauge = value;
85
86         vl.values = values;
87         vl.values_len = 1;
88         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
89         sstrncpy (vl.plugin, "smart", sizeof (vl.plugin));
90         sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
91         sstrncpy (vl.type, type, sizeof (vl.type));
92         sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
93
94         plugin_dispatch_values (&vl);
95 }
96
97 static void smart_handle_disk_attribute(SkDisk *d, const SkSmartAttributeParsedData *a,
98                                         void* userdata)
99 {
100   const char *dev = userdata;
101   value_t values[4];
102   value_list_t vl = VALUE_LIST_INIT;
103
104   if (!a->current_value_valid || !a->worst_value_valid) return;
105   values[0].gauge = a->current_value;
106   values[1].gauge = a->worst_value;
107   values[2].gauge = a->threshold_valid?a->threshold:0;
108   values[3].gauge = a->pretty_value;
109
110   vl.values = values;
111   vl.values_len = 4;
112   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
113   sstrncpy (vl.plugin, "smart", sizeof (vl.plugin));
114   sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
115   sstrncpy (vl.type, "smart_attribute", sizeof (vl.type));
116   sstrncpy (vl.type_instance, a->name, sizeof (vl.type_instance));
117
118   plugin_dispatch_values (&vl);
119
120   if (a->threshold_valid && a->current_value <= a->threshold)
121   {
122     notification_t notif = { NOTIF_WARNING,
123                              cdtime (),
124                              "",
125                              "",
126                              "smart", "",
127                              "smart_attribute",
128                              "",
129                              NULL };
130     sstrncpy (notif.host, hostname_g, sizeof (notif.host));
131     sstrncpy (notif.plugin_instance, dev, sizeof (notif.plugin_instance));
132     sstrncpy (notif.type_instance, a->name, sizeof (notif.type_instance));
133     ssnprintf (notif.message, sizeof (notif.message),
134                "attribute %s is below allowed threshold (%d < %d)",
135                a->name, a->current_value, a->threshold);
136     plugin_dispatch_notification (&notif);
137   }
138 }
139
140 static void smart_handle_disk (const char *dev)
141 {
142   SkDisk *d = NULL;
143   SkBool awake = FALSE;
144   SkBool available = FALSE;
145   const char *shortname;
146   const SkSmartParsedData *spd;
147   uint64_t poweron, powercycles, badsectors, temperature;
148
149   shortname = strrchr(dev, '/');
150   if (!shortname) return;
151   shortname++;
152   if (ignorelist_match (ignorelist, shortname) != 0) {
153     DEBUG ("smart plugin: ignoring %s.", dev);
154     return;
155   }
156
157   DEBUG ("smart plugin: checking SMART status of %s.",
158          dev);
159
160   if (sk_disk_open (dev, &d) < 0)
161   {
162     ERROR ("smart plugin: unable to open %s.", dev);
163     return;
164   }
165   if (sk_disk_identify_is_available (d, &available) < 0 || !available)
166   {
167     DEBUG ("smart plugin: disk %s cannot be identified.", dev);
168     goto end;
169   }
170   if (sk_disk_smart_is_available (d, &available) < 0 || !available)
171   {
172     DEBUG ("smart plugin: disk %s has no SMART support.", dev);
173     goto end;
174   }
175   if (!ignore_sleep_mode)
176   {
177     if (sk_disk_check_sleep_mode (d, &awake) < 0 || !awake)
178     {
179       DEBUG ("smart plugin: disk %s is sleeping.", dev);
180       goto end;
181     }
182   }
183   if (sk_disk_smart_read_data (d) < 0)
184   {
185     ERROR ("smart plugin: unable to get SMART data for disk %s.", dev);
186     goto end;
187   }
188   if (sk_disk_smart_parse (d, &spd) < 0)
189   {
190     ERROR ("smart plugin: unable to parse SMART data for disk %s.", dev);
191     goto end;
192   }
193
194   /* Get some specific values */
195   if (sk_disk_smart_get_power_on (d, &poweron) < 0)
196   {
197     WARNING ("smart plugin: unable to get milliseconds since power on for %s.",
198              dev);
199   }
200   else
201     smart_submit (shortname, "smart_poweron", "", poweron / 1000.);
202
203   if (sk_disk_smart_get_power_cycle (d, &powercycles) < 0)
204   {
205     WARNING ("smart plugin: unable to get number of power cycles for %s.",
206              dev);
207   }
208   else
209     smart_submit (shortname, "smart_powercycles", "", powercycles);
210
211   if (sk_disk_smart_get_bad (d, &badsectors) < 0)
212   {
213     WARNING ("smart plugin: unable to get number of bad sectors for %s.",
214              dev);
215   }
216   else
217     smart_submit (shortname, "smart_badsectors", "", badsectors);
218
219   if (sk_disk_smart_get_temperature (d, &temperature) < 0)
220   {
221     WARNING ("smart plugin: unable to get temperature for %s.",
222              dev);
223   }
224   else
225     smart_submit (shortname, "smart_temperature", "", temperature / 1000. - 273.15);
226
227   /* Grab all attributes */
228   if (sk_disk_smart_parse_attributes(d, smart_handle_disk_attribute,
229                                      (char *)shortname) < 0)
230   {
231     ERROR ("smart plugin: unable to handle SMART attributes for %s.",
232            dev);
233   }
234
235 end:
236   sk_disk_free(d);
237 }
238
239 static int smart_read (void)
240 {
241   struct udev *handle_udev;
242   struct udev_enumerate *enumerate;
243   struct udev_list_entry *devices, *dev_list_entry;
244   struct udev_device *dev;
245
246   /* Use udev to get a list of disks */
247   handle_udev = udev_new();
248   if (!handle_udev)
249   {
250     ERROR ("smart plugin: unable to initialize udev.");
251     return (-1);
252   }
253   enumerate = udev_enumerate_new (handle_udev);
254   udev_enumerate_add_match_subsystem (enumerate, "block");
255   udev_enumerate_add_match_property (enumerate, "DEVTYPE", "disk");
256   udev_enumerate_scan_devices (enumerate);
257   devices = udev_enumerate_get_list_entry (enumerate);
258   udev_list_entry_foreach (dev_list_entry, devices)
259   {
260     const char *path, *devpath;
261     path = udev_list_entry_get_name (dev_list_entry);
262     dev = udev_device_new_from_syspath (handle_udev, path);
263     devpath = udev_device_get_devnode (dev);
264
265     /* Query status with libatasmart */
266     smart_handle_disk (devpath);
267     udev_device_unref (dev);
268   }
269
270   udev_enumerate_unref (enumerate);
271   udev_unref (handle_udev);
272
273   return (0);
274 } /* int smart_read */
275
276 void module_register (void)
277 {
278   plugin_register_config ("smart", smart_config,
279                           config_keys, config_keys_num);
280   plugin_register_read ("smart", smart_read);
281 } /* void module_register */