netapp plugin: Refactor the VolumePerf collection.
[collectd.git] / src / netapp.c
1 /**
2  * collectd - src/netapp.c
3  * Copyright (C) 2009  Sven Trenkel
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  *   Sven Trenkel <collectd at semidefinite.de>  
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "utils_ignorelist.h"
30
31 #include <netapp_api.h>
32
33 #define HAS_ALL_FLAGS(has,needs) (((has) & (needs)) == (needs))
34
35 typedef struct host_config_s host_config_t;
36 typedef void service_handler_t(host_config_t *host, na_elem_t *result, void *data);
37
38 struct cna_interval_s
39 {
40         time_t interval;
41         time_t last_read;
42 };
43 typedef struct cna_interval_s cna_interval_t;
44
45 /*! Data types for WAFL statistics {{{
46  *
47  * \brief Persistent data for WAFL performance counters. (a.k.a. cache performance)
48  *
49  * The cache counters use old counter values to calculate a hit ratio for each
50  * counter. The "cfg_wafl_t" struct therefore contains old counter values along
51  * with flags, which are set if the counter is valid.
52  *
53  * The function "cna_handle_wafl_data" will fill a new structure of this kind
54  * with new values, then pass both, new and old data, to "submit_wafl_data".
55  * That function calculates the hit ratios, submits the calculated values and
56  * updates the old counter values for the next iteration.
57  */
58 #define CFG_WAFL_NAME_CACHE        0x0001
59 #define CFG_WAFL_DIR_CACHE         0x0002
60 #define CFG_WAFL_BUF_CACHE         0x0004
61 #define CFG_WAFL_INODE_CACHE       0x0008
62 #define CFG_WAFL_ALL               0x000F
63 #define HAVE_WAFL_NAME_CACHE_HIT   0x0100
64 #define HAVE_WAFL_NAME_CACHE_MISS  0x0200
65 #define HAVE_WAFL_NAME_CACHE       (HAVE_WAFL_NAME_CACHE_HIT | HAVE_WAFL_NAME_CACHE_MISS)
66 #define HAVE_WAFL_FIND_DIR_HIT     0x0400
67 #define HAVE_WAFL_FIND_DIR_MISS    0x0800
68 #define HAVE_WAFL_FIND_DIR         (HAVE_WAFL_FIND_DIR_HIT | HAVE_WAFL_FIND_DIR_MISS)
69 #define HAVE_WAFL_BUF_HASH_HIT     0x1000
70 #define HAVE_WAFL_BUF_HASH_MISS    0x2000
71 #define HAVE_WAFL_BUF_HASH         (HAVE_WAFL_BUF_HASH_HIT | HAVE_WAFL_BUF_HASH_MISS)
72 #define HAVE_WAFL_INODE_CACHE_HIT  0x4000
73 #define HAVE_WAFL_INODE_CACHE_MISS 0x8000
74 #define HAVE_WAFL_INODE_CACHE      (HAVE_WAFL_INODE_CACHE_HIT | HAVE_WAFL_INODE_CACHE_MISS)
75 #define HAVE_WAFL_ALL              0xff00
76 typedef struct {
77         uint32_t flags;
78         cna_interval_t interval;
79         na_elem_t *query;
80
81         time_t timestamp;
82         uint64_t name_cache_hit;
83         uint64_t name_cache_miss;
84         uint64_t find_dir_hit;
85         uint64_t find_dir_miss;
86         uint64_t buf_hash_hit;
87         uint64_t buf_hash_miss;
88         uint64_t inode_cache_hit;
89         uint64_t inode_cache_miss;
90 } cfg_wafl_t;
91 /* }}} cfg_wafl_t */
92
93 /*! Data types for disk statistics {{{
94  *
95  * \brief A disk in the NetApp.
96  *
97  * A disk doesn't have any more information than its name at the moment.
98  * The name includes the "disk_" prefix.
99  */
100 #define HAVE_DISK_BUSY   0x10
101 #define HAVE_DISK_BASE   0x20
102 #define HAVE_DISK_ALL    0x30
103 typedef struct disk_s {
104         char *name;
105         uint32_t flags;
106         time_t timestamp;
107         uint64_t disk_busy;
108         uint64_t base_for_disk_busy;
109         double disk_busy_percent;
110         struct disk_s *next;
111 } disk_t;
112
113 #define CFG_DISK_BUSIEST 0x01
114 #define CFG_DISK_ALL     0x01
115 typedef struct {
116         uint32_t flags;
117         cna_interval_t interval;
118         na_elem_t *query;
119         disk_t *disks;
120 } cfg_disk_t;
121 /* }}} cfg_disk_t */
122
123 /*! Data types for volume performance statistics {{{
124  *
125  * \brief Persistent data for volume performance data.
126  *
127  * The code below uses the difference of the operations and latency counters to
128  * calculate an average per-operation latency. For this, old counters need to
129  * be stored in the "data_volume_perf_t" structure. The byte-counters are just
130  * kept for completeness sake. The "flags" member indicates if each counter is
131  * valid or not.
132  *
133  * The "cna_handle_volume_perf_data" function will fill a new struct of this
134  * type and pass both, old and new data, to "submit_volume_perf_data". In that
135  * function, the per-operation latency is calculated and dispatched, then the
136  * old counters are updated.
137  */
138 #define CFG_VOLUME_PERF_INIT           0x0001
139 #define CFG_VOLUME_PERF_IO             0x0002
140 #define CFG_VOLUME_PERF_OPS            0x0003
141 #define CFG_VOLUME_PERF_LATENCY        0x0008
142 #define CFG_VOLUME_PERF_ALL            0x000F
143 #define HAVE_VOLUME_PERF_BYTES_READ    0x0010
144 #define HAVE_VOLUME_PERF_BYTES_WRITE   0x0020
145 #define HAVE_VOLUME_PERF_OPS_READ      0x0040
146 #define HAVE_VOLUME_PERF_OPS_WRITE     0x0080
147 #define HAVE_VOLUME_PERF_LATENCY_READ  0x0100
148 #define HAVE_VOLUME_PERF_LATENCY_WRITE 0x0200
149 #define HAVE_VOLUME_PERF_ALL           0x03F0
150 struct data_volume_perf_s;
151 typedef struct data_volume_perf_s data_volume_perf_t;
152 struct data_volume_perf_s {
153         char *name;
154         uint32_t flags;
155         time_t timestamp;
156
157         uint64_t read_bytes;
158         uint64_t write_bytes;
159         uint64_t read_ops;
160         uint64_t write_ops;
161         uint64_t read_latency;
162         uint64_t write_latency;
163
164         data_volume_perf_t *next;
165 };
166
167 typedef struct {
168         cna_interval_t interval;
169         na_elem_t *query;
170
171         ignorelist_t *il_octets;
172         ignorelist_t *il_operations;
173         ignorelist_t *il_latency;
174
175         data_volume_perf_t *volumes;
176 } cfg_volume_perf_t;
177 /* }}} data_volume_perf_t */
178
179 /*! Data types for volume usage statistics {{{
180  *
181  * \brief Configuration struct for volume usage data (free / used).
182  */
183 #define CFG_VOLUME_USAGE_DF             0x0002
184 #define CFG_VOLUME_USAGE_SNAP           0x0004
185 #define CFG_VOLUME_USAGE_ALL            0x0006
186 #define HAVE_VOLUME_USAGE_NORM_FREE     0x0010
187 #define HAVE_VOLUME_USAGE_NORM_USED     0x0020
188 #define HAVE_VOLUME_USAGE_SNAP_RSVD     0x0040
189 #define HAVE_VOLUME_USAGE_SNAP_USED     0x0080
190 #define HAVE_VOLUME_USAGE_SIS_SAVED     0x0100
191 #define HAVE_VOLUME_USAGE_ALL           0x01f0
192 struct data_volume_usage_s;
193 typedef struct data_volume_usage_s data_volume_usage_t;
194 struct data_volume_usage_s {
195         char *name;
196         uint32_t flags;
197
198         uint64_t norm_free;
199         uint64_t norm_used;
200         uint64_t snap_reserved;
201         uint64_t snap_used;
202         uint64_t sis_saved;
203
204         data_volume_usage_t *next;
205 };
206
207 typedef struct {
208         cna_interval_t interval;
209         na_elem_t *query;
210
211         ignorelist_t *il_capacity;
212         ignorelist_t *il_snapshot;
213
214         data_volume_usage_t *volumes;
215 } cfg_volume_usage_t;
216 /* }}} cfg_volume_usage_t */
217
218 /*! Data types for system statistics {{{
219  *
220  * \brief Persistent data for system performance counters
221  */
222 #define CFG_SYSTEM_CPU  0x01
223 #define CFG_SYSTEM_NET  0x02
224 #define CFG_SYSTEM_OPS  0x04
225 #define CFG_SYSTEM_DISK 0x08
226 #define CFG_SYSTEM_ALL  0x0F
227 typedef struct {
228         uint32_t flags;
229         cna_interval_t interval;
230         na_elem_t *query;
231 } cfg_system_t;
232 /* }}} cfg_system_t */
233
234 typedef struct service_config_s {
235         na_elem_t *query;
236         service_handler_t *handler;
237         int multiplier;
238         int skip_countdown;
239         int interval;
240         void *data;
241         struct service_config_s *next;
242 } cfg_service_t;
243 #define SERVICE_INIT {0, 0, 1, 1, 0, 0, 0}
244
245 /*!
246  * \brief Struct representing a volume.
247  *
248  * A volume currently has a name and two sets of values:
249  *
250  *  - Performance data, such as bytes read/written, number of operations
251  *    performed and average time per operation.
252  *
253  *  - Usage data, i. e. amount of used and free space in the volume.
254  */
255 typedef struct volume_s {
256         char *name;
257         data_volume_perf_t perf_data;
258         struct volume_s *next;
259 } volume_t;
260
261 struct host_config_s {
262         char *name;
263         na_server_transport_t protocol;
264         char *host;
265         int port;
266         char *username;
267         char *password;
268         int interval;
269
270         na_server_t *srv;
271         cfg_service_t *services;
272         cfg_wafl_t *cfg_wafl;
273         cfg_disk_t *cfg_disk;
274         cfg_volume_perf_t *cfg_volume_perf;
275         cfg_volume_usage_t *cfg_volume_usage;
276         cfg_system_t *cfg_system;
277         volume_t *volumes;
278
279         struct host_config_s *next;
280 };
281 #define HOST_INIT { NULL, NA_SERVER_TRANSPORT_HTTPS, NULL, 0, NULL, NULL, 0, \
282         NULL, NULL, NULL, NULL, NULL, NULL, NULL, \
283         NULL}
284
285 static host_config_t *global_host_config;
286
287 /*
288  * Free functions
289  *
290  * Used to free the various structures above.
291  */
292 static void free_volume (volume_t *volume) /* {{{ */
293 {
294         volume_t *next;
295
296         if (volume == NULL)
297                 return;
298
299         next = volume->next;
300
301         sfree (volume->name);
302         sfree (volume);
303
304         free_volume (next);
305 } /* }}} void free_volume */
306
307 static void free_disk (disk_t *disk) /* {{{ */
308 {
309         disk_t *next;
310
311         if (disk == NULL)
312                 return;
313
314         next = disk->next;
315
316         sfree (disk->name);
317         sfree (disk);
318
319         free_disk (next);
320 } /* }}} void free_disk */
321
322 static void free_cfg_wafl (cfg_wafl_t *cw) /* {{{ */
323 {
324         if (cw == NULL)
325                 return;
326
327         if (cw->query != NULL)
328                 na_elem_free (cw->query);
329
330         sfree (cw);
331 } /* }}} void free_cfg_wafl */
332
333 static void free_cfg_disk (cfg_disk_t *cfg_disk) /* {{{ */
334 {
335         if (cfg_disk == NULL)
336                 return;
337
338         if (cfg_disk->query != NULL)
339                 na_elem_free (cfg_disk->query);
340
341         free_disk (cfg_disk->disks);
342         sfree (cfg_disk);
343 } /* }}} void free_cfg_disk */
344
345 static void free_cfg_volume_perf (cfg_volume_perf_t *cvp) /* {{{ */
346 {
347         data_volume_perf_t *data;
348
349         if (cvp == NULL)
350                 return;
351
352         /* Free the ignorelists */
353         ignorelist_free (cvp->il_octets);
354         ignorelist_free (cvp->il_operations);
355         ignorelist_free (cvp->il_latency);
356
357         /* Free the linked list of volumes */
358         data = cvp->volumes;
359         while (data != NULL)
360         {
361                 data_volume_perf_t *next = data->next;
362                 sfree (data->name);
363                 sfree (data);
364                 data = next;
365         }
366
367         if (cvp->query != NULL)
368                 na_elem_free (cvp->query);
369
370         sfree (cvp);
371 } /* }}} void free_cfg_volume_perf */
372
373 static void free_cfg_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
374 {
375         data_volume_usage_t *data;
376
377         if (cvu == NULL)
378                 return;
379
380         /* Free the ignorelists */
381         ignorelist_free (cvu->il_capacity);
382         ignorelist_free (cvu->il_snapshot);
383
384         /* Free the linked list of volumes */
385         data = cvu->volumes;
386         while (data != NULL)
387         {
388                 data_volume_usage_t *next = data->next;
389                 sfree (data->name);
390                 sfree (data);
391                 data = next;
392         }
393
394         if (cvu->query != NULL)
395                 na_elem_free (cvu->query);
396
397         sfree (cvu);
398 } /* }}} void free_cfg_volume_usage */
399
400 static void free_cfg_system (cfg_system_t *cs) /* {{{ */
401 {
402         if (cs == NULL)
403                 return;
404
405         if (cs->query != NULL)
406                 na_elem_free (cs->query);
407
408         sfree (cs);
409 } /* }}} void free_cfg_system */
410
411 static void free_cfg_service (cfg_service_t *service) /* {{{ */
412 {
413         cfg_service_t *next;
414
415         if (service == NULL)
416                 return;
417         
418         next = service->next;
419
420         /* FIXME: Free service->data? */
421         na_elem_free(service->query);
422         
423         sfree (service);
424
425         free_cfg_service (next);
426 } /* }}} void free_cfg_service */
427
428 static void free_host_config (host_config_t *hc) /* {{{ */
429 {
430         host_config_t *next;
431
432         if (hc == NULL)
433                 return;
434
435         next = hc->next;
436
437         sfree (hc->name);
438         sfree (hc->host);
439         sfree (hc->username);
440         sfree (hc->password);
441
442         free_cfg_service (hc->services);
443         free_cfg_disk (hc->cfg_disk);
444         free_cfg_wafl (hc->cfg_wafl);
445         free_cfg_volume_perf (hc->cfg_volume_perf);
446         free_cfg_volume_usage (hc->cfg_volume_usage);
447         free_cfg_system (hc->cfg_system);
448         free_volume (hc->volumes);
449
450         sfree (hc);
451
452         free_host_config (next);
453 } /* }}} void free_host_config */
454
455 /*
456  * Auxiliary functions
457  *
458  * Used to look up volumes and disks or to handle flags.
459  */
460 static disk_t *get_disk(cfg_disk_t *cd, const char *name) /* {{{ */
461 {
462         disk_t *d;
463
464         if ((cd == NULL) || (name == NULL))
465                 return (NULL);
466
467         for (d = cd->disks; d != NULL; d = d->next) {
468                 if (strcmp(d->name, name) == 0)
469                         return d;
470         }
471
472         d = malloc(sizeof(*d));
473         if (d == NULL)
474                 return (NULL);
475         memset (d, 0, sizeof (*d));
476         d->next = NULL;
477
478         d->name = strdup(name);
479         if (d->name == NULL) {
480                 sfree (d);
481                 return (NULL);
482         }
483
484         d->next = cd->disks;
485         cd->disks = d;
486
487         return d;
488 } /* }}} disk_t *get_disk */
489
490 static data_volume_usage_t *get_volume_usage (cfg_volume_usage_t *cvu, /* {{{ */
491                 const char *name)
492 {
493         data_volume_usage_t *last;
494         data_volume_usage_t *new;
495
496         int ignore_capacity = 0;
497         int ignore_snapshot = 0;
498
499         if ((cvu == NULL) || (name == NULL))
500                 return (NULL);
501
502         last = cvu->volumes;
503         while (last != NULL)
504         {
505                 if (strcmp (last->name, name) == 0)
506                         return (last);
507
508                 if (last->next == NULL)
509                         break;
510
511                 last = last->next;
512         }
513
514         /* Check the ignorelists. If *both* tell us to ignore a volume, return NULL. */
515         ignore_capacity = ignorelist_match (cvu->il_capacity, name);
516         ignore_snapshot = ignorelist_match (cvu->il_snapshot, name);
517         if ((ignore_capacity != 0) && (ignore_snapshot != 0))
518                 return (NULL);
519
520         /* Not found: allocate. */
521         new = malloc (sizeof (*new));
522         if (new == NULL)
523                 return (NULL);
524         memset (new, 0, sizeof (*new));
525         new->next = NULL;
526
527         new->name = strdup (name);
528         if (new->name == NULL)
529         {
530                 sfree (new);
531                 return (NULL);
532         }
533
534         if (ignore_capacity == 0)
535                 new->flags |= CFG_VOLUME_USAGE_DF;
536         if (ignore_snapshot == 0)
537                 new->flags |= CFG_VOLUME_USAGE_SNAP;
538
539         /* Add to end of list. */
540         if (last == NULL)
541                 cvu->volumes = new;
542         else
543                 last->next = new;
544
545         return (new);
546 } /* }}} data_volume_usage_t *get_volume_usage */
547
548 static data_volume_perf_t *get_volume_perf (cfg_volume_perf_t *cvp, /* {{{ */
549                 const char *name)
550 {
551         data_volume_perf_t *last;
552         data_volume_perf_t *new;
553
554         int ignore_octets = 0;
555         int ignore_operations = 0;
556         int ignore_latency = 0;
557
558         if ((cvp == NULL) || (name == NULL))
559                 return (NULL);
560
561         last = cvp->volumes;
562         while (last != NULL)
563         {
564                 if (strcmp (last->name, name) == 0)
565                         return (last);
566
567                 if (last->next == NULL)
568                         break;
569
570                 last = last->next;
571         }
572
573         /* Check the ignorelists. If *all three* tell us to ignore a volume, return
574          * NULL. */
575         ignore_octets = ignorelist_match (cvp->il_octets, name);
576         ignore_operations = ignorelist_match (cvp->il_operations, name);
577         ignore_latency = ignorelist_match (cvp->il_latency, name);
578         if ((ignore_octets != 0) || (ignore_operations != 0)
579                         || (ignore_latency != 0))
580                 return (NULL);
581
582         /* Not found: allocate. */
583         new = malloc (sizeof (*new));
584         if (new == NULL)
585                 return (NULL);
586         memset (new, 0, sizeof (*new));
587         new->next = NULL;
588
589         new->name = strdup (name);
590         if (new->name == NULL)
591         {
592                 sfree (new);
593                 return (NULL);
594         }
595
596         if (ignore_octets == 0)
597                 new->flags |= CFG_VOLUME_PERF_IO;
598         if (ignore_operations == 0)
599                 new->flags |= CFG_VOLUME_PERF_OPS;
600         if (ignore_latency == 0)
601                 new->flags |= CFG_VOLUME_PERF_LATENCY;
602
603         /* Add to end of list. */
604         if (last == NULL)
605                 cvp->volumes = new;
606         else
607                 last->next = new;
608
609         return (new);
610 } /* }}} data_volume_perf_t *get_volume_perf */
611
612 /*
613  * Various submit functions.
614  *
615  * They all eventually call "submit_values" which creates a value_list_t and
616  * dispatches it to the daemon.
617  */
618 static int submit_values (const char *host, /* {{{ */
619                 const char *plugin_inst,
620                 const char *type, const char *type_inst,
621                 value_t *values, int values_len,
622                 time_t timestamp)
623 {
624         value_list_t vl = VALUE_LIST_INIT;
625
626         vl.values = values;
627         vl.values_len = values_len;
628
629         if (timestamp > 0)
630                 vl.time = timestamp;
631
632         if (host != NULL)
633                 sstrncpy (vl.host, host, sizeof (vl.host));
634         else
635                 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
636         sstrncpy (vl.plugin, "netapp", sizeof (vl.plugin));
637         if (plugin_inst != NULL)
638                 sstrncpy (vl.plugin_instance, plugin_inst, sizeof (vl.plugin_instance));
639         sstrncpy (vl.type, type, sizeof (vl.type));
640         if (type_inst != NULL)
641                 sstrncpy (vl.type_instance, type_inst, sizeof (vl.type_instance));
642
643         return (plugin_dispatch_values (&vl));
644 } /* }}} int submit_uint64 */
645
646 static int submit_two_counters (const char *host, const char *plugin_inst, /* {{{ */
647                 const char *type, const char *type_inst, counter_t val0, counter_t val1,
648                 time_t timestamp)
649 {
650         value_t values[2];
651
652         values[0].counter = val0;
653         values[1].counter = val1;
654
655         return (submit_values (host, plugin_inst, type, type_inst,
656                                 values, 2, timestamp));
657 } /* }}} int submit_two_counters */
658
659 static int submit_counter (const char *host, const char *plugin_inst, /* {{{ */
660                 const char *type, const char *type_inst, counter_t counter, time_t timestamp)
661 {
662         value_t v;
663
664         v.counter = counter;
665
666         return (submit_values (host, plugin_inst, type, type_inst,
667                                 &v, 1, timestamp));
668 } /* }}} int submit_counter */
669
670 static int submit_two_gauge (const char *host, const char *plugin_inst, /* {{{ */
671                 const char *type, const char *type_inst, gauge_t val0, gauge_t val1,
672                 time_t timestamp)
673 {
674         value_t values[2];
675
676         values[0].gauge = val0;
677         values[1].gauge = val1;
678
679         return (submit_values (host, plugin_inst, type, type_inst,
680                                 values, 2, timestamp));
681 } /* }}} int submit_two_gauge */
682
683 static int submit_double (const char *host, const char *plugin_inst, /* {{{ */
684                 const char *type, const char *type_inst, double d, time_t timestamp)
685 {
686         value_t v;
687
688         v.gauge = (gauge_t) d;
689
690         return (submit_values (host, plugin_inst, type, type_inst,
691                                 &v, 1, timestamp));
692 } /* }}} int submit_uint64 */
693
694 /* Calculate hit ratio from old and new counters and submit the resulting
695  * percentage. Used by "submit_wafl_data". */
696 static int submit_cache_ratio (const char *host, /* {{{ */
697                 const char *plugin_inst,
698                 const char *type_inst,
699                 uint64_t new_hits,
700                 uint64_t new_misses,
701                 uint64_t old_hits,
702                 uint64_t old_misses,
703                 time_t timestamp)
704 {
705         value_t v;
706
707         if ((new_hits >= old_hits) && (new_misses >= old_misses)) {
708                 uint64_t hits;
709                 uint64_t misses;
710
711                 hits = new_hits - old_hits;
712                 misses = new_misses - old_misses;
713
714                 v.gauge = 100.0 * ((gauge_t) hits) / ((gauge_t) (hits + misses));
715         } else {
716                 v.gauge = NAN;
717         }
718
719         return (submit_values (host, plugin_inst, "cache_ratio", type_inst,
720                                 &v, 1, timestamp));
721 } /* }}} int submit_cache_ratio */
722
723 /* Submits all the caches used by WAFL. Uses "submit_cache_ratio". */
724 static int submit_wafl_data (const char *hostname, const char *instance, /* {{{ */
725                 cfg_wafl_t *old_data, const cfg_wafl_t *new_data)
726 {
727         /* Submit requested counters */
728         if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_NAME_CACHE | HAVE_WAFL_NAME_CACHE)
729                         && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_NAME_CACHE))
730                 submit_cache_ratio (hostname, instance, "name_cache_hit",
731                                 new_data->name_cache_hit, new_data->name_cache_miss,
732                                 old_data->name_cache_hit, old_data->name_cache_miss,
733                                 new_data->timestamp);
734
735         if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_DIR_CACHE | HAVE_WAFL_FIND_DIR)
736                         && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_FIND_DIR))
737                 submit_cache_ratio (hostname, instance, "find_dir_hit",
738                                 new_data->find_dir_hit, new_data->find_dir_miss,
739                                 old_data->find_dir_hit, old_data->find_dir_miss,
740                                 new_data->timestamp);
741
742         if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_BUF_CACHE | HAVE_WAFL_BUF_HASH)
743                         && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_BUF_HASH))
744                 submit_cache_ratio (hostname, instance, "buf_hash_hit",
745                                 new_data->buf_hash_hit, new_data->buf_hash_miss,
746                                 old_data->buf_hash_hit, old_data->buf_hash_miss,
747                                 new_data->timestamp);
748
749         if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_INODE_CACHE | HAVE_WAFL_INODE_CACHE)
750                         && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_INODE_CACHE))
751                 submit_cache_ratio (hostname, instance, "inode_cache_hit",
752                                 new_data->inode_cache_hit, new_data->inode_cache_miss,
753                                 old_data->inode_cache_hit, old_data->inode_cache_miss,
754                                 new_data->timestamp);
755
756         /* Clear old HAVE_* flags */
757         old_data->flags &= ~HAVE_WAFL_ALL;
758
759         /* Copy all counters */
760         old_data->timestamp        = new_data->timestamp;
761         old_data->name_cache_hit   = new_data->name_cache_hit;
762         old_data->name_cache_miss  = new_data->name_cache_miss;
763         old_data->find_dir_hit     = new_data->find_dir_hit;
764         old_data->find_dir_miss    = new_data->find_dir_miss;
765         old_data->buf_hash_hit     = new_data->buf_hash_hit;
766         old_data->buf_hash_miss    = new_data->buf_hash_miss;
767         old_data->inode_cache_hit  = new_data->inode_cache_hit;
768         old_data->inode_cache_miss = new_data->inode_cache_miss;
769
770         /* Copy HAVE_* flags */
771         old_data->flags |= (new_data->flags & HAVE_WAFL_ALL);
772
773         return (0);
774 } /* }}} int submit_wafl_data */
775
776 /* Submits volume performance data to the daemon, taking care to honor and
777  * update flags appropriately. */
778 static int submit_volume_perf_data (const char *hostname, /* {{{ */
779                 data_volume_perf_t *old_data,
780                 const data_volume_perf_t *new_data)
781 {
782         /* Check for and submit disk-octet values */
783         if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_IO)
784                         && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_BYTES_READ | HAVE_VOLUME_PERF_BYTES_WRITE))
785         {
786                 submit_two_counters (hostname, old_data->name, "disk_octets", /* type instance = */ NULL,
787                                 (counter_t) new_data->read_bytes, (counter_t) new_data->write_bytes, new_data->timestamp);
788         }
789
790         /* Check for and submit disk-operations values */
791         if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_OPS)
792                         && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE))
793         {
794                 submit_two_counters (hostname, old_data->name, "disk_ops", /* type instance = */ NULL,
795                                 (counter_t) new_data->read_ops, (counter_t) new_data->write_ops, new_data->timestamp);
796         }
797
798         /* Check for, calculate and submit disk-latency values */
799         if (HAS_ALL_FLAGS (old_data->flags, CFG_VOLUME_PERF_LATENCY
800                                 | HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE
801                                 | HAVE_VOLUME_PERF_LATENCY_READ | HAVE_VOLUME_PERF_LATENCY_WRITE)
802                         && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE
803                                 | HAVE_VOLUME_PERF_LATENCY_READ | HAVE_VOLUME_PERF_LATENCY_WRITE))
804         {
805                 gauge_t latency_per_op_read;
806                 gauge_t latency_per_op_write;
807
808                 latency_per_op_read = NAN;
809                 latency_per_op_write = NAN;
810
811                 /* Check if a counter wrapped around. */
812                 if ((new_data->read_ops > old_data->read_ops)
813                                 && (new_data->read_latency > old_data->read_latency))
814                 {
815                         uint64_t diff_ops_read;
816                         uint64_t diff_latency_read;
817
818                         diff_ops_read = new_data->read_ops - old_data->read_ops;
819                         diff_latency_read = new_data->read_latency - old_data->read_latency;
820
821                         if (diff_ops_read > 0)
822                                 latency_per_op_read = ((gauge_t) diff_latency_read) / ((gauge_t) diff_ops_read);
823                 }
824
825                 if ((new_data->write_ops > old_data->write_ops)
826                                 && (new_data->write_latency > old_data->write_latency))
827                 {
828                         uint64_t diff_ops_write;
829                         uint64_t diff_latency_write;
830
831                         diff_ops_write = new_data->write_ops - old_data->write_ops;
832                         diff_latency_write = new_data->write_latency - old_data->write_latency;
833
834                         if (diff_ops_write > 0)
835                                 latency_per_op_write = ((gauge_t) diff_latency_write) / ((gauge_t) diff_ops_write);
836                 }
837
838                 submit_two_gauge (hostname, old_data->name, "disk_latency", /* type instance = */ NULL,
839                                 latency_per_op_read, latency_per_op_write, new_data->timestamp);
840         }
841
842         /* Clear all HAVE_* flags. */
843         old_data->flags &= ~HAVE_VOLUME_PERF_ALL;
844
845         /* Copy all counters */
846         old_data->timestamp = new_data->timestamp;
847         old_data->read_bytes = new_data->read_bytes;
848         old_data->write_bytes = new_data->write_bytes;
849         old_data->read_ops = new_data->read_ops;
850         old_data->write_ops = new_data->write_ops;
851         old_data->read_latency = new_data->read_latency;
852         old_data->write_latency = new_data->write_latency;
853
854         /* Copy the HAVE_* flags */
855         old_data->flags |= (new_data->flags & HAVE_VOLUME_PERF_ALL);
856
857         return (0);
858 } /* }}} int submit_volume_perf_data */
859
860 /* 
861  * Query functions
862  *
863  * These functions are called with appropriate data returned by the libnetapp
864  * interface which is parsed and submitted with the above functions.
865  */
866 /* Data corresponding to <WAFL /> */
867 static int cna_handle_wafl_data (const char *hostname, cfg_wafl_t *cfg_wafl, /* {{{ */
868                 na_elem_t *data)
869 {
870         cfg_wafl_t perf_data;
871         const char *plugin_inst;
872
873         na_elem_t *instances;
874         na_elem_t *counter;
875         na_elem_iter_t counter_iter;
876
877         memset (&perf_data, 0, sizeof (perf_data));
878         
879         perf_data.timestamp = (time_t) na_child_get_uint64 (data, "timestamp", 0);
880
881         instances = na_elem_child(na_elem_child (data, "instances"), "instance-data");
882         if (instances == NULL)
883         {
884                 ERROR ("netapp plugin: cna_handle_wafl_data: "
885                                 "na_elem_child (\"instances\") failed.");
886                 return (-1);
887         }
888
889         plugin_inst = na_child_get_string(instances, "name");
890         if (plugin_inst == NULL)
891         {
892                 ERROR ("netapp plugin: cna_handle_wafl_data: "
893                                 "na_child_get_string (\"name\") failed.");
894                 return (-1);
895         }
896
897         /* Iterate over all counters */
898         counter_iter = na_child_iterator (na_elem_child (instances, "counters"));
899         for (counter = na_iterator_next (&counter_iter);
900                         counter != NULL;
901                         counter = na_iterator_next (&counter_iter))
902         {
903                 const char *name;
904                 uint64_t value;
905
906                 name = na_child_get_string(counter, "name");
907                 if (name == NULL)
908                         continue;
909
910                 value = na_child_get_uint64(counter, "value", UINT64_MAX);
911                 if (value == UINT64_MAX)
912                         continue;
913
914                 if (!strcmp(name, "name_cache_hit")) {
915                         perf_data.name_cache_hit = value;
916                         perf_data.flags |= HAVE_WAFL_NAME_CACHE_HIT;
917                 } else if (!strcmp(name, "name_cache_miss")) {
918                         perf_data.name_cache_miss = value;
919                         perf_data.flags |= HAVE_WAFL_NAME_CACHE_MISS;
920                 } else if (!strcmp(name, "find_dir_hit")) {
921                         perf_data.find_dir_hit = value;
922                         perf_data.flags |= HAVE_WAFL_FIND_DIR_HIT;
923                 } else if (!strcmp(name, "find_dir_miss")) {
924                         perf_data.find_dir_miss = value;
925                         perf_data.flags |= HAVE_WAFL_FIND_DIR_MISS;
926                 } else if (!strcmp(name, "buf_hash_hit")) {
927                         perf_data.buf_hash_hit = value;
928                         perf_data.flags |= HAVE_WAFL_BUF_HASH_HIT;
929                 } else if (!strcmp(name, "buf_hash_miss")) {
930                         perf_data.buf_hash_miss = value;
931                         perf_data.flags |= HAVE_WAFL_BUF_HASH_MISS;
932                 } else if (!strcmp(name, "inode_cache_hit")) {
933                         perf_data.inode_cache_hit = value;
934                         perf_data.flags |= HAVE_WAFL_INODE_CACHE_HIT;
935                 } else if (!strcmp(name, "inode_cache_miss")) {
936                         perf_data.inode_cache_miss = value;
937                         perf_data.flags |= HAVE_WAFL_INODE_CACHE_MISS;
938                 } else {
939                         DEBUG("netapp plugin: cna_handle_wafl_data: "
940                                         "Found unexpected child: %s", name);
941                 }
942         }
943
944         return (submit_wafl_data (hostname, plugin_inst, cfg_wafl, &perf_data));
945 } /* }}} void cna_handle_wafl_data */
946
947 static int cna_setup_wafl (cfg_wafl_t *cw) /* {{{ */
948 {
949         na_elem_t *e;
950
951         if (cw == NULL)
952                 return (EINVAL);
953
954         if (cw->query != NULL)
955                 return (0);
956
957         cw->query = na_elem_new("perf-object-get-instances");
958         if (cw->query == NULL)
959         {
960                 ERROR ("netapp plugin: na_elem_new failed.");
961                 return (-1);
962         }
963         na_child_add_string (cw->query, "objectname", "wafl");
964
965         e = na_elem_new("counters");
966         if (e == NULL)
967         {
968                 na_elem_free (cw->query);
969                 cw->query = NULL;
970                 ERROR ("netapp plugin: na_elem_new failed.");
971                 return (-1);
972         }
973         na_child_add_string(e, "foo", "name_cache_hit");
974         na_child_add_string(e, "foo", "name_cache_miss");
975         na_child_add_string(e, "foo", "find_dir_hit");
976         na_child_add_string(e, "foo", "find_dir_miss");
977         na_child_add_string(e, "foo", "buf_hash_hit");
978         na_child_add_string(e, "foo", "buf_hash_miss");
979         na_child_add_string(e, "foo", "inode_cache_hit");
980         na_child_add_string(e, "foo", "inode_cache_miss");
981
982         na_child_add(cw->query, e);
983
984         return (0);
985 } /* }}} int cna_setup_wafl */
986
987 static int cna_query_wafl (host_config_t *host) /* {{{ */
988 {
989         na_elem_t *data;
990         int status;
991         time_t now;
992
993         if (host == NULL)
994                 return (EINVAL);
995
996         /* If WAFL was not configured, return without doing anything. */
997         if (host->cfg_wafl == NULL)
998                 return (0);
999
1000         now = time (NULL);
1001         if ((host->cfg_wafl->interval.interval + host->cfg_wafl->interval.last_read) > now)
1002                 return (0);
1003
1004         status = cna_setup_wafl (host->cfg_wafl);
1005         if (status != 0)
1006                 return (status);
1007         assert (host->cfg_wafl->query != NULL);
1008
1009         data = na_server_invoke_elem(host->srv, host->cfg_wafl->query);
1010         if (na_results_status (data) != NA_OK)
1011         {
1012                 ERROR ("netapp plugin: cna_query_wafl: na_server_invoke_elem failed: %s",
1013                                 na_results_reason (data));
1014                 na_elem_free (data);
1015                 return (-1);
1016         }
1017
1018         status = cna_handle_wafl_data (host->name, host->cfg_wafl, data);
1019
1020         if (status == 0)
1021                 host->cfg_wafl->interval.last_read = now;
1022
1023         na_elem_free (data);
1024         return (status);
1025 } /* }}} int cna_query_wafl */
1026
1027 /* Data corresponding to <Disks /> */
1028 static int cna_handle_disk_data (const char *hostname, /* {{{ */
1029                 cfg_disk_t *cfg_disk, na_elem_t *data)
1030 {
1031         time_t timestamp;
1032         na_elem_t *instances;
1033         na_elem_t *instance;
1034         na_elem_iter_t instance_iter;
1035         disk_t *worst_disk = NULL;
1036
1037         if ((cfg_disk == NULL) || (data == NULL))
1038                 return (EINVAL);
1039         
1040         timestamp = (time_t) na_child_get_uint64(data, "timestamp", 0);
1041
1042         instances = na_elem_child (data, "instances");
1043         if (instances == NULL)
1044         {
1045                 ERROR ("netapp plugin: cna_handle_disk_data: "
1046                                 "na_elem_child (\"instances\") failed.");
1047                 return (-1);
1048         }
1049
1050         /* Iterate over all children */
1051         instance_iter = na_child_iterator (instances);
1052         for (instance = na_iterator_next (&instance_iter);
1053                         instance != NULL;
1054                         instance = na_iterator_next(&instance_iter))
1055         {
1056                 disk_t *old_data;
1057                 disk_t  new_data;
1058
1059                 na_elem_iter_t counter_iterator;
1060                 na_elem_t *counter;
1061
1062                 memset (&new_data, 0, sizeof (new_data));
1063                 new_data.timestamp = timestamp;
1064                 new_data.disk_busy_percent = NAN;
1065
1066                 old_data = get_disk(cfg_disk, na_child_get_string (instance, "name"));
1067                 if (old_data == NULL)
1068                         continue;
1069
1070                 /* Look for the "disk_busy" and "base_for_disk_busy" counters */
1071                 counter_iterator = na_child_iterator(na_elem_child(instance, "counters"));
1072                 for (counter = na_iterator_next(&counter_iterator);
1073                                 counter != NULL;
1074                                 counter = na_iterator_next(&counter_iterator))
1075                 {
1076                         const char *name;
1077                         uint64_t value;
1078
1079                         name = na_child_get_string(counter, "name");
1080                         if (name == NULL)
1081                                 continue;
1082
1083                         value = na_child_get_uint64(counter, "value", UINT64_MAX);
1084                         if (value == UINT64_MAX)
1085                                 continue;
1086
1087                         if (strcmp(name, "disk_busy") == 0)
1088                         {
1089                                 new_data.disk_busy = value;
1090                                 new_data.flags |= HAVE_DISK_BUSY;
1091                         }
1092                         else if (strcmp(name, "base_for_disk_busy") == 0)
1093                         {
1094                                 new_data.base_for_disk_busy = value;
1095                                 new_data.flags |= HAVE_DISK_BASE;
1096                         }
1097                         else
1098                         {
1099                                 DEBUG ("netapp plugin: cna_handle_disk_data: "
1100                                                 "Counter not handled: %s = %"PRIu64,
1101                                                 name, value);
1102                         }
1103                 }
1104
1105                 /* If all required counters are available and did not just wrap around,
1106                  * calculate the busy percentage. Otherwise, the value is initialized to
1107                  * NAN at the top of the for-loop. */
1108                 if (HAS_ALL_FLAGS (old_data->flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
1109                                 && HAS_ALL_FLAGS (new_data.flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
1110                                 && (new_data.disk_busy >= old_data->disk_busy)
1111                                 && (new_data.base_for_disk_busy > old_data->base_for_disk_busy))
1112                 {
1113                         uint64_t busy_diff;
1114                         uint64_t base_diff;
1115
1116                         busy_diff = new_data.disk_busy - old_data->disk_busy;
1117                         base_diff = new_data.base_for_disk_busy - old_data->base_for_disk_busy;
1118
1119                         new_data.disk_busy_percent = 100.0
1120                                 * ((gauge_t) busy_diff) / ((gauge_t) base_diff);
1121                 }
1122
1123                 /* Clear HAVE_* flags */
1124                 old_data->flags &= ~HAVE_DISK_ALL;
1125
1126                 /* Copy data */
1127                 old_data->timestamp = new_data.timestamp;
1128                 old_data->disk_busy = new_data.disk_busy;
1129                 old_data->base_for_disk_busy = new_data.base_for_disk_busy;
1130                 old_data->disk_busy_percent = new_data.disk_busy_percent;
1131
1132                 /* Copy flags */
1133                 old_data->flags |= (new_data.flags & HAVE_DISK_ALL);
1134
1135                 if ((worst_disk == NULL)
1136                                 || (worst_disk->disk_busy_percent < old_data->disk_busy_percent))
1137                         worst_disk = old_data;
1138         } /* for (all disks) */
1139
1140         if ((cfg_disk->flags & CFG_DISK_BUSIEST) && (worst_disk != NULL))
1141                 submit_double (hostname, "system", "percent", "disk_busy",
1142                                 worst_disk->disk_busy_percent, timestamp);
1143
1144         return (0);
1145 } /* }}} int cna_handle_disk_data */
1146
1147 static int cna_setup_disk (cfg_disk_t *cd) /* {{{ */
1148 {
1149         na_elem_t *e;
1150
1151         if (cd == NULL)
1152                 return (EINVAL);
1153
1154         if (cd->query != NULL)
1155                 return (0);
1156
1157         cd->query = na_elem_new ("perf-object-get-instances");
1158         if (cd->query == NULL)
1159         {
1160                 ERROR ("netapp plugin: na_elem_new failed.");
1161                 return (-1);
1162         }
1163         na_child_add_string (cd->query, "objectname", "disk");
1164
1165         e = na_elem_new("counters");
1166         if (e == NULL)
1167         {
1168                 na_elem_free (cd->query);
1169                 cd->query = NULL;
1170                 ERROR ("netapp plugin: na_elem_new failed.");
1171                 return (-1);
1172         }
1173         na_child_add_string(e, "foo", "disk_busy");
1174         na_child_add_string(e, "foo", "base_for_disk_busy");
1175         na_child_add(cd->query, e);
1176
1177         return (0);
1178 } /* }}} int cna_setup_disk */
1179
1180 static int cna_query_disk (host_config_t *host) /* {{{ */
1181 {
1182         na_elem_t *data;
1183         int status;
1184         time_t now;
1185
1186         if (host == NULL)
1187                 return (EINVAL);
1188
1189         /* If the user did not configure disk statistics, return without doing
1190          * anything. */
1191         if (host->cfg_disk == NULL)
1192                 return (0);
1193
1194         now = time (NULL);
1195         if ((host->cfg_disk->interval.interval + host->cfg_disk->interval.last_read) > now)
1196                 return (0);
1197
1198         status = cna_setup_disk (host->cfg_disk);
1199         if (status != 0)
1200                 return (status);
1201         assert (host->cfg_disk->query != NULL);
1202
1203         data = na_server_invoke_elem(host->srv, host->cfg_disk->query);
1204         if (na_results_status (data) != NA_OK)
1205         {
1206                 ERROR ("netapp plugin: cna_query_disk: na_server_invoke_elem failed: %s",
1207                                 na_results_reason (data));
1208                 na_elem_free (data);
1209                 return (-1);
1210         }
1211
1212         status = cna_handle_disk_data (host->name, host->cfg_disk, data);
1213
1214         if (status == 0)
1215                 host->cfg_disk->interval.last_read = now;
1216
1217         na_elem_free (data);
1218         return (status);
1219 } /* }}} int cna_query_disk */
1220
1221 /* Data corresponding to <VolumePerf /> */
1222 static int cna_handle_volume_perf_data (const char *hostname, /* {{{ */
1223                 cfg_volume_perf_t *cvp, na_elem_t *data)
1224 {
1225         time_t timestamp;
1226         na_elem_t *elem_instances;
1227         na_elem_iter_t iter_instances;
1228         na_elem_t *elem_instance;
1229         
1230         timestamp = (time_t) na_child_get_uint64(data, "timestamp", 0);
1231
1232         elem_instances = na_elem_child(data, "instances");
1233         if (elem_instances == NULL)
1234         {
1235                 ERROR ("netapp plugin: handle_volume_perf_data: "
1236                                 "na_elem_child (\"instances\") failed.");
1237                 return (-1);
1238         }
1239
1240         iter_instances = na_child_iterator (elem_instances);
1241         for (elem_instance = na_iterator_next(&iter_instances);
1242                         elem_instance != NULL;
1243                         elem_instance = na_iterator_next(&iter_instances))
1244         {
1245                 const char *name;
1246
1247                 data_volume_perf_t perf_data;
1248                 data_volume_perf_t *v;
1249
1250                 na_elem_t *elem_counters;
1251                 na_elem_iter_t iter_counters;
1252                 na_elem_t *elem_counter;
1253
1254                 memset (&perf_data, 0, sizeof (perf_data));
1255                 perf_data.timestamp = timestamp;
1256
1257                 name = na_child_get_string (elem_instance, "name");
1258                 if (name == NULL)
1259                         continue;
1260
1261                 /* get_volume_perf may return NULL if this volume is to be ignored. */
1262                 v = get_volume_perf (cvp, perf_data.name);
1263                 if (v == NULL)
1264                         continue;
1265
1266                 elem_counters = na_elem_child (elem_instance, "counters");
1267                 if (elem_counters == NULL)
1268                         continue;
1269
1270                 iter_counters = na_child_iterator (elem_counters);
1271                 for (elem_counter = na_iterator_next(&iter_counters);
1272                                 elem_counter != NULL;
1273                                 elem_counter = na_iterator_next(&iter_counters))
1274                 {
1275                         const char *name;
1276                         uint64_t value;
1277
1278                         name = na_child_get_string (elem_counter, "name");
1279                         if (name == NULL)
1280                                 continue;
1281
1282                         value = na_child_get_uint64 (elem_counter, "value", UINT64_MAX);
1283                         if (value == UINT64_MAX)
1284                                 continue;
1285
1286                         if (!strcmp(name, "read_data")) {
1287                                 perf_data.read_bytes = value;
1288                                 perf_data.flags |= HAVE_VOLUME_PERF_BYTES_READ;
1289                         } else if (!strcmp(name, "write_data")) {
1290                                 perf_data.write_bytes = value;
1291                                 perf_data.flags |= HAVE_VOLUME_PERF_BYTES_WRITE;
1292                         } else if (!strcmp(name, "read_ops")) {
1293                                 perf_data.read_ops = value;
1294                                 perf_data.flags |= HAVE_VOLUME_PERF_OPS_READ;
1295                         } else if (!strcmp(name, "write_ops")) {
1296                                 perf_data.write_ops = value;
1297                                 perf_data.flags |= HAVE_VOLUME_PERF_OPS_WRITE;
1298                         } else if (!strcmp(name, "read_latency")) {
1299                                 perf_data.read_latency = value;
1300                                 perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_READ;
1301                         } else if (!strcmp(name, "write_latency")) {
1302                                 perf_data.write_latency = value;
1303                                 perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_WRITE;
1304                         }
1305                 } /* for (elem_counter) */
1306
1307                 submit_volume_perf_data (hostname, v, &perf_data);
1308         } /* for (volume) */
1309
1310         return (0);
1311 } /* }}} int cna_handle_volume_perf_data */
1312
1313 static int cna_setup_volume_perf (cfg_volume_perf_t *cd) /* {{{ */
1314 {
1315         na_elem_t *e;
1316
1317         if (cd == NULL)
1318                 return (EINVAL);
1319
1320         if (cd->query != NULL)
1321                 return (0);
1322
1323         cd->query = na_elem_new ("perf-object-get-instances");
1324         if (cd->query == NULL)
1325         {
1326                 ERROR ("netapp plugin: na_elem_new failed.");
1327                 return (-1);
1328         }
1329         na_child_add_string (cd->query, "objectname", "volume");
1330
1331         e = na_elem_new("counters");
1332         if (e == NULL)
1333         {
1334                 na_elem_free (cd->query);
1335                 cd->query = NULL;
1336                 ERROR ("netapp plugin: na_elem_new failed.");
1337                 return (-1);
1338         }
1339         /* "foo" means: This string has to be here but the content doesn't matter. */
1340         na_child_add_string(e, "foo", "read_ops");
1341         na_child_add_string(e, "foo", "write_ops");
1342         na_child_add_string(e, "foo", "read_data");
1343         na_child_add_string(e, "foo", "write_data");
1344         na_child_add_string(e, "foo", "read_latency");
1345         na_child_add_string(e, "foo", "write_latency");
1346         na_child_add(cd->query, e);
1347
1348         return (0);
1349 } /* }}} int cna_setup_volume_perf */
1350
1351 static int cna_query_volume_perf (host_config_t *host) /* {{{ */
1352 {
1353         na_elem_t *data;
1354         int status;
1355         time_t now;
1356
1357         if (host == NULL)
1358                 return (EINVAL);
1359
1360         /* If the user did not configure volume performance statistics, return
1361          * without doing anything. */
1362         if (host->cfg_volume_perf == NULL)
1363                 return (0);
1364
1365         now = time (NULL);
1366         if ((host->cfg_volume_perf->interval.interval + host->cfg_volume_perf->interval.last_read) > now)
1367                 return (0);
1368
1369         status = cna_setup_volume_perf (host->cfg_volume_perf);
1370         if (status != 0)
1371                 return (status);
1372         assert (host->cfg_volume_perf->query != NULL);
1373
1374         data = na_server_invoke_elem (host->srv, host->cfg_volume_perf->query);
1375         if (na_results_status (data) != NA_OK)
1376         {
1377                 ERROR ("netapp plugin: cna_query_volume_perf: na_server_invoke_elem failed: %s",
1378                                 na_results_reason (data));
1379                 na_elem_free (data);
1380                 return (-1);
1381         }
1382
1383         status = cna_handle_volume_perf_data (host->name, host->cfg_volume_perf, data);
1384
1385         if (status == 0)
1386                 host->cfg_volume_perf->interval.last_read = now;
1387
1388         na_elem_free (data);
1389         return (status);
1390 } /* }}} int cna_query_volume_perf */
1391
1392 /* Data corresponding to <VolumeUsage /> */
1393 static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
1394                 cfg_volume_usage_t *cfg_volume)
1395 {
1396         data_volume_usage_t *v;
1397
1398         for (v = cfg_volume->volumes; v != NULL; v = v->next)
1399         {
1400                 if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_FREE))
1401                         submit_double (hostname, /* plugin instance = */ v->name,
1402                                         "df_complex", "free",
1403                                         (double) v->norm_free, /* timestamp = */ 0);
1404
1405                 if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED))
1406                         submit_double (hostname, /* plugin instance = */ v->name,
1407                                         "df_complex", "used",
1408                                         (double) v->norm_used, /* timestamp = */ 0);
1409
1410                 if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_RSVD))
1411                         submit_double (hostname, /* plugin instance = */ v->name,
1412                                         "df_complex", "snap_reserved",
1413                                         (double) v->snap_reserved, /* timestamp = */ 0);
1414
1415                 if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED))
1416                         submit_double (hostname, /* plugin instance = */ v->name,
1417                                         "df_complex", "snap_used",
1418                                         (double) v->snap_used, /* timestamp = */ 0);
1419
1420                 if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SIS_SAVED))
1421                         submit_double (hostname, /* plugin instance = */ v->name,
1422                                         "df_complex", "sis_saved",
1423                                         (double) v->sis_saved, /* timestamp = */ 0);
1424
1425                 /* Clear all the HAVE_* flags */
1426                 v->flags &= ~HAVE_VOLUME_USAGE_ALL;
1427         } /* for (v = cfg_volume->volumes) */
1428
1429         return (0);
1430 } /* }}} int cna_submit_volume_usage_data */
1431
1432 static int cna_handle_volume_usage_data (const char *hostname, /* {{{ */
1433                 cfg_volume_usage_t *cfg_volume, na_elem_t *data)
1434 {
1435         na_elem_t *elem_volume;
1436         na_elem_t *elem_volumes;
1437         na_elem_iter_t iter_volume;
1438
1439         elem_volumes = na_elem_child (data, "volumes");
1440         if (elem_volumes == NULL)
1441         {
1442                 ERROR ("netapp plugin: cna_handle_volume_usage_data: "
1443                                 "na_elem_child (\"volumes\") failed.");
1444                 return (-1);
1445         }
1446
1447         iter_volume = na_child_iterator (elem_volumes);
1448         for (elem_volume = na_iterator_next (&iter_volume);
1449                         elem_volume != NULL;
1450                         elem_volume = na_iterator_next (&iter_volume))
1451         {
1452                 const char *volume_name;
1453
1454                 data_volume_usage_t *v;
1455                 uint64_t value;
1456
1457                 na_elem_t *sis;
1458                 const char *sis_state;
1459                 uint64_t sis_saved_reported;
1460
1461                 volume_name = na_child_get_string (elem_volume, "name");
1462                 if (volume_name == NULL)
1463                         continue;
1464
1465                 /* get_volume_usage may return NULL if the volume is to be ignored. */
1466                 v = get_volume_usage (cfg_volume, volume_name);
1467                 if (v == NULL)
1468                         continue;
1469
1470                 if ((v->flags & CFG_VOLUME_USAGE_DF) == 0)
1471                         continue;
1472
1473                 /* 2^4 exa-bytes? This will take a while ;) */
1474                 value = na_child_get_uint64(elem_volume, "size-available", UINT64_MAX);
1475                 if (value != UINT64_MAX) {
1476                         v->norm_free = value;
1477                         v->flags |= HAVE_VOLUME_USAGE_NORM_FREE;
1478                 }
1479
1480                 value = na_child_get_uint64(elem_volume, "size-used", UINT64_MAX);
1481                 if (value != UINT64_MAX) {
1482                         v->norm_used = value;
1483                         v->flags |= HAVE_VOLUME_USAGE_NORM_USED;
1484                 }
1485
1486                 value = na_child_get_uint64(elem_volume, "snapshot-blocks-reserved", UINT64_MAX);
1487                 if (value != UINT64_MAX) {
1488                         /* 1 block == 1024 bytes  as per API docs */
1489                         v->norm_used = 1024 * value;
1490                         v->flags |= HAVE_VOLUME_USAGE_SNAP_RSVD;
1491                 }
1492
1493                 sis = na_elem_child(elem_volume, "sis");
1494                 if (sis == NULL)
1495                         continue;
1496
1497                 sis_state = na_child_get_string(sis, "state");
1498                 if (sis_state == NULL)
1499                         continue;
1500
1501                 /* If SIS is not enabled, set the HAVE_VOLUME_USAGE_SIS_SAVED flag and set
1502                  * sis_saved to UINT64_MAX to signal this condition to the submit function. */
1503                 if (strcmp ("enabled", sis_state) != 0) {
1504                         v->sis_saved = UINT64_MAX;
1505                         v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
1506                         continue;
1507                 }
1508
1509                 sis_saved_reported = na_child_get_uint64(sis, "size-saved", UINT64_MAX);
1510                 if (sis_saved_reported == UINT64_MAX)
1511                         continue;
1512
1513                 /* size-saved is actually a 32 bit number, so ... time for some guesswork. */
1514                 if ((sis_saved_reported >> 32) != 0) {
1515                         /* In case they ever fix this bug. */
1516                         v->sis_saved = sis_saved_reported;
1517                         v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
1518                 } else { /* really hacky work-around code. {{{ */
1519                         uint64_t sis_saved_percent;
1520                         uint64_t sis_saved_guess;
1521                         uint64_t overflow_guess;
1522                         uint64_t guess1, guess2, guess3;
1523
1524                         /* Check if we have v->norm_used. Without it, we cannot calculate
1525                          * sis_saved_guess. */
1526                         if ((v->flags & HAVE_VOLUME_USAGE_NORM_USED) == 0)
1527                                 continue;
1528
1529                         sis_saved_percent = na_child_get_uint64(sis, "percentage-saved", UINT64_MAX);
1530                         if (sis_saved_percent > 100)
1531                                 continue;
1532
1533                         /* The "size-saved" value is a 32bit unsigned integer. This is a bug and
1534                          * will hopefully be fixed in later versions. To work around the bug, try
1535                          * to figure out how often the 32bit integer wrapped around by using the
1536                          * "percentage-saved" value. Because the percentage is in the range
1537                          * [0-100], this should work as long as the saved space does not exceed
1538                          * 400 GBytes. */
1539                         /* percentage-saved = size-saved / (size-saved + size-used) */
1540                         if (sis_saved_percent < 100)
1541                                 sis_saved_guess = v->norm_used * sis_saved_percent / (100 - sis_saved_percent);
1542                         else
1543                                 sis_saved_guess = v->norm_used;
1544
1545                         overflow_guess = sis_saved_guess >> 32;
1546                         guess1 = overflow_guess ? ((overflow_guess - 1) << 32) + sis_saved_reported : sis_saved_reported;
1547                         guess2 = (overflow_guess << 32) + sis_saved_reported;
1548                         guess3 = ((overflow_guess + 1) << 32) + sis_saved_reported;
1549
1550                         if (sis_saved_guess < guess2) {
1551                                 if ((sis_saved_guess - guess1) < (guess2 - sis_saved_guess))
1552                                         v->sis_saved = guess1;
1553                                 else
1554                                         v->sis_saved = guess2;
1555                         } else {
1556                                 if ((sis_saved_guess - guess2) < (guess3 - sis_saved_guess))
1557                                         v->sis_saved = guess2;
1558                                 else
1559                                         v->sis_saved = guess3;
1560                         }
1561                         v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
1562                 } /* }}} end of 32-bit workaround */
1563         } /* for (elem_volume) */
1564
1565         return (cna_submit_volume_usage_data (hostname, cfg_volume));
1566 } /* }}} int cna_handle_volume_usage_data */
1567
1568 static int cna_setup_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
1569 {
1570         if (cvu == NULL)
1571                 return (EINVAL);
1572
1573         if (cvu->query != NULL)
1574                 return (0);
1575
1576         cvu->query = na_elem_new ("volume-list-info");
1577         if (cvu->query == NULL)
1578         {
1579                 ERROR ("netapp plugin: na_elem_new failed.");
1580                 return (-1);
1581         }
1582
1583         /* TODO: cvu->snap_query = na_elem_new("snapshot-list-info"); */
1584
1585         return (0);
1586 } /* }}} int cna_setup_volume_usage */
1587
1588 static int cna_query_volume_usage (host_config_t *host) /* {{{ */
1589 {
1590         na_elem_t *data;
1591         int status;
1592         time_t now;
1593
1594         if (host == NULL)
1595                 return (EINVAL);
1596
1597         /* If the user did not configure volume_usage statistics, return without
1598          * doing anything. */
1599         if (host->cfg_volume_usage == NULL)
1600                 return (0);
1601
1602         now = time (NULL);
1603         if ((host->cfg_volume_usage->interval.interval + host->cfg_volume_usage->interval.last_read) > now)
1604                 return (0);
1605
1606         status = cna_setup_volume_usage (host->cfg_volume_usage);
1607         if (status != 0)
1608                 return (status);
1609         assert (host->cfg_volume_usage->query != NULL);
1610
1611         data = na_server_invoke_elem(host->srv, host->cfg_volume_usage->query);
1612         if (na_results_status (data) != NA_OK)
1613         {
1614                 ERROR ("netapp plugin: cna_query_volume_usage: na_server_invoke_elem failed: %s",
1615                                 na_results_reason (data));
1616                 na_elem_free (data);
1617                 return (-1);
1618         }
1619
1620         status = cna_handle_volume_usage_data (host->name, host->cfg_volume_usage, data);
1621
1622         if (status == 0)
1623                 host->cfg_volume_usage->interval.last_read = now;
1624
1625         na_elem_free (data);
1626         return (status);
1627 } /* }}} int cna_query_volume_usage */
1628
1629 /* Data corresponding to <System /> */
1630 static int cna_handle_system_data (const char *hostname, /* {{{ */
1631                 cfg_system_t *cfg_system, na_elem_t *data)
1632 {
1633         na_elem_t *instances;
1634         na_elem_t *counter;
1635         na_elem_iter_t counter_iter;
1636
1637         counter_t disk_read = 0, disk_written = 0;
1638         counter_t net_recv = 0, net_sent = 0;
1639         counter_t cpu_busy = 0, cpu_total = 0;
1640         uint32_t counter_flags = 0;
1641
1642         const char *instance;
1643         time_t timestamp;
1644         
1645         timestamp = (time_t) na_child_get_uint64 (data, "timestamp", 0);
1646
1647         instances = na_elem_child(na_elem_child (data, "instances"), "instance-data");
1648         if (instances == NULL)
1649         {
1650                 ERROR ("netapp plugin: cna_handle_system_data: "
1651                                 "na_elem_child (\"instances\") failed.");
1652                 return (-1);
1653         }
1654
1655         instance = na_child_get_string (instances, "name");
1656         if (instance == NULL)
1657         {
1658                 ERROR ("netapp plugin: cna_handle_system_data: "
1659                                 "na_child_get_string (\"name\") failed.");
1660                 return (-1);
1661         }
1662
1663         counter_iter = na_child_iterator (na_elem_child (instances, "counters"));
1664         for (counter = na_iterator_next (&counter_iter);
1665                         counter != NULL;
1666                         counter = na_iterator_next (&counter_iter))
1667         {
1668                 const char *name;
1669                 uint64_t value;
1670
1671                 name = na_child_get_string(counter, "name");
1672                 if (name == NULL)
1673                         continue;
1674
1675                 value = na_child_get_uint64(counter, "value", UINT64_MAX);
1676                 if (value == UINT64_MAX)
1677                         continue;
1678
1679                 if (!strcmp(name, "disk_data_read")) {
1680                         disk_read = (counter_t) (value * 1024);
1681                         counter_flags |= 0x01;
1682                 } else if (!strcmp(name, "disk_data_written")) {
1683                         disk_written = (counter_t) (value * 1024);
1684                         counter_flags |= 0x02;
1685                 } else if (!strcmp(name, "net_data_recv")) {
1686                         net_recv = (counter_t) (value * 1024);
1687                         counter_flags |= 0x04;
1688                 } else if (!strcmp(name, "net_data_sent")) {
1689                         net_sent = (counter_t) (value * 1024);
1690                         counter_flags |= 0x08;
1691                 } else if (!strcmp(name, "cpu_busy")) {
1692                         cpu_busy = (counter_t) value;
1693                         counter_flags |= 0x10;
1694                 } else if (!strcmp(name, "cpu_elapsed_time")) {
1695                         cpu_total = (counter_t) value;
1696                         counter_flags |= 0x20;
1697                 } else if ((cfg_system->flags & CFG_SYSTEM_OPS)
1698                                 && (value > 0) && (strlen(name) > 4)
1699                                 && (!strcmp(name + strlen(name) - 4, "_ops"))) {
1700                         submit_counter (hostname, instance, "disk_ops_complex", name,
1701                                         (counter_t) value, timestamp);
1702                 }
1703         } /* for (counter) */
1704
1705         if ((cfg_system->flags & CFG_SYSTEM_DISK)
1706                         && (HAS_ALL_FLAGS (counter_flags, 0x01 | 0x02)))
1707                 submit_two_counters (hostname, instance, "disk_octets", NULL,
1708                                 disk_read, disk_written, timestamp);
1709                                 
1710         if ((cfg_system->flags & CFG_SYSTEM_NET)
1711                         && (HAS_ALL_FLAGS (counter_flags, 0x04 | 0x08)))
1712                 submit_two_counters (hostname, instance, "if_octets", NULL,
1713                                 net_recv, net_sent, timestamp);
1714
1715         if ((cfg_system->flags & CFG_SYSTEM_CPU)
1716                         && (HAS_ALL_FLAGS (counter_flags, 0x10 | 0x20)))
1717         {
1718                 submit_counter (hostname, instance, "cpu", "system",
1719                                 cpu_busy, timestamp);
1720                 submit_counter (hostname, instance, "cpu", "idle",
1721                                 cpu_total - cpu_busy, timestamp);
1722         }
1723
1724         return (0);
1725 } /* }}} int cna_handle_system_data */
1726
1727 static int cna_setup_system (cfg_system_t *cs) /* {{{ */
1728 {
1729         if (cs == NULL)
1730                 return (EINVAL);
1731
1732         if (cs->query != NULL)
1733                 return (0);
1734
1735         cs->query = na_elem_new ("perf-object-get-instances");
1736         if (cs->query == NULL)
1737         {
1738                 ERROR ("netapp plugin: na_elem_new failed.");
1739                 return (-1);
1740         }
1741         na_child_add_string (cs->query, "objectname", "system");
1742
1743         return (0);
1744 } /* }}} int cna_setup_system */
1745
1746 static int cna_query_system (host_config_t *host) /* {{{ */
1747 {
1748         na_elem_t *data;
1749         int status;
1750         time_t now;
1751
1752         if (host == NULL)
1753                 return (EINVAL);
1754
1755         /* If system statistics were not configured, return without doing anything. */
1756         if (host->cfg_system == NULL)
1757                 return (0);
1758
1759         now = time (NULL);
1760         if ((host->cfg_system->interval.interval + host->cfg_system->interval.last_read) > now)
1761                 return (0);
1762
1763         status = cna_setup_system (host->cfg_system);
1764         if (status != 0)
1765                 return (status);
1766         assert (host->cfg_system->query != NULL);
1767
1768         data = na_server_invoke_elem(host->srv, host->cfg_system->query);
1769         if (na_results_status (data) != NA_OK)
1770         {
1771                 ERROR ("netapp plugin: cna_query_system: na_server_invoke_elem failed: %s",
1772                                 na_results_reason (data));
1773                 na_elem_free (data);
1774                 return (-1);
1775         }
1776
1777         status = cna_handle_system_data (host->name, host->cfg_system, data);
1778
1779         if (status == 0)
1780                 host->cfg_system->interval.last_read = now;
1781
1782         na_elem_free (data);
1783         return (status);
1784 } /* }}} int cna_query_system */
1785
1786 /*
1787  * Configuration handling
1788  */
1789 /* Sets a given flag if the boolean argument is true and unsets the flag if it
1790  * is false. On error, the flag-field is not changed. */
1791 static int cna_config_bool_to_flag (const oconfig_item_t *ci, /* {{{ */
1792                 uint32_t *flags, uint32_t flag)
1793 {
1794         if ((ci == NULL) || (flags == NULL))
1795                 return (EINVAL);
1796
1797         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
1798         {
1799                 WARNING ("netapp plugin: The %s option needs exactly one boolean argument.",
1800                                 ci->key);
1801                 return (-1);
1802         }
1803
1804         if (ci->values[0].value.boolean)
1805                 *flags |= flag;
1806         else
1807                 *flags &= ~flag;
1808
1809         return (0);
1810 } /* }}} int cna_config_bool_to_flag */
1811
1812 /* Handling of the "Interval" option which is allowed in every block. */
1813 static int cna_config_get_interval (const oconfig_item_t *ci, /* {{{ */
1814                 cna_interval_t *out_interval)
1815 {
1816         time_t tmp;
1817
1818         if ((ci == NULL) || (out_interval == NULL))
1819                 return (EINVAL);
1820
1821         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
1822         {
1823                 WARNING ("netapp plugin: The `Multiplier' option needs exactly one numeric argument.");
1824                 return (-1);
1825         }
1826
1827         tmp = (time_t) (ci->values[0].value.number + .5);
1828         if (tmp < 1)
1829         {
1830                 WARNING ("netapp plugin: The `Multiplier' option needs a positive integer argument.");
1831                 return (-1);
1832         }
1833
1834         out_interval->interval = tmp;
1835         out_interval->last_read = 0;
1836
1837         return (0);
1838 } /* }}} int cna_config_get_interval */
1839
1840 /* Handling of the "GetIO", "GetOps" and "GetLatency" options within a
1841  * <VolumePerf /> block. */
1842 static void cna_config_volume_perf_option (cfg_volume_perf_t *cvp, /* {{{ */
1843                 const oconfig_item_t *ci)
1844 {
1845         char *name;
1846         ignorelist_t * il;
1847
1848         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
1849         {
1850                 WARNING ("netapp plugin: The %s option requires exactly one string argument.",
1851                                 ci->key);
1852                 return;
1853         }
1854
1855         name = ci->values[0].value.string;
1856
1857         if (strcasecmp ("GetIO", ci->key) == 0)
1858                 il = cvp->il_octets;
1859         else if (strcasecmp ("GetOps", ci->key) == 0)
1860                 il = cvp->il_operations;
1861         else if (strcasecmp ("GetLatency", ci->key) == 0)
1862                 il = cvp->il_latency;
1863         else
1864                 return;
1865
1866         ignorelist_add (il, name);
1867 } /* }}} void cna_config_volume_perf_option */
1868
1869 /* Handling of the "IgnoreSelectedIO", "IgnoreSelectedOps" and
1870  * "IgnoreSelectedLatency" options within a <VolumePerf /> block. */
1871 static void cna_config_volume_perf_default (cfg_volume_perf_t *cvp, /* {{{ */
1872                 const oconfig_item_t *ci)
1873 {
1874         ignorelist_t *il;
1875
1876         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
1877         {
1878                 WARNING ("netapp plugin: The %s option requires exactly one string argument.",
1879                                 ci->key);
1880                 return;
1881         }
1882
1883         if (strcasecmp ("IgnoreSelectedIO", ci->key) == 0)
1884                 il = cvp->il_octets;
1885         else if (strcasecmp ("IgnoreSelectedOps", ci->key) == 0)
1886                 il = cvp->il_operations;
1887         else if (strcasecmp ("IgnoreSelectedLatency", ci->key) == 0)
1888                 il = cvp->il_latency;
1889         else
1890                 return;
1891
1892         if (ci->values[0].value.boolean)
1893                 ignorelist_set_invert (il, /* invert = */ 0);
1894         else
1895                 ignorelist_set_invert (il, /* invert = */ 1);
1896 } /* }}} void cna_config_volume_perf_default */
1897
1898 /* Corresponds to a <Disks /> block */
1899 /*
1900  * <VolumePerf>
1901  *   GetIO "vol0"
1902  *   GetIO "vol1"
1903  *   IgnoreSelectedIO false
1904  *
1905  *   GetOps "vol0"
1906  *   GetOps "vol2"
1907  *   IgnoreSelectedOps false
1908  *
1909  *   GetLatency "vol2"
1910  *   GetLatency "vol3"
1911  *   IgnoreSelectedLatency false
1912  * </VolumePerf>
1913  */
1914 /* Corresponds to a <VolumePerf /> block */
1915 static int cna_config_volume_performance (host_config_t *host, /* {{{ */
1916                 const oconfig_item_t *ci)
1917 {
1918         cfg_volume_perf_t *cfg_volume_perf;
1919         int i;
1920
1921         if ((host == NULL) || (ci == NULL))
1922                 return (EINVAL);
1923
1924         if (host->cfg_volume_perf == NULL)
1925         {
1926                 cfg_volume_perf = malloc (sizeof (*cfg_volume_perf));
1927                 if (cfg_volume_perf == NULL)
1928                         return (ENOMEM);
1929                 memset (cfg_volume_perf, 0, sizeof (*cfg_volume_perf));
1930
1931                 /* Set default flags */
1932                 cfg_volume_perf->query = NULL;
1933                 cfg_volume_perf->volumes = NULL;
1934
1935                 cfg_volume_perf->il_octets = ignorelist_create (/* invert = */ 1);
1936                 if (cfg_volume_perf->il_octets == NULL)
1937                 {
1938                         sfree (cfg_volume_perf);
1939                         return (ENOMEM);
1940                 }
1941
1942                 cfg_volume_perf->il_operations = ignorelist_create (/* invert = */ 1);
1943                 if (cfg_volume_perf->il_operations == NULL)
1944                 {
1945                         ignorelist_free (cfg_volume_perf->il_octets);
1946                         sfree (cfg_volume_perf);
1947                         return (ENOMEM);
1948                 }
1949
1950                 cfg_volume_perf->il_latency = ignorelist_create (/* invert = */ 1);
1951                 if (cfg_volume_perf->il_latency == NULL)
1952                 {
1953                         ignorelist_free (cfg_volume_perf->il_octets);
1954                         ignorelist_free (cfg_volume_perf->il_operations);
1955                         sfree (cfg_volume_perf);
1956                         return (ENOMEM);
1957                 }
1958
1959                 host->cfg_volume_perf = cfg_volume_perf;
1960         }
1961         cfg_volume_perf = host->cfg_volume_perf;
1962         
1963         for (i = 0; i < ci->children_num; ++i) {
1964                 oconfig_item_t *item = ci->children + i;
1965                 
1966                 /* if (!item || !item->key || !*item->key) continue; */
1967                 if (strcasecmp(item->key, "Interval") == 0)
1968                         cna_config_get_interval (item, &cfg_volume_perf->interval);
1969                 else if (!strcasecmp(item->key, "GetIO"))
1970                         cna_config_volume_perf_option (cfg_volume_perf, item);
1971                 else if (!strcasecmp(item->key, "GetOps"))
1972                         cna_config_volume_perf_option (cfg_volume_perf, item);
1973                 else if (!strcasecmp(item->key, "GetLatency"))
1974                         cna_config_volume_perf_option (cfg_volume_perf, item);
1975                 else if (!strcasecmp(item->key, "IgnoreSelectedIO"))
1976                         cna_config_volume_perf_default (cfg_volume_perf, item);
1977                 else if (!strcasecmp(item->key, "IgnoreSelectedOps"))
1978                         cna_config_volume_perf_default (cfg_volume_perf, item);
1979                 else if (!strcasecmp(item->key, "IgnoreSelectedLatency"))
1980                         cna_config_volume_perf_default (cfg_volume_perf, item);
1981                 else
1982                         WARNING ("netapp plugin: The option %s is not allowed within "
1983                                         "`VolumePerf' blocks.", item->key);
1984         }
1985
1986         return (0);
1987 } /* }}} int cna_config_volume_performance */
1988
1989 /* Handling of the "Capacity" and "Snapshot" options within a <VolumeUsage />
1990  * block. */
1991 static void cna_config_volume_usage_option (cfg_volume_usage_t *cvu, /* {{{ */
1992                 const oconfig_item_t *ci)
1993 {
1994         char *name;
1995         ignorelist_t * il;
1996
1997         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
1998         {
1999                 WARNING ("netapp plugin: The %s option requires exactly one string argument.",
2000                                 ci->key);
2001                 return;
2002         }
2003
2004         name = ci->values[0].value.string;
2005
2006         if (strcasecmp ("Capacity", ci->key) == 0)
2007                 il = cvu->il_capacity;
2008         else if (strcasecmp ("Snapshot", ci->key) == 0)
2009                 il = cvu->il_snapshot;
2010         else
2011                 return;
2012
2013         ignorelist_add (il, name);
2014 } /* }}} void cna_config_volume_usage_option */
2015
2016 /* Handling of the "IgnoreSelectedCapacity" and "IgnoreSelectedSnapshot"
2017  * options within a <VolumeUsage /> block. */
2018 static void cna_config_volume_usage_default (cfg_volume_usage_t *cvu, /* {{{ */
2019                 const oconfig_item_t *ci)
2020 {
2021         ignorelist_t *il;
2022
2023         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
2024         {
2025                 WARNING ("netapp plugin: The %s option requires exactly one string argument.",
2026                                 ci->key);
2027                 return;
2028         }
2029
2030         if (strcasecmp ("IgnoreSelectedCapacity", ci->key) == 0)
2031                 il = cvu->il_capacity;
2032         else if (strcasecmp ("IgnoreSelectedSnapshot", ci->key) == 0)
2033                 il = cvu->il_snapshot;
2034         else
2035                 return;
2036
2037         if (ci->values[0].value.boolean)
2038                 ignorelist_set_invert (il, /* invert = */ 0);
2039         else
2040                 ignorelist_set_invert (il, /* invert = */ 1);
2041 } /* }}} void cna_config_volume_usage_default */
2042
2043 /* Corresponds to a <Disks /> block */
2044 static int cna_config_disk(host_config_t *host, oconfig_item_t *ci) { /* {{{ */
2045         cfg_disk_t *cfg_disk;
2046         int i;
2047
2048         if ((host == NULL) || (ci == NULL))
2049                 return (EINVAL);
2050
2051         if (host->cfg_disk == NULL)
2052         {
2053                 cfg_disk = malloc (sizeof (*cfg_disk));
2054                 if (cfg_disk == NULL)
2055                         return (ENOMEM);
2056                 memset (cfg_disk, 0, sizeof (*cfg_disk));
2057
2058                 /* Set default flags */
2059                 cfg_disk->flags = CFG_DISK_ALL;
2060                 cfg_disk->query = NULL;
2061                 cfg_disk->disks = NULL;
2062
2063                 host->cfg_disk = cfg_disk;
2064         }
2065         cfg_disk = host->cfg_disk;
2066         
2067         for (i = 0; i < ci->children_num; ++i) {
2068                 oconfig_item_t *item = ci->children + i;
2069                 
2070                 /* if (!item || !item->key || !*item->key) continue; */
2071                 if (strcasecmp(item->key, "Interval") == 0)
2072                         cna_config_get_interval (item, &cfg_disk->interval);
2073                 else if (strcasecmp(item->key, "GetBusy") == 0)
2074                         cna_config_bool_to_flag (item, &cfg_disk->flags, CFG_DISK_BUSIEST);
2075         }
2076
2077         if ((cfg_disk->flags & CFG_DISK_ALL) == 0)
2078         {
2079                 NOTICE ("netapp plugin: All disk related values have been disabled. "
2080                                 "Collection of per-disk data will be disabled entirely.");
2081                 free_cfg_disk (host->cfg_disk);
2082                 host->cfg_disk = NULL;
2083         }
2084
2085         return (0);
2086 } /* }}} int cna_config_disk */
2087
2088 /* Corresponds to a <WAFL /> block */
2089 static int cna_config_wafl(host_config_t *host, oconfig_item_t *ci) /* {{{ */
2090 {
2091         cfg_wafl_t *cfg_wafl;
2092         int i;
2093
2094         if ((host == NULL) || (ci == NULL))
2095                 return (EINVAL);
2096
2097         if (host->cfg_wafl == NULL)
2098         {
2099                 cfg_wafl = malloc (sizeof (*cfg_wafl));
2100                 if (cfg_wafl == NULL)
2101                         return (ENOMEM);
2102                 memset (cfg_wafl, 0, sizeof (*cfg_wafl));
2103
2104                 /* Set default flags */
2105                 cfg_wafl->flags = CFG_WAFL_ALL;
2106
2107                 host->cfg_wafl = cfg_wafl;
2108         }
2109         cfg_wafl = host->cfg_wafl;
2110
2111         for (i = 0; i < ci->children_num; ++i) {
2112                 oconfig_item_t *item = ci->children + i;
2113                 
2114                 if (strcasecmp(item->key, "Interval") == 0)
2115                         cna_config_get_interval (item, &cfg_wafl->interval);
2116                 else if (!strcasecmp(item->key, "GetNameCache"))
2117                         cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_NAME_CACHE);
2118                 else if (!strcasecmp(item->key, "GetDirCache"))
2119                         cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_DIR_CACHE);
2120                 else if (!strcasecmp(item->key, "GetBufferCache"))
2121                         cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_BUF_CACHE);
2122                 else if (!strcasecmp(item->key, "GetInodeCache"))
2123                         cna_config_bool_to_flag (item, &cfg_wafl->flags, CFG_WAFL_INODE_CACHE);
2124                 else
2125                         WARNING ("netapp plugin: The %s config option is not allowed within "
2126                                         "`WAFL' blocks.", item->key);
2127         }
2128
2129         if ((cfg_wafl->flags & CFG_WAFL_ALL) == 0)
2130         {
2131                 NOTICE ("netapp plugin: All WAFL related values have been disabled. "
2132                                 "Collection of WAFL data will be disabled entirely.");
2133                 free_cfg_wafl (host->cfg_wafl);
2134                 host->cfg_wafl = NULL;
2135         }
2136
2137         return (0);
2138 } /* }}} int cna_config_wafl */
2139
2140 /*
2141  * <VolumeUsage>
2142  *   Capacity "vol0"
2143  *   Capacity "vol1"
2144  *   Capacity "vol2"
2145  *   Capacity "vol3"
2146  *   Capacity "vol4"
2147  *   IgnoreSelectedCapacity false
2148  *
2149  *   Snapshot "vol0"
2150  *   Snapshot "vol3"
2151  *   Snapshot "vol4"
2152  *   Snapshot "vol7"
2153  *   IgnoreSelectedSnapshot false
2154  * </VolumeUsage>
2155  */
2156 /* Corresponds to a <VolumeUsage /> block */
2157 static int cna_config_volume_usage(host_config_t *host, /* {{{ */
2158                 const oconfig_item_t *ci)
2159 {
2160         cfg_volume_usage_t *cfg_volume_usage;
2161         int i;
2162
2163         if ((host == NULL) || (ci == NULL))
2164                 return (EINVAL);
2165
2166         if (host->cfg_volume_usage == NULL)
2167         {
2168                 cfg_volume_usage = malloc (sizeof (*cfg_volume_usage));
2169                 if (cfg_volume_usage == NULL)
2170                         return (ENOMEM);
2171                 memset (cfg_volume_usage, 0, sizeof (*cfg_volume_usage));
2172
2173                 /* Set default flags */
2174                 cfg_volume_usage->query = NULL;
2175                 cfg_volume_usage->volumes = NULL;
2176
2177                 cfg_volume_usage->il_capacity = ignorelist_create (/* invert = */ 1);
2178                 if (cfg_volume_usage->il_capacity == NULL)
2179                 {
2180                         sfree (cfg_volume_usage);
2181                         return (ENOMEM);
2182                 }
2183
2184                 cfg_volume_usage->il_snapshot = ignorelist_create (/* invert = */ 1);
2185                 if (cfg_volume_usage->il_snapshot == NULL)
2186                 {
2187                         ignorelist_free (cfg_volume_usage->il_capacity);
2188                         sfree (cfg_volume_usage);
2189                         return (ENOMEM);
2190                 }
2191
2192                 host->cfg_volume_usage = cfg_volume_usage;
2193         }
2194         cfg_volume_usage = host->cfg_volume_usage;
2195         
2196         for (i = 0; i < ci->children_num; ++i) {
2197                 oconfig_item_t *item = ci->children + i;
2198                 
2199                 /* if (!item || !item->key || !*item->key) continue; */
2200                 if (strcasecmp(item->key, "Interval") == 0)
2201                         cna_config_get_interval (item, &cfg_volume_usage->interval);
2202                 else if (!strcasecmp(item->key, "Capacity"))
2203                         cna_config_volume_usage_option (cfg_volume_usage, item);
2204                 else if (!strcasecmp(item->key, "Snapshot"))
2205                         cna_config_volume_usage_option (cfg_volume_usage, item);
2206                 else if (!strcasecmp(item->key, "IgnoreSelectedCapacity"))
2207                         cna_config_volume_usage_default (cfg_volume_usage, item);
2208                 else if (!strcasecmp(item->key, "IgnoreSelectedSnapshot"))
2209                         cna_config_volume_usage_default (cfg_volume_usage, item);
2210                 else
2211                         WARNING ("netapp plugin: The option %s is not allowed within "
2212                                         "`VolumeUsage' blocks.", item->key);
2213         }
2214
2215         return (0);
2216 } /* }}} int cna_config_volume_usage */
2217
2218 /* Corresponds to a <System /> block */
2219 static int cna_config_system (host_config_t *host, /* {{{ */
2220                 oconfig_item_t *ci, const cfg_service_t *default_service)
2221 {
2222         cfg_system_t *cfg_system;
2223         int i;
2224         
2225         if ((host == NULL) || (ci == NULL))
2226                 return (EINVAL);
2227
2228         if (host->cfg_system == NULL)
2229         {
2230                 cfg_system = malloc (sizeof (*cfg_system));
2231                 if (cfg_system == NULL)
2232                         return (ENOMEM);
2233                 memset (cfg_system, 0, sizeof (*cfg_system));
2234
2235                 /* Set default flags */
2236                 cfg_system->flags = CFG_SYSTEM_ALL;
2237                 cfg_system->query = NULL;
2238
2239                 host->cfg_system = cfg_system;
2240         }
2241         cfg_system = host->cfg_system;
2242
2243         for (i = 0; i < ci->children_num; ++i) {
2244                 oconfig_item_t *item = ci->children + i;
2245
2246                 if (strcasecmp(item->key, "Interval") == 0) {
2247                         cna_config_get_interval (item, &cfg_system->interval);
2248                 } else if (!strcasecmp(item->key, "GetCPULoad")) {
2249                         cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_CPU);
2250                 } else if (!strcasecmp(item->key, "GetInterfaces")) {
2251                         cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_NET);
2252                 } else if (!strcasecmp(item->key, "GetDiskOps")) {
2253                         cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_OPS);
2254                 } else if (!strcasecmp(item->key, "GetDiskIO")) {
2255                         cna_config_bool_to_flag (item, &cfg_system->flags, CFG_SYSTEM_DISK);
2256                 } else {
2257                         WARNING ("netapp plugin: The %s config option is not allowed within "
2258                                         "`System' blocks.", item->key);
2259                 }
2260         }
2261
2262         if ((cfg_system->flags & CFG_SYSTEM_ALL) == 0)
2263         {
2264                 NOTICE ("netapp plugin: All system related values have been disabled. "
2265                                 "Collection of system data will be disabled entirely.");
2266                 free_cfg_system (host->cfg_system);
2267                 host->cfg_system = NULL;
2268         }
2269
2270         return (0);
2271 } /* }}} int cna_config_system */
2272
2273 /* Corresponds to a <Host /> block. */
2274 static host_config_t *cna_config_host (const oconfig_item_t *ci, /* {{{ */
2275                 const host_config_t *default_host, const cfg_service_t *def_def_service)
2276 {
2277         oconfig_item_t *item;
2278         host_config_t *host;
2279         cfg_service_t default_service = *def_def_service;
2280         int status;
2281         int i;
2282         
2283         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
2284                 WARNING("netapp plugin: \"Host\" needs exactly one string argument. Ignoring host block.");
2285                 return 0;
2286         }
2287
2288         host = malloc(sizeof(*host));
2289         memcpy (host, default_host, sizeof (*host));
2290
2291         status = cf_util_get_string (ci, &host->name);
2292         if (status != 0)
2293         {
2294                 sfree (host);
2295                 return (NULL);
2296         }
2297
2298         for (i = 0; i < ci->children_num; ++i) {
2299                 item = ci->children + i;
2300
2301                 status = 0;
2302
2303                 if (!strcasecmp(item->key, "Address")) {
2304                         status = cf_util_get_string (item, &host->host);
2305                 } else if (!strcasecmp(item->key, "Port")) {
2306                         int tmp;
2307
2308                         tmp = cf_util_get_port_number (item);
2309                         if (tmp > 0)
2310                                 host->port = tmp;
2311                 } else if (!strcasecmp(item->key, "Protocol")) {
2312                         if ((item->values_num != 1) || (item->values[0].type != OCONFIG_TYPE_STRING) || (strcasecmp(item->values[0].value.string, "http") && strcasecmp(item->values[0].value.string, "https"))) {
2313                                 WARNING("netapp plugin: \"Protocol\" needs to be either \"http\" or \"https\". Ignoring host block \"%s\".", ci->values[0].value.string);
2314                                 return 0;
2315                         }
2316                         if (!strcasecmp(item->values[0].value.string, "http")) host->protocol = NA_SERVER_TRANSPORT_HTTP;
2317                         else host->protocol = NA_SERVER_TRANSPORT_HTTPS;
2318                 } else if (!strcasecmp(item->key, "User")) {
2319                         status = cf_util_get_string (item, &host->username);
2320                 } else if (!strcasecmp(item->key, "Password")) {
2321                         status = cf_util_get_string (item, &host->password);
2322                 } else if (!strcasecmp(item->key, "Interval")) {
2323                         if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_NUMBER || item->values[0].value.number != (int) item->values[0].value.number || item->values[0].value.number < 2) {
2324                                 WARNING("netapp plugin: \"Interval\" of host %s needs exactly one integer argument.", ci->values[0].value.string);
2325                                 continue;
2326                         }
2327                         host->interval = item->values[0].value.number;
2328                 } else if (!strcasecmp(item->key, "WAFL")) {
2329                         cna_config_wafl(host, item);
2330                 } else if (!strcasecmp(item->key, "Disks")) {
2331                         cna_config_disk(host, item);
2332                 } else if (!strcasecmp(item->key, "VolumePerf")) {
2333                         cna_config_volume_performance(host, item);
2334                 } else if (!strcasecmp(item->key, "VolumeUsage")) {
2335                         cna_config_volume_usage(host, item);
2336                 } else if (!strcasecmp(item->key, "System")) {
2337                         cna_config_system(host, item, &default_service);
2338                 } else {
2339                         WARNING("netapp plugin: Ignoring unknown config option \"%s\" in host block \"%s\".",
2340                                         item->key, ci->values[0].value.string);
2341                 }
2342
2343                 if (status != 0)
2344                         break;
2345         }
2346
2347         if (host->host == NULL)
2348                 host->host = strdup (host->name);
2349
2350         if (host->host == NULL)
2351                 status = -1;
2352
2353         if (host->port <= 0)
2354                 host->port = (host->protocol == NA_SERVER_TRANSPORT_HTTP) ? 80 : 443;
2355
2356         if ((host->username == NULL) || (host->password == NULL)) {
2357                 WARNING("netapp plugin: Please supply login information for host \"%s\". "
2358                                 "Ignoring host block.", host->name);
2359                 status = -1;
2360         }
2361
2362         if (status != 0)
2363         {
2364                 free_host_config (host);
2365                 return (NULL);
2366         }
2367
2368         return host;
2369 } /* }}} host_config_t *cna_config_host */
2370
2371 /*
2372  * Callbacks registered with the daemon
2373  *
2374  * Pretty standard stuff here.
2375  */
2376 static int cna_init(void) { /* {{{ */
2377         char err[256];
2378         host_config_t *host;
2379         
2380         if (!global_host_config) {
2381                 WARNING("netapp plugin: Plugin loaded but no hosts defined.");
2382                 return 1;
2383         }
2384
2385         memset (err, 0, sizeof (err));
2386         if (!na_startup(err, sizeof(err))) {
2387                 err[sizeof (err) - 1] = 0;
2388                 ERROR("netapp plugin: Error initializing netapp API: %s", err);
2389                 return 1;
2390         }
2391
2392         for (host = global_host_config; host; host = host->next) {
2393                 /* Request version 1.1 of the ONTAP API */
2394                 host->srv = na_server_open(host->host,
2395                                 /* major version = */ 1, /* minor version = */ 1); 
2396                 if (host->srv == NULL) {
2397                         ERROR ("netapp plugin: na_server_open (%s) failed.", host->host);
2398                         continue;
2399                 }
2400
2401                 if (host->interval < interval_g)
2402                         host->interval = interval_g;
2403
2404                 na_server_set_transport_type(host->srv, host->protocol,
2405                                 /* transportarg = */ NULL);
2406                 na_server_set_port(host->srv, host->port);
2407                 na_server_style(host->srv, NA_STYLE_LOGIN_PASSWORD);
2408                 na_server_adminuser(host->srv, host->username, host->password);
2409                 na_server_set_timeout(host->srv, 5 /* seconds */);
2410         }
2411         return 0;
2412 } /* }}} int cna_init */
2413
2414 static int cna_config (oconfig_item_t *ci) { /* {{{ */
2415         int i;
2416         oconfig_item_t *item;
2417         host_config_t default_host = HOST_INIT;
2418         cfg_service_t default_service = SERVICE_INIT;
2419         
2420         for (i = 0; i < ci->children_num; ++i) {
2421                 item = ci->children + i;
2422
2423                 if (!strcasecmp(item->key, "Host")) {
2424                         host_config_t *host;
2425                         host_config_t *tmp;
2426
2427                         host = cna_config_host(item, &default_host, &default_service);
2428                         if (host == NULL)
2429                                 continue;
2430
2431                         for (tmp = global_host_config; tmp != NULL; tmp = tmp->next)
2432                         {
2433                                 if (strcasecmp (host->name, tmp->name) == 0)
2434                                         WARNING ("netapp plugin: Duplicate definition of host `%s'. "
2435                                                         "This is probably a bad idea.",
2436                                                         host->name);
2437
2438                                 if (tmp->next == NULL)
2439                                         break;
2440                         }
2441
2442                         host->next = NULL;
2443                         if (tmp == NULL)
2444                                 global_host_config = host;
2445                         else
2446                                 tmp->next = host;
2447                 } else {
2448                         WARNING("netapp plugin: Ignoring unknown config option \"%s\".", item->key);
2449                 }
2450         }
2451         return 0;
2452 } /* }}} int cna_config */
2453
2454 static int cna_read (void) { /* {{{ */
2455         na_elem_t *out;
2456         host_config_t *host;
2457         cfg_service_t *service;
2458         
2459         for (host = global_host_config; host; host = host->next) {
2460                 for (service = host->services; service; service = service->next) {
2461                         if (--service->skip_countdown > 0) continue;
2462                         service->skip_countdown = service->multiplier;
2463                         out = na_server_invoke_elem(host->srv, service->query);
2464                         if (na_results_status(out) != NA_OK) {
2465                                 int netapp_errno = na_results_errno(out);
2466                                 ERROR("netapp plugin: Error %d from host %s: %s", netapp_errno, host->name, na_results_reason(out));
2467                                 na_elem_free(out);
2468                                 if (netapp_errno == EIO || netapp_errno == ETIMEDOUT) {
2469                                         /* Network problems. Just give up on all other services on this host. */
2470                                         break;
2471                                 }
2472                                 continue;
2473                         }
2474                         service->handler(host, out, service->data);
2475                         na_elem_free(out);
2476                 } /* for (host->services) */
2477
2478                 cna_query_wafl (host);
2479                 cna_query_disk (host);
2480                 cna_query_volume_perf (host);
2481                 cna_query_volume_usage (host);
2482                 cna_query_system (host);
2483         }
2484         return 0;
2485 } /* }}} int cna_read */
2486
2487 void module_register(void) {
2488         plugin_register_complex_config("netapp", cna_config);
2489         plugin_register_init("netapp", cna_init);
2490         plugin_register_read("netapp", cna_read);
2491 }
2492
2493 /* vim: set sw=2 ts=2 noet fdm=marker : */