Implemented parsing of devstat section.

Implemented Statistics tab.
Increased borders in some tabs.
Some code style changes.
This commit is contained in:
Alexander Shaduri
2017-09-01 13:42:51 +00:00
parent 9f5db6d034
commit e0a13c6867
8 changed files with 499 additions and 242 deletions
+6 -10
View File
@@ -26,9 +26,8 @@ Don't rely on smartctl return code (2), parse the output instead.
Need usage cases.
Add ability (through ctrl-C?) to copy individual rows from attributes page,
Add ability (through ctrl-C?) to copy individual rows from attributes and statistics pages,
in tab-separated format (for easy pasting into libreoffice).
Same for Devstat page.
Add menu item - "update drive database"
@@ -54,15 +53,12 @@ Add ability to load --xall output:
Some of this stuff is better than attributes, maybe show them before attributes.
Also needs the warnings and descriptions.
Per-line selftest error highlighting.
Tab highlighting according to error severity.
Show temperature in Temperature Log, separately (even if SCT is unsupported. use devstat / attributes).
Add a button (with dropdown) to Info window, to run
-l sataphy (advanced)
-l directory (very advanced)
-l scttemp (rarely supported and difficult to parse)
Maybe add -l selective here (and remove it from GUI).
Just show the output in a text window.
"Put this all in Error Log", raname to Logs.
Make it a dropdown combo and "Retrieve" button, showing the output where selective is now.
Document RAID support.
+4 -4
View File
@@ -375,7 +375,7 @@ void Cmdex::stopped_cleanup()
// Called when child exits
void Cmdex::on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data)
{
DBG_FUNCTION_ENTER_MSG;
// DBG_FUNCTION_ENTER_MSG;
Cmdex* self = static_cast<Cmdex*>(data);
g_timer_stop(self->timer_); // stop the timer
@@ -426,7 +426,7 @@ void Cmdex::on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer da
if (self->exited_callback_)
self->exited_callback_(self->exited_callback_data_);
DBG_FUNCTION_EXIT_MSG;
// DBG_FUNCTION_EXIT_MSG;
}
@@ -436,7 +436,7 @@ void Cmdex::on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer da
gboolean Cmdex::on_channel_io(GIOChannel* channel,
GIOCondition cond, Cmdex* self, channel_t type)
{
DBG_FUNCTION_ENTER_MSG;
// DBG_FUNCTION_ENTER_MSG;
// debug_out_dump("app", "Cmdex::on_channel_io("
// << (type == channel_type_stdout ? "STDOUT" : "STDERR") << ") " << int(cond) << "\n");
@@ -481,7 +481,7 @@ gboolean Cmdex::on_channel_io(GIOChannel* channel,
}
} while (g_io_channel_get_buffer_condition(channel) & G_IO_IN);
DBG_FUNCTION_EXIT_MSG;
// DBG_FUNCTION_EXIT_MSG;
// false if the source should be removed, true otherwise.
return continue_events;
+73 -18
View File
@@ -1980,11 +1980,11 @@ Index Estimated Time Temperature Celsius
// supported / unsupported
{
StorageProperty p(pt);
p.set_name("SCT commands supported", "sct_supported");
p.set_name("SCT commands unsupported", "sct_unsupported");
// p.reported_value; // nothing
p.value_type = StorageProperty::value_type_bool;
p.value_bool = !app_pcre_match("/(SCT Commands not supported)|(SCT Data Table command not supported)/mi", sub);
p.value_bool = app_pcre_match("/(SCT Commands not supported)|(SCT Data Table command not supported)/mi", sub);
add_property(p);
data_found = true;
@@ -2043,7 +2043,7 @@ bool SmartctlParser::parse_section_data_subsection_devstat(const std::string& su
{
StorageProperty pt; // template for easy copying
pt.section = StorageProperty::section_data;
pt.subsection = StorageProperty::subsection_erc_log;
pt.subsection = StorageProperty::subsection_devstat;
// devstat log contains:
/*
@@ -2062,18 +2062,6 @@ Page Offset Size Value Flags Description
0x03 0x008 4 6356 -D- Spindle Motor Power-on Hours
0x03 0x010 4 6356 -D- Head Flying Hours
*/
bool data_found = false; // true if something was found.
// the whole subsection
{
StorageProperty p(pt);
p.set_name("Device statistics", "devstat_log");
p.reported_value = sub;
p.value_type = StorageProperty::value_type_string;
p.value_string = p.reported_value;
add_property(p);
}
// supported / unsupported
{
@@ -2085,12 +2073,79 @@ Page Offset Size Value Flags Description
p.value_bool = !app_pcre_match("/Device Statistics \\(GP\\/SMART Log 0x04\\) not supported/mi", sub);
add_property(p);
data_found = true;
}
// TODO
bool entries_found = false; // at least one entry was found
return data_found;}
// split to lines
std::vector<std::string> lines;
hz::string_split(sub, '\n', lines, true);
std::string space_re = "[ \\t]+";
std::string flag_re = "([A-Z=-]{3,})";
// Page Offset Size Value Flags Description
std::string line_re = "[ \\t]*([0-9a-z]+)" + space_re + "([0-9a-z=]+)" + space_re + "([0-9=]+)" + space_re + "([0-9=-]+)" + space_re + flag_re + space_re + "(.+)";
pcrecpp::RE re_stat_line = app_pcre_re("/" + line_re + "/mi");
pcrecpp::RE re_flag_descr = app_pcre_re("/^[\\t ]+\\|/mi");
for(unsigned int i = 0; i < lines.size(); ++i) {
std::string line = lines[i];
// skip the non-informative lines
if (line.empty()
|| app_pcre_match("/Device Statistics \\(GP Log 0x04\\)/mi", line)
|| app_pcre_match("/Page[ \\t]+Offset[ \\t]+Size/mi", line)) {
continue;
}
if (re_flag_descr.PartialMatch(line)) { // " |||_ C monitored condition met", etc...
continue; // skip flag description lines
}
std::string page, offset, size, value, flags, description;
bool matched = true;
if (!re_stat_line.FullMatch(line, &page, &offset, &size, &value, &flags, &description)) {
matched = false;
debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse devstat line.\n");
}
if (!matched) {
debug_out_dump("app", "------------ Begin unparsable devstat line dump ------------\n");
debug_out_dump("app", line << "\n");
debug_out_dump("app", "------------- End unparsable devstat line dump -------------\n");
continue; // continue to the next line
}
StorageStatistic st;
st.is_header = (hz::string_trim_copy(value) == "=");
st.flags = st.is_header ? std::string() : hz::string_trim_copy(flags);
st.value = st.is_header ? std::string() : hz::string_trim_copy(value);
hz::string_is_numeric(st.value, st.value_int, false);
if (st.is_header) {
description = hz::string_trim_copy(hz::string_trim_copy(description, "="));
}
StorageProperty p(pt);
p.set_name(hz::string_trim_copy(description));
p.reported_value = line; // use the whole line here
p.value_type = StorageProperty::value_type_statistic;
p.value_statistic = st;
add_property(p);
entries_found = true;
}
if (!entries_found)
set_error_msg("No entries found in Statistics section.");
return entries_found;
}
@@ -66,6 +66,14 @@ std::ostream& operator<< (std::ostream& os, const StorageAttribute& p)
std::ostream& operator<<(std::ostream& os, const StorageStatistic& p)
{
os << p.value;
return os;
}
std::string StorageErrorBlock::get_readable_error_types(const std::vector<std::string>& types)
{
std::map<std::string, std::string> m;
@@ -174,6 +182,9 @@ void StorageProperty::dump(std::ostream& os, int internal_offset) const
case StorageProperty::value_type_attribute:
os << value_attribute;
break;
case StorageProperty::value_type_statistic:
os << value_statistic;
break;
case StorageProperty::value_type_error_block:
os << value_error_block;
break;
@@ -205,6 +216,8 @@ std::string StorageProperty::format_value(bool add_reported_too) const
return hz::stream_cast<std::string>(value_capability);
case StorageProperty::value_type_attribute:
return hz::stream_cast<std::string>(value_attribute);
case StorageProperty::value_type_statistic:
return hz::stream_cast<std::string>(value_statistic);
case StorageProperty::value_type_error_block:
return hz::stream_cast<std::string>(value_error_block);
case StorageProperty::value_type_selftest_entry:
+25 -1
View File
@@ -129,7 +129,7 @@ class StorageAttribute {
int32_t id; ///< Attribute ID (most vendors agree on this)
std::string flag; ///< Some have it in 0xXXXX format, others in "PO--C-" format (some WD drives?).
std::string flag; ///< "Old" format is "0xXXXX", "brief" format is "PO--C-".
hz::OptionalValue<uint8_t> value; ///< Normalized value. May be unset ("---").
hz::OptionalValue<uint8_t> worst; ///< Worst ever value. May be unset ("---").
hz::OptionalValue<uint8_t> threshold; ///< Threshold for normalized value. May be unset ("---").
@@ -148,6 +148,27 @@ std::ostream& operator<< (std::ostream& os, const StorageAttribute& p);
/// Holds one line of "devstat" subsection
class StorageStatistic {
public:
/// Constructor
StorageStatistic() : is_header(false), value_int(0)
{ }
bool is_header; ///< If the line is a header
std::string flags; ///< Flags in "NDC" / "---" format
std::string value; ///< Value as a string, as presented by smartctl (formatted).
int64_t value_int; ///< Same as value, but parsed as int64.
};
/// Output operator for debug purposes
std::ostream& operator<< (std::ostream& os, const StorageStatistic& p);
/// Holds one error block of "error log" subsection
class StorageErrorBlock {
public:
@@ -290,6 +311,7 @@ class StorageProperty {
value_type_capability, ///< For "capabilities" subsection (non-time-interval blocks only)
value_type_attribute, ///< For "attributes" subsection
value_type_statistic, ///< For "devstat" subsection
value_type_error_block, // For "error_log" subsection
value_type_selftest_entry // For "selftest_log" subsection
};
@@ -305,6 +327,7 @@ class StorageProperty {
case value_type_time_length: return "time_length";
case value_type_capability: return "capability";
case value_type_attribute: return "attribute";
case value_type_statistic: return "statistic";
case value_type_error_block: return "error_block";
case value_type_selftest_entry: return "selftest_entry";
}
@@ -457,6 +480,7 @@ class StorageProperty {
StorageCapability value_capability; ///< Value (if it's a capability)
StorageAttribute value_attribute; ///< Value (if it's an attribute)
StorageStatistic value_statistic; ///< Value (if it's a statistic from devstat)
StorageErrorBlock value_error_block; ///< Value (if it's a error block)
StorageSelftestEntry value_selftest_entry; ///< Value (if it's a self-test entry)
@@ -1306,57 +1306,76 @@ bool storage_property_autoset_description(StorageProperty& p, StorageAttribute::
found = true;
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_health) {
found = auto_set(p, "overall_health", "Overall health self-assessment test result. Note: If the drive passes this test, it doesn't mean it's OK. "
"However, if the drive doesn't pass it, then it's either already dead, or it's predicting its own failure within the next 24 hours. In this case do a backup immediately!");
} else if (p.section == StorageProperty::section_data) {
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_capabilities) {
found = auto_set(p, "offline_status_group", "Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. "
"This should show if Automatic Offline Data Collection is enabled.")
|| auto_set(p, "iodc_total_time_length", "Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. "
"This value shows the estimated time required to perform this operation in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "short_total_time_length", "This value shows the estimated time required to perform a short self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "long_total_time_length", "This value shows the estimated time required to perform a long self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "conveyance_total_time_length", "This value shows the estimated time required to perform a conveyance self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "last_selftest_cap_group", "Status of the last self-test run.")
|| auto_set(p, "offline_cap_group", "Drive properties related to Offline Data Collection and self-tests.")
|| auto_set(p, "smart_cap_group", "Drive properties related to SMART handling.")
|| auto_set(p, "error_log_cap_group", "Drive properties related to error logging.")
|| auto_set(p, "sct_cap_group", "Drive properties related to temperature information.");
switch (p.subsection) {
case StorageProperty::subsection_health:
found = auto_set(p, "overall_health", "Overall health self-assessment test result. Note: If the drive passes this test, it doesn't mean it's OK. "
"However, if the drive doesn't pass it, then it's either already dead, or it's predicting its own failure within the next 24 hours. In this case do a backup immediately!");
break;
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_attributes) {
found = auto_set(p, "data_structure_version", p.readable_name.c_str());
if (!found) {
auto_set_attr(p, disk_type);
found = true; // true, because auto_set_attr() may set "Unknown attribute", which is still "found".
case StorageProperty::subsection_capabilities:
found = auto_set(p, "offline_status_group", "Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. "
"This should show if Automatic Offline Data Collection is enabled.")
|| auto_set(p, "iodc_total_time_length", "Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. "
"This value shows the estimated time required to perform this operation in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "short_total_time_length", "This value shows the estimated time required to perform a short self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "long_total_time_length", "This value shows the estimated time required to perform a long self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "conveyance_total_time_length", "This value shows the estimated time required to perform a conveyance self-test in idle conditions. A value of 0 means unsupported.")
|| auto_set(p, "last_selftest_cap_group", "Status of the last self-test run.")
|| auto_set(p, "offline_cap_group", "Drive properties related to Offline Data Collection and self-tests.")
|| auto_set(p, "smart_cap_group", "Drive properties related to SMART handling.")
|| auto_set(p, "error_log_cap_group", "Drive properties related to error logging.")
|| auto_set(p, "sct_cap_group", "Drive properties related to temperature information.");
break;
case StorageProperty::subsection_attributes:
found = auto_set(p, "data_structure_version", p.readable_name.c_str());
if (!found) {
auto_set_attr(p, disk_type);
found = true; // true, because auto_set_attr() may set "Unknown attribute", which is still "found".
}
break;
case StorageProperty::subsection_devstat:
// TODO
break;
case StorageProperty::subsection_error_log:
found = auto_set(p, "error_log_version", p.readable_name.c_str());
found = auto_set(p, "error_count", "Number of errors in error log. Note: Some manufacturers may list completely harmless errors in this log "
"(e.g., command invalid, not implemented, etc...).");
// || auto_set(p, "error_log_unsupported", "This device does not support error logging."); // the property text already says that
break;
case StorageProperty::subsection_selftest_log:
found = auto_set(p, "selftest_log_version", p.readable_name.c_str());
found = auto_set(p, "selftest_num_entries", "Number of tests in selftest log. Note: This log usually contains only the last 20 or so manual tests. ");
// || auto_set(p, "selftest_log_unsupported", "This device does not support self-test logging."); // the property text already says that
break;
case StorageProperty::subsection_selective_selftest_log:
// nothing here
break;
case StorageProperty::subsection_temperature_log:
// nothing here
break;
case StorageProperty::subsection_erc_log:
// nothing here
break;
case StorageProperty::subsection_phy_log:
// nothing here
break;
case StorageProperty::subsection_directory_log:
// nothing here
break;
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_error_log) {
found = auto_set(p, "error_log_version", p.readable_name.c_str());
found = auto_set(p, "error_count", "Number of errors in error log. Note: Some manufacturers may list completely harmless errors in this log "
"(e.g., command invalid, not implemented, etc...).");
// || auto_set(p, "error_log_unsupported", "This device does not support error logging."); // the property text already says that
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_selftest_log) {
found = auto_set(p, "selftest_log_version", p.readable_name.c_str());
found = auto_set(p, "selftest_num_entries", "Number of tests in selftest log. Note: This log usually contains only the last 20 or so manual tests. ");
// || auto_set(p, "selftest_log_unsupported", "This device does not support self-test logging."); // the property text already says that
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_selective_selftest_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_temperature_log) {
// TODO Current temperature
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_erc_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_phy_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_directory_log) {
// nothing here
}
return found;
}
@@ -1386,143 +1405,164 @@ StorageProperty::warning_t storage_property_autoset_warning(StorageProperty& p)
"Additionally, some drives do not log useful data with SMART disabled, so it's advisable to keep it always enabled.";
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_health) {
if (name_match(p, "overall_health") && p.value_string != "PASSED") {
w = StorageProperty::warning_alert;
reason = "The drive is reporting that it will FAIL very soon. Please back up as soon as possible!";
}
} else if (p.section == StorageProperty::section_data) {
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_capabilities) {
// nothing
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_attributes) {
// Set notices for known pre-fail attributes
// Reallocated Sector Count
if (attr_match(p, "reallocated_sector_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Spin-up Retry Count
} else if (attr_match(p, "spin_up_retry_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. Your drive may have problems spinning up, which could lead to a complete mechanical failure. Please back up.";
// Soft Read Error Rate
} else if (attr_match(p, "soft_read_error_rate") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Temperature (for some it may be 10xTemp, so limit the upper bound.)
} else if (attr_match(p, "temperature_celsius")
&& p.value_attribute.raw_value_int > 50 && p.value_attribute.raw_value_int <= 120) { // 50C
w = StorageProperty::warning_notice;
reason = "The temperature of the drive is higher than 50 degrees Celsius. This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.";
// Temperature (for some it may be 10xTemp, so limit the upper bound.)
} else if (attr_match(p, "temperature_celsius_x10") && p.value_attribute.raw_value_int > 500) { // 50C
w = StorageProperty::warning_notice;
reason = "The temperature of the drive is higher than 50 degrees Celsius. This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.";
// Reallocation Event Count
} else if (attr_match(p, "reallocation_event_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Current Pending Sector Count
} else if ((attr_match(p, "current_pending_sector_count") || attr_match(p, "total_pending_sectors"))
&& p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Uncorrectable Sector Count
} else if ((attr_match(p, "offline_uncorrectable") || attr_match(p, "total_offline_uncorrectable"))
&& p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// SSD Life Left (%)
} else if ((attr_match(p, "ssd_life_left"))
&& p.value_attribute.value.value() < 50) {
w = StorageProperty::warning_notice;
reason = "The drive has less than half of its life left.";
// SSD Life Used (%)
} else if ((attr_match(p, "ssd_life_used"))
&& p.value_attribute.value.value() >= 50) {
w = StorageProperty::warning_notice;
reason = "The drive has less than half of its life left.";
}
// Now override this with SMART warnings / errors
if (p.value_type == StorageProperty::value_type_attribute) {
if (p.value_attribute.when_failed == StorageAttribute::fail_time_now) { // NOW
if (p.value_attribute.attr_type == StorageAttribute::attr_type_oldage) { // old-age
w = StorageProperty::warning_warn;
reason = "The drive has a failing old-age attribute. Usually this indicates a wear-out. You should consider replacing the drive.";
} else { // pre-fail
switch(p.subsection) {
case StorageProperty::subsection_health:
if (name_match(p, "overall_health") && p.value_string != "PASSED") {
w = StorageProperty::warning_alert;
reason = "The drive has a failing pre-fail attribute. Usually this indicates a that the drive will FAIL soon. Please back up immediately!";
reason = "The drive is reporting that it will FAIL very soon. Please back up as soon as possible!";
}
break;
case StorageProperty::subsection_capabilities:
// nothing
break;
case StorageProperty::subsection_attributes:
// Set notices for known pre-fail attributes
// Reallocated Sector Count
if (attr_match(p, "reallocated_sector_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Spin-up Retry Count
} else if (attr_match(p, "spin_up_retry_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. Your drive may have problems spinning up, which could lead to a complete mechanical failure. Please back up.";
// Soft Read Error Rate
} else if (attr_match(p, "soft_read_error_rate") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Temperature (for some it may be 10xTemp, so limit the upper bound.)
} else if (attr_match(p, "temperature_celsius")
&& p.value_attribute.raw_value_int > 50 && p.value_attribute.raw_value_int <= 120) { // 50C
w = StorageProperty::warning_notice;
reason = "The temperature of the drive is higher than 50 degrees Celsius. This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.";
// Temperature (for some it may be 10xTemp, so limit the upper bound.)
} else if (attr_match(p, "temperature_celsius_x10") && p.value_attribute.raw_value_int > 500) { // 50C
w = StorageProperty::warning_notice;
reason = "The temperature of the drive is higher than 50 degrees Celsius. This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.";
// Reallocation Event Count
} else if (attr_match(p, "reallocation_event_count") && p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Current Pending Sector Count
} else if ((attr_match(p, "current_pending_sector_count") || attr_match(p, "total_pending_sectors"))
&& p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// Uncorrectable Sector Count
} else if ((attr_match(p, "offline_uncorrectable") || attr_match(p, "total_offline_uncorrectable"))
&& p.value_attribute.raw_value_int > 0) {
w = StorageProperty::warning_notice;
reason = "The drive has a non-zero Raw value, but there is no SMART warning yet. This could be an indication of future failures and/or potential data loss in bad sectors.";
// SSD Life Left (%)
} else if ((attr_match(p, "ssd_life_left"))
&& p.value_attribute.value.value() < 50) {
w = StorageProperty::warning_notice;
reason = "The drive has less than half of its life left.";
// SSD Life Used (%)
} else if ((attr_match(p, "ssd_life_used"))
&& p.value_attribute.value.value() >= 50) {
w = StorageProperty::warning_notice;
reason = "The drive has less than half of its life left.";
}
} else if (p.value_attribute.when_failed == StorageAttribute::fail_time_past) { // PAST
// Now override this with SMART warnings / errors
if (p.value_attribute.attr_type == StorageAttribute::attr_type_oldage) { // old-age
// nothing. we don't warn about e.g. temperature increase in the past
} else { // pre-fail
w = StorageProperty::warning_warn; // there was a problem, it got corrected (hopefully)
reason = "The drive had a failing pre-fail attribute, but it has been restored to a normal value. This may be a serious problem, you should consider replacing the drive.";
if (p.value_type == StorageProperty::value_type_attribute) {
if (p.value_attribute.when_failed == StorageAttribute::fail_time_now) { // NOW
if (p.value_attribute.attr_type == StorageAttribute::attr_type_oldage) { // old-age
w = StorageProperty::warning_warn;
reason = "The drive has a failing old-age attribute. Usually this indicates a wear-out. You should consider replacing the drive.";
} else { // pre-fail
w = StorageProperty::warning_alert;
reason = "The drive has a failing pre-fail attribute. Usually this indicates a that the drive will FAIL soon. Please back up immediately!";
}
} else if (p.value_attribute.when_failed == StorageAttribute::fail_time_past) { // PAST
if (p.value_attribute.attr_type == StorageAttribute::attr_type_oldage) { // old-age
// nothing. we don't warn about e.g. temperature increase in the past
} else { // pre-fail
w = StorageProperty::warning_warn; // there was a problem, it got corrected (hopefully)
reason = "The drive had a failing pre-fail attribute, but it has been restored to a normal value. This may be a serious problem, you should consider replacing the drive.";
}
}
}
}
break;
case StorageProperty::subsection_devstat:
// TODO
break;
case StorageProperty::subsection_error_log:
// Note: The error list table doesn't display any descriptions, so if any
// error-entry related descriptions are added here, don't forget to enable
// the tooltips.
if (name_match(p, "error_count") && p.value_integer > 0) {
w = StorageProperty::warning_warn;
reason = "The drive is reporting internal errors. Usually this means uncorrectable data loss and similar severe errors. "
"Check the actual errors for details.";
} else if (name_match(p, "error_log_unsupported")) {
w = StorageProperty::warning_notice;
reason = "The drive does not support error logging. This means that SMART error history is unavailable.";
}
break;
case StorageProperty::subsection_selftest_log:
// Note: The error list table doesn't display any descriptions, so if any
// error-entry related descriptions are added here, don't forget to enable
// the tooltips.
// Don't include selftest warnings - they may be old or something.
// Self-tests are carried manually anyway, so the user is expected to check their status anyway.
if (name_match(p, "selftest_log_unsupported")) {
w = StorageProperty::warning_notice;
reason = "The drive does not support self-test logging. This means that SMART test results won't be logged.";
}
break;
case StorageProperty::subsection_selective_selftest_log:
// nothing here
break;
case StorageProperty::subsection_temperature_log:
if (name_match(p, "sct_unsupported")) { // TODO show this somewhere
w = StorageProperty::warning_notice;
reason = "The drive does not support SCT Temperature logging. Check the Attributes tab for current temperature.";
}
// TODO Current temperature
break;
case StorageProperty::subsection_erc_log:
// nothing here
break;
case StorageProperty::subsection_phy_log:
// nothing here
break;
case StorageProperty::subsection_directory_log:
// nothing here
break;
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_error_log) {
// Note: The error list table doesn't display any descriptions, so if any
// error-entry related descriptions are added here, don't forget to enable
// the tooltips.
if (name_match(p, "error_count") && p.value_integer > 0) {
w = StorageProperty::warning_warn;
reason = "The drive is reporting internal errors. Usually this means uncorrectable data loss and similar severe errors. "
"Check the actual errors for details.";
} else if (name_match(p, "error_log_unsupported")) {
w = StorageProperty::warning_notice;
reason = "The drive does not support error logging. This means that SMART error history is unavailable.";
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_selftest_log) {
// Note: The error list table doesn't display any descriptions, so if any
// error-entry related descriptions are added here, don't forget to enable
// the tooltips.
// Don't include selftest warnings - they may be old or something.
// Self-tests are carried manually anyway, so the user is expected to check their status anyway.
if (name_match(p, "selftest_log_unsupported")) {
w = StorageProperty::warning_notice;
reason = "The drive does not support self-test logging. This means that SMART test results won't be logged.";
}
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_selective_selftest_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_temperature_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_erc_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_phy_log) {
// nothing here
} else if (p.section == StorageProperty::section_data && p.subsection == StorageProperty::subsection_directory_log) {
// nothing here
}
+127 -7
View File
@@ -68,13 +68,16 @@ namespace {
delete wv[i]; // since it's without parent anymore, it won't be auto-deleted.
}
vbox->set_visible(!label_strings.empty());
if (label_strings.empty()) {
// add one label only
Gtk::Label* label = Gtk::manage(new Gtk::Label("No data available", Gtk::ALIGN_START));
label->set_padding(6, 0);
vbox->pack_start(*label, false, false);
// Gtk::Label* label = Gtk::manage(new Gtk::Label("No data available", Gtk::ALIGN_START));
// label->set_padding(6, 0);
// vbox->pack_start(*label, false, false);
} else {
// add one label per element
for (label_list_t::const_iterator iter = label_strings.begin(); iter != label_strings.end(); ++iter) {
std::string label_text = Glib::Markup::escape_text(iter->label);
@@ -126,6 +129,35 @@ namespace {
/// Cell renderer functions for attribute cells
inline void app_statistic_cell_renderer_func(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,
Gtk::TreeModelColumn<const StorageProperty*> storage_column)
{
const StorageProperty* p = (*iter)[storage_column];
Gtk::CellRendererText* crt = hz::down_cast<Gtk::CellRendererText*>(cr);
if (crt) {
std::string fg, bg;
if (app_property_get_row_highlight_colors(p->warning, fg, bg)) {
// Note: property_cell_background makes horizontal tree lines disappear around it,
// but property_background doesn't play nice with sorted column color.
crt->property_cell_background() = bg;
crt->property_foreground() = fg;
} else {
// this is needed because cellrenderer is shared in column, so the previous call
// may set the color for all subsequent cells.
crt->property_cell_background().reset_value();
crt->property_foreground().reset_value();
}
if (p->value_statistic.is_header) {
crt->property_weight() = Pango::WEIGHT_BOLD;
} else {
crt->property_weight().reset_value();
}
}
}
/// Highlight a tab label according to \c warning
inline void app_highlight_tab_label(Gtk::Widget* label_widget,
StorageProperty::warning_t warning, const Glib::ustring& original_label)
@@ -567,7 +599,7 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests
treeview->set_search_column(col_name.index());
Gtk::CellRendererText* cr_name = hz::down_cast<Gtk::CellRendererText*>(treeview->get_column_cell_renderer(num_tree_cols - 1));
if (cr_name)
cr_name->property_weight() = Pango::WEIGHT_BOLD ;
cr_name->property_weight() = Pango::WEIGHT_BOLD;
Gtk::TreeModelColumn<Glib::ustring> col_failed;
model_columns.add(col_failed);
@@ -641,7 +673,6 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests
StorageProperty::warning_t max_tab_warning = StorageProperty::warning_none;
int index = 1;
label_list_t label_strings; // outside-of-tree properties
for (prop_iterator iter = props.begin(); iter != props.end(); ++iter) {
@@ -682,8 +713,6 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests
if (int(iter->warning) > int(max_tab_warning))
max_tab_warning = iter->warning;
++index;
}
@@ -700,6 +729,97 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests
// ------------------------------------------- Statistics
do {
Gtk::TreeView* treeview = lookup_widget<Gtk::TreeView*>("statistics_treeview");
if (!treeview)
break;
Gtk::TreeModelColumnRecord model_columns;
unsigned int num_tree_cols = 0;
Gtk::TreeModelColumn<Glib::ustring> col_description;
model_columns.add(col_description);
num_tree_cols = app_gtkmm_create_tree_view_column(col_description, *treeview,
"Description", "Entry description", true);
treeview->set_search_column(col_description.index());
// Gtk::CellRendererText* cr_name = hz::down_cast<Gtk::CellRendererText*>(treeview->get_column_cell_renderer(num_tree_cols - 1));
// if (cr_name)
// cr_name->property_weight() = Pango::WEIGHT_BOLD ;
Gtk::TreeModelColumn<std::string> col_value;
model_columns.add(col_value);
num_tree_cols = app_gtkmm_create_tree_view_column(col_value, *treeview,
"Value", "Value (can be normalized if 'N' flag is present)", false);
Gtk::TreeModelColumn<std::string> col_flags;
model_columns.add(col_flags);
num_tree_cols = app_gtkmm_create_tree_view_column(col_flags, *treeview,
"Flags", "Flags\n\n"
"N: value is normalized\n"
"D: supports DSN\n"
"C: monitored condition met", false);
Gtk::TreeModelColumn<Glib::ustring> col_tooltip;
model_columns.add(col_tooltip);
treeview->set_tooltip_column(col_tooltip.index());
Gtk::TreeModelColumn<const StorageProperty*> col_storage;
model_columns.add(col_storage);
// create a TreeModel (ListStore)
Glib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);
treeview->set_model(list_store);
// No sorting (we don't want to screw up the headers).
for (unsigned int i = 0; i < num_tree_cols; ++i) {
Gtk::TreeViewColumn* tcol = treeview->get_column(i);
tcol->set_cell_data_func(*(tcol->get_first_cell()),
sigc::bind(sigc::ptr_fun(app_statistic_cell_renderer_func), col_storage));
}
StorageProperty::warning_t max_tab_warning = StorageProperty::warning_none;
label_list_t label_strings; // outside-of-tree properties
for (prop_iterator iter = props.begin(); iter != props.end(); ++iter) {
if (iter->section != StorageProperty::section_data || iter->subsection != StorageProperty::subsection_devstat)
continue;
// add non-entry-type properties to label above
if (iter->value_type != StorageProperty::value_type_statistic) {
label_strings.push_back(PropertyLabel(iter->readable_name + ": " + iter->format_value(), &(*iter)));
if (int(iter->warning) > int(max_tab_warning))
max_tab_warning = iter->warning;
continue;
}
Gtk::TreeRow row = *(list_store->append());
row[col_description] = (iter->value_statistic.is_header ? iter->readable_name : (" " + iter->readable_name));
row[col_value] = iter->value_statistic.value;
row[col_flags] = iter->value_statistic.flags; // it's a string, not int.
row[col_tooltip] = iter->get_description();
row[col_storage] = &(*iter);
if (int(iter->warning) > int(max_tab_warning))
max_tab_warning = iter->warning;
}
Gtk::Box* label_vbox = lookup_widget<Gtk::Box*>("statistics_label_vbox");
app_set_top_labels(label_vbox, label_strings);
// tab label
app_highlight_tab_label(lookup_widget("statistics_tab_label"), max_tab_warning, tab_statistics_name);
} while (false);
// ------------------------------------------- Tests
+35 -26
View File
@@ -86,6 +86,7 @@
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<child>
<object class="GtkGrid" id="identity_table">
<property name="visible">True</property>
@@ -148,7 +149,6 @@
<property name="use_underline">True</property>
</object>
<packing>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -156,7 +156,9 @@
<object class="GtkBox" id="attributes_tab_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="attributes_label_vbox">
<property name="visible">True</property>
@@ -212,7 +214,6 @@
</object>
<packing>
<property name="position">1</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -220,36 +221,47 @@
<object class="GtkBox" id="statistics_tab_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="vbox3">
<object class="GtkBox" id="statistics_label_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkPaned" id="vpaned2">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow10">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="statistics_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">vertical</property>
<property name="position_set">True</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
<property name="headers_clickable">False</property>
<property name="enable_grid_lines">both</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
</object>
@@ -268,7 +280,6 @@
</object>
<packing>
<property name="position">2</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -276,7 +287,7 @@
<object class="GtkBox" id="test_tab_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">11</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
@@ -296,7 +307,7 @@
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
@@ -515,7 +526,6 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">6</property>
<property name="position">0</property>
</packing>
</child>
@@ -523,6 +533,7 @@
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">6</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
@@ -611,7 +622,6 @@ Self-test log contains information about the most recent manually performed SMAR
</object>
<packing>
<property name="position">3</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -619,12 +629,14 @@ Self-test log contains information about the most recent manually performed SMAR
<object class="GtkBox" id="error_log_tab_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="error_log_label_vbox">
<property name="visible">True</property>
@@ -735,7 +747,6 @@ Self-test log contains information about the most recent manually performed SMAR
</object>
<packing>
<property name="position">4</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -776,7 +787,7 @@ Self-test log contains information about the most recent manually performed SMAR
<child type="tab">
<object class="GtkLabel" id="temperature_tab_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Current temperature and history</property>
<property name="label" translatable="yes">Temperature Log</property>
</object>
@@ -795,7 +806,6 @@ Self-test log contains information about the most recent manually performed SMAR
<object class="GtkNotebook" id="advanced_notebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="show_border">False</property>
<child>
<object class="GtkScrolledWindow" id="capabilities_scrolledwindow">
<property name="visible">True</property>
@@ -975,7 +985,6 @@ Self-test log contains information about the most recent manually performed SMAR
</object>
<packing>
<property name="position">6</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>