Fetches SMART data from Scrutiny's InfluxDB and calculates failure probabilities using the exact BackBlaze tables from SnapRAID source. Key features: - Calculates both v12 (with attr 193) and v13 (without attr 193) algorithms - v12 matches SnapRAID pre-v13.0 (includes Load Cycle Count) - v13 matches modern SnapRAID v13.0+ (excludes Load Cycle Count) - Stores both values for trending and comparison - Correctly applies bit masks (16-bit for 187/188, 32-bit for others) - Annualizes monthly rates and applies Poisson distribution - Logs to PostgreSQL with device metadata Solved the mystery: SnapRAID v12 uses attribute 193 which was removed in v13.0. Load cycle count has massive impact on failure predictions for some drives (80% vs 4% difference). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
80 lines
2.3 KiB
Python
Executable File
80 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Check the VALUE field (not raw_value) for SMART attributes
|
|
"""
|
|
|
|
import os
|
|
from influxdb_client import InfluxDBClient
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
url = os.getenv('INFLUXDB_URL')
|
|
token = os.getenv('INFLUXDB_TOKEN')
|
|
org = os.getenv('INFLUXDB_ORG', 'scrutiny')
|
|
bucket = os.getenv('INFLUXDB_BUCKET', 'metrics')
|
|
|
|
client = InfluxDBClient(url=url, token=token, org=org)
|
|
query_api = client.query_api()
|
|
|
|
# Get VALUE field (normalized 0-100) for critical attributes
|
|
query = f'''
|
|
from(bucket: "{bucket}")
|
|
|> range(start: -1h)
|
|
|> filter(fn: (r) => r._measurement == "smart")
|
|
|> filter(fn: (r) => r.device_wwn == "0x5000c500c570663e")
|
|
|> filter(fn: (r) =>
|
|
r._field == "attr.5.value" or
|
|
r._field == "attr.187.value" or
|
|
r._field == "attr.188.value" or
|
|
r._field == "attr.197.value" or
|
|
r._field == "attr.198.value" or
|
|
r._field == "attr.5.raw_value" or
|
|
r._field == "attr.187.raw_value" or
|
|
r._field == "attr.188.raw_value" or
|
|
r._field == "attr.197.raw_value" or
|
|
r._field == "attr.198.raw_value"
|
|
)
|
|
|> last()
|
|
'''
|
|
|
|
print("=" * 80)
|
|
print("/dev/sdc (ZLW0A3QJ) - should show 81% failure")
|
|
print("Comparing InfluxDB values to smartctl output")
|
|
print("=" * 80)
|
|
|
|
result = query_api.query(query)
|
|
|
|
attrs = {}
|
|
for table in result:
|
|
for record in table.records:
|
|
field = record.get_field()
|
|
value = record.get_value()
|
|
|
|
parts = field.split('.')
|
|
if len(parts) >= 3:
|
|
attr_id = parts[1]
|
|
field_type = parts[2]
|
|
|
|
if attr_id not in attrs:
|
|
attrs[attr_id] = {}
|
|
attrs[attr_id][field_type] = value
|
|
|
|
print("\nInfluxDB Data:")
|
|
print("-" * 80)
|
|
for attr_id in sorted(attrs.keys(), key=int):
|
|
print(f"\nAttribute {attr_id}:")
|
|
print(f" value (normalized): {attrs[attr_id].get('value', 'N/A')}")
|
|
print(f" raw_value: {attrs[attr_id].get('raw_value', 'N/A')}")
|
|
|
|
print("\n" + "=" * 80)
|
|
print("From smartctl -A /dev/sdc:")
|
|
print("-" * 80)
|
|
print(" 5 Reallocated_Sector_Ct: VALUE=100, RAW_VALUE=0")
|
|
print("187 Reported_Uncorrect: VALUE=100, RAW_VALUE=0")
|
|
print("188 Command_Timeout: VALUE=100, RAW_VALUE=0")
|
|
print("197 Current_Pending_Sector: VALUE=100, RAW_VALUE=0")
|
|
print("198 Offline_Uncorrectable: VALUE=100, RAW_VALUE=0")
|
|
|
|
client.close()
|