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
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Explore Scrutiny's InfluxDB schema to understand how SMART data is stored.
|
||||
Run this first to understand the data structure before running the main logger.
|
||||
"""
|
||||
|
||||
import os
|
||||
from influxdb_client import InfluxDBClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
def explore_influxdb():
|
||||
"""Connect to InfluxDB and explore the schema."""
|
||||
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}")
|
||||
print(f"Organization: {org}")
|
||||
print(f"Bucket: {bucket}\n")
|
||||
|
||||
client = InfluxDBClient(url=url, token=token, org=org)
|
||||
query_api = client.query_api()
|
||||
|
||||
# 1. List all measurements in the bucket
|
||||
print("=" * 80)
|
||||
print("STEP 1: Discovering measurements in bucket")
|
||||
print("=" * 80)
|
||||
|
||||
query_measurements = f'''
|
||||
import "influxdata/influxdb/schema"
|
||||
schema.measurements(bucket: "{bucket}")
|
||||
'''
|
||||
|
||||
try:
|
||||
result = query_api.query(query_measurements)
|
||||
measurements = []
|
||||
for table in result:
|
||||
for record in table.records:
|
||||
measurement = record.get_value()
|
||||
measurements.append(measurement)
|
||||
print(f" - {measurement}")
|
||||
|
||||
if not measurements:
|
||||
print(" No measurements found!")
|
||||
return
|
||||
|
||||
print(f"\nFound {len(measurements)} measurement(s)\n")
|
||||
|
||||
# 2. For each measurement, show field keys
|
||||
print("=" * 80)
|
||||
print("STEP 2: Discovering fields for each measurement")
|
||||
print("=" * 80)
|
||||
|
||||
for measurement in measurements[:5]: # Limit to first 5 measurements
|
||||
print(f"\nMeasurement: {measurement}")
|
||||
query_fields = f'''
|
||||
import "influxdata/influxdb/schema"
|
||||
schema.measurementFieldKeys(
|
||||
bucket: "{bucket}",
|
||||
measurement: "{measurement}"
|
||||
)
|
||||
'''
|
||||
|
||||
result = query_api.query(query_fields)
|
||||
for table in result:
|
||||
for record in table.records:
|
||||
field = record.get_value()
|
||||
print(f" Field: {field}")
|
||||
|
||||
# 3. Show sample data from the first measurement
|
||||
print("\n" + "=" * 80)
|
||||
print("STEP 3: Sample data from first measurement")
|
||||
print("=" * 80)
|
||||
|
||||
first_measurement = measurements[0]
|
||||
query_sample = f'''
|
||||
from(bucket: "{bucket}")
|
||||
|> range(start: -24h)
|
||||
|> filter(fn: (r) => r._measurement == "{first_measurement}")
|
||||
|> limit(n: 10)
|
||||
'''
|
||||
|
||||
print(f"\nSample records from '{first_measurement}':\n")
|
||||
result = query_api.query(query_sample)
|
||||
for table in result:
|
||||
for record in table.records:
|
||||
print(f"Time: {record.get_time()}")
|
||||
print(f" Measurement: {record.get_measurement()}")
|
||||
print(f" Field: {record.get_field()}")
|
||||
print(f" Value: {record.get_value()}")
|
||||
print(f" Tags: {record.values}")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error querying InfluxDB: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not os.getenv('INFLUXDB_URL'):
|
||||
print("ERROR: Please create a .env file with your InfluxDB configuration")
|
||||
print("Copy .env.example to .env and fill in your details")
|
||||
exit(1)
|
||||
|
||||
explore_influxdb()
|
||||
Reference in New Issue
Block a user