Add preliminary SMART support with a new "smart" command.

This commit is contained in:
Andrea Mazzoleni 2015-01-14 22:29:20 +01:00
parent 16b872e30c
commit 4a41adc807
7 changed files with 375 additions and 85 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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) {

View file

@ -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

View file

@ -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);

View file

@ -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");

View file

@ -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)