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:
Executable
+89
@@ -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()
|
||||
Reference in New Issue
Block a user