Complete rewrite: SnapRAID SMART logger with BackBlaze algorithm

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>
This commit is contained in:
Wes
2025-12-07 06:55:39 -05:00
co-authored by Claude Sonnet 4.5
commit 27afedb32e
25 changed files with 3207 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Helper script to extract WWN to device mappings from InfluxDB.
Run this to help populate the devices table.
"""
import os
from influxdb_client import InfluxDBClient
from dotenv import load_dotenv
load_dotenv()
def get_device_info():
"""Extract unique device WWNs and their latest data from InfluxDB."""
url = os.getenv('INFLUXDB_URL')
token = os.getenv('INFLUXDB_TOKEN')
org = os.getenv('INFLUXDB_ORG', 'scrutiny')
bucket = os.getenv('INFLUXDB_BUCKET', 'metrics')
print(f"Connecting to InfluxDB at {url}...")
client = InfluxDBClient(url=url, token=token, org=org)
query_api = client.query_api()
# Get unique device WWNs with their latest data
query = f'''
from(bucket: "{bucket}")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "smart")
|> filter(fn: (r) =>
r._field == "temp" or
r._field == "power_on_hours"
)
|> last()
|> group(columns: ["device_wwn", "device_protocol"])
'''
print("\nDiscovered devices:\n")
print("=" * 80)
result = query_api.query(query)
devices = {}
for table in result:
for record in table.records:
wwn = record.values.get('device_wwn')
protocol = record.values.get('device_protocol')
if wwn not in devices:
devices[wwn] = {
'wwn': wwn,
'protocol': protocol,
'temp': None,
'power_on_hours': None
}
field = record.get_field()
if field == 'temp':
devices[wwn]['temp'] = record.get_value()
elif field == 'power_on_hours':
devices[wwn]['power_on_hours'] = record.get_value()
# Print devices
for i, (wwn, info) in enumerate(sorted(devices.items()), 1):
power_on_days = int(info['power_on_hours']) // 24 if info['power_on_hours'] else 0
print(f"Device {i}:")
print(f" WWN: {wwn}")
print(f" Protocol: {info['protocol']}")
print(f" Temperature: {info['temp']}°C")
print(f" Power-On Time: {power_on_days} days ({info['power_on_hours']} hours)")
print()
print("=" * 80)
print("\nNow run 'snapraid smart' or 'smartctl -a /dev/sdX' on each device to get:")
print(" - Serial number")
print(" - Model name")
print(" - Capacity")
print(" - Device path (/dev/sdX)")
print("\nThen update populate_devices.sql with this information.")
client.close()
if __name__ == '__main__':
if not os.getenv('INFLUXDB_URL'):
print("ERROR: Please create a .env file with your InfluxDB configuration")
exit(1)
get_device_info()