mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 19:01:57 -04:00
Add thermal protection options: temp_limit and temp_sleep
Introduce two new configuration directives to protect disks from overheating: - temp_limit <TEMPERATURE_CELSIUS> Sets the maximum allowed disk temperature. When any disk exceeds this limit, SnapRAID stops all operations and spins down the disks. - temp_sleep <TIME_IN_MINUTES> Specifies how long disks remain in standby after a temperature event. Defaults to 15 minutes. These options allow SnapRAID to automatically pause activity and cool down disks when a temperature threshold is reached, improving data safety and hardware longevity.
This commit is contained in:
parent
b5bbece95e
commit
906d215bf5
23 changed files with 1144 additions and 81 deletions
10
HISTORY
10
HISTORY
|
|
@ -3,6 +3,16 @@ SnapRAID HISTORY
|
|||
|
||||
13.0 2025/XX
|
||||
============
|
||||
* Add new thermal protection configuration options:
|
||||
- temp_limit TEMPERATURE_CELSIUS
|
||||
Sets the maximum allowed disk temperature. When any disk exceeds this
|
||||
limit, SnapRAID stops all operations and spins down the disks to prevent
|
||||
overheating.
|
||||
- temp_sleep TIME_IN_MINUTES
|
||||
Defines how long the disks remain in standby after a temperature limit
|
||||
event. Defaults to 15 minutes.
|
||||
These options allow SnapRAID to automatically pause activity and cool down
|
||||
disks when a temperature threshold is reached.
|
||||
* Add a new "probe" command that shows the spinning status of all disks.
|
||||
* Add a new -s, --spin-down-on-error option that spins down all disks
|
||||
when a command ends with an error.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ snapraid_SOURCES = \
|
|||
cmdline/speed.c \
|
||||
cmdline/import.c \
|
||||
cmdline/search.c \
|
||||
cmdline/thermal.c \
|
||||
cmdline/mingw.c \
|
||||
cmdline/unix.c
|
||||
|
||||
|
|
@ -96,6 +97,7 @@ noinst_HEADERS = \
|
|||
cmdline/fnmatch.h \
|
||||
cmdline/import.h \
|
||||
cmdline/search.h \
|
||||
cmdline/thermal.h \
|
||||
cmdline/mingw.h \
|
||||
cmdline/unix.h
|
||||
|
||||
|
|
|
|||
|
|
@ -1458,6 +1458,21 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna
|
|||
break;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* thermal control */
|
||||
if (state_thermal_alarm(state)) {
|
||||
/* until now is misc */
|
||||
state_usage_misc(state);
|
||||
|
||||
state_progress_stop(state);
|
||||
|
||||
state_thermal_cooldown(state);
|
||||
|
||||
state_progress_restart(state);
|
||||
|
||||
/* drop until now */
|
||||
state_usage_waste(state);
|
||||
}
|
||||
}
|
||||
|
||||
/* for each disk, recover empty files, symlinks and empty dirs */
|
||||
|
|
|
|||
|
|
@ -345,6 +345,21 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit
|
|||
break;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* thermal control */
|
||||
if (state_thermal_alarm(state)) {
|
||||
/* until now is misc */
|
||||
state_usage_misc(state);
|
||||
|
||||
state_progress_stop(state);
|
||||
|
||||
state_thermal_cooldown(state);
|
||||
|
||||
state_progress_restart(state);
|
||||
|
||||
/* drop until now */
|
||||
state_usage_waste(state);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
|
|
|
|||
|
|
@ -1549,9 +1549,16 @@ const char* windows_stat_desc(struct stat* st)
|
|||
return st->st_desc;
|
||||
}
|
||||
|
||||
void windows_sleep(unsigned seconds)
|
||||
unsigned windows_sleep(unsigned seconds)
|
||||
{
|
||||
Sleep(seconds * 1000);
|
||||
while (seconds) {
|
||||
if (global_interrupt) /* SIGINT should stop the sleep */
|
||||
break;
|
||||
Sleep(1000);
|
||||
--seconds;
|
||||
}
|
||||
|
||||
return seconds;
|
||||
}
|
||||
|
||||
void windows_usleep(uint64_t useconds)
|
||||
|
|
@ -3029,5 +3036,10 @@ int windows_join(thread_id_t thread, void** retval)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int ambient_temperature(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ const char* windows_stat_desc(struct stat* st);
|
|||
/**
|
||||
* Like sleep().
|
||||
*/
|
||||
void windows_sleep(unsigned seconds);
|
||||
unsigned windows_sleep(unsigned seconds);
|
||||
|
||||
/**
|
||||
* List usleep().
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
#include "stream.h"
|
||||
#include "support.h"
|
||||
|
||||
volatile int global_interrupt = 0;
|
||||
|
||||
#define STREAM_MAX 8
|
||||
#define BUFFER_MAX 64
|
||||
#define STR_MAX 128
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "support.h"
|
||||
|
||||
volatile int global_interrupt = 0;
|
||||
|
||||
/****************************************************************************/
|
||||
/* random */
|
||||
|
||||
|
|
|
|||
|
|
@ -533,5 +533,17 @@ typedef struct devinfo_struct devinfo_t;
|
|||
*/
|
||||
int devquery(tommy_list* high, tommy_list* low, int operation, int others);
|
||||
|
||||
/**
|
||||
* Get the ambient temperature in degree
|
||||
*
|
||||
* Return 0 if not available.
|
||||
*/
|
||||
int ambient_temperature(void);
|
||||
|
||||
/**
|
||||
* Global variable to identify if Ctrl+C is pressed.
|
||||
*/
|
||||
extern volatile int global_interrupt;
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -627,6 +627,21 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* thermal control */
|
||||
if (state_thermal_alarm(state)) {
|
||||
/* until now is misc */
|
||||
state_usage_misc(state);
|
||||
|
||||
state_progress_stop(state);
|
||||
|
||||
state_thermal_cooldown(state);
|
||||
|
||||
state_progress_restart(state);
|
||||
|
||||
/* drop until now */
|
||||
state_usage_waste(state);
|
||||
}
|
||||
|
||||
/* autosave */
|
||||
if (state->autosave != 0
|
||||
&& autosavedone >= autosavelimit /* if we have reached the limit */
|
||||
|
|
|
|||
169
cmdline/state.c
169
cmdline/state.c
|
|
@ -26,6 +26,7 @@
|
|||
#include "stream.h"
|
||||
#include "handle.h"
|
||||
#include "io.h"
|
||||
#include "thermal.h"
|
||||
#include "raid/raid.h"
|
||||
#include "raid/cpu.h"
|
||||
|
||||
|
|
@ -177,12 +178,20 @@ void state_init(struct snapraid_state* state)
|
|||
state->no_conf = 0;
|
||||
for (i = 0; i < SMART_IGNORE_MAX; ++i)
|
||||
state->smartignore[i] = 0;
|
||||
state->thermal_stop_gathering = 0;
|
||||
state->thermal_ambient_temperature = 0;
|
||||
state->thermal_highest_temperature = 0;
|
||||
state->thermal_first = 0;
|
||||
state->thermal_latest = 0;
|
||||
state->thermal_temperature_limit = 0;
|
||||
state->thermal_cooldown_time = 0;
|
||||
|
||||
tommy_list_init(&state->disklist);
|
||||
tommy_list_init(&state->maplist);
|
||||
tommy_list_init(&state->contentlist);
|
||||
tommy_list_init(&state->filterlist);
|
||||
tommy_list_init(&state->importlist);
|
||||
tommy_list_init(&state->thermallist);
|
||||
tommy_hashdyn_init(&state->importset);
|
||||
tommy_hashdyn_init(&state->previmportset);
|
||||
tommy_hashdyn_init(&state->searchset);
|
||||
|
|
@ -196,6 +205,7 @@ void state_done(struct snapraid_state* state)
|
|||
tommy_list_foreach(&state->contentlist, (tommy_foreach_func*)content_free);
|
||||
tommy_list_foreach(&state->filterlist, (tommy_foreach_func*)filter_free);
|
||||
tommy_list_foreach(&state->importlist, (tommy_foreach_func*)import_file_free);
|
||||
tommy_list_foreach(&state->thermallist, (tommy_foreach_func*)thermal_free);
|
||||
tommy_hashdyn_foreach(&state->searchset, (tommy_foreach_func*)search_file_free);
|
||||
tommy_hashdyn_done(&state->importset);
|
||||
tommy_hashdyn_done(&state->previmportset);
|
||||
|
|
@ -1154,6 +1164,42 @@ void state_config(struct snapraid_state* state, const char* path, const char* co
|
|||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
} else if (strcmp(tag, "temp_limit") == 0) {
|
||||
unsigned temp;
|
||||
|
||||
ret = sgetu32(f, &temp);
|
||||
if (ret < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Invalid 'temp_limit' specification in '%s' at line %u\n", path, line);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
if (temp < 40 || temp > 70) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Invalid 'temp_limit' temperature specification in '%s' at line %u. It must be between 40 and 70\n", path, line);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
state->thermal_temperature_limit = temp;
|
||||
} else if (strcmp(tag, "temp_sleep") == 0) {
|
||||
unsigned time;
|
||||
|
||||
ret = sgetu32(f, &time);
|
||||
if (ret < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Invalid 'temp_sleep' specification in '%s' at line %u\n", path, line);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
if (time < 5 || time > 120) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Invalid 'temp_sleep' temperature specification in '%s' at line %u. It must be between 5 and 120\n", path, line);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
state->thermal_cooldown_time = time * 60;
|
||||
} else if (strcmp(tag, "nohidden") == 0) {
|
||||
state->filter_hidden = 1;
|
||||
} else if (strcmp(tag, "exclude") == 0) {
|
||||
|
|
@ -4300,11 +4346,14 @@ int state_progress_begin(struct snapraid_state* state, block_off_t blockstart, b
|
|||
now = time(0);
|
||||
|
||||
state->progress_whole_start = now;
|
||||
|
||||
state->progress_tick = 0;
|
||||
state->progress_ptr = 0;
|
||||
state->progress_wasted = 0;
|
||||
|
||||
/* start of thermal control */
|
||||
if (!state_thermal_begin(state, now))
|
||||
return 0;
|
||||
|
||||
/* stop if requested */
|
||||
if (global_interrupt) {
|
||||
/* LCOV_EXCL_START */
|
||||
|
|
@ -4403,13 +4452,37 @@ static void state_progress_latest(struct snapraid_state* state)
|
|||
/**
|
||||
* Number of columns
|
||||
*/
|
||||
#define GRAPH_COLUMN 68
|
||||
#define GRAPH_COLUMN 78
|
||||
|
||||
/**
|
||||
* Get the reference value, 0 if index is PROGRESS_MAX
|
||||
*/
|
||||
#define ref(map, index) (index < PROGRESS_MAX ? map[index] : 0)
|
||||
|
||||
static struct snapraid_thermal* state_thermal_find(struct snapraid_state* state, const char* name)
|
||||
{
|
||||
tommy_node* i;
|
||||
struct snapraid_thermal* found = 0;
|
||||
|
||||
for (i = tommy_list_head(&state->thermallist); i != 0; i = i->next) {
|
||||
struct snapraid_thermal* thermal = i->data;
|
||||
if (strcmp(thermal->name, name) == 0) {
|
||||
if (found == 0
|
||||
// if multiple matches, return the one with highest temperature
|
||||
|| found->latest_temperature < thermal->latest_temperature
|
||||
// or, if the temperature is equal, the one with the higher r_squared
|
||||
|| (found->latest_temperature == thermal->latest_temperature && found->params.r_squared < thermal->params.r_squared)
|
||||
) {
|
||||
found = thermal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
#define THERMAL_PAD " "
|
||||
|
||||
static void state_progress_graph(struct snapraid_state* state, struct snapraid_io* io, unsigned current, unsigned oldest)
|
||||
{
|
||||
uint64_t v;
|
||||
|
|
@ -4418,6 +4491,7 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
tommy_node* i;
|
||||
unsigned l;
|
||||
size_t pad;
|
||||
size_t pre;
|
||||
|
||||
tick_total = 0;
|
||||
|
||||
|
|
@ -4445,10 +4519,11 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
/* extra space */
|
||||
pad += 1;
|
||||
|
||||
if (pad + 30 < GRAPH_COLUMN)
|
||||
bar = GRAPH_COLUMN - pad;
|
||||
pre = 4;
|
||||
if (pad + pre + 30 < GRAPH_COLUMN)
|
||||
bar = GRAPH_COLUMN - pad - pre;
|
||||
else
|
||||
bar = 30;
|
||||
bar = 30; /* at least a bar of 30 */
|
||||
|
||||
if (io) {
|
||||
const char* legend = "cached blocks (instant, more is better)";
|
||||
|
|
@ -4458,7 +4533,6 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
|
||||
printf("\n");
|
||||
|
||||
/* search for the slowest */
|
||||
for (i = state->disklist; i != 0; i = i->next) {
|
||||
struct snapraid_disk* disk = i->data;
|
||||
v = disk->cached_blocks;
|
||||
|
|
@ -4493,10 +4567,33 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
|
||||
printf("\n");
|
||||
|
||||
pre = 4;
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
pre += strlen(THERMAL_PAD);
|
||||
if (pad + pre + 30 < GRAPH_COLUMN)
|
||||
bar = GRAPH_COLUMN - pad - pre;
|
||||
else
|
||||
bar = 30; /* at least a bar of 30 */
|
||||
|
||||
for (i = state->disklist; i != 0; i = i->next) {
|
||||
struct snapraid_disk* disk = i->data;
|
||||
|
||||
|
||||
v = disk->progress_tick[current] - ref(disk->progress_tick, oldest);
|
||||
printr(disk->name, pad);
|
||||
|
||||
if (state->thermal_temperature_limit != 0) {
|
||||
struct snapraid_thermal* thermal = state_thermal_find(state, disk->name);
|
||||
if (thermal) {
|
||||
if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
|
||||
printf(" %2u (%2u)", thermal->latest_temperature, (int)thermal->params.t_steady);
|
||||
else
|
||||
printf(" %2u ", thermal->latest_temperature);
|
||||
} else {
|
||||
printf(THERMAL_PAD);
|
||||
}
|
||||
}
|
||||
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
|
@ -4508,6 +4605,19 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
for (l = 0; l < state->level; ++l) {
|
||||
v = state->parity[l].progress_tick[current] - ref(state->parity[l].progress_tick, oldest);
|
||||
printr(lev_config_name(l), pad);
|
||||
|
||||
if (state->thermal_temperature_limit != 0) {
|
||||
struct snapraid_thermal* thermal = state_thermal_find(state, lev_config_name(l));
|
||||
if (thermal) {
|
||||
if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
|
||||
printf(" %2u (%2u)", thermal->latest_temperature, (int)thermal->params.t_steady);
|
||||
else
|
||||
printf(" %2u ", thermal->latest_temperature);
|
||||
} else {
|
||||
printf(THERMAL_PAD);
|
||||
}
|
||||
}
|
||||
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
|
@ -4515,40 +4625,54 @@ static void state_progress_graph(struct snapraid_state* state, struct snapraid_i
|
|||
|
||||
v = state->progress_tick_raid[current] - ref(state->progress_tick_raid, oldest);
|
||||
printr("raid", pad);
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
||||
v = state->progress_tick_hash[current] - ref(state->progress_tick_hash, oldest);
|
||||
printr("hash", pad);
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
||||
v = state->progress_tick_sched[current] - ref(state->progress_tick_sched, oldest);
|
||||
printr("sched", pad);
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
||||
v = state->progress_tick_misc[current] - ref(state->progress_tick_misc, oldest);
|
||||
printr("misc", pad);
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printf("%3u%% | ", muldiv(v, 100, tick_total));
|
||||
printc('*', v * bar / tick_total);
|
||||
printf("\n");
|
||||
|
||||
printc(' ', pad);
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printf(" |_");
|
||||
printc('_', bar);
|
||||
printf("\n");
|
||||
|
||||
if (oldest == PROGRESS_MAX) {
|
||||
const char* legend = "wait time (total, less is better)";
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printc(' ', 5 + pad + 1 + bar / 2 - strlen(legend) / 2);
|
||||
printf("%s", legend);
|
||||
printf("\n");
|
||||
} else {
|
||||
const char* legend_d = "wait time (last %d secs, less is better)";
|
||||
if (state->thermal_temperature_limit != 0)
|
||||
printf(THERMAL_PAD);
|
||||
printc(' ', 5 + pad + 1 + bar / 2 - strlen(legend_d) / 2);
|
||||
printf(legend_d, PROGRESS_MAX);
|
||||
printf("\n");
|
||||
|
|
@ -4564,6 +4688,12 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
|
|||
|
||||
now = time(0);
|
||||
|
||||
/* thermal measure */
|
||||
if (now > state->thermal_latest + THERMAL_PERIOD_SECONDS) {
|
||||
state->thermal_latest = now;
|
||||
state_thermal(state, now);
|
||||
}
|
||||
|
||||
/* previous position */
|
||||
pred = state->progress_ptr + PROGRESS_MAX - 1;
|
||||
if (pred >= PROGRESS_MAX)
|
||||
|
|
@ -4579,6 +4709,8 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
|
|||
unsigned out_block_speed = 0;
|
||||
unsigned out_cpu = 0;
|
||||
unsigned out_eta = 0;
|
||||
int out_temperature = 0;
|
||||
int out_steady = 0;
|
||||
int out_computed = 0;
|
||||
|
||||
/* store the new measure */
|
||||
|
|
@ -4609,6 +4741,7 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
|
|||
uint64_t delta_tick_total;
|
||||
uint64_t oldest_tick_cpu;
|
||||
uint64_t oldest_tick_total;
|
||||
tommy_node* i;
|
||||
|
||||
/* number of past measures */
|
||||
past = state->progress_tick;
|
||||
|
|
@ -4668,6 +4801,24 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
|
|||
state_progress_graph(state, io, state->progress_ptr, oldest);
|
||||
}
|
||||
|
||||
if (state->thermal_temperature_limit != 0) {
|
||||
/* get the max temperature */
|
||||
out_temperature = 0;
|
||||
for (i = tommy_list_head(&state->thermallist); i != 0; i = i->next) {
|
||||
struct snapraid_thermal* thermal = i->data;
|
||||
if (out_temperature < thermal->latest_temperature) {
|
||||
out_temperature = thermal->latest_temperature;
|
||||
if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
|
||||
out_steady = thermal->params.t_steady;
|
||||
else
|
||||
out_steady = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out_temperature = 0;
|
||||
out_steady = 0;
|
||||
}
|
||||
|
||||
/* we have the output value */
|
||||
out_computed = 1;
|
||||
}
|
||||
|
|
@ -4681,6 +4832,12 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
|
|||
msg_bar(", %u MB/s", out_size_speed);
|
||||
msg_bar(", %u stripe/s", out_block_speed);
|
||||
msg_bar(", CPU %u%%", out_cpu);
|
||||
if (out_temperature != 0) {
|
||||
if (out_steady != 0)
|
||||
msg_bar(", Tmax %u (%u)", out_temperature, out_steady);
|
||||
else
|
||||
msg_bar(", Tmax %u", out_temperature);
|
||||
}
|
||||
msg_bar(", %u:%02u ETA", out_eta / 60, out_eta % 60);
|
||||
}
|
||||
msg_bar("%s\r", PROGRESS_CLEAR);
|
||||
|
|
|
|||
|
|
@ -56,11 +56,6 @@ const char* lev_config_name(unsigned level);
|
|||
#define GIBI (1024 * 1024 * 1024)
|
||||
#define TEBI (1024 * 1024 * 1024 * 1024LL)
|
||||
|
||||
/**
|
||||
* Global variable to identify if Ctrl+C is pressed.
|
||||
*/
|
||||
extern volatile int global_interrupt;
|
||||
|
||||
#define SORT_PHYSICAL 1 /**< Sort by physical order. */
|
||||
#define SORT_INODE 2 /**< Sort by inode. */
|
||||
#define SORT_ALPHA 3 /**< Sort by alphabetic order. */
|
||||
|
|
@ -155,6 +150,16 @@ struct snapraid_state {
|
|||
tommy_hashdyn searchset; /**< Hashtable by timestamp of all the search files. */
|
||||
tommy_arrayblkof infoarr; /**< Block information array. */
|
||||
|
||||
/* Thermal */
|
||||
tommy_list thermallist; /**< List of all thermal state */
|
||||
int thermal_stop_gathering; /**< If thermal data gathering is stopped */
|
||||
int thermal_ambient_temperature; /**< Ambient themperature. 0 if not available */
|
||||
int thermal_highest_temperature; /**< Current highest temperature */
|
||||
time_t thermal_first; /**< Time of the first measure */
|
||||
time_t thermal_latest; /**< Time of latest measure */
|
||||
int thermal_cooldown_time; /**< Cooldown wait time in seconds */
|
||||
int thermal_temperature_limit; /**< Upper limit temperature of the operating range */
|
||||
|
||||
/**
|
||||
* Cumulative time used for computations.
|
||||
*/
|
||||
|
|
@ -377,6 +382,26 @@ void state_usage_print(struct snapraid_state* state);
|
|||
*/
|
||||
void state_fscheck(struct snapraid_state* state, const char* ope);
|
||||
|
||||
/**
|
||||
* Measure the temperature of all disks
|
||||
*/
|
||||
void state_thermal(struct snapraid_state* state, time_t now);
|
||||
|
||||
/**
|
||||
* Check if the temperature is outside the operating range
|
||||
*/
|
||||
int state_thermal_alarm(struct snapraid_state* state);
|
||||
|
||||
/**
|
||||
* Cool down the system
|
||||
*/
|
||||
void state_thermal_cooldown(struct snapraid_state* state);
|
||||
|
||||
/**
|
||||
* Start of thermal processing
|
||||
*/
|
||||
int state_thermal_begin(struct snapraid_state* state, time_t now);
|
||||
|
||||
/****************************************************************************/
|
||||
/* misc */
|
||||
|
||||
|
|
|
|||
|
|
@ -631,6 +631,57 @@ unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* d
|
|||
return mac;
|
||||
}
|
||||
|
||||
void strtrim(char* str)
|
||||
{
|
||||
char* begin;
|
||||
char* end;
|
||||
|
||||
begin = str;
|
||||
while (begin[0] && isspace((unsigned char)begin[0]))
|
||||
++begin;
|
||||
|
||||
end = begin + strlen(begin);
|
||||
while (end > begin && isspace((unsigned char)end[-1]))
|
||||
--end;
|
||||
|
||||
end[0] = 0;
|
||||
|
||||
if (begin != end)
|
||||
memmove(str, begin, end - begin + 1);
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
void strlwr(char* s)
|
||||
{
|
||||
while (*s) {
|
||||
*s = tolower((unsigned char)*s);
|
||||
++s;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
char* worddigitstr(const char* haystack, const char* needle)
|
||||
{
|
||||
size_t len = strlen(needle);
|
||||
const char* s;
|
||||
|
||||
if (len == 0)
|
||||
return NULL;
|
||||
|
||||
for (s = haystack; (s = strstr(s, needle)) != NULL; ++s) {
|
||||
|
||||
/* left boundary */
|
||||
if (s == haystack || isspace((unsigned char)s[-1]) || isdigit((unsigned char)s[-1])) {
|
||||
/* right boundary */
|
||||
if (s[len] == '\0' || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) {
|
||||
return (char *)s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* path */
|
||||
|
||||
|
|
|
|||
|
|
@ -250,6 +250,26 @@ char* strpolish(char* s);
|
|||
*/
|
||||
unsigned strsplit(char** split_map, unsigned split_max, char* line, const char* delimiters);
|
||||
|
||||
/**
|
||||
* Trim spaces from the start and the end
|
||||
*/
|
||||
void strtrim(char* s);
|
||||
|
||||
/**
|
||||
* Lower case
|
||||
*/
|
||||
#ifndef __MINGW32__
|
||||
void strlwr(char* s);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Find the first occurrence of 'needle' in 'haystack' only if it
|
||||
* appears as a separate word (space, number or string boundaries).
|
||||
*
|
||||
* Returns a pointer to the beginning of the match, or NULL if not found.
|
||||
*/
|
||||
char* worddigitstr(const char* haystack, const char* needle);
|
||||
|
||||
/****************************************************************************/
|
||||
/* path */
|
||||
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,31 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* thermal control */
|
||||
if (state_thermal_alarm(state)) {
|
||||
/* until now is misc */
|
||||
state_usage_misc(state);
|
||||
|
||||
state_progress_stop(state);
|
||||
|
||||
/* before spinning down flush all the caches */
|
||||
ret = state_flush(state, &io, parity_handle, blockcur);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
state_thermal_cooldown(state);
|
||||
|
||||
state_progress_restart(state);
|
||||
|
||||
/* drop until now */
|
||||
state_usage_waste(state);
|
||||
}
|
||||
|
||||
/* autosave */
|
||||
if ((state->autosave != 0
|
||||
&& autosavedone >= autosavelimit /* if we have reached the limit */
|
||||
|
|
|
|||
367
cmdline/thermal.c
Normal file
367
cmdline/thermal.c
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Andrea Mazzoleni
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "portable.h"
|
||||
|
||||
#include "thermal.h"
|
||||
#include "state.h"
|
||||
#include "io.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
struct snapraid_thermal* thermal_alloc(uint64_t dev, const char* name)
|
||||
{
|
||||
struct snapraid_thermal* thermal = malloc_nofail(sizeof(struct snapraid_thermal));
|
||||
|
||||
thermal->device = dev;
|
||||
thermal->latest_temperature = 0;
|
||||
thermal->count = 0;
|
||||
pathcpy(thermal->name, sizeof(thermal->name), name);
|
||||
|
||||
return thermal;
|
||||
}
|
||||
|
||||
void thermal_free(struct snapraid_thermal* thermal)
|
||||
{
|
||||
free(thermal);
|
||||
}
|
||||
|
||||
// Fit exponential heating model to data using least squares
|
||||
struct snapraid_thermal_params fit_thermal_model(const struct snapraid_thermal_point* points, int n_points, double t_ambient)
|
||||
{
|
||||
struct snapraid_thermal_params model;
|
||||
double t_steady_try;
|
||||
double k_try;
|
||||
|
||||
memset(&model, 0, sizeof(model));
|
||||
|
||||
model.t_ambient = t_ambient;
|
||||
|
||||
/* at least three points to have a result */
|
||||
if (n_points < 3)
|
||||
return model;
|
||||
|
||||
double last_temp = points[n_points - 1].temperature;
|
||||
|
||||
/* iterative refinement to find best k_heat and t_steady */
|
||||
double best_error = 1e10;
|
||||
double best_k = 0;
|
||||
double best_t_steady = 0;
|
||||
|
||||
/* grid search for parameters */
|
||||
for (t_steady_try = last_temp + 2.0; t_steady_try <= last_temp + 25.0; t_steady_try += 0.5) {
|
||||
for (k_try = 0.00001; k_try <= 0.001; k_try *= 1.2) {
|
||||
double error = 0.0;
|
||||
int i;
|
||||
|
||||
/* calculate error for this parameter set */
|
||||
for (i = 0; i < n_points; i++) {
|
||||
double t = points[i].time;
|
||||
double t_predicted = t_steady_try - (t_steady_try - points[0].temperature) * exp(-k_try * t);
|
||||
double diff = points[i].temperature - t_predicted;
|
||||
error += diff * diff;
|
||||
}
|
||||
|
||||
if (error < best_error) {
|
||||
best_error = error;
|
||||
best_k = k_try;
|
||||
best_t_steady = t_steady_try;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.k_heat = best_k;
|
||||
model.t_steady = best_t_steady;
|
||||
|
||||
/* calculate quality metrics */
|
||||
double sum_squared_residuals = 0.0;
|
||||
double sum_total = 0.0;
|
||||
double mean_temp = 0.0;
|
||||
model.max_error = 0.0;
|
||||
int i;
|
||||
|
||||
/* calculate mean temperature */
|
||||
for (i = 0; i < n_points; i++)
|
||||
mean_temp += points[i].temperature;
|
||||
mean_temp /= n_points;
|
||||
|
||||
/* calculate R-squared and errors */
|
||||
for (i = 0; i < n_points; i++) {
|
||||
double t = points[i].time;
|
||||
double t_predicted = model.t_steady - (model.t_steady - points[0].temperature) * exp(-model.k_heat * t);
|
||||
double residual = points[i].temperature - t_predicted;
|
||||
|
||||
sum_squared_residuals += residual * residual;
|
||||
sum_total += (points[i].temperature - mean_temp) * (points[i].temperature - mean_temp);
|
||||
|
||||
double abs_error = fabs(residual);
|
||||
if (abs_error > model.max_error) {
|
||||
model.max_error = abs_error;
|
||||
}
|
||||
}
|
||||
|
||||
model.rmse = sqrt(sum_squared_residuals / n_points);
|
||||
model.r_squared = 1.0 - (sum_squared_residuals / sum_total);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
static int smart_temp(devinfo_t* devinfo)
|
||||
{
|
||||
uint64_t t = devinfo->smart[SMART_TEMPERATURE_CELSIUS];
|
||||
|
||||
/* validate temperature */
|
||||
if (t == SMART_UNASSIGNED)
|
||||
return -1;
|
||||
if (t == 0)
|
||||
return -1;
|
||||
if (t > 100)
|
||||
return -1;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void state_thermal(struct snapraid_state* state, time_t now)
|
||||
{
|
||||
tommy_node* i;
|
||||
unsigned j;
|
||||
tommy_list high;
|
||||
tommy_list low;
|
||||
int ret;
|
||||
|
||||
if (state->thermal_temperature_limit == 0)
|
||||
return;
|
||||
|
||||
tommy_list_init(&high);
|
||||
tommy_list_init(&low);
|
||||
|
||||
/* for all disks */
|
||||
for (i = state->disklist; i != 0; i = i->next) {
|
||||
struct snapraid_disk* disk = i->data;
|
||||
devinfo_t* entry;
|
||||
|
||||
entry = calloc_nofail(1, sizeof(devinfo_t));
|
||||
|
||||
entry->device = disk->device;
|
||||
pathcpy(entry->name, sizeof(entry->name), disk->name);
|
||||
pathcpy(entry->mount, sizeof(entry->mount), disk->dir);
|
||||
pathcpy(entry->smartctl, sizeof(entry->smartctl), disk->smartctl);
|
||||
memcpy(entry->smartignore, disk->smartignore, sizeof(entry->smartignore));
|
||||
|
||||
tommy_list_insert_tail(&high, &entry->node, entry);
|
||||
}
|
||||
|
||||
/* for all parities */
|
||||
for (j = 0; j < state->level; ++j) {
|
||||
devinfo_t* entry;
|
||||
unsigned s;
|
||||
|
||||
for (s = 0; s < state->parity[j].split_mac; ++s) {
|
||||
entry = calloc_nofail(1, sizeof(devinfo_t));
|
||||
|
||||
entry->device = state->parity[j].split_map[s].device;
|
||||
pathcpy(entry->name, sizeof(entry->name), lev_config_name(j));
|
||||
pathcpy(entry->mount, sizeof(entry->mount), state->parity[j].split_map[s].path);
|
||||
pathcpy(entry->smartctl, sizeof(entry->smartctl), state->parity[j].smartctl);
|
||||
memcpy(entry->smartignore, state->parity[j].smartignore, sizeof(entry->smartignore));
|
||||
pathcut(entry->mount); /* remove the parity file */
|
||||
|
||||
tommy_list_insert_tail(&high, &entry->node, entry);
|
||||
}
|
||||
}
|
||||
|
||||
ret = devquery(&high, &low, DEVICE_SMART, 0 /* only disks in the array */);
|
||||
|
||||
/* on error, just disable thermal gathering */
|
||||
if (ret != 0)
|
||||
return;
|
||||
|
||||
/* if the list is empty, it's not supported in this platform */
|
||||
if (tommy_list_empty(&low))
|
||||
return;
|
||||
|
||||
/* if ambient temperature is not set, set it now with the lowest HD temperature */
|
||||
if (state->thermal_ambient_temperature == 0) {
|
||||
state->thermal_ambient_temperature = ambient_temperature();
|
||||
|
||||
for (i = tommy_list_head(&low); i != 0; i = i->next) {
|
||||
devinfo_t* devinfo = i->data;
|
||||
|
||||
int temp = smart_temp(devinfo);
|
||||
if (temp < 0)
|
||||
continue;
|
||||
|
||||
log_tag("thermal:ambient:candidate:%d\n", temp);
|
||||
|
||||
if (state->thermal_ambient_temperature == 0 || state->thermal_ambient_temperature > temp)
|
||||
state->thermal_ambient_temperature = temp;
|
||||
}
|
||||
|
||||
log_tag("thermal:ambient:final:%d\n", state->thermal_ambient_temperature);
|
||||
}
|
||||
|
||||
int highest_temperature = 0;
|
||||
for (i = tommy_list_head(&low); i != 0; i = i->next) {
|
||||
tommy_node* t;
|
||||
struct snapraid_thermal* found;
|
||||
devinfo_t* devinfo = i->data;
|
||||
unsigned k;
|
||||
|
||||
int temperature = smart_temp(devinfo);
|
||||
if (temperature < 0)
|
||||
continue;
|
||||
|
||||
/* search of the entry */
|
||||
found = 0;
|
||||
for (t = tommy_list_head(&state->thermallist); t != 0; t = t->next) {
|
||||
struct snapraid_thermal* thermal = t->data;
|
||||
if (thermal->device == devinfo->device) {
|
||||
found = thermal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if not found, create it */
|
||||
if (found == 0) {
|
||||
found = thermal_alloc(devinfo->device, devinfo->name);
|
||||
tommy_list_insert_tail(&state->thermallist, &found->node, found);
|
||||
}
|
||||
|
||||
found->latest_temperature = temperature;
|
||||
|
||||
if (highest_temperature < temperature)
|
||||
highest_temperature = temperature;
|
||||
|
||||
log_tag("thermal:current:%s:%" PRIu64 ":%d\n", devinfo->name, devinfo->device, temperature);
|
||||
|
||||
if (state->thermal_stop_gathering)
|
||||
continue;
|
||||
|
||||
if (found->count + 1 >= THERMAL_MAX) /* keep one extra space at the end */
|
||||
continue;
|
||||
|
||||
/* only monotone increasing temperature */
|
||||
if (found->count > 0 && found->data[found->count - 1].temperature >= temperature)
|
||||
continue;
|
||||
|
||||
/* insert the new data point */
|
||||
found->data[found->count].temperature = temperature;
|
||||
found->data[found->count].time = now - state->thermal_first;
|
||||
++found->count;
|
||||
|
||||
log_tag("thermal:heat:%s:%" PRIu64 ":%u:", devinfo->name, devinfo->device, found->count);
|
||||
for (k = 0; k < found->count; ++k)
|
||||
log_tag("%s%d/%d", k > 0 ? "," : "", (int)found->data[k].temperature, (int)found->data[k].time);
|
||||
log_tag("\n");
|
||||
|
||||
/* estimate parameters */
|
||||
found->params = fit_thermal_model(found->data, found->count, state->thermal_ambient_temperature);
|
||||
|
||||
log_tag("thermal:params:%s:%" PRIu64 ":%g:%g:%g:%g:%g:%g\n", devinfo->name, devinfo->device,
|
||||
found->params.k_heat, found->params.t_ambient, found->params.t_steady,
|
||||
found->params.rmse, found->params.r_squared, found->params.max_error);
|
||||
}
|
||||
|
||||
/* always update the highest temperature */
|
||||
state->thermal_highest_temperature = highest_temperature;
|
||||
|
||||
log_tag("thermal:highest:%d\n", highest_temperature);
|
||||
log_flush();
|
||||
|
||||
tommy_list_foreach(&high, free);
|
||||
tommy_list_foreach(&low, free);
|
||||
}
|
||||
|
||||
int state_thermal_alarm(struct snapraid_state* state)
|
||||
{
|
||||
/* if no limit, there is no thermal support */
|
||||
if (state->thermal_temperature_limit == 0)
|
||||
return 0;
|
||||
|
||||
if (state->thermal_highest_temperature <= state->thermal_temperature_limit)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void state_thermal_cooldown(struct snapraid_state* state)
|
||||
{
|
||||
unsigned sleep_time = state->thermal_cooldown_time;
|
||||
|
||||
if (sleep_time == 0)
|
||||
sleep_time = 15 * 60; /* default sleep time */
|
||||
if (sleep_time < 5 * 60)
|
||||
sleep_time = 5 * 60; /* minimum sleep time */
|
||||
|
||||
/* from now on, stop any further data gathering as the heating is interrupted */
|
||||
state->thermal_stop_gathering = 1;
|
||||
|
||||
log_tag("thermal:spindown\n");
|
||||
state_device(state, DEVICE_DOWN, 0);
|
||||
|
||||
msg_progress("Cooldown...\n");
|
||||
|
||||
log_tag("thermal:cooldown:%d\n", sleep_time);
|
||||
printf("Waiting for %d minutes...\n", sleep_time / 60);
|
||||
|
||||
log_flush();
|
||||
|
||||
sleep(sleep_time);
|
||||
|
||||
if (!global_interrupt) { /* don't wake-up if we are interrupting */
|
||||
log_tag("thermal:spinup\n");
|
||||
|
||||
/* spinup */
|
||||
state_device(state, DEVICE_UP, 0);
|
||||
|
||||
/* log new thermal info */
|
||||
state_thermal(state, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int state_thermal_begin(struct snapraid_state* state, time_t now)
|
||||
{
|
||||
if (state->thermal_temperature_limit == 0)
|
||||
return 1;
|
||||
|
||||
/* initial thermal measure */
|
||||
state->thermal_first = now;
|
||||
state->thermal_latest = now;
|
||||
state_thermal(state, now);
|
||||
|
||||
if (state->thermal_ambient_temperature != 0) {
|
||||
printf("Ambient temperature %u\n", state->thermal_ambient_temperature);
|
||||
|
||||
if (state->thermal_temperature_limit != 0 && state->thermal_temperature_limit <= state->thermal_ambient_temperature) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Ambient temperature %d higher than the temperature limit %d. Impossible to proceeed!\n", state->thermal_ambient_temperature, state->thermal_temperature_limit);
|
||||
log_flush();
|
||||
return 0;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
if (state_thermal_alarm(state)) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Hard disk temperature %d is already outside the operating range. Impossible to proceeed!\n", state->thermal_highest_temperature);
|
||||
log_flush();
|
||||
return 0;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
75
cmdline/thermal.h
Normal file
75
cmdline/thermal.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Andrea Mazzoleni
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __THERMAL_H
|
||||
#define __THERMAL_H
|
||||
|
||||
#include "elem.h"
|
||||
|
||||
/****************************************************************************/
|
||||
/* thermal */
|
||||
|
||||
/**
|
||||
* Period of temperature measures
|
||||
*/
|
||||
#define THERMAL_PERIOD_SECONDS 60
|
||||
|
||||
/**
|
||||
* Max number of temperature measures
|
||||
*/
|
||||
#define THERMAL_MAX 100
|
||||
|
||||
/**
|
||||
* Minimum value of the R^2 to consider valid a measure
|
||||
*/
|
||||
#define THERMAL_R_SQUARED_LIMIT 0.9
|
||||
|
||||
struct snapraid_thermal_point {
|
||||
double temperature; /**< Temperatures in celsius */
|
||||
double time; /**< Time in seconds */
|
||||
};
|
||||
|
||||
struct snapraid_thermal_params {
|
||||
double k_heat; /**< Heating coefficient */
|
||||
double t_steady; /**< Steady state temperature during heating */
|
||||
double t_ambient; /**< Ambient temperature */
|
||||
double rmse; /**< Root mean square error of the fit */
|
||||
double r_squared; /**< Coefficient of determination */
|
||||
double max_error; /**< Maximum absolute error in fit */
|
||||
};
|
||||
|
||||
struct snapraid_thermal {
|
||||
uint64_t device; /**< Device ID. */
|
||||
char name[PATH_MAX]; /**< Name of the disk. Note that it's not unique as more physical disks may map to the same logical disk */
|
||||
int latest_temperature; /**< Latest temperature */
|
||||
struct snapraid_thermal_point data[THERMAL_MAX];
|
||||
unsigned count; /**< Number of measures */
|
||||
struct snapraid_thermal_params params; /**< Estimated thermal parameters */
|
||||
tommy_node node; /**< Next node in the list. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocate a thermal state.
|
||||
*/
|
||||
struct snapraid_thermal* thermal_alloc(uint64_t dev, const char* name);
|
||||
|
||||
/**
|
||||
* Deallocate a thermal state.
|
||||
*/
|
||||
void thermal_free(struct snapraid_thermal* thermal);
|
||||
|
||||
#endif
|
||||
213
cmdline/unix.c
213
cmdline/unix.c
|
|
@ -68,6 +68,52 @@ const char* stat_desc(struct stat* st)
|
|||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a file from sys
|
||||
*
|
||||
* Return -1 on error, otherwise the data read
|
||||
*/
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int sysread(const char* path, char* buf, size_t buf_size)
|
||||
{
|
||||
int f;
|
||||
int ret;
|
||||
int len;
|
||||
|
||||
f = open(path, O_RDONLY);
|
||||
if (f == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Failed to open '%s'.\n", path);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
len = read(f, buf, buf_size);
|
||||
if (len < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(f);
|
||||
log_fatal("Failed to read '%s'.\n", path);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
ret = close(f);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Failed to close '%s'.\n", path);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (len == (int)buf_size)
|
||||
buf[buf_size - 1] = 0;
|
||||
else
|
||||
buf[len] = 0;
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the device file from the device number.
|
||||
*
|
||||
|
|
@ -248,47 +294,22 @@ static int devdereference(uint64_t device, uint64_t* new_device)
|
|||
#if HAVE_LINUX_DEVICE
|
||||
static int tagread(const char* path, const char* tag, char* value, size_t value_size)
|
||||
{
|
||||
int f;
|
||||
int ret;
|
||||
int len;
|
||||
char buf[512];
|
||||
size_t tag_len;
|
||||
char* i;
|
||||
char* e;
|
||||
|
||||
f = open(path, O_RDONLY);
|
||||
if (f == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Failed to open '%s'.\n", path);
|
||||
return 0;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
len = read(f, buf, sizeof(buf));
|
||||
if (len < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(f);
|
||||
log_fatal("Failed to read '%s'.\n", path);
|
||||
ret = sysread(path, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
if (len == sizeof(buf)) {
|
||||
if (ret == sizeof(buf)) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(f);
|
||||
log_fatal("Too long read '%s'.\n", path);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
ret = close(f);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Failed to close '%s'.\n", path);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
buf[len] = 0;
|
||||
tag_len = strlen(tag);
|
||||
|
||||
for (i = buf; *i; ++i) {
|
||||
|
|
@ -1706,5 +1727,141 @@ size_t direct_size(void)
|
|||
return size;
|
||||
}
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
|
||||
/* List of possible ambient temperature labels */
|
||||
const char* AMBIENT_LABEL[] = {
|
||||
"systin",
|
||||
"auxtin",
|
||||
"mb",
|
||||
"board",
|
||||
"motherboard",
|
||||
"system",
|
||||
"chassis",
|
||||
"case",
|
||||
"room",
|
||||
"ambient",
|
||||
0
|
||||
};
|
||||
|
||||
int ambient_temperature(void)
|
||||
{
|
||||
DIR* dir;
|
||||
struct dirent* entry;
|
||||
int lowest_temp = 0;
|
||||
|
||||
dir = opendir("/sys/class/hwmon");
|
||||
if (!dir)
|
||||
return 0;
|
||||
|
||||
/* iterate through hwmon devices */
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
char path[PATH_MAX];
|
||||
DIR* hwmon_dir;
|
||||
struct dirent* hwmon_entry;
|
||||
|
||||
if (strncmp(entry->d_name, "hwmon", 5) != 0)
|
||||
continue;
|
||||
|
||||
pathprint(path, sizeof(path), "/sys/class/hwmon/%s", entry->d_name);
|
||||
|
||||
/* iterate through temp*_input files */
|
||||
hwmon_dir = opendir(path);
|
||||
if (!hwmon_dir)
|
||||
continue;
|
||||
|
||||
while ((hwmon_entry = readdir(hwmon_dir)) != NULL) {
|
||||
char value[128];
|
||||
char name[128];
|
||||
char label[128];
|
||||
char* dash;
|
||||
int ret;
|
||||
char* e;
|
||||
int temp;
|
||||
|
||||
if (strncmp(hwmon_entry->d_name, "temp", 4) != 0)
|
||||
continue;
|
||||
|
||||
dash = strrchr(hwmon_entry->d_name, '_');
|
||||
if (dash == 0)
|
||||
continue;
|
||||
|
||||
if (strcmp(dash, "_input") != 0)
|
||||
continue;
|
||||
|
||||
/* read the temperature */
|
||||
pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s", entry->d_name, hwmon_entry->d_name);
|
||||
|
||||
ret = sysread(path, value, sizeof(value));
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
temp = strtol(value, &e, 10) / 1000;
|
||||
if (*e != 0 && !isspace(*e))
|
||||
continue;
|
||||
|
||||
/* cut the file name at "_input" */
|
||||
*dash = 0;
|
||||
|
||||
/* read the corresponding name */
|
||||
pathprint(path, sizeof(path), "/sys/class/hwmon/%s/name", entry->d_name);
|
||||
ret = sysread(path, name, sizeof(name));
|
||||
if (ret < 0) {
|
||||
/* fallback to using the hwmon entry */
|
||||
pathcpy(name, sizeof(name), entry->d_name);
|
||||
}
|
||||
|
||||
/* read the corresponding label file */
|
||||
pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s_label", entry->d_name, hwmon_entry->d_name);
|
||||
ret = sysread(path, label, sizeof(label));
|
||||
if (ret < 0) {
|
||||
/* fallback to using the temp* name (e.g., temp1, temp2) */
|
||||
pathcpy(label, sizeof(label), hwmon_entry->d_name);
|
||||
}
|
||||
|
||||
/* trim spaces */
|
||||
strtrim(label);
|
||||
|
||||
log_tag("thermal:ambient:device:%s:%s:%s:%s:%d\n", entry->d_name, name, hwmon_entry->d_name, label, temp);
|
||||
|
||||
/* check if temperature is in reasonable range */
|
||||
if (temp < 15 || temp > 40)
|
||||
continue;
|
||||
|
||||
/* lower case */
|
||||
strlwr(label);
|
||||
|
||||
/* check if label matches possible ambient labels */
|
||||
for (int i = 0; AMBIENT_LABEL[i]; ++i) {
|
||||
if (worddigitstr(label, AMBIENT_LABEL[i]) != 0) {
|
||||
log_tag("thermal:ambient:candidate:%d\n", temp);
|
||||
if (lowest_temp == 0 || lowest_temp > temp)
|
||||
lowest_temp = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* accept also generic "temp1" */
|
||||
if (strcmp(label, "temp1") == 0) {
|
||||
log_tag("thermal:ambient:candidate:%d\n", temp);
|
||||
if (lowest_temp == 0 || lowest_temp > temp)
|
||||
lowest_temp = temp;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(hwmon_dir);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return lowest_temp;
|
||||
}
|
||||
#else
|
||||
int ambient_temperature(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
15
snapraid.1
15
snapraid.1
|
|
@ -1448,11 +1448,22 @@ RAM = (8 * 4 * 10^12) * (1+8) / (512 * 2^10) = 0.51 GiB
|
|||
.PD 0
|
||||
.PD
|
||||
.SS autosave SIZE_IN_GIGABYTES
|
||||
Automatically save the state when syncing or scrubbing after the specified amount
|
||||
of GB processed.
|
||||
Automatically save the state when syncing or scrubbing after the
|
||||
specified amount of GB processed.
|
||||
This option is useful to avoid to restart from scratch long \[dq]sync\[dq]
|
||||
commands interrupted by a machine crash, or any other event that
|
||||
may interrupt SnapRAID.
|
||||
.SS temp_limit TEMPERATURE_CELSIUS
|
||||
Sets the maximum allowed disk temperature in Celsius. When specified,
|
||||
SnapRAID periodically checks the temperature of all disks using the
|
||||
smartctl tool. The current disk temperatures are displayed during
|
||||
processing. If any disk exceeds this limit, all operations stop and
|
||||
the disks are spun down (put into standby) for the duration defined
|
||||
by \[dq]temp_sleep\[dq].
|
||||
.SS temp_sleep TIME_IN_MINUTES
|
||||
Sets the standby time, in minutes, when the temperature limit is
|
||||
reached. During this period, the disks remain spun down. The default
|
||||
is 15 minutes.
|
||||
.SS pool DIR
|
||||
Defines the pooling directory where the virtual view of the disk
|
||||
array is created using the \[dq]pool\[dq] command.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,18 @@ exclude /lost+found/
|
|||
# Format: "autosave SIZE_IN_GB"
|
||||
#autosave 500
|
||||
|
||||
# Set the maximum allowed disk temperature (in Celsius).
|
||||
# If any disk reaches or exceeds this temperature,
|
||||
# SnapRAID stops all operations and spins down all disks
|
||||
# to prevent overheating.
|
||||
#temp_limit 50
|
||||
|
||||
# Set the standby duration (in minutes) after a temperature event.
|
||||
# During this period, disks remain spun down to cool down
|
||||
# before SnapRAID automatically resumes operations.
|
||||
# Default is 15 minutes if not specified.
|
||||
#temp_sleep 10
|
||||
|
||||
# Defines the pooling directory where the virtual view of the disk
|
||||
# array is created using the "pool" command (uncomment to enable).
|
||||
# The files are not really copied here, but just linked using
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ exclude \Windows\
|
|||
# Format: "autosave SIZE_IN_GB"
|
||||
#autosave 500
|
||||
|
||||
# Set the maximum allowed disk temperature (in Celsius).
|
||||
# If any disk reaches or exceeds this temperature,
|
||||
# SnapRAID stops all operations and spins down all disks
|
||||
# to prevent overheating.
|
||||
#temp_limit 50
|
||||
|
||||
# Set the standby duration (in minutes) after a temperature event.
|
||||
# During this period, disks remain spun down to cool down
|
||||
# before SnapRAID automatically resumes operations.
|
||||
# Default is 15 minutes if not specified.
|
||||
#temp_sleep 10
|
||||
|
||||
# Defines the pooling directory where the virtual view of the disk
|
||||
# array is created using the "pool" command (uncomment to enable).
|
||||
# The files are not really copied here, but just linked using
|
||||
|
|
|
|||
17
snapraid.d
17
snapraid.d
|
|
@ -1166,12 +1166,25 @@ Configuration
|
|||
:RAM = (8 * 4 * 10^12) * (1+8) / (512 * 2^10) = 0.51 GiB
|
||||
|
||||
autosave SIZE_IN_GIGABYTES
|
||||
Automatically save the state when syncing or scrubbing after the specified amount
|
||||
of GB processed.
|
||||
Automatically save the state when syncing or scrubbing after the
|
||||
specified amount of GB processed.
|
||||
This option is useful to avoid to restart from scratch long "sync"
|
||||
commands interrupted by a machine crash, or any other event that
|
||||
may interrupt SnapRAID.
|
||||
|
||||
temp_limit TEMPERATURE_CELSIUS
|
||||
Sets the maximum allowed disk temperature in Celsius. When specified,
|
||||
SnapRAID periodically checks the temperature of all disks using the
|
||||
smartctl tool. The current disk temperatures are displayed during
|
||||
processing. If any disk exceeds this limit, all operations stop and
|
||||
the disks are spun down (put into standby) for the duration defined
|
||||
by "temp_sleep".
|
||||
|
||||
temp_sleep TIME_IN_MINUTES
|
||||
Sets the standby time, in minutes, when the temperature limit is
|
||||
reached. During this period, the disks remain spun down. The default
|
||||
is 15 minutes.
|
||||
|
||||
pool DIR
|
||||
Defines the pooling directory where the virtual view of the disk
|
||||
array is created using the "pool" command.
|
||||
|
|
|
|||
123
snapraid.txt
123
snapraid.txt
|
|
@ -12,7 +12,7 @@ snapraid [-c, --conf CONFIG]
|
|||
[-a, --audit-only] [-h, --pre-hash] [-i, --import DIR]
|
||||
[-p, --plan PERC|bad|new|full]
|
||||
[-o, --older-than DAYS] [-l, --log FILE]
|
||||
[-w, --bw-limit RATE]
|
||||
[-s, --spin-down-on-error] [-w, --bw-limit RATE]
|
||||
[-Z, --force-zero] [-E, --force-empty]
|
||||
[-U, --force-uuid] [-D, --force-device]
|
||||
[-N, --force-nocopy] [-F, --force-full]
|
||||
|
|
@ -20,8 +20,8 @@ snapraid [-c, --conf CONFIG]
|
|||
[-S, --start BLKSTART] [-B, --count BLKCOUNT]
|
||||
[-L, --error-limit NUMBER]
|
||||
[-v, --verbose] [-q, --quiet]
|
||||
status|smart|up|down|diff|sync|scrub|fix|check|list|dup
|
||||
|pool|devices|touch|rehash
|
||||
status|smart|probe|up|down|diff|sync|scrub|fix|check
|
||||
|list|dup|pool|devices|touch|rehash
|
||||
|
||||
snapraid [-V, --version] [-H, --help] [-C, --gen-conf CONTENT]
|
||||
|
||||
|
|
@ -403,7 +403,7 @@ If everything is recovered, this command is immediate.
|
|||
SnapRAID provides a few simple commands that allow to:
|
||||
|
||||
* Prints the status of the array -> "status"
|
||||
* Controls the disks -> "smart", "up", "down"
|
||||
* Controls the disks -> "smart", "probe", "up", "down"
|
||||
* Makes a backup/snapshot -> "sync"
|
||||
* Periodically checks data -> "scrub"
|
||||
* Restore the last backup/snapshot -> "fix".
|
||||
|
|
@ -434,7 +434,7 @@ Nothing is modified.
|
|||
5.2 smart
|
||||
---------
|
||||
|
||||
Prints a SMART report of all the disks of the array.
|
||||
Prints a SMART report of all the disks in the system.
|
||||
|
||||
It includes an estimation of the probability of failure in the next
|
||||
year allowing to plan maintenance replacements of the disks that show
|
||||
|
|
@ -459,16 +459,33 @@ If the -v, --verbose option is specified a deeper statistical analysis
|
|||
is provided. This analysis can help you to decide if you need more
|
||||
or less parity.
|
||||
|
||||
This command uses the "smartctl" tool, and it's equivalent to run
|
||||
"smartctl -a" on all the devices.
|
||||
This command uses the "smartctl" tool and is equivalent to running
|
||||
"smartctl -a" on all devices.
|
||||
|
||||
If your devices are not auto-detected correctly, you can configure
|
||||
If your devices are not auto-detected correctly, you can specify
|
||||
a custom command using the "smartctl" option in the configuration
|
||||
file.
|
||||
|
||||
Nothing is modified.
|
||||
|
||||
5.3 up
|
||||
5.3 probe
|
||||
---------
|
||||
|
||||
Prints the POWER state of all disks in the system.
|
||||
|
||||
"Standby" means the disk is not spinning. "Active" means
|
||||
the disk is spinning.
|
||||
|
||||
This command uses the "smartctl" tool and is equivalent to running
|
||||
"smartctl -n standby -i" on all devices.
|
||||
|
||||
If your devices are not auto-detected correctly, you can specify
|
||||
a custom command using the "smartctl" option in the configuration
|
||||
file.
|
||||
|
||||
Nothing is modified.
|
||||
|
||||
5.4 up
|
||||
------
|
||||
|
||||
Spins up all the disks of the array.
|
||||
|
|
@ -480,7 +497,7 @@ Ensure that your power-supply can sustain that.
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.4 down
|
||||
5.5 down
|
||||
--------
|
||||
|
||||
Spins down all the disks of the array.
|
||||
|
|
@ -488,11 +505,16 @@ Spins down all the disks of the array.
|
|||
This command uses the "smartctl" tool, and it's equivalent to run
|
||||
"smartctl -s standby,now" on all the devices.
|
||||
|
||||
You can spin-down only some specific disks using the -d, --filter-disk option.
|
||||
You can spin-down only some specific disks using the -d, --filter-disk
|
||||
option.
|
||||
|
||||
To automatically spin down on error, you can use the option
|
||||
-s, --spin-down-on-error with any other command, which is equivalent
|
||||
to running "down" manually when an error occurs.
|
||||
|
||||
Nothing is modified.
|
||||
|
||||
5.5 diff
|
||||
5.6 diff
|
||||
--------
|
||||
|
||||
Lists all the files modified from the last "sync" that need to have
|
||||
|
|
@ -525,7 +547,7 @@ default 0. The return code 1 is instead for a generic error condition.
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.6 sync
|
||||
5.7 sync
|
||||
--------
|
||||
|
||||
Updates the parity information. All the modified files
|
||||
|
|
@ -552,7 +574,7 @@ but the previously computed hash information is kept.
|
|||
The "content" and "parity" files are modified if necessary.
|
||||
The files in the array are NOT modified.
|
||||
|
||||
5.7 scrub
|
||||
5.8 scrub
|
||||
---------
|
||||
|
||||
Scrubs the array, checking for silent or input/output errors in data
|
||||
|
|
@ -597,7 +619,7 @@ of each block, and to mark bad blocks.
|
|||
The "parity" files are NOT modified.
|
||||
The files in the array are NOT modified.
|
||||
|
||||
5.8 fix
|
||||
5.9 fix
|
||||
-------
|
||||
|
||||
Fix all the files and the parity data.
|
||||
|
|
@ -634,8 +656,8 @@ The "content" file is NOT modified.
|
|||
The "parity" files are modified if necessary.
|
||||
The files in the array are modified if necessary.
|
||||
|
||||
5.9 check
|
||||
---------
|
||||
5.10 check
|
||||
----------
|
||||
|
||||
Verify all the files and the parity data.
|
||||
|
||||
|
|
@ -654,7 +676,7 @@ Files are identified only by path, and not by inode.
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.10 list
|
||||
5.11 list
|
||||
---------
|
||||
|
||||
Lists all the files contained in the array at the time of the
|
||||
|
|
@ -662,7 +684,7 @@ last "sync".
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.11 dup
|
||||
5.12 dup
|
||||
--------
|
||||
|
||||
Lists all the duplicate files. Two files are assumed equal if their
|
||||
|
|
@ -671,7 +693,7 @@ pre-computed hashes are used.
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.12 pool
|
||||
5.13 pool
|
||||
---------
|
||||
|
||||
Creates or updates in the "pooling" directory a virtual view of all
|
||||
|
|
@ -686,7 +708,7 @@ view of the array. Any other regular file is left in place.
|
|||
|
||||
Nothing is modified outside the pool directory.
|
||||
|
||||
5.13 devices
|
||||
5.14 devices
|
||||
------------
|
||||
|
||||
Prints the low level devices used by the array.
|
||||
|
|
@ -704,7 +726,7 @@ low level devices used by a single disk in the array.
|
|||
|
||||
Nothing is modified.
|
||||
|
||||
5.14 touch
|
||||
5.15 touch
|
||||
----------
|
||||
|
||||
Sets arbitrarily the sub-second time-stamp of all the files
|
||||
|
|
@ -723,7 +745,7 @@ size and time-stamp.
|
|||
Note that the second precision time-stamp is not modified,
|
||||
and all the dates and times of your files will be maintained.
|
||||
|
||||
5.15 rehash
|
||||
5.16 rehash
|
||||
-----------
|
||||
|
||||
Schedules a rehash of the whole array.
|
||||
|
|
@ -846,6 +868,14 @@ SnapRAID provides the following options:
|
|||
identified regardless of their name.
|
||||
This option can be used only with "check" and "fix".
|
||||
|
||||
-s, --spin-down-on-error
|
||||
On any error, spin down all managed disks before exiting with
|
||||
a non-zero status code. This prevents the drives from
|
||||
remaining active and spinning after an aborted operation,
|
||||
helping to avoid unnecessary heat build-up and power
|
||||
consumption. Use this option to ensure disks are safely
|
||||
stopped even when a command fails.
|
||||
|
||||
-w, --bw-limit RATE
|
||||
Applies a global bandwidth limit for all disks. The RATE is
|
||||
the number of bytes per second. You can specify a multiplier
|
||||
|
|
@ -1218,13 +1248,30 @@ RAM = (8 * 4 * 10^12) * (1+8) / (512 * 2^10) = 0.51 GiB
|
|||
7.10 autosave SIZE_IN_GIGABYTES
|
||||
-------------------------------
|
||||
|
||||
Automatically save the state when syncing or scrubbing after the specified amount
|
||||
of GB processed.
|
||||
Automatically save the state when syncing or scrubbing after the
|
||||
specified amount of GB processed.
|
||||
This option is useful to avoid to restart from scratch long "sync"
|
||||
commands interrupted by a machine crash, or any other event that
|
||||
may interrupt SnapRAID.
|
||||
|
||||
7.11 pool DIR
|
||||
7.11 temp_limit TEMPERATURE_CELSIUS
|
||||
-----------------------------------
|
||||
|
||||
Sets the maximum allowed disk temperature in Celsius. When specified,
|
||||
SnapRAID periodically checks the temperature of all disks using the
|
||||
smartctl tool. The current disk temperatures are displayed during
|
||||
processing. If any disk exceeds this limit, all operations stop and
|
||||
the disks are spun down (put into standby) for the duration defined
|
||||
by "temp_sleep".
|
||||
|
||||
7.12 temp_sleep TIME_IN_MINUTES
|
||||
-------------------------------
|
||||
|
||||
Sets the standby time, in minutes, when the temperature limit is
|
||||
reached. During this period, the disks remain spun down. The default
|
||||
is 15 minutes.
|
||||
|
||||
7.13 pool DIR
|
||||
-------------
|
||||
|
||||
Defines the pooling directory where the virtual view of the disk
|
||||
|
|
@ -1232,7 +1279,7 @@ array is created using the "pool" command.
|
|||
|
||||
The directory must already exist.
|
||||
|
||||
7.12 share UNC_DIR
|
||||
7.14 share UNC_DIR
|
||||
------------------
|
||||
|
||||
Defines the Windows UNC path required to access the disks remotely.
|
||||
|
|
@ -1248,26 +1295,30 @@ file dir and name.
|
|||
|
||||
This option is only required for Windows.
|
||||
|
||||
7.13 smartctl DISK/PARITY OPTIONS...
|
||||
7.15 smartctl DISK/PARITY OPTIONS...
|
||||
------------------------------------
|
||||
|
||||
Defines a custom smartctl command to obtain the SMART attributes
|
||||
for each disk. This may be required for RAID controllers and for
|
||||
some USB disk that cannot be auto-detected.
|
||||
Defines a custom smartctl options to obtain the SMART attributes for
|
||||
each disk. This may be required for RAID controllers and for some USB
|
||||
disk that cannot be auto-detected.
|
||||
|
||||
DISK is the same disk name specified in the "disk" option.
|
||||
PARITY is one of the parity name as "parity,(1,2,3,4,5,6,z)-parity".
|
||||
|
||||
In the specified OPTIONS, the "%s" string is replaced by the
|
||||
device name. Note that in case of RAID controllers the device is likely
|
||||
fixed, and you don't have to use "%s".
|
||||
device name. Note that in case of RAID controllers the device is
|
||||
likely fixed, and you don't have to use "%s".
|
||||
|
||||
Refers at the smartmontools documentation about the possible options:
|
||||
|
||||
https://www.smartmontools.org/wiki/Supported_RAID-Controllers
|
||||
https://www.smartmontools.org/wiki/Supported_USB-Devices
|
||||
|
||||
7.14 smartignore DISK/PARITY ATTR [ATTR...]
|
||||
For example:
|
||||
|
||||
smartctl parity -d sat %s
|
||||
|
||||
7.16 smartignore DISK/PARITY ATTR [ATTR...]
|
||||
-------------------------------------------
|
||||
|
||||
Ignores the specified SMART attribute when computing the probability
|
||||
|
|
@ -1286,7 +1337,7 @@ To ignore it only on the first parity disk:
|
|||
|
||||
smartignore parity 193
|
||||
|
||||
7.15 Examples
|
||||
7.17 Examples
|
||||
-------------
|
||||
|
||||
An example of a typical configuration for Unix is:
|
||||
|
|
@ -1303,6 +1354,7 @@ An example of a typical configuration for Unix is:
|
|||
smartctl d2 -d usbjmicron %s
|
||||
smartctl parity -d areca,1/1 /dev/sg0
|
||||
smartctl 2-parity -d areca,2/1 /dev/sg0
|
||||
smartignore * 193
|
||||
|
||||
An example of a typical configuration for Windows is:
|
||||
|
||||
|
|
@ -1319,6 +1371,7 @@ An example of a typical configuration for Windows is:
|
|||
smartctl d2 -d usbjmicron %s
|
||||
smartctl parity -d areca,1/1 /dev/arcmsr0
|
||||
smartctl 2-parity -d areca,2/1 /dev/arcmsr0
|
||||
smartignore * 193
|
||||
|
||||
|
||||
8 PATTERN
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue