diff --git a/src/applib/smartctl_json_ata_parser.cpp b/src/applib/smartctl_json_ata_parser.cpp index 282e3da..1af3bc9 100644 --- a/src/applib/smartctl_json_ata_parser.cpp +++ b/src/applib/smartctl_json_ata_parser.cpp @@ -378,6 +378,11 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_info( {"write_cache/enabled", _("Write Cache"), bool_formatter(_("Enabled"), _("Disabled"))}, {"ata_dsn/enabled", _("DSN Feature"), bool_formatter(_("Enabled"), _("Disabled"))}, {"ata_security/string", _("ATA Security"), string_formatter()}, + + // Protocol-independent JSON-only values + {"power_cycle_count", _("Number of Power Cycles"), integer_formatter()}, + {"power_on_time/hours", _("Powered for"), integer_formatter("{} hours")}, + {"temperature/current", _("Current Temperature"), integer_formatter("{}° Celsius")}, }; bool any_found = false; @@ -1090,58 +1095,162 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_sctte std::vector lines; if (get_node_exists(json_root_node, "ata_sct_status/format_version").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/format_version", _("SCT status version")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/format_version").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("SCT status version: {}", get_node_data(json_root_node, "ata_sct_status/format_version").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/sct_version").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/sct_version", _("SCT format version")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/sct_version").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("SCT format version: {}", get_node_data(json_root_node, "ata_sct_status/sct_version").value_or(0))); } - if (get_node_exists(json_root_node, "ata_sct_status/device_state").value_or(false)) { + if (get_node_exists(json_root_node, "ata_sct_status/device_state/string").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/device_state/string", _("Device state")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/device_state/string").value_or(std::string()); + add_property(p); + lines.emplace_back(fmt::format("Device state: {}", get_node_data(json_root_node, "ata_sct_status/device_state/string").value_or(std::string()))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/current").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/current", _("Current temperature (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/current").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Current temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/current").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/power_cycle_min").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/power_cycle_min", _("Power cycle min. temperature (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_min").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Power cycle min. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_min").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/power_cycle_max").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/power_cycle_max", _("Power cycle max. temperature (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_max").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Power cycle max. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_max").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/lifetime_min").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/lifetime_min", _("Lifetime min. temperature (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_min").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Lifetime min. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_min").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/lifetime_max").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/lifetime_max", _("Lifetime max. temperature (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_max").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Lifetime max. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_max").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/under_limit_count").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/under_limit_count", _("Under limit count")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/under_limit_count").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Under limit count: {}", get_node_data(json_root_node, "ata_sct_status/temperature/under_limit_count").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_status/temperature/over_limit_count").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_status/temperature/over_limit_count", _("Over limit count")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_status/temperature/over_limit_count").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Over limit count: {}", get_node_data(json_root_node, "ata_sct_status/temperature/over_limit_count").value_or(0))); } + lines.emplace_back(); + if (get_node_exists(json_root_node, "ata_sct_temperature_history/version").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/version", _("SCT temperature history version")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/version").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("SCT temperature history version: {}", get_node_data(json_root_node, "ata_sct_temperature_history/version").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/sampling_period_minutes").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/sampling_period_minutes", _("Temperature sampling period (min)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/sampling_period_minutes").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Temperature sampling period: {} min.", get_node_data(json_root_node, "ata_sct_temperature_history/sampling_period_minutes").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/logging_interval_minutes").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/logging_interval_minutes", _("Temperature logging interval (min)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/logging_interval_minutes").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Temperature logging interval: {} min.", get_node_data(json_root_node, "ata_sct_temperature_history/logging_interval_minutes").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/op_limit_min").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/temperature/op_limit_min", _("Recommended operating temperature (minimum) (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_min").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Recommended operating temperature (minimum): {}° Celsius", get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_min").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/op_limit_max").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/temperature/op_limit_max", _("Recommended operating temperature (maximum) (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_max").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Recommended operating temperature (maximum): {}° Celsius", get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_max").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/limit_min").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/temperature/limit_min", _("Allowed operating temperature (minimum) (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_min").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Allowed operating temperature (minimum): {}° Celsius", get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_min").value_or(0))); } if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/limit_max").value_or(false)) { + StorageProperty p; + p.set_name("ata_sct_temperature_history/temperature/limit_max", _("Allowed operating temperature (maximum) (C)")); + p.section = StoragePropertySection::TemperatureLog; + p.value = get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_max").value_or(0); + add_property(p); + lines.emplace_back(fmt::format("Allowed operating temperature (maximum): {}° Celsius", get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_max").value_or(0))); } @@ -1230,8 +1339,8 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_devst StorageProperty page_prop; { - std::string gen_name = get_node_data(page_entry, "name").value_or(std::string()); - std::string disp_name = gen_name; // TODO: Translate + const std::string gen_name = get_node_data(page_entry, "name").value_or(std::string()); + const std::string disp_name = gen_name; // TODO: Translate page_prop.set_name(gen_name, disp_name); page_prop.section = StoragePropertySection::Statistics; page_prop.value = page_stat; @@ -1251,7 +1360,7 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_devst s.offset = get_node_data(table_entry, "offset").value_or(0); StorageProperty p; - std::string gen_name = get_node_data(table_entry, "name").value_or(std::string()); + const std::string gen_name = get_node_data(table_entry, "name").value_or(std::string()); p.set_name(gen_name, gen_name, gen_name); // The description database will correct this. p.section = StoragePropertySection::Statistics; p.value = s; diff --git a/src/applib/storage_property_descr_ata_attribute.cpp b/src/applib/storage_property_descr_ata_attribute.cpp index 6eb0d11..09963ef 100644 --- a/src/applib/storage_property_descr_ata_attribute.cpp +++ b/src/applib/storage_property_descr_ata_attribute.cpp @@ -21,6 +21,7 @@ Copyright: #include "storage_property_descr_ata_attribute.h" //#include "warning_colors.h" #include "storage_property_descr_helpers.h" +#include "hz/string_num.h" namespace { @@ -1282,12 +1283,16 @@ void storage_property_ata_attribute_autoset_warning(StorageProperty& p) "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, "attr_temperature_celsius") - && attr.raw_value_int > 50 && attr.raw_value_int <= 120) { // 50C - w = WarningLevel::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."; - + } else if (attr_match(p, "attr_temperature_celsius")) { + // Raw value may be 27, or 253403791387 (which encodes min/max values as well). + // Use string instead. + std::int64_t temp_int = 0; + if (hz::string_is_numeric_nolocale(attr.raw_value, temp_int, false) + && temp_int > 50 && temp_int <= 120) { // 50C + w = WarningLevel::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, "attr_temperature_celsius_x10") && attr.raw_value_int > 500) { // 50C w = WarningLevel::Notice; diff --git a/src/applib/storage_property_descr_ata_statistic.cpp b/src/applib/storage_property_descr_ata_statistic.cpp index 88e204a..67dbdb4 100644 --- a/src/applib/storage_property_descr_ata_statistic.cpp +++ b/src/applib/storage_property_descr_ata_statistic.cpp @@ -156,7 +156,7 @@ namespace { // Temperature Statistics - add("Current Temperature", "Current Temperature (C)", "", + add("Current Temperature", "Current Temperature (C)", "stat_temperature_celsius", "Drive temperature (Celsius)"); add("Average Short Term Temperature", "Average Short Term Temperature (C)", "", diff --git a/src/gui/gsc_info_window.cpp b/src/gui/gsc_info_window.cpp index 3bc12f3..b43d3b2 100644 --- a/src/gui/gsc_info_window.cpp +++ b/src/gui/gsc_info_window.cpp @@ -1734,11 +1734,16 @@ void GscInfoWindow::fill_ui_temperature_log(const StoragePropertyRepository& pro std::string temperature; StorageProperty temp_property; - enum { temp_attr2 = 1, temp_attr1, temp_stat, temp_sct }; // less important to more important + enum { temp_attr2 = 1, temp_attr1, temp_stat, temp_sct, temp_info }; // less important to more important int temp_prop_source = 0; for (const auto& p : props) { // Find temperature + if (temp_prop_source < temp_info && p.generic_name == "temperature/current") { // Protocol-independent temperature + temperature = hz::number_to_string_locale(p.get_value()); + temp_property = p; + temp_prop_source = temp_info; + } if (temp_prop_source < temp_sct && p.generic_name == "ata_sct_status/temperature/current") { temperature = hz::number_to_string_locale(p.get_value()); temp_property = p; @@ -1750,9 +1755,14 @@ void GscInfoWindow::fill_ui_temperature_log(const StoragePropertyRepository& pro temp_prop_source = temp_stat; } if (temp_prop_source < temp_attr1 && p.generic_name == "attr_temperature_celsius") { - temperature = hz::number_to_string_locale(p.get_value().raw_value_int); - temp_property = p; - temp_prop_source = temp_attr1; + // Note: raw value may encode min/max as well, leading to very large values. + // Instead, convert the string value (can be "27" or "27 (Min/Max 11/59)"). + std::int64_t temp_int = 0; + if (hz::string_is_numeric_nolocale(p.get_value().raw_value, temp_int, false)) { + temperature = hz::number_to_string_locale(temp_int); + temp_property = p; + temp_prop_source = temp_attr1; + } } if (temp_prop_source < temp_attr2 && p.generic_name == "attr_temperature_celsius_x10") { temperature = hz::number_to_string_locale(p.get_value().raw_value_int / 10); @@ -1785,7 +1795,7 @@ void GscInfoWindow::fill_ui_temperature_log(const StoragePropertyRepository& pro if (temperature.empty()) { temperature = C_("value", "Unknown"); } else { - temperature = Glib::ustring::compose(C_("temperature", "%1 C"), temperature); + temperature = Glib::ustring::compose(C_("temperature", "%1° C"), temperature); } temp_property.set_description(_("Current drive temperature in Celsius.")); // overrides attribute description label_strings.emplace_back(Glib::ustring::compose(_("Current temperature: %1"),