#include <errno.h>
#include <limits.h>
+#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
return r;
}
+static int td_source_check_step(td_source* self, const char* path, rrd_info_t* info) {
+ int step = 0;
+
+ // Find the step
+ while (info) {
+ if (td_string_equals(info->key, "step")) {
+ switch (info->type) {
+ case RD_I_CNT:
+ step = info->value.u_cnt;
+ break;
+
+ default:
+ DEBUG(self->ctx, "%s: Step is not an integer\n", path);
+ return -EINVAL;
+ }
+ }
+
+ info = info->next;
+ }
+
+ // Fail if we could not find the step
+ if (!step) {
+ ERROR(self->ctx, "Failed to determine step of %s\n", path);
+ return -EIO;
+ }
+
+ // All okay if the step matches
+ if (step == USEC_TO_SEC(HEARTBEAT / 2))
+ return 0;
+
+ // The step does not match
+ DEBUG(self->ctx, "%s: Step does not match (%d, but should be %d)\n",
+ path, step, USEC_TO_SEC(HEARTBEAT / 2));
+
+ return 1;
+}
+
+// Continues to read line as long as it matches s
+static int consume(char** line, const char* s) {
+ char* p = *line;
+
+ for (;;) {
+ // Successful if we have consumed all of s
+ if (!*s)
+ break;
+
+ // It is bad if we have run out of input
+ if (!*p)
+ return -EBADMSG;
+
+ // The strings don't seem to match
+ if (*p != *s)
+ return -EBADMSG;
+
+ // Advance both pointers
+ p++;
+ s++;
+ }
+
+ // Restore the line
+ *line = p;
+
+ return 0;
+}
+
+static int td_source_get_info(td_source* self, rrd_info_t* info,
+ const char* field, const char* key, rrd_info_type_t type, rrd_info_t** result) {
+ char* p = NULL;
+ int r;
+
+ // Find the step
+ for (; info; info = info->next) {
+ // Skip anything that does not match by type
+ if (info->type != type)
+ continue;
+
+ // Start at the beginning of the string
+ p = info->key;
+
+ // Lines must start with "ds["
+ r = consume(&p, "ds[");
+ if (r < 0)
+ continue;
+
+ // After that, we must have the field name
+ r = consume(&p, field);
+ if (r < 0)
+ continue;
+
+ // Then, we must have "]."
+ r = consume(&p, "].");
+ if (r < 0)
+ continue;
+
+ // Then we must have the key
+ r = consume(&p, key);
+ if (r < 0)
+ continue;
+
+ // Skip if we didn't reach the end of the string
+ if (*p)
+ continue;
+
+ // Match!
+ *result = info;
+ break;
+ }
+
+ return 0;
+}
+
+static int td_source_check_field_index(td_source* self, const char* path,
+ rrd_info_t* info, const char* field, unsigned long index) {
+ rrd_info_t* i = NULL;
+ int r;
+
+ // Fetch the field
+ r = td_source_get_info(self, info, field, "index", RD_I_CNT, &i);
+ if (r < 0)
+ return r;
+
+ // The field was not found
+ if (!i) {
+ DEBUG(self->ctx, "%s: Field %s does not seem to exist\n", path, field);
+ return 1;
+ }
+
+ // All good, the index matches
+ if (i->value.u_cnt == index)
+ return 0;
+
+ // The index does not match
+ DEBUG(self->ctx, "%s: Index of field %s does not match (is %d, but should be %d)\n",
+ path, field, i->value.u_cnt, index);
+
+ return 1;
+}
+
+static int td_source_check_field_type(td_source* self, const char* path,
+ rrd_info_t* info, const char* field, const char* type) {
+ rrd_info_t* i = NULL;
+ int r;
+
+ // Fetch the field
+ r = td_source_get_info(self, info, field, "type", RD_I_STR, &i);
+ if (r < 0)
+ return r;
+
+ // The field was not found
+ if (!i) {
+ DEBUG(self->ctx, "%s: Field %s does not seem to exist\n", path, field);
+ return 1;
+ }
+
+ // All good, the type matches
+ if (td_string_equals(i->value.u_str, type))
+ return 0;
+
+ // The type does not match
+ DEBUG(self->ctx, "%s: Type of field %s does not match (is %s, but should be %s)\n",
+ path, field, i->value.u_str, type);
+
+ return 1;
+}
+
+/*
+ This is necessary as rrdtool stores the limits as double and we store integers
+*/
+static int td_source_compare_limits(double current, int target) {
+ // -1 is for unlimited which is stored as NaN
+ if (isnan(current))
+ return (target == -1);
+
+ return fabs(current - target) < 1e-9;
+}
+
+static int td_source_check_field_minimum(td_source* self, const char* path,
+ rrd_info_t* info, const char* field, int min) {
+ rrd_info_t* i = NULL;
+ int r;
+
+ // Fetch the field
+ r = td_source_get_info(self, info, field, "min", RD_I_VAL, &i);
+ if (r < 0)
+ return r;
+
+ // The field was not found
+ if (!i) {
+ DEBUG(self->ctx, "%s: Field %s does not seem to exist\n", path, field);
+ return 1;
+ }
+
+ // All good, the value matches
+ if (td_source_compare_limits(i->value.u_val, min))
+ return 0;
+
+ // The minimum does not match
+ DEBUG(self->ctx, "%s: Minimum value of field %s does not match (is %lf, but should be %d)\n",
+ path, field, i->value.u_val, min);
+
+ return 1;
+}
+
+static int td_source_check_field_maximum(td_source* self, const char* path,
+ rrd_info_t* info, const char* field, int max) {
+ rrd_info_t* i = NULL;
+ int r;
+
+ // Fetch the field
+ r = td_source_get_info(self, info, field, "max", RD_I_VAL, &i);
+ if (r < 0)
+ return r;
+
+ // The field was not found
+ if (!i) {
+ DEBUG(self->ctx, "%s: Field %s does not seem to exist\n", path, field);
+ return 1;
+ }
+
+ // All good, the value matches
+ if (td_source_compare_limits(i->value.u_val, max))
+ return 0;
+
+ // The maximum does not match
+ DEBUG(self->ctx, "%s: Maximum value of field %s does not match (is %lf, but should be %d)\n",
+ path, field, i->value.u_val, max);
+
+ return 1;
+}
+
+static int td_source_needs_migration(td_source* self, const char* path, rrd_info_t* info) {
+ unsigned long index = 0;
+ int r;
+
+ // Check if the step matches
+ r = td_source_check_step(self, path, info);
+ if (r)
+ return r;
+
+ // Check all data sources
+ for (const td_rrd_ds* ds = self->impl->rrd_dss; ds->field; ds++, index++) {
+ // Check if the field index matches
+ r = td_source_check_field_index(self, path, info, ds->field, index);
+ if (r)
+ return r;
+
+ // Check if the field type matches
+ r = td_source_check_field_type(self, path, info, ds->field, ds->type);
+ if (r)
+ return r;
+
+ // Check if the field minimum matches
+ r = td_source_check_field_minimum(self, path, info, ds->field, ds->min);
+ if (r)
+ return r;
+
+ // Check if the field maximum matches
+ r = td_source_check_field_maximum(self, path, info, ds->field, ds->max);
+ if (r)
+ return r;
+ }
+
+ // XXX Check RRAs
+
+ return 0;
+}
+
+static int td_source_migrate(td_source* self, const char* path) {
+ rrd_info_t* info = NULL;
+ int r;
+
+ // Fetch information from the RRD file
+ info = rrd_info_r(path);
+
+ // Does this RRD need migration?
+ r = td_source_needs_migration(self, path, info);
+ if (r < 0) {
+ ERROR(self->ctx, "Failed to check whether %s needs migration: %s\n", path, strerror(-r));
+ goto ERROR;
+
+ // Migrate
+ } else if (r) {
+ DEBUG(self->ctx, "%s needs migration!\n", path);
+
+ // XXX TODO
+ }
+
+ERROR:
+ if (info)
+ rrd_info_free(info);
+
+ return r;
+}
+
/*
Called to write all collected samples to disk
*/
}
}
+ // Migrate the RRD file
+ r = td_source_migrate(self, path);
+ if (r < 0)
+ return r;
+
// Write the samples
r = rrd_update_r(path, NULL, num_samples, samples);
if (r < 0) {