Implement proper storage metrics (P0-009)\n\n- Add dedicated storage_metrics table\n- Create StorageMetricReport models with proper field names\n- Add ReportStorageMetrics to agent client\n- Update storage scanner to use new method\n- Implement server-side handlers and queries\n- Register new routes and update UI\n- Remove legacy Scan() method\n- Follow ETHOS principles: honest naming, clean architecture
This commit is contained in:
167
aggregator-server/internal/database/queries/storage_metrics.go
Normal file
167
aggregator-server/internal/database/queries/storage_metrics.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/aggregator-server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// StorageMetricsQueries handles storage metrics database operations
|
||||
type StorageMetricsQueries struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewStorageMetricsQueries creates a new storage metrics queries instance
|
||||
func NewStorageMetricsQueries(db *sql.DB) *StorageMetricsQueries {
|
||||
return &StorageMetricsQueries{db: db}
|
||||
}
|
||||
|
||||
// InsertStorageMetric inserts a new storage metric
|
||||
func (q *StorageMetricsQueries) InsertStorageMetric(ctx context.Context, metric models.StorageMetric) error {
|
||||
query := `
|
||||
INSERT INTO storage_metrics (
|
||||
id, agent_id, mountpoint, device, disk_type, filesystem,
|
||||
total_bytes, used_bytes, available_bytes, used_percent,
|
||||
severity, metadata, created_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
`
|
||||
|
||||
_, err := q.db.ExecContext(ctx, query,
|
||||
metric.ID, metric.AgentID, metric.Mountpoint, metric.Device,
|
||||
metric.DiskType, metric.Filesystem, metric.TotalBytes,
|
||||
metric.UsedBytes, metric.AvailableBytes, metric.UsedPercent,
|
||||
metric.Severity, pq.Array(metric.Metadata), metric.CreatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert storage metric: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStorageMetricsByAgentID retrieves storage metrics for an agent
|
||||
func (q *StorageMetricsQueries) GetStorageMetricsByAgentID(ctx context.Context, agentID uuid.UUID, limit, offset int) ([]models.StorageMetric, error) {
|
||||
query := `
|
||||
SELECT id, agent_id, mountpoint, device, disk_type, filesystem,
|
||||
total_bytes, used_bytes, available_bytes, used_percent,
|
||||
severity, metadata, created_at
|
||||
FROM storage_metrics
|
||||
WHERE agent_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
rows, err := q.db.QueryContext(ctx, query, agentID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query storage metrics: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var metrics []models.StorageMetric
|
||||
for rows.Next() {
|
||||
var metric models.StorageMetric
|
||||
var metadataMap map[string]interface{}
|
||||
|
||||
err := rows.Scan(
|
||||
&metric.ID, &metric.AgentID, &metric.Mountpoint, &metric.Device,
|
||||
&metric.DiskType, &metric.Filesystem, &metric.TotalBytes,
|
||||
&metric.UsedBytes, &metric.AvailableBytes, &metric.UsedPercent,
|
||||
&metric.Severity, &metadataMap, &metric.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan storage metric: %w", err)
|
||||
}
|
||||
|
||||
metric.Metadata = metadataMap
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating storage metrics: %w", err)
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetLatestStorageMetrics retrieves the most recent storage metrics per mountpoint
|
||||
func (q *StorageMetricsQueries) GetLatestStorageMetrics(ctx context.Context, agentID uuid.UUID) ([]models.StorageMetric, error) {
|
||||
query := `
|
||||
SELECT DISTINCT ON (mountpoint)
|
||||
id, agent_id, mountpoint, device, disk_type, filesystem,
|
||||
total_bytes, used_bytes, available_bytes, used_percent,
|
||||
severity, metadata, created_at
|
||||
FROM storage_metrics
|
||||
WHERE agent_id = $1
|
||||
ORDER BY mountpoint, created_at DESC
|
||||
`
|
||||
|
||||
rows, err := q.db.QueryContext(ctx, query, agentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query latest storage metrics: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var metrics []models.StorageMetric
|
||||
for rows.Next() {
|
||||
var metric models.StorageMetric
|
||||
var metadataMap map[string]interface{}
|
||||
|
||||
err := rows.Scan(
|
||||
&metric.ID, &metric.AgentID, &metric.Mountpoint, &metric.Device,
|
||||
&metric.DiskType, &metric.Filesystem, &metric.TotalBytes,
|
||||
&metric.UsedBytes, &metric.AvailableBytes, &metric.UsedPercent,
|
||||
&metric.Severity, &metadataMap, &metric.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan storage metric: %w", err)
|
||||
}
|
||||
|
||||
metric.Metadata = metadataMap
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating latest storage metrics: %w", err)
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// GetStorageMetricsSummary returns summary statistics for an agent
|
||||
func (q *StorageMetricsQueries) GetStorageMetricsSummary(ctx context.Context, agentID uuid.UUID) (map[string]interface{}, error) {
|
||||
query := `
|
||||
SELECT
|
||||
COUNT(*) as total_disks,
|
||||
COUNT(CASE WHEN severity = 'critical' THEN 1 END) as critical_disks,
|
||||
COUNT(CASE WHEN severity = 'important' THEN 1 END) as important_disks,
|
||||
AVG(used_percent) as avg_used_percent,
|
||||
MAX(used_percent) as max_used_percent,
|
||||
MIN(created_at) as first_collected_at,
|
||||
MAX(created_at) as last_collected_at
|
||||
FROM storage_metrics
|
||||
WHERE agent_id = $1
|
||||
AND created_at >= NOW() - INTERVAL '24 hours'
|
||||
`
|
||||
|
||||
var summary map[string]interface{}
|
||||
err := q.db.QueryRowContext(ctx, query, agentID).Scan(
|
||||
&summary["total_disks"],
|
||||
&summary["critical_disks"],
|
||||
&summary["important_disks"],
|
||||
&summary["avg_used_percent"],
|
||||
&summary["max_used_percent"],
|
||||
&summary["first_collected_at"],
|
||||
&summary["last_collected_at"],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get storage metrics summary: %w", err)
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
Reference in New Issue
Block a user