Add WHOIS stats to status page with adaptive fetcher improvements
- Add WHOIS Fetcher card showing fresh/stale/never-fetched ASN counts - Display hourly success/error counts and current fetch interval - Increase max WHOIS rate to 1/sec (down from 10 sec minimum) - Select random stale ASN instead of oldest for better distribution - Add index on whois_updated_at for query performance - Track success/error timestamps for hourly stats - Add GetWHOISStats database method for freshness statistics
This commit is contained in:
@@ -1633,18 +1633,16 @@ func (d *Database) GetRandomPrefixesByLengthContext(
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// GetNextStaleASN returns an ASN that needs WHOIS data refresh.
|
||||
// Priority: ASNs with no whois_updated_at, then oldest whois_updated_at.
|
||||
// GetNextStaleASN returns a random ASN that needs WHOIS data refresh.
|
||||
func (d *Database) GetNextStaleASN(ctx context.Context, staleThreshold time.Duration) (int, error) {
|
||||
cutoff := time.Now().Add(-staleThreshold)
|
||||
|
||||
// Select a random stale ASN using ORDER BY RANDOM()
|
||||
query := `
|
||||
SELECT asn FROM asns
|
||||
WHERE whois_updated_at IS NULL
|
||||
OR whois_updated_at < ?
|
||||
ORDER BY
|
||||
CASE WHEN whois_updated_at IS NULL THEN 0 ELSE 1 END,
|
||||
whois_updated_at ASC
|
||||
ORDER BY RANDOM()
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
@@ -1661,6 +1659,41 @@ func (d *Database) GetNextStaleASN(ctx context.Context, staleThreshold time.Dura
|
||||
return asn, nil
|
||||
}
|
||||
|
||||
// WHOISStats contains statistics about WHOIS data freshness.
|
||||
type WHOISStats struct {
|
||||
TotalASNs int `json:"total_asns"`
|
||||
StaleASNs int `json:"stale_asns"`
|
||||
FreshASNs int `json:"fresh_asns"`
|
||||
NeverFetched int `json:"never_fetched"`
|
||||
}
|
||||
|
||||
// GetWHOISStats returns statistics about WHOIS data freshness.
|
||||
func (d *Database) GetWHOISStats(ctx context.Context, staleThreshold time.Duration) (*WHOISStats, error) {
|
||||
cutoff := time.Now().Add(-staleThreshold)
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN whois_updated_at IS NULL THEN 1 ELSE 0 END) as never_fetched,
|
||||
SUM(CASE WHEN whois_updated_at IS NOT NULL AND whois_updated_at < ? THEN 1 ELSE 0 END) as stale,
|
||||
SUM(CASE WHEN whois_updated_at IS NOT NULL AND whois_updated_at >= ? THEN 1 ELSE 0 END) as fresh
|
||||
FROM asns
|
||||
`
|
||||
|
||||
var stats WHOISStats
|
||||
err := d.db.QueryRowContext(ctx, query, cutoff, cutoff).Scan(
|
||||
&stats.TotalASNs,
|
||||
&stats.NeverFetched,
|
||||
&stats.StaleASNs,
|
||||
&stats.FreshASNs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get WHOIS stats: %w", err)
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// UpdateASNWHOIS updates an ASN record with WHOIS data.
|
||||
func (d *Database) UpdateASNWHOIS(ctx context.Context, update *ASNWHOISUpdate) error {
|
||||
d.lock("UpdateASNWHOIS")
|
||||
|
||||
@@ -67,6 +67,7 @@ type Store interface {
|
||||
// ASN WHOIS operations
|
||||
GetNextStaleASN(ctx context.Context, staleThreshold time.Duration) (int, error)
|
||||
UpdateASNWHOIS(ctx context.Context, update *ASNWHOISUpdate) error
|
||||
GetWHOISStats(ctx context.Context, staleThreshold time.Duration) (*WHOISStats, error)
|
||||
|
||||
// AS and prefix detail operations
|
||||
GetASDetails(asn int) (*ASN, []LiveRoute, error)
|
||||
|
||||
@@ -90,6 +90,7 @@ CREATE INDEX IF NOT EXISTS idx_peerings_lookup ON peerings(as_a, as_b);
|
||||
|
||||
-- Indexes for asns table
|
||||
CREATE INDEX IF NOT EXISTS idx_asns_asn ON asns(asn);
|
||||
CREATE INDEX IF NOT EXISTS idx_asns_whois_updated_at ON asns(whois_updated_at);
|
||||
|
||||
-- Indexes for bgp_peers table
|
||||
CREATE INDEX IF NOT EXISTS idx_bgp_peers_asn ON bgp_peers(peer_asn);
|
||||
|
||||
Reference in New Issue
Block a user