Add human-friendly formatting for SSD write/read statistics#114
Add human-friendly formatting for SSD write/read statistics#114
Conversation
- Added storage_property_ata_attribute_humanize_ssd_writes() function - Converts sector counts and increment-based counters to readable byte sizes - Handles multiple SSD vendor-specific write attributes (IDs 199, 241, 243, 245, 246, 249, 251) - Uses binary units (KiB, MiB, GiB, TiB) for consistency - Called automatically after setting attribute descriptions Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
- Added attribute 225: Host_Writes_32MiB (Intel SSDs) - Added read attributes for consistency: 198, 226, 242, 244, 251 - Ensures comprehensive coverage of SSD data attributes Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds human-friendly formatting for certain ATA SSD read/write SMART attributes so raw counters (sectors / 32MiB / 1GiB increments) are displayed in readable byte sizes (GiB/TiB), integrated into the existing property description auto-setup path.
Changes:
- Added
storage_property_ata_attribute_humanize_ssd_writes()helper to convert specific (ID, reported_name) ATA attributes into byte counts and format viahz::format_size(). - Wired the helper into
storage_property_autoset_description()right afterauto_set_ata_attribute_description(). - Added the required
hz/format_unit.hinclude forformat_size().
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/applib/storage_property_descr_ata_attribute.h |
Declares the new humanization helper with brief API docs. |
src/applib/storage_property_descr_ata_attribute.cpp |
Implements attribute-specific raw→bytes conversion and formatting using hz::format_size(). |
src/applib/storage_property_descr.cpp |
Invokes the new humanization step during ATA attribute description auto-setup. |
| // Attribute 225: Host_Writes_32MiB (Intel SSDs) | ||
| else if (id == 225 && name == "Host_Writes_32MiB") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | ||
| } | ||
| // Attribute 241: Host_Writes_32MiB (various SSDs) | ||
| // Raw value increased by 1 for every 32 MiB written | ||
| else if (id == 241 && name == "Host_Writes_32MiB") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | ||
| } | ||
| // Attribute 243: Host_Writes_32MiB (SanDisk SSDs) | ||
| else if (id == 243 && name == "Host_Writes_32MiB") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | ||
| } | ||
| // Attribute 245: Flash_Writes_32MiB (Innodisk SSDs) | ||
| else if (id == 245 && name == "Flash_Writes_32MiB") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | ||
| } | ||
| // Attribute 245: TLC_Writes_32MiB (SiliconMotion SSDs) | ||
| else if (id == 245 && name == "TLC_Writes_32MiB") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | ||
| } |
There was a problem hiding this comment.
The long if/else if chain duplicates the same conversion logic for multiple (id,name) pairs (e.g., several Host_*_32MiB variants). Consider consolidating this into a small static table (id + name + multiplier) and a single lookup, which will reduce the chance of missing an entry or introducing inconsistent multipliers as this list grows.
| // Attribute 225: Host_Writes_32MiB (Intel SSDs) | |
| else if (id == 225 && name == "Host_Writes_32MiB") { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| } | |
| // Attribute 241: Host_Writes_32MiB (various SSDs) | |
| // Raw value increased by 1 for every 32 MiB written | |
| else if (id == 241 && name == "Host_Writes_32MiB") { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| } | |
| // Attribute 243: Host_Writes_32MiB (SanDisk SSDs) | |
| else if (id == 243 && name == "Host_Writes_32MiB") { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| } | |
| // Attribute 245: Flash_Writes_32MiB (Innodisk SSDs) | |
| else if (id == 245 && name == "Flash_Writes_32MiB") { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| } | |
| // Attribute 245: TLC_Writes_32MiB (SiliconMotion SSDs) | |
| else if (id == 245 && name == "TLC_Writes_32MiB") { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| } | |
| // Attributes whose raw value is a count of 32 MiB units | |
| else { | |
| struct WriteUnits32MiBEntry { | |
| int32_t id; | |
| const char* name; | |
| }; | |
| // Raw value increased by 1 for every 32 MiB written | |
| static constexpr WriteUnits32MiBEntry write_32mib_entries[] = { | |
| // Attribute 225: Host_Writes_32MiB (Intel SSDs) | |
| {225, "Host_Writes_32MiB"}, | |
| // Attribute 241: Host_Writes_32MiB (various SSDs) | |
| {241, "Host_Writes_32MiB"}, | |
| // Attribute 243: Host_Writes_32MiB (SanDisk SSDs) | |
| {243, "Host_Writes_32MiB"}, | |
| // Attribute 245: Flash_Writes_32MiB (Innodisk SSDs) | |
| {245, "Flash_Writes_32MiB"}, | |
| // Attribute 245: TLC_Writes_32MiB (SiliconMotion SSDs) | |
| {245, "TLC_Writes_32MiB"}, | |
| // Attribute 246: SLC_Writes_32MiB (SiliconMotion SSDs) | |
| {246, "SLC_Writes_32MiB"}, | |
| }; | |
| for (const auto& entry : write_32mib_entries) { | |
| if (id == entry.id && name == entry.name) { | |
| bytes = static_cast<uint64_t>(attr.raw_value_int) * mib_32; | |
| break; | |
| } | |
| } | |
| } |
| /// Humanize SSD write statistics by converting raw values to readable byte counts. | ||
| /// Sets the readable_value field for applicable write-related attributes. |
There was a problem hiding this comment.
storage_property_ata_attribute_humanize_ssd_writes() name/docs say it only humanizes write-related attributes, but the implementation also converts several read-related attributes (e.g. Read_Sectors_Tot_Ct, Host_Reads_32MiB). This mismatch makes the API misleading; consider renaming the function (and updating call sites) or splitting into separate read/write helpers, and update the header comment accordingly.
| /// Humanize SSD write statistics by converting raw values to readable byte counts. | |
| /// Sets the readable_value field for applicable write-related attributes. | |
| /// Humanize SSD I/O statistics (both read- and write-related) by converting raw | |
| /// values to readable byte counts. Sets the readable_value field for applicable | |
| /// SSD attributes. |
| if (id == 199 && name == "Write_Sectors_Tot_Ct") { | ||
| bytes = static_cast<uint64_t>(attr.raw_value_int) * bytes_per_sector; | ||
| } |
There was a problem hiding this comment.
attr.raw_value_int is int64_t; casting a negative value to uint64_t before multiplying will wrap and produce an enormous byte count (and bytes > 0 will still pass). It would be safer to explicitly require attr.raw_value_int > 0 before conversion and to guard against uint64_t overflow when multiplying by sector/32MiB/1GiB (e.g., via a checked multiply or max()/multiplier pre-check).
|
@copilot apply changes based on the comments in this thread |
Applied all three review feedback items in commit
|
|
@copilot apply changes based on the comments in this thread |
Applied all three review feedback items in commit
|
|
@copilot apply changes based on the comments in this thread |
All three review feedback items are applied in commit
|
|
@copilot The last commits could not be applied. retry. |
All three changes are applied in commit |
raw_value_int <= 0before casting touint64_tmultiplier != 0andraw <= MAX / multiplierchecks#include <limits>forstd::numeric_limitsOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.