+ timestamp = cna_child_get_cdtime (data);
+
+ instances = na_elem_child (data, "instances");
+ if (instances == NULL)
+ {
+ ERROR ("netapp plugin: cna_handle_disk_data: "
+ "na_elem_child (\"instances\") failed "
+ "for host %s.", hostname);
+ return (-1);
+ }
+
+ /* Iterate over all children */
+ instance_iter = na_child_iterator (instances);
+ for (instance = na_iterator_next (&instance_iter);
+ instance != NULL;
+ instance = na_iterator_next(&instance_iter))
+ {
+ disk_t *old_data;
+ disk_t new_data;
+
+ na_elem_iter_t counter_iterator;
+ na_elem_t *counter;
+
+ memset (&new_data, 0, sizeof (new_data));
+ new_data.timestamp = timestamp;
+ new_data.disk_busy_percent = NAN;
+
+ old_data = get_disk(cfg_disk, na_child_get_string (instance, "name"));
+ if (old_data == NULL)
+ continue;
+
+ /* Look for the "disk_busy" and "base_for_disk_busy" counters */
+ counter_iterator = na_child_iterator(na_elem_child(instance, "counters"));
+ for (counter = na_iterator_next(&counter_iterator);
+ counter != NULL;
+ counter = na_iterator_next(&counter_iterator))
+ {
+ const char *name;
+ uint64_t value;
+
+ name = na_child_get_string(counter, "name");
+ if (name == NULL)
+ continue;
+
+ value = na_child_get_uint64(counter, "value", UINT64_MAX);
+ if (value == UINT64_MAX)
+ continue;
+
+ if (strcmp(name, "disk_busy") == 0)
+ {
+ new_data.disk_busy = value;
+ new_data.flags |= HAVE_DISK_BUSY;
+ }
+ else if (strcmp(name, "base_for_disk_busy") == 0)
+ {
+ new_data.base_for_disk_busy = value;
+ new_data.flags |= HAVE_DISK_BASE;
+ }
+ else
+ {
+ DEBUG ("netapp plugin: cna_handle_disk_data: "
+ "Counter not handled: %s = %"PRIu64,
+ name, value);
+ }
+ }
+
+ /* If all required counters are available and did not just wrap around,
+ * calculate the busy percentage. Otherwise, the value is initialized to
+ * NAN at the top of the for-loop. */
+ if (HAS_ALL_FLAGS (old_data->flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
+ && HAS_ALL_FLAGS (new_data.flags, HAVE_DISK_BUSY | HAVE_DISK_BASE)
+ && (new_data.disk_busy >= old_data->disk_busy)
+ && (new_data.base_for_disk_busy > old_data->base_for_disk_busy))
+ {
+ uint64_t busy_diff;
+ uint64_t base_diff;
+
+ busy_diff = new_data.disk_busy - old_data->disk_busy;
+ base_diff = new_data.base_for_disk_busy - old_data->base_for_disk_busy;
+
+ new_data.disk_busy_percent = 100.0
+ * ((gauge_t) busy_diff) / ((gauge_t) base_diff);
+ }
+
+ /* Clear HAVE_* flags */
+ old_data->flags &= ~HAVE_DISK_ALL;
+
+ /* Copy data */
+ old_data->timestamp = new_data.timestamp;
+ old_data->disk_busy = new_data.disk_busy;
+ old_data->base_for_disk_busy = new_data.base_for_disk_busy;
+ old_data->disk_busy_percent = new_data.disk_busy_percent;
+
+ /* Copy flags */
+ old_data->flags |= (new_data.flags & HAVE_DISK_ALL);
+
+ if ((worst_disk == NULL)
+ || (worst_disk->disk_busy_percent < old_data->disk_busy_percent))
+ worst_disk = old_data;
+ } /* for (all disks) */
+
+ if ((cfg_disk->flags & CFG_DISK_BUSIEST) && (worst_disk != NULL))
+ submit_double (hostname, "system", "percent", "disk_busy",
+ worst_disk->disk_busy_percent, timestamp, interval);
+
+ return (0);
+} /* }}} int cna_handle_disk_data */
+
+static int cna_setup_disk (cfg_disk_t *cd) /* {{{ */
+{
+ na_elem_t *e;
+
+ if (cd == NULL)
+ return (EINVAL);
+
+ if (cd->query != NULL)
+ return (0);
+
+ cd->query = na_elem_new ("perf-object-get-instances");
+ if (cd->query == NULL)
+ {
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);
+ }
+ na_child_add_string (cd->query, "objectname", "disk");
+
+ e = na_elem_new("counters");
+ if (e == NULL)
+ {
+ na_elem_free (cd->query);
+ cd->query = NULL;
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);
+ }
+ na_child_add_string(e, "counter", "disk_busy");
+ na_child_add_string(e, "counter", "base_for_disk_busy");
+ na_child_add(cd->query, e);
+
+ return (0);
+} /* }}} int cna_setup_disk */
+
+static int cna_query_disk (host_config_t *host) /* {{{ */
+{
+ na_elem_t *data;
+ int status;
+ cdtime_t now;
+
+ if (host == NULL)
+ return (EINVAL);
+
+ /* If the user did not configure disk statistics, return without doing
+ * anything. */
+ if (host->cfg_disk == NULL)
+ return (0);
+
+ now = cdtime ();
+ if ((host->cfg_disk->interval.interval + host->cfg_disk->interval.last_read) > now)
+ return (0);
+
+ status = cna_setup_disk (host->cfg_disk);
+ if (status != 0)
+ return (status);
+ assert (host->cfg_disk->query != NULL);
+
+ data = na_server_invoke_elem(host->srv, host->cfg_disk->query);
+ if (na_results_status (data) != NA_OK)
+ {
+ ERROR ("netapp plugin: cna_query_disk: na_server_invoke_elem failed for host %s: %s",
+ host->name, na_results_reason (data));
+ na_elem_free (data);
+ return (-1);
+ }
+
+ status = cna_handle_disk_data (host->name, host->cfg_disk, data,
+ host->cfg_disk->interval.interval);
+
+ if (status == 0)
+ host->cfg_disk->interval.last_read = now;
+
+ na_elem_free (data);
+ return (status);
+} /* }}} int cna_query_disk */
+
+/* Data corresponding to <VolumePerf /> */
+static int cna_handle_volume_perf_data (const char *hostname, /* {{{ */
+ cfg_volume_perf_t *cvp, na_elem_t *data, cdtime_t interval)
+{
+ cdtime_t timestamp;
+ na_elem_t *elem_instances;
+ na_elem_iter_t iter_instances;
+ na_elem_t *elem_instance;
+
+ timestamp = cna_child_get_cdtime (data);
+
+ elem_instances = na_elem_child(data, "instances");
+ if (elem_instances == NULL)
+ {
+ ERROR ("netapp plugin: handle_volume_perf_data: "
+ "na_elem_child (\"instances\") failed "
+ "for host %s.", hostname);
+ return (-1);
+ }
+
+ iter_instances = na_child_iterator (elem_instances);
+ for (elem_instance = na_iterator_next(&iter_instances);
+ elem_instance != NULL;
+ elem_instance = na_iterator_next(&iter_instances))
+ {
+ const char *name;
+
+ data_volume_perf_t perf_data;
+ data_volume_perf_t *v;
+
+ na_elem_t *elem_counters;
+ na_elem_iter_t iter_counters;
+ na_elem_t *elem_counter;
+
+ memset (&perf_data, 0, sizeof (perf_data));
+ perf_data.timestamp = timestamp;
+
+ name = na_child_get_string (elem_instance, "name");
+ if (name == NULL)
+ continue;
+
+ /* get_volume_perf may return NULL if this volume is to be ignored. */
+ v = get_volume_perf (cvp, name);
+ if (v == NULL)
+ continue;
+
+ elem_counters = na_elem_child (elem_instance, "counters");
+ if (elem_counters == NULL)
+ continue;
+
+ iter_counters = na_child_iterator (elem_counters);
+ for (elem_counter = na_iterator_next(&iter_counters);
+ elem_counter != NULL;
+ elem_counter = na_iterator_next(&iter_counters))
+ {
+ const char *name;
+ uint64_t value;
+
+ name = na_child_get_string (elem_counter, "name");
+ if (name == NULL)
+ continue;
+
+ value = na_child_get_uint64 (elem_counter, "value", UINT64_MAX);
+ if (value == UINT64_MAX)
+ continue;
+
+ if (!strcmp(name, "read_data")) {
+ perf_data.read_bytes = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_BYTES_READ;
+ } else if (!strcmp(name, "write_data")) {
+ perf_data.write_bytes = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_BYTES_WRITE;
+ } else if (!strcmp(name, "read_ops")) {
+ perf_data.read_ops = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_OPS_READ;
+ } else if (!strcmp(name, "write_ops")) {
+ perf_data.write_ops = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_OPS_WRITE;
+ } else if (!strcmp(name, "read_latency")) {
+ perf_data.read_latency = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_READ;
+ } else if (!strcmp(name, "write_latency")) {
+ perf_data.write_latency = value;
+ perf_data.flags |= HAVE_VOLUME_PERF_LATENCY_WRITE;
+ }
+ } /* for (elem_counter) */
+
+ submit_volume_perf_data (hostname, v, &perf_data, interval);
+ } /* for (volume) */
+
+ return (0);
+} /* }}} int cna_handle_volume_perf_data */
+
+static int cna_setup_volume_perf (cfg_volume_perf_t *cd) /* {{{ */
+{
+ na_elem_t *e;
+
+ if (cd == NULL)
+ return (EINVAL);
+
+ if (cd->query != NULL)
+ return (0);
+
+ cd->query = na_elem_new ("perf-object-get-instances");
+ if (cd->query == NULL)
+ {
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);
+ }
+ na_child_add_string (cd->query, "objectname", "volume");
+
+ e = na_elem_new("counters");
+ if (e == NULL)
+ {
+ na_elem_free (cd->query);
+ cd->query = NULL;
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);
+ }
+ na_child_add_string(e, "counter", "read_ops");
+ na_child_add_string(e, "counter", "write_ops");
+ na_child_add_string(e, "counter", "read_data");
+ na_child_add_string(e, "counter", "write_data");
+ na_child_add_string(e, "counter", "read_latency");
+ na_child_add_string(e, "counter", "write_latency");
+ na_child_add(cd->query, e);
+
+ return (0);
+} /* }}} int cna_setup_volume_perf */
+
+static int cna_query_volume_perf (host_config_t *host) /* {{{ */
+{
+ na_elem_t *data;
+ int status;
+ cdtime_t now;
+
+ if (host == NULL)
+ return (EINVAL);
+
+ /* If the user did not configure volume performance statistics, return
+ * without doing anything. */
+ if (host->cfg_volume_perf == NULL)
+ return (0);
+
+ now = cdtime ();
+ if ((host->cfg_volume_perf->interval.interval + host->cfg_volume_perf->interval.last_read) > now)
+ return (0);
+
+ status = cna_setup_volume_perf (host->cfg_volume_perf);
+ if (status != 0)
+ return (status);
+ assert (host->cfg_volume_perf->query != NULL);
+
+ data = na_server_invoke_elem (host->srv, host->cfg_volume_perf->query);
+ if (na_results_status (data) != NA_OK)
+ {
+ ERROR ("netapp plugin: cna_query_volume_perf: na_server_invoke_elem failed for host %s: %s",
+ host->name, na_results_reason (data));
+ na_elem_free (data);
+ return (-1);
+ }
+
+ status = cna_handle_volume_perf_data (host->name, host->cfg_volume_perf, data,
+ host->cfg_volume_perf->interval.interval);
+
+ if (status == 0)
+ host->cfg_volume_perf->interval.last_read = now;
+
+ na_elem_free (data);
+ return (status);
+} /* }}} int cna_query_volume_perf */
+
+/* Data corresponding to <VolumeUsage /> */
+static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
+ cfg_volume_usage_t *cfg_volume, int interval)
+{
+ data_volume_usage_t *v;
+
+ for (v = cfg_volume->volumes; v != NULL; v = v->next)
+ {
+ char plugin_instance[DATA_MAX_NAME_LEN];
+
+ uint64_t norm_used = v->norm_used;
+ uint64_t norm_free = v->norm_free;
+ uint64_t sis_saved = v->sis_saved;
+ uint64_t compress_saved = v->compress_saved;
+ uint64_t dedup_saved = v->dedup_saved;
+ uint64_t snap_reserve_used = 0;
+ uint64_t snap_reserve_free = v->snap_reserved;
+ uint64_t snap_norm_used = v->snap_used;
+
+ ssnprintf (plugin_instance, sizeof (plugin_instance),
+ "volume-%s", v->name);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED | HAVE_VOLUME_USAGE_SNAP_RSVD)) {
+ if (v->snap_reserved > v->snap_used) {
+ snap_reserve_free = v->snap_reserved - v->snap_used;
+ snap_reserve_used = v->snap_used;
+ snap_norm_used = 0;
+ } else {
+ snap_reserve_free = 0;
+ snap_reserve_used = v->snap_reserved;
+ snap_norm_used = v->snap_used - v->snap_reserved;
+ }
+ }
+
+ /* The space used by snapshots but not reserved for them is included in
+ * both, norm_used and snap_norm_used. If possible, subtract this here. */
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED | HAVE_VOLUME_USAGE_SNAP_USED))
+ {
+ if (norm_used >= snap_norm_used)
+ norm_used -= snap_norm_used;
+ else
+ {
+ ERROR ("netapp plugin: (norm_used = %"PRIu64") < (snap_norm_used = "
+ "%"PRIu64") for host %s. Invalidating both.",
+ norm_used, snap_norm_used, hostname);
+ v->flags &= ~(HAVE_VOLUME_USAGE_NORM_USED | HAVE_VOLUME_USAGE_SNAP_USED);
+ }
+ }
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_FREE))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "free",
+ (double) norm_free, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SIS_SAVED))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "sis_saved",
+ (double) sis_saved, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_COMPRESS_SAVED))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "compression_saved",
+ (double) compress_saved, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_DEDUP_SAVED))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "dedup_saved",
+ (double) dedup_saved, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "used",
+ (double) norm_used, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_RSVD))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "snap_reserved",
+ (double) snap_reserve_free, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED | HAVE_VOLUME_USAGE_SNAP_RSVD))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "snap_reserve_used",
+ (double) snap_reserve_used, /* timestamp = */ 0, interval);
+
+ if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED))
+ submit_double (hostname, /* plugin instance = */ plugin_instance,
+ "df_complex", "snap_normal_used",
+ (double) snap_norm_used, /* timestamp = */ 0, interval);
+
+ /* Clear all the HAVE_* flags */
+ v->flags &= ~HAVE_VOLUME_USAGE_ALL;
+ } /* for (v = cfg_volume->volumes) */
+
+ return (0);
+} /* }}} int cna_submit_volume_usage_data */
+
+/* Switch the state of a volume between online and offline and send out a
+ * notification. */
+static int cna_change_volume_status (const char *hostname, /* {{{ */
+ data_volume_usage_t *v)
+{
+ notification_t n;
+
+ memset (&n, 0, sizeof (n));
+ n.time = cdtime ();
+ sstrncpy (n.host, hostname, sizeof (n.host));
+ sstrncpy (n.plugin, "netapp", sizeof (n.plugin));
+ sstrncpy (n.plugin_instance, v->name, sizeof (n.plugin_instance));
+
+ if ((v->flags & IS_VOLUME_USAGE_OFFLINE) != 0) {
+ n.severity = NOTIF_OKAY;
+ ssnprintf (n.message, sizeof (n.message),
+ "Volume %s is now online.", v->name);
+ v->flags &= ~IS_VOLUME_USAGE_OFFLINE;
+ } else {
+ n.severity = NOTIF_WARNING;
+ ssnprintf (n.message, sizeof (n.message),
+ "Volume %s is now offline.", v->name);
+ v->flags |= IS_VOLUME_USAGE_OFFLINE;
+ }
+
+ return (plugin_dispatch_notification (&n));
+} /* }}} int cna_change_volume_status */
+
+static void cna_handle_volume_snap_usage(const host_config_t *host, /* {{{ */
+ data_volume_usage_t *v)
+{
+ uint64_t snap_used = 0, value;
+ na_elem_t *data, *elem_snap, *elem_snapshots;
+ na_elem_iter_t iter_snap;
+
+ data = na_server_invoke_elem(host->srv, v->snap_query);
+ if (na_results_status(data) != NA_OK)
+ {
+ if (na_results_errno(data) == EVOLUMEOFFLINE) {
+ if ((v->flags & IS_VOLUME_USAGE_OFFLINE) == 0)
+ cna_change_volume_status (host->name, v);
+ } else {
+ ERROR ("netapp plugin: cna_handle_volume_snap_usage: na_server_invoke_elem for "
+ "volume \"%s\" on host %s failed with error %d: %s", v->name,
+ host->name, na_results_errno(data), na_results_reason(data));
+ }
+ na_elem_free(data);
+ return;
+ }
+
+ if ((v->flags & IS_VOLUME_USAGE_OFFLINE) != 0)
+ cna_change_volume_status (host->name, v);
+
+ elem_snapshots = na_elem_child (data, "snapshots");
+ if (elem_snapshots == NULL)
+ {
+ ERROR ("netapp plugin: cna_handle_volume_snap_usage: "
+ "na_elem_child (\"snapshots\") failed "
+ "for host %s.", host->name);
+ na_elem_free(data);
+ return;
+ }
+
+ iter_snap = na_child_iterator (elem_snapshots);
+ for (elem_snap = na_iterator_next (&iter_snap);
+ elem_snap != NULL;
+ elem_snap = na_iterator_next (&iter_snap))
+ {
+ value = na_child_get_uint64(elem_snap, "cumulative-total", 0);
+ /* "cumulative-total" is the total size of the oldest snapshot plus all
+ * newer ones in blocks (1KB). We therefore are looking for the highest
+ * number of all snapshots - that's the size required for the snapshots. */
+ if (value > snap_used)
+ snap_used = value;
+ }
+ na_elem_free (data);
+ /* snap_used is in 1024 byte blocks */
+ v->snap_used = snap_used * 1024;
+ v->flags |= HAVE_VOLUME_USAGE_SNAP_USED;
+} /* }}} void cna_handle_volume_snap_usage */
+
+static void cna_handle_volume_sis_data (const host_config_t *host, /* {{{ */
+ data_volume_usage_t *v, na_elem_t *sis)
+{
+ const char *sis_state;
+ uint64_t sis_saved_reported;
+
+ if (na_elem_child(sis, "sis-info"))
+ sis = na_elem_child(sis, "sis-info");
+
+ sis_state = na_child_get_string(sis, "state");
+ if (sis_state == NULL)
+ return;
+
+ /* If SIS is not enabled, there's nothing left to do for this volume. */
+ if (strcmp ("enabled", sis_state) != 0)
+ return;
+
+ sis_saved_reported = na_child_get_uint64(sis, "size-saved", UINT64_MAX);
+ if (sis_saved_reported == UINT64_MAX)
+ return;
+
+ /* size-saved is actually a 32 bit number, so ... time for some guesswork. */
+ if ((sis_saved_reported >> 32) != 0) {
+ /* In case they ever fix this bug. */
+ v->sis_saved = sis_saved_reported;
+ v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+ } else { /* really hacky work-around code. {{{ */
+ uint64_t sis_saved_percent;
+ uint64_t sis_saved_guess;
+ uint64_t overflow_guess;
+ uint64_t guess1, guess2, guess3;
+
+ /* Check if we have v->norm_used. Without it, we cannot calculate
+ * sis_saved_guess. */
+ if ((v->flags & HAVE_VOLUME_USAGE_NORM_USED) == 0)
+ return;
+
+ sis_saved_percent = na_child_get_uint64(sis, "percentage-saved", UINT64_MAX);
+ if (sis_saved_percent > 100)
+ return;
+
+ /* The "size-saved" value is a 32bit unsigned integer. This is a bug and
+ * will hopefully be fixed in later versions. To work around the bug, try
+ * to figure out how often the 32bit integer wrapped around by using the
+ * "percentage-saved" value. Because the percentage is in the range
+ * [0-100], this should work as long as the saved space does not exceed
+ * 400 GBytes. */
+ /* percentage-saved = size-saved / (size-saved + size-used) */
+ if (sis_saved_percent < 100)
+ sis_saved_guess = v->norm_used * sis_saved_percent / (100 - sis_saved_percent);
+ else
+ sis_saved_guess = v->norm_used;
+
+ overflow_guess = sis_saved_guess >> 32;
+ guess1 = overflow_guess ? ((overflow_guess - 1) << 32) + sis_saved_reported : sis_saved_reported;
+ guess2 = (overflow_guess << 32) + sis_saved_reported;
+ guess3 = ((overflow_guess + 1) << 32) + sis_saved_reported;
+
+ if (sis_saved_guess < guess2) {
+ if ((sis_saved_guess - guess1) < (guess2 - sis_saved_guess))
+ v->sis_saved = guess1;
+ else
+ v->sis_saved = guess2;
+ } else {
+ if ((sis_saved_guess - guess2) < (guess3 - sis_saved_guess))
+ v->sis_saved = guess2;
+ else
+ v->sis_saved = guess3;
+ }
+ v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+ } /* }}} end of 32-bit workaround */
+} /* }}} void cna_handle_volume_sis_data */
+
+/* ONTAP >= 8.1 uses SIS for managing dedup and compression */
+static void cna_handle_volume_sis_saved (const host_config_t *host, /* {{{ */
+ data_volume_usage_t *v, na_elem_t *sis)
+{
+ uint64_t saved;
+
+ if (na_elem_child(sis, "sis-info"))
+ sis = na_elem_child(sis, "sis-info");
+
+ saved = na_child_get_uint64(sis, "compress-saved", UINT64_MAX);
+ if (saved != UINT64_MAX) {
+ v->compress_saved = saved;
+ v->flags |= HAVE_VOLUME_USAGE_COMPRESS_SAVED;
+ }
+
+ saved = na_child_get_uint64(sis, "dedup-saved", UINT64_MAX);
+ if (saved != UINT64_MAX) {
+ v->dedup_saved = saved;
+ v->flags |= HAVE_VOLUME_USAGE_DEDUP_SAVED;
+ }
+} /* }}} void cna_handle_volume_sis_saved */
+
+static int cna_handle_volume_usage_data (const host_config_t *host, /* {{{ */
+ cfg_volume_usage_t *cfg_volume, na_elem_t *data)
+{
+ na_elem_t *elem_volume;
+ na_elem_t *elem_volumes;
+ na_elem_iter_t iter_volume;
+
+ elem_volumes = na_elem_child (data, "volumes");
+ if (elem_volumes == NULL)
+ {
+ ERROR ("netapp plugin: cna_handle_volume_usage_data: "
+ "na_elem_child (\"volumes\") failed "
+ "for host %s.", host->name);
+ return (-1);
+ }
+
+ iter_volume = na_child_iterator (elem_volumes);
+ for (elem_volume = na_iterator_next (&iter_volume);
+ elem_volume != NULL;
+ elem_volume = na_iterator_next (&iter_volume))
+ {
+ const char *volume_name, *state;
+
+ data_volume_usage_t *v;
+ uint64_t value;
+
+ na_elem_t *sis;
+
+ volume_name = na_child_get_string (elem_volume, "name");
+ if (volume_name == NULL)
+ continue;
+
+ state = na_child_get_string (elem_volume, "state");
+ if ((state == NULL) || (strcmp(state, "online") != 0))
+ continue;
+
+ /* get_volume_usage may return NULL if the volume is to be ignored. */
+ v = get_volume_usage (cfg_volume, volume_name);
+ if (v == NULL)
+ continue;
+
+ if ((v->flags & CFG_VOLUME_USAGE_SNAP) != 0)
+ cna_handle_volume_snap_usage(host, v);
+
+ if ((v->flags & CFG_VOLUME_USAGE_DF) == 0)
+ continue;
+
+ /* 2^4 exa-bytes? This will take a while ;) */
+ value = na_child_get_uint64(elem_volume, "size-available", UINT64_MAX);
+ if (value != UINT64_MAX) {
+ v->norm_free = value;
+ v->flags |= HAVE_VOLUME_USAGE_NORM_FREE;
+ }
+
+ value = na_child_get_uint64(elem_volume, "size-used", UINT64_MAX);
+ if (value != UINT64_MAX) {
+ v->norm_used = value;
+ v->flags |= HAVE_VOLUME_USAGE_NORM_USED;
+ }
+
+ value = na_child_get_uint64(elem_volume, "snapshot-blocks-reserved", UINT64_MAX);
+ if (value != UINT64_MAX) {
+ /* 1 block == 1024 bytes as per API docs */
+ v->snap_reserved = 1024 * value;
+ v->flags |= HAVE_VOLUME_USAGE_SNAP_RSVD;
+ }
+
+ sis = na_elem_child(elem_volume, "sis");
+ if (sis != NULL) {
+ cna_handle_volume_sis_data (host, v, sis);
+ cna_handle_volume_sis_saved (host, v, sis);
+ }
+ } /* for (elem_volume) */
+
+ return (cna_submit_volume_usage_data (host->name, cfg_volume,
+ host->cfg_volume_usage->interval.interval));
+} /* }}} int cna_handle_volume_usage_data */
+
+static int cna_setup_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
+{
+ if (cvu == NULL)
+ return (EINVAL);
+
+ if (cvu->query != NULL)
+ return (0);
+
+ cvu->query = na_elem_new ("volume-list-info");
+ if (cvu->query == NULL)
+ {
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);
+ }
+
+ return (0);
+} /* }}} int cna_setup_volume_usage */
+
+static int cna_query_volume_usage (host_config_t *host) /* {{{ */
+{
+ na_elem_t *data;
+ int status;
+ cdtime_t now;
+
+ if (host == NULL)
+ return (EINVAL);
+
+ /* If the user did not configure volume_usage statistics, return without
+ * doing anything. */
+ if (host->cfg_volume_usage == NULL)
+ return (0);
+
+ now = cdtime ();
+ if ((host->cfg_volume_usage->interval.interval + host->cfg_volume_usage->interval.last_read) > now)
+ return (0);
+
+ status = cna_setup_volume_usage (host->cfg_volume_usage);
+ if (status != 0)
+ return (status);
+ assert (host->cfg_volume_usage->query != NULL);
+
+ data = na_server_invoke_elem(host->srv, host->cfg_volume_usage->query);
+ if (na_results_status (data) != NA_OK)
+ {
+ ERROR ("netapp plugin: cna_query_volume_usage: na_server_invoke_elem failed for host %s: %s",
+ host->name, na_results_reason (data));
+ na_elem_free (data);
+ return (-1);
+ }
+
+ status = cna_handle_volume_usage_data (host, host->cfg_volume_usage, data);
+
+ if (status == 0)
+ host->cfg_volume_usage->interval.last_read = now;
+
+ na_elem_free (data);
+ return (status);
+} /* }}} int cna_query_volume_usage */
+
+/* Data corresponding to <Quota /> */
+static int cna_handle_quota_data (const host_config_t *host, /* {{{ */
+ cfg_quota_t *cfg_quota, na_elem_t *data)
+{
+ na_elem_t *elem_quota;
+ na_elem_t *elem_quotas;
+ na_elem_iter_t iter_quota;
+
+ elem_quotas = na_elem_child (data, "quotas");
+ if (elem_quotas == NULL)
+ {
+ ERROR ("netapp plugin: cna_handle_quota_data: "
+ "na_elem_child (\"quotas\") failed "
+ "for host %s.", host->name);
+ return (-1);
+ }
+
+ iter_quota = na_child_iterator (elem_quotas);
+ for (elem_quota = na_iterator_next (&iter_quota);
+ elem_quota != NULL;
+ elem_quota = na_iterator_next (&iter_quota))
+ {
+ const char *quota_type, *volume_name, *tree_name;
+ uint64_t value;
+
+ char plugin_instance[DATA_MAX_NAME_LEN];
+
+ quota_type = na_child_get_string (elem_quota, "quota-type");
+ if (quota_type == NULL)
+ continue;
+
+ /* possible TODO: support other types as well */
+ if (strcmp (quota_type, "tree") != 0)
+ continue;
+
+ tree_name = na_child_get_string (elem_quota, "tree");
+ if ((tree_name == NULL) || (*tree_name == '\0'))
+ continue;
+
+ volume_name = na_child_get_string (elem_quota, "volume");
+ if (volume_name == NULL)
+ continue;
+
+ ssnprintf (plugin_instance, sizeof (plugin_instance),
+ "quota-%s-%s", volume_name, tree_name);
+
+ value = na_child_get_uint64 (elem_quota, "disk-used", UINT64_MAX);
+ if (value != UINT64_MAX) {
+ value *= 1024; /* disk-used reports kilobytes */
+ submit_double (host->name, plugin_instance,
+ /* type = */ "df_complex", /* type instance = */ NULL,
+ (double)value, /* timestamp = */ 0,
+ host->cfg_quota->interval.interval);
+ }
+
+ value = na_child_get_uint64 (elem_quota, "files-used", UINT64_MAX);
+ if (value != UINT64_MAX) {
+ submit_double (host->name, plugin_instance,
+ /* type = */ "files", /* type instance = */ NULL,
+ (double)value, /* timestamp = */ 0,
+ host->cfg_quota->interval.interval);
+ }
+ } /* for (elem_quota) */
+
+ return (0);
+} /* }}} int cna_handle_volume_usage_data */
+
+static int cna_setup_quota (cfg_quota_t *cq) /* {{{ */
+{
+ if (cq == NULL)
+ return (EINVAL);
+
+ if (cq->query != NULL)
+ return (0);
+
+ cq->query = na_elem_new ("quota-report");
+ if (cq->query == NULL)
+ {
+ ERROR ("netapp plugin: na_elem_new failed.");
+ return (-1);