diff --git a/HISTORY b/HISTORY index 8069a4bc..cf04c206 100644 --- a/HISTORY +++ b/HISTORY @@ -3,6 +3,8 @@ SnapRAID HISTORY 8.0 2015/01 BETA ================ + * Adds new "smart" command to print a SMART report of the array. + At now it's available only in Linux. * Adds new "up" and "down" commands to spin up and down the disks of the array. Note that "down" is available only in Linux. * Allows "sync" and "scrub" to continue after the first diff --git a/cmdline/device.c b/cmdline/device.c index 31804dd9..4073c5a6 100644 --- a/cmdline/device.c +++ b/cmdline/device.c @@ -20,18 +20,202 @@ #include "support.h" #include "state.h" +/** + * Annual Failure Rate data points from Backblaze. + * + * From: + * https://www.backblaze.com/blog-smart-stats-2014-8.html + */ +struct afr_point { + uint64_t value; /**< Value of the SMART raw attribute. */ + double afr; /**< Annual Failure Rate at this value. */ +}; + +static struct afr_point AFR_5[] = { + { 0, 0 }, + { 1, 0.027432608477803388 }, + { 4, 0.07501976284584981 }, + { 16, 0.23589260654405794 }, + { 70, 0.36193219378600433 }, + { 260, 0.5676621428968173 }, + { 1100, 1.5028253400346423 }, + { 4500, 2.0659987547404763 }, + { 17000, 1.7755385684503124 }, + { 0, 0 } +}; + +static struct afr_point AFR_187[] = { + { 0, 0 }, + { 1, 0.33877621175661743 }, + { 3, 0.5014425058387142 }, + { 11, 0.5346094598348444 }, + { 20, 0.8428063943161636 }, + { 35, 1.4429071005017484 }, + { 65, 1.6190935390549661 }, + { 0, 0 } +}; + +static struct afr_point AFR_188[] = { + { 0, 0 }, + { 1, 0.10044174089362015 }, + { 13000000000ULL, 0.334030592234279 }, + { 26000000000ULL, 0.36724705400842445 }, + { 0, 0 } +}; + +static struct afr_point AFR_193[] = { + { 0, 0 }, + { 1300, 0.024800489215129725 }, + { 5500, 0.05859661417772557 }, + { 21000, 0.19566577603409208 }, + { 90000, 0.2673688205712117 }, + { 0, 0 } +}; + +static struct afr_point AFR_197[] = { + { 0, 0 }, + { 1, 0.34196613799103254 }, + { 2, 0.6823772508117681 }, + { 16, 0.9564879341127684 }, + { 40, 1.6519989942167461 }, + { 100, 2.5137741046831956 }, + { 250, 3.3203378817413904 }, + { 0, 0 } +}; + +static struct afr_point AFR_198[] = { + { 0, 0 }, + { 1, 0.8135764944275583 }, + { 2, 1.1173469387755102 }, + { 4, 1.3558692421991083 }, + { 10, 1.7464114832535886 }, + { 12, 2.6449275362318843 }, + { 0, 0 } +}; + +/** + * Computes the estimated AFR from a set of data points. + */ +static double smart_afr_value(struct afr_point* tab, uint64_t value) +{ + unsigned i; + double delta_afr; + double delta_value; + + /* if first point */ + if (value == 0) + return 0; + + i = 1; + while (tab[i].value != 0 && tab[i].value < value) + ++i; + + /* if last point */ + if (tab[i].value == 0) + return tab[i-1].afr; + + /* if exact value */ + if (tab[i].value == value) + return tab[i].afr; + + delta_afr = tab[i].afr - tab[i-1].afr; + delta_value = tab[i].value - tab[i-1].value; + + /* linear interpolation between the two points */ + return tab[i-1].afr + (value - tab[i-1].value) * delta_afr / delta_value; +} + +/** + * Computes the estimated AFR of a set of SMART attributes. + */ +static double smart_afr(uint64_t* smart) +{ + double afr = 0; + + if (smart[5] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_5, smart[5]); + + if (smart[187] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_187, smart[187]); + + if (smart[188] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_188, smart[188]); + + if (smart[193] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_193, smart[193]); + + if (smart[197] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_197, smart[197]); + + if (smart[198] != SMART_UNASSIGNED) + afr += smart_afr_value(AFR_198, smart[198]); + + return afr; +} + +static void state_smart(tommy_list* low) +{ + tommy_node* i; + + printf("SnapRAID SMART report:\n"); + printf("\n"); + printf(" Temp"); + printf(" Power"); + printf("\n"); + printf(" C°"); + printf(" OnDays"); + printf(" AFR"); + printf(" Size"); + printf(" Serial/Name"); + printf("\n"); + printf(" --------------------------------------------------------------------------\n"); + + for (i = tommy_list_head(low); i != 0; i = i->next) { + devinfo_t* devinfo = i->data; + double afr; + + if (devinfo->smart[194] != SMART_UNASSIGNED) + printf("%7" PRIu64, devinfo->smart[194]); + else if (devinfo->smart[190] != SMART_UNASSIGNED) + printf("%7" PRIu64, devinfo->smart[190]); + else + printf(" -"); + + if (devinfo->smart[9] != SMART_UNASSIGNED) + printf("%7" PRIu64, devinfo->smart[9] / 24); + else + printf(" -"); + + afr = smart_afr(devinfo->smart); + + printf("%5.0f%%", afr * 100); + + printf(" %2.1fT", devinfo->smart_size / 1E12); + + printf(" %s", devinfo->smart_serial); + + printf("/%s", devinfo->name); + + printf("\n"); + } + + printf("\n"); +} + void state_device(struct snapraid_state* state, int operation) { tommy_node* i; unsigned j; - tommy_list list; + tommy_list high; + tommy_list low; switch (operation) { case DEVICE_UP : printf("Spinup...\n"); break; case DEVICE_DOWN : printf("Spindown...\n"); break; } - tommy_list_init(&list); + tommy_list_init(&high); + tommy_list_init(&low); /* for all disks */ for (i = state->disklist; i != 0; i = i->next) { @@ -44,7 +228,7 @@ void state_device(struct snapraid_state* state, int operation) pathcpy(entry->name, sizeof(entry->name), disk->name); pathcpy(entry->mount, sizeof(entry->mount), disk->dir); - tommy_list_insert_tail(&list, &entry->node, entry); + tommy_list_insert_tail(&high, &entry->node, entry); } /* for all parities */ @@ -58,18 +242,35 @@ void state_device(struct snapraid_state* state, int operation) pathcpy(entry->mount, sizeof(entry->mount), state->parity[j].path); pathcut(entry->mount); /* remove the parity file */ - tommy_list_insert_tail(&list, &entry->node, entry); + tommy_list_insert_tail(&high, &entry->node, entry); } - if (devquery(&list, operation) != 0) { + if (devquery(&high, &low, operation) != 0) { switch (operation) { case DEVICE_UP : fprintf(stderr, "Spinup"); break; case DEVICE_DOWN : fprintf(stderr, "Spindown"); break; case DEVICE_LIST : fprintf(stderr, "List"); break; + case DEVICE_SMART : fprintf(stderr, "SMART"); break; } fprintf(stderr, " unsupported in this platform.\n"); } - tommy_list_foreach(&list, free); +#ifndef _WIN32 + if (operation == DEVICE_LIST) { + for (i = tommy_list_head(&low); i != 0; i = i->next) { + devinfo_t* devinfo = i->data; + devinfo_t* parent = devinfo->parent; + + printf("%u:%u\t%s\t%u:%u\t%s\t%s\n", major(devinfo->device), minor(devinfo->device), devinfo->file, major(parent->device), minor(parent->device), parent->file, parent->name); + } + } +#endif + + if (operation == DEVICE_SMART) { + state_smart(&low); + } + + tommy_list_foreach(&high, free); + tommy_list_foreach(&low, free); } diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 203a6423..25b1c4f5 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -1821,17 +1821,19 @@ static void* thread_spinup(void* arg) return 0; } -int devquery(tommy_list* list, int operation) +int devquery(tommy_list* high, tommy_list* low, int operation) { tommy_node* i; int fail = 0; + (void)low; + /* we support only thread_spinup */ if (operation != DEVICE_UP) return -1; /* starts all threads */ - for (i = tommy_list_head(list); i != 0; i = i->next) { + for (i = tommy_list_head(high); i != 0; i = i->next) { devinfo_t* devinfo = i->data; if (pthread_create(&devinfo->thread, 0, thread_spinup, devinfo) != 0) { @@ -1843,7 +1845,7 @@ int devquery(tommy_list* list, int operation) } /* joins all threads */ - for (i = tommy_list_head(list); i != 0; i = i->next) { + for (i = tommy_list_head(high); i != 0; i = i->next) { devinfo_t* devinfo = i->data; void* retval; if (pthread_join(devinfo->thread, &retval) != 0) { diff --git a/cmdline/portable.h b/cmdline/portable.h index 728fb84c..97d19888 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -303,13 +303,32 @@ int exit_failure; int randomize(void* ptr, size_t size); /** - * Disk entry. + * SMART attributes. + */ +#define SMART_COUNT 256 + +/** + * SMART max attribute length. + */ +#define SMART_MAX 64 + +/** + * Value for unassigned SMART attribute. + */ +#define SMART_UNASSIGNED 0xFFFFFFFFFFFFFFFFULL + +/** + * Device info entry. */ struct devinfo_struct { uint64_t device; /**< Device ID. */ char name[PATH_MAX]; /**< Name of the disk. */ char mount[PATH_MAX]; /**< Mount point or other contained directory. */ char file[PATH_MAX]; /**< File device. */ + struct devinfo_struct* parent; /**< Pointer at the parent if any. */ + uint64_t smart[SMART_COUNT]; /**< SMART raw attributes. */ + uint64_t smart_size; /**< SMART size. */ + char smart_serial[SMART_MAX]; /**< SMART serial number. */ #if HAVE_PTHREAD_CREATE pthread_t thread; #endif @@ -317,14 +336,18 @@ struct devinfo_struct { }; typedef struct devinfo_struct devinfo_t; -#define DEVICE_DOWN 0 -#define DEVICE_UP 1 -#define DEVICE_LIST -1 +#define DEVICE_LIST 0 +#define DEVICE_DOWN 1 +#define DEVICE_UP 2 +#define DEVICE_SMART 3 /** - * Spin all disks. + * Query all the "high" level devices with the specified operation, + * and produces a list of "low" level devices to operate on. + * + * The resulting "low" device list must be already initialized. */ -int devquery(tommy_list* list, int operation); +int devquery(tommy_list* high, tommy_list* low, int operation); #endif diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 14da10e0..1c244688 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -37,7 +37,7 @@ void usage(void) { version(); - printf("Usage: " PACKAGE " status|diff|sync|scrub|list|dup|up|down|pool|check|fix [options]\n"); + printf("Usage: " PACKAGE " status|diff|sync|scrub|list|dup|up|down|smart|pool|check|fix [options]\n"); printf("\n"); printf("Commands:\n"); printf(" status Print the status of the array\n"); @@ -48,6 +48,7 @@ void usage(void) printf(" dup Find duplicate files\n"); printf(" up Spinup the array\n"); printf(" down Spindown the array\n"); + printf(" smart SMART attributes of the array\n"); printf(" pool Create or update the virtual view of the array\n"); printf(" check Check the array\n"); printf(" fix Fix the array\n"); @@ -384,6 +385,7 @@ void signal_handler(int signal) #define OPERATION_SPINUP 13 #define OPERATION_SPINDOWN 14 #define OPERATION_DEVICES 15 +#define OPERATION_SMART 16 int main(int argc, char* argv[]) { @@ -743,6 +745,8 @@ int main(int argc, char* argv[]) operation = OPERATION_SPINDOWN; } else if (strcmp(argv[optind], "test-devices") == 0) { operation = OPERATION_DEVICES; + } else if (strcmp(argv[optind], "smart") == 0) { + operation = OPERATION_SMART; } else { /* LCOV_EXCL_START */ fprintf(stderr, "Unknown command '%s'\n", argv[optind]); @@ -861,7 +865,7 @@ int main(int argc, char* argv[]) case OPERATION_STATUS : case OPERATION_REWRITE : case OPERATION_REHASH : - case OPERATION_SPINUP : /* skip because we want to do it in parallel */ + case OPERATION_SPINUP : /* we want to do it in different threads to avoid blocking */ /* avoid to check and access data disks if not needed */ opt.skip_disk_access = 1; break; @@ -876,7 +880,7 @@ int main(int argc, char* argv[]) case OPERATION_REWRITE : case OPERATION_REHASH : case OPERATION_NANO : - case OPERATION_SPINUP : /* skip because we want to do it in parallel */ + case OPERATION_SPINUP : /* we want to do it in different threads to avoid blocking */ /* avoid to check and access parity disks if not needed */ opt.skip_parity_access = 1; break; @@ -891,6 +895,7 @@ int main(int argc, char* argv[]) case OPERATION_SPINUP : case OPERATION_SPINDOWN : case OPERATION_DEVICES : + case OPERATION_SMART : opt.skip_self = 1; break; } @@ -1083,6 +1088,8 @@ int main(int argc, char* argv[]) state_device(&state, DEVICE_DOWN); } else if (operation == OPERATION_DEVICES) { state_device(&state, DEVICE_LIST); + } else if (operation == OPERATION_SMART) { + state_device(&state, DEVICE_SMART); } else if (operation == OPERATION_STATUS) { state_read(&state); diff --git a/cmdline/status.c b/cmdline/status.c index 1cb08022..b78bc143 100644 --- a/cmdline/status.c +++ b/cmdline/status.c @@ -95,6 +95,7 @@ int state_status(struct snapraid_state* state) } fprintf(stdlog, "summary:parity_block_free_min:%u\n", parity_block_free); + printf("SnapRAID status report:\n"); printf("\n"); printf(" Files Fragmented Excess Wasted Used Free Use Name\n"); printf(" Files Fragments GiB GiB GiB \n"); diff --git a/cmdline/unix.c b/cmdline/unix.c index 1d57db6f..9a251bd9 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -458,7 +458,7 @@ static int devresolve(dev_t device, char* path, size_t path_size) * Read a device tree filling the specified list of disk_t entries. */ #if HAVE_LINUX_DEVICE -static int devtree(const char* name, dev_t device, tommy_list* list) +static int devtree(const char* name, dev_t device, devinfo_t* parent, tommy_list* list) { char path[PATH_MAX]; DIR* d; @@ -483,7 +483,7 @@ static int devtree(const char* name, dev_t device, tommy_list* list) /* LCOV_EXCL_STOP */ } - if (devtree(name, device, list) != 0) { + if (devtree(name, device, parent, list) != 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ @@ -516,7 +516,7 @@ static int devtree(const char* name, dev_t device, tommy_list* list) } } - /* get the real name of the device */ + /* get the device file */ if (devresolve(device, path, sizeof(path)) != 0) { /* LCOV_EXCL_START */ return -1; @@ -528,6 +528,7 @@ static int devtree(const char* name, dev_t device, tommy_list* list) devinfo->device = device; pathcpy(devinfo->name, sizeof(devinfo->name), name); pathcpy(devinfo->file, sizeof(devinfo->file), path); + devinfo->parent = parent; /* insert in the list */ tommy_list_insert_tail(list, &devinfo->node, devinfo); @@ -537,6 +538,84 @@ static int devtree(const char* name, dev_t device, tommy_list* list) } #endif +/** + * Gets SMART attributes. + */ +static int devsmart(dev_t device, uint64_t* smart, uint64_t* size, char* serial) +{ + char cmd[128]; + FILE* f; + int inside; + unsigned i; + + *serial = 0; + for (i = 0; i < SMART_COUNT; ++i) + smart[i] = SMART_UNASSIGNED; + + snprintf(cmd, sizeof(cmd), "smartctl -a /dev/block/%u:%u", major(device), minor(device)); + + f = popen(cmd, "r"); + if (!f) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Failed to run smartctrl -a on device '%u:%u'.\n", major(device), minor(device)); + return -1; + /* LCOV_EXCL_STOP */ + } + + inside = 0; + while (1) { + char buf[256]; + char attr[64]; + unsigned id; + unsigned long long raw; + char* s; + + s = fgets(buf, sizeof(buf), f); + + if (s == 0) + break; + + /* skip initial spaces */ + while (isspace(*s)) + ++s; + + if (*s == 0) { + inside = 0; + } else if (strncmp(s, "ID# ", 4) == 0) { + inside = 1; + } else if (sscanf(s, "Serial Number: %63s", serial) == 1) { + } else if (sscanf(s, "User Capacity: %63s", attr) == 1) { + *size = 0; + for (i = 0; attr[i]; ++i) { + if (isdigit(attr[i])) { + *size *= 10; + *size += attr[i] - '0'; + } + } + } else if (inside) { + if (sscanf(s, "%u %*s %*s %*u %*u %*u %*s %*s %*s %llu", &id, &raw) != 2) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Invalid smartctrl line '%s'.\n", buf); + return -1; + /* LCOV_EXCL_STOP */ + } + + if (id >= SMART_COUNT) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Invalid SMART id '%u'.\n", id); + return -1; + /* LCOV_EXCL_STOP */ + } + + smart[id] = raw; + } + } + + pclose(f); + + return 0; +} + /** * Spin down a specific device. */ @@ -645,7 +724,7 @@ static void* thread_spinup(void* arg) /* set the device number for printing */ devinfo->device = st.st_dev; - /* get the block device */ + /* get the device file */ if (devresolve(devinfo->device, devinfo->file, sizeof(devinfo->file)) != 0) { /* LCOV_EXCL_START */ return (void*)-1; @@ -670,12 +749,11 @@ static void* thread_spindown(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; - dev_t device = devinfo->device; uint64_t start; start = tick_ms(); - if (devdown(device) != 0) { + if (devdown(devinfo->device) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ @@ -691,48 +769,20 @@ static void* thread_spindown(void* arg) } /** - * Lists all the devices. + * Thread for spinning down. */ -#if HAVE_LINUX_DEVICE -static int device_list(tommy_list* list) +static void* thread_smart(void* arg) { - tommy_node* i; + devinfo_t* devinfo = arg; - for (i = tommy_list_head(list); i != 0; i = i->next) { - tommy_list tree; - tommy_node* j; - devinfo_t* devinfo = i->data; - - tommy_list_init(&tree); - - /* get the block device */ - if (devresolve(devinfo->device, devinfo->file, sizeof(devinfo->file)) != 0) { - /* LCOV_EXCL_START */ - return -1; - /* LCOV_EXCL_STOP */ - } - - /* expand the tree of devices */ - if (devtree(devinfo->name, devinfo->device, &tree) != 0) { - /* LCOV_EXCL_START */ - tommy_list_foreach(&tree, free); - fprintf(stderr, "Failed to expand device '%u:%u'.\n", major(devinfo->device), minor(devinfo->device)); - return -1; - /* LCOV_EXCL_STOP */ - } - - for (j = tommy_list_head(&tree); j != 0; j = j->next) { - devinfo_t* entry = j->data; - - printf("%u:%u\t%s\t%u:%u\t%s\t%s\n", major(entry->device), minor(entry->device), entry->file, major(devinfo->device), minor(devinfo->device), devinfo->file, devinfo->name); - } - - tommy_list_foreach(&tree, free); + if (devsmart(devinfo->device, devinfo->smart, &devinfo->smart_size, devinfo->smart_serial) != 0) { + /* LCOV_EXCL_START */ + return (void*)-1; + /* LCOV_EXCL_STOP */ } return 0; } -#endif static int device_thread(tommy_list* list, void* (*func)(void* arg)) { @@ -784,16 +834,13 @@ static int device_thread(tommy_list* list, void* (*func)(void* arg)) return 0; } -int devquery(tommy_list* list, int operation) +int devquery(tommy_list* high, tommy_list* low, int operation) { tommy_node* i; - tommy_list tree; - int ret; - - tommy_list_init(&tree); + void* (*func)(void* arg) = 0; #if HAVE_LINUX_DEVICE - if (operation == DEVICE_DOWN || operation == DEVICE_LIST) { + if (operation != DEVICE_UP) { struct stat st; /* sysfs and devfs interfaces are required */ if (stat("/sys/dev/block", &st) != 0) { @@ -808,20 +855,22 @@ int devquery(tommy_list* list, int operation) return -1; /* LCOV_EXCL_STOP */ } - } - if (operation == DEVICE_LIST) - return device_list(list); - - if (operation == DEVICE_DOWN) { /* for each device */ - for (i = tommy_list_head(list); i != 0; i = i->next) { + for (i = tommy_list_head(high); i != 0; i = i->next) { devinfo_t* devinfo = i->data; - /* expand the tree of devices */ - if (devtree(devinfo->name, devinfo->device, &tree) != 0) { + /* get the device file */ + if (devresolve(devinfo->device, devinfo->file, sizeof(devinfo->file)) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Failed to resolve device '%u:%u'.\n", major(devinfo->device), minor(devinfo->device)); + return -1; + /* LCOV_EXCL_STOP */ + } + + /* expand the tree of devices */ + if (devtree(devinfo->name, devinfo->device, devinfo, low) != 0) { /* LCOV_EXCL_START */ - tommy_list_foreach(&tree, free); fprintf(stderr, "Failed to expand device '%u:%u'.\n", major(devinfo->device), minor(devinfo->device)); return -1; /* LCOV_EXCL_STOP */ @@ -831,8 +880,8 @@ int devquery(tommy_list* list, int operation) #endif if (operation == DEVICE_UP) { - /* duplicate the list */ - for (i = tommy_list_head(list); i != 0; i = i->next) { + /* duplicate the high */ + for (i = tommy_list_head(high); i != 0; i = i->next) { devinfo_t* devinfo = i->data; devinfo_t* entry; @@ -842,25 +891,30 @@ int devquery(tommy_list* list, int operation) pathcpy(entry->name, sizeof(entry->name), devinfo->name); pathcpy(entry->mount, sizeof(entry->mount), devinfo->mount); - /* insert in the list */ - tommy_list_insert_tail(&tree, &entry->node, entry); + /* insert in the high */ + tommy_list_insert_tail(low, &entry->node, entry); } } - /* sort the list */ - tommy_list_sort(&tree, devcompare); + /* sort the high */ + tommy_list_sort(low, devcompare); /* removes duplicates */ - for (i = tommy_list_head(&tree); i != 0; i = i->next) { + for (i = tommy_list_head(low); i != 0; i = i->next) { while (i->next != 0 && devcompare(i->data, i->next->data) == 0) - free(tommy_list_remove_existing(&tree, i->next)); + free(tommy_list_remove_existing(low, i->next)); } - ret = device_thread(&tree, operation == DEVICE_UP ? thread_spinup : thread_spindown); + switch (operation) { + case DEVICE_UP : func = thread_spinup; break; + case DEVICE_DOWN : func = thread_spindown; break; + case DEVICE_SMART : func = thread_smart; break; + } - tommy_list_foreach(&tree, free); + if (!func) + return 0; - return ret; + return device_thread(low, func); } void os_init(int opt)