Skip to content
API Documentation
BastionGuard™
device_health_probe.cpp
Go to the documentation of this file.
2
3#include "disk_reader.hpp"
4
5#include <algorithm>
6#include <array>
7#include <chrono>
8
9namespace {
10DeviceProbeSample take_sample(DiskReader& reader, uint64_t offset, size_t size) {
11 DeviceProbeSample sample;
12 sample.offset = offset;
13 sample.size = size;
14 std::array<unsigned char, 4096> buffer{};
15 const size_t to_read = std::min<size_t>(size, buffer.size());
16 const auto start = std::chrono::steady_clock::now();
17 sample.success = reader.read_at(offset, buffer.data(), to_read);
18 sample.latency_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
19 std::chrono::steady_clock::now() - start).count());
20 if (!sample.success) {
21 sample.error = "read failed";
22 }
23 return sample;
24}
25
27 switch (state) {
28 case DeviceHealthState::Healthy: return "Healthy";
29 case DeviceHealthState::ReadableWithErrors: return "Readable with errors";
30 case DeviceHealthState::PartiallyReadable: return "Partially readable";
31 case DeviceHealthState::EnumeratedButUnstable: return "Enumerated but unstable";
32 case DeviceHealthState::NotReadable: return "Not readable";
33 }
34 return "Unknown";
35}
36}
37
38DeviceHealthReport DeviceHealthProbe::probe(const std::string& device_path, CancelCallback cancel_cb) {
39 DeviceHealthReport report;
40 report.device_path = device_path;
41
42 DiskReader reader;
43 std::string error;
44 report.open_ok = reader.open_readonly(device_path, error);
45 if (!report.open_ok) {
46 report.summary = "Device could not be opened: " + error;
48 return report;
49 }
50
51 report.total_size = reader.size();
53 report.size_suspicious = (report.total_size == 0 || report.total_size < 4096);
54
55 std::vector<uint64_t> offsets;
56 offsets.push_back(0);
57 if (report.total_size > (1ull << 20)) offsets.push_back(1ull << 20);
58 if (report.total_size > (16ull << 20)) offsets.push_back(16ull << 20);
59 if (report.total_size > 0) offsets.push_back(report.total_size / 2);
60 if (report.total_size > 4096) offsets.push_back(report.total_size - 4096);
61
62 size_t failures = 0;
63 size_t slow = 0;
64 for (const auto offset : offsets) {
65 if (cancel_cb && cancel_cb()) break;
66 auto sample = take_sample(reader, offset, 4096);
67 if (offset == 0) report.read_lba0_ok = sample.success;
68 if (offset + 4096 >= report.total_size) report.read_tail_ok = sample.success;
69 if (!sample.success) ++failures;
70 if (sample.latency_ms > 250) ++slow;
71 report.samples.push_back(std::move(sample));
72 }
73
74 report.unstable_latency = slow > 1;
75 report.intermittent_failures = failures > 0 && failures < report.samples.size();
76
77 if (failures == 0 && !report.size_suspicious && !report.unstable_latency) {
79 } else if (failures == 0 && report.unstable_latency) {
81 } else if (failures < report.samples.size() / 2 + 1) {
83 } else if (failures < report.samples.size()) {
85 } else {
87 }
88
89 report.summary = health_to_string(report.state);
90 if (report.size_suspicious) report.summary += "; suspicious size";
91 if (report.unstable_latency) report.summary += "; unstable latency";
92 if (report.intermittent_failures) report.summary += "; intermittent failures";
93
94 return report;
95}
static DeviceHealthReport probe(const std::string &device_path, CancelCallback cancel_cb={})
std::function< bool()> CancelCallback
uint32_t logical_sector_size() const
bool read_at(uint64_t offset, void *buffer, size_t len)
bool open_readonly(const std::string &path, std::string &error)
uint64_t size() const
DeviceHealthState
std::string health_to_string(DeviceHealthState state)
DeviceProbeSample take_sample(DiskReader &reader, uint64_t offset, size_t size)
DeviceHealthState state
std::vector< DeviceProbeSample > samples