diff --git a/gsmartcontrol/TODO b/gsmartcontrol/TODO index 3b6b0a2..2f296c7 100644 --- a/gsmartcontrol/TODO +++ b/gsmartcontrol/TODO @@ -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. diff --git a/gsmartcontrol/src/applib/cmdex.cpp b/gsmartcontrol/src/applib/cmdex.cpp index dc55256..6ed29c1 100644 --- a/gsmartcontrol/src/applib/cmdex.cpp +++ b/gsmartcontrol/src/applib/cmdex.cpp @@ -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(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; diff --git a/gsmartcontrol/src/applib/smartctl_parser.cpp b/gsmartcontrol/src/applib/smartctl_parser.cpp index d571448..1a51d01 100644 --- a/gsmartcontrol/src/applib/smartctl_parser.cpp +++ b/gsmartcontrol/src/applib/smartctl_parser.cpp @@ -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 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; +} diff --git a/gsmartcontrol/src/applib/storage_property.cpp b/gsmartcontrol/src/applib/storage_property.cpp index 3fa080b..a9f2e16 100644 --- a/gsmartcontrol/src/applib/storage_property.cpp +++ b/gsmartcontrol/src/applib/storage_property.cpp @@ -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& types) { std::map 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(value_capability); case StorageProperty::value_type_attribute: return hz::stream_cast(value_attribute); + case StorageProperty::value_type_statistic: + return hz::stream_cast(value_statistic); case StorageProperty::value_type_error_block: return hz::stream_cast(value_error_block); case StorageProperty::value_type_selftest_entry: diff --git a/gsmartcontrol/src/applib/storage_property.h b/gsmartcontrol/src/applib/storage_property.h index b2e3efc..8c805e6 100644 --- a/gsmartcontrol/src/applib/storage_property.h +++ b/gsmartcontrol/src/applib/storage_property.h @@ -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 value; ///< Normalized value. May be unset ("---"). hz::OptionalValue worst; ///< Worst ever value. May be unset ("---"). hz::OptionalValue 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) diff --git a/gsmartcontrol/src/applib/storage_property_descr.cpp b/gsmartcontrol/src/applib/storage_property_descr.cpp index d20f140..df015ee 100644 --- a/gsmartcontrol/src/applib/storage_property_descr.cpp +++ b/gsmartcontrol/src/applib/storage_property_descr.cpp @@ -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 } diff --git a/gsmartcontrol/src/gsc_info_window.cpp b/gsmartcontrol/src/gsc_info_window.cpp index 30898a0..c753965 100644 --- a/gsmartcontrol/src/gsc_info_window.cpp +++ b/gsmartcontrol/src/gsc_info_window.cpp @@ -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 storage_column) + { + const StorageProperty* p = (*iter)[storage_column]; + Gtk::CellRendererText* crt = hz::down_cast(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(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 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("statistics_treeview"); + if (!treeview) + break; + + Gtk::TreeModelColumnRecord model_columns; + unsigned int num_tree_cols = 0; + + Gtk::TreeModelColumn 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(treeview->get_column_cell_renderer(num_tree_cols - 1)); +// if (cr_name) +// cr_name->property_weight() = Pango::WEIGHT_BOLD ; + + Gtk::TreeModelColumn 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 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 col_tooltip; + model_columns.add(col_tooltip); + treeview->set_tooltip_column(col_tooltip.index()); + + + Gtk::TreeModelColumn col_storage; + model_columns.add(col_storage); + + + // create a TreeModel (ListStore) + Glib::RefPtr 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("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 diff --git a/gsmartcontrol/src/res/gsc_info_window.ui b/gsmartcontrol/src/res/gsc_info_window.ui index 5775347..4c2cb33 100644 --- a/gsmartcontrol/src/res/gsc_info_window.ui +++ b/gsmartcontrol/src/res/gsc_info_window.ui @@ -86,6 +86,7 @@ True False + 6 True @@ -148,7 +149,6 @@ True - True False @@ -156,7 +156,9 @@ True False + 6 vertical + 6 True @@ -212,7 +214,6 @@ 1 - True False @@ -220,36 +221,47 @@ True False + 6 vertical + 6 - + True False vertical + 3 - + + + + + False + True + 4 + 0 + + + + + True + True + in + + True True - vertical - True - - - - - + False + both + + - - True - True - 0 - True True - 2 + 1 @@ -268,7 +280,6 @@ 2 - True False @@ -276,7 +287,7 @@ True False - 11 + 12 vertical 6 @@ -296,7 +307,7 @@ False - True + False 0 @@ -515,7 +526,6 @@ False True - 6 0 @@ -523,6 +533,7 @@ True False + 6 0 none @@ -611,7 +622,6 @@ Self-test log contains information about the most recent manually performed SMAR 3 - True False @@ -619,12 +629,14 @@ Self-test log contains information about the most recent manually performed SMAR True False + 6 vertical True False vertical + 6 True @@ -735,7 +747,6 @@ Self-test log contains information about the most recent manually performed SMAR 4 - True False @@ -776,7 +787,7 @@ Self-test log contains information about the most recent manually performed SMAR True - False + True Current temperature and history Temperature Log @@ -795,7 +806,6 @@ Self-test log contains information about the most recent manually performed SMAR True True - False True @@ -975,7 +985,6 @@ Self-test log contains information about the most recent manually performed SMAR 6 - True False