+35
internal/api/handlers.go
+35
internal/api/handlers.go
···
333
333
"scanned_at": scan.ScannedAt,
334
334
}
335
335
336
+
if scan.Status != storage.EndpointStatusOnline && scan.ScanData != nil && scan.ScanData.Metadata != nil {
337
+
if errorMsg, ok := scan.ScanData.Metadata["error"].(string); ok && errorMsg != "" {
338
+
scanMap["error"] = errorMsg
339
+
}
340
+
}
341
+
336
342
if scan.ResponseTime > 0 {
337
343
scanMap["response_time"] = scan.ResponseTime
338
344
}
···
1637
1643
}
1638
1644
1639
1645
resp.json(result)
1646
+
}
1647
+
1648
+
// ===== DEBUG HANDLERS =====
1649
+
1650
+
func (s *Server) handleGetDBSizes(w http.ResponseWriter, r *http.Request) {
1651
+
resp := newResponse(w)
1652
+
ctx := r.Context()
1653
+
schema := "public" // Or make configurable if needed
1654
+
1655
+
tableSizes, err := s.db.GetTableSizes(ctx, schema)
1656
+
if err != nil {
1657
+
log.Error("Failed to get table sizes: %v", err)
1658
+
resp.error("Failed to retrieve table sizes", http.StatusInternalServerError)
1659
+
return
1660
+
}
1661
+
1662
+
indexSizes, err := s.db.GetIndexSizes(ctx, schema)
1663
+
if err != nil {
1664
+
log.Error("Failed to get index sizes: %v", err)
1665
+
resp.error("Failed to retrieve index sizes", http.StatusInternalServerError)
1666
+
return
1667
+
}
1668
+
1669
+
resp.json(map[string]interface{}{
1670
+
"schema": schema,
1671
+
"tables": tableSizes,
1672
+
"indexes": indexSizes,
1673
+
"retrievedAt": time.Now().UTC(),
1674
+
})
1640
1675
}
1641
1676
1642
1677
// ===== UTILITY FUNCTIONS =====
+2
-1
internal/api/server.go
+2
-1
internal/api/server.go
···
105
105
// Metrics routes
106
106
api.HandleFunc("/metrics/plc", s.handleGetPLCMetrics).Methods("GET")
107
107
108
-
// Job status endpoint
108
+
// Debug Endpoints
109
+
api.HandleFunc("/debug/db/sizes", s.handleGetDBSizes).Methods("GET")
109
110
api.HandleFunc("/jobs", s.handleGetJobStatus).Methods("GET")
110
111
111
112
// Health check
+4
internal/storage/db.go
+4
internal/storage/db.go
···
87
87
GetPDSRepos(ctx context.Context, endpointID int64, activeOnly bool, limit int, offset int) ([]*PDSRepo, error) // Updated
88
88
GetReposByDID(ctx context.Context, did string) ([]*PDSRepo, error)
89
89
GetPDSRepoStats(ctx context.Context, endpointID int64) (map[string]interface{}, error)
90
+
91
+
// Internal
92
+
GetTableSizes(ctx context.Context, schema string) ([]TableSizeInfo, error)
93
+
GetIndexSizes(ctx context.Context, schema string) ([]IndexSizeInfo, error)
90
94
}
+98
internal/storage/postgres.go
+98
internal/storage/postgres.go
···
2337
2337
"recent_changes": recentChanges,
2338
2338
}, nil
2339
2339
}
2340
+
2341
+
// GetTableSizes fetches size information (in bytes) for all tables in the specified schema.
2342
+
func (p *PostgresDB) GetTableSizes(ctx context.Context, schema string) ([]TableSizeInfo, error) {
2343
+
// Query now selects raw byte values directly
2344
+
query := `
2345
+
SELECT
2346
+
c.relname AS table_name,
2347
+
pg_total_relation_size(c.oid) AS total_bytes,
2348
+
pg_relation_size(c.oid) AS table_heap_bytes,
2349
+
pg_indexes_size(c.oid) AS indexes_bytes
2350
+
FROM
2351
+
pg_class c
2352
+
LEFT JOIN
2353
+
pg_namespace n ON n.oid = c.relnamespace
2354
+
WHERE
2355
+
c.relkind = 'r' -- 'r' = ordinary table
2356
+
AND n.nspname = $1
2357
+
ORDER BY
2358
+
total_bytes DESC;
2359
+
`
2360
+
rows, err := p.db.QueryContext(ctx, query, schema)
2361
+
if err != nil {
2362
+
return nil, fmt.Errorf("failed to query table sizes: %w", err)
2363
+
}
2364
+
defer rows.Close()
2365
+
2366
+
var results []TableSizeInfo
2367
+
for rows.Next() {
2368
+
var info TableSizeInfo
2369
+
// Scan directly into int64 fields
2370
+
if err := rows.Scan(
2371
+
&info.TableName,
2372
+
&info.TotalBytes,
2373
+
&info.TableHeapBytes,
2374
+
&info.IndexesBytes,
2375
+
); err != nil {
2376
+
return nil, fmt.Errorf("failed to scan table size row: %w", err)
2377
+
}
2378
+
results = append(results, info)
2379
+
}
2380
+
if err := rows.Err(); err != nil {
2381
+
return nil, fmt.Errorf("error iterating table size rows: %w", err)
2382
+
}
2383
+
2384
+
return results, nil
2385
+
}
2386
+
2387
+
// GetIndexSizes fetches size information (in bytes) for all indexes in the specified schema.
2388
+
func (p *PostgresDB) GetIndexSizes(ctx context.Context, schema string) ([]IndexSizeInfo, error) {
2389
+
// Query now selects raw byte values directly
2390
+
query := `
2391
+
SELECT
2392
+
c.relname AS index_name,
2393
+
COALESCE(i.indrelid::regclass::text, 'N/A') AS table_name,
2394
+
pg_relation_size(c.oid) AS index_bytes
2395
+
FROM
2396
+
pg_class c
2397
+
LEFT JOIN
2398
+
pg_index i ON i.indexrelid = c.oid
2399
+
LEFT JOIN
2400
+
pg_namespace n ON n.oid = c.relnamespace
2401
+
WHERE
2402
+
c.relkind = 'i' -- 'i' = index
2403
+
AND n.nspname = $1
2404
+
ORDER BY
2405
+
index_bytes DESC;
2406
+
`
2407
+
rows, err := p.db.QueryContext(ctx, query, schema)
2408
+
if err != nil {
2409
+
return nil, fmt.Errorf("failed to query index sizes: %w", err)
2410
+
}
2411
+
defer rows.Close()
2412
+
2413
+
var results []IndexSizeInfo
2414
+
for rows.Next() {
2415
+
var info IndexSizeInfo
2416
+
var tableName sql.NullString
2417
+
// Scan directly into int64 field
2418
+
if err := rows.Scan(
2419
+
&info.IndexName,
2420
+
&tableName,
2421
+
&info.IndexBytes,
2422
+
); err != nil {
2423
+
return nil, fmt.Errorf("failed to scan index size row: %w", err)
2424
+
}
2425
+
if tableName.Valid {
2426
+
info.TableName = tableName.String
2427
+
} else {
2428
+
info.TableName = "N/A"
2429
+
}
2430
+
results = append(results, info)
2431
+
}
2432
+
if err := rows.Err(); err != nil {
2433
+
return nil, fmt.Errorf("error iterating index size rows: %w", err)
2434
+
}
2435
+
2436
+
return results, nil
2437
+
}
+35
-3
internal/storage/types.go
+35
-3
internal/storage/types.go
···
1
1
package storage
2
2
3
3
import (
4
+
"database/sql"
4
5
"fmt"
5
6
"path/filepath"
6
7
"time"
···
170
171
// DIDRecord represents a DID entry in the database
171
172
type DIDRecord struct {
172
173
DID string `json:"did"`
174
+
Handle string `json:"handle,omitempty"`
175
+
CurrentPDS string `json:"current_pds,omitempty"`
176
+
LastOpAt time.Time `json:"last_op_at,omitempty"`
173
177
BundleNumbers []int `json:"bundle_numbers"`
174
178
CreatedAt time.Time `json:"created_at"`
179
+
}
180
+
181
+
// GlobalDIDInfo consolidates DID data from PLC and PDS tables
182
+
type GlobalDIDInfo struct {
183
+
DIDRecord // Embeds all fields: DID, Handle, CurrentPDS, etc.
184
+
HostingOn []*PDSRepo `json:"hosting_on"`
175
185
}
176
186
177
187
// IPInfo represents IP information (stored with IP as primary key)
···
275
285
Status string
276
286
}
277
287
278
-
type GlobalDIDInfo struct {
279
-
DIDRecord
280
-
HostingOn []*PDSRepo `json:"hosting_on"`
288
+
type DIDBackfillInfo struct {
289
+
DID string
290
+
LastBundleNum int
291
+
}
292
+
293
+
type DIDStateUpdateData struct {
294
+
DID string
295
+
Handle sql.NullString // Use sql.NullString for potential NULLs
296
+
PDS sql.NullString
297
+
OpTime time.Time
298
+
}
299
+
300
+
// TableSizeInfo holds size information for a database table.
301
+
type TableSizeInfo struct {
302
+
TableName string `json:"table_name"`
303
+
TotalBytes int64 `json:"total_bytes"` // Raw bytes
304
+
TableHeapBytes int64 `json:"table_heap_bytes"` // Raw bytes
305
+
IndexesBytes int64 `json:"indexes_bytes"` // Raw bytes
306
+
}
307
+
308
+
// IndexSizeInfo holds size information for a database index.
309
+
type IndexSizeInfo struct {
310
+
IndexName string `json:"index_name"`
311
+
TableName string `json:"table_name"`
312
+
IndexBytes int64 `json:"index_bytes"` // Raw bytes
281
313
}