Skip to content

Commit 7803337

Browse files
committed
Refactor configuration to support multiple Prometheus exporters
- Removed PrometheusConfig and replaced it with an Exporters array in Config struct. - Introduced ExporterConfig struct to define individual exporter settings. - Added validation for exporters in the configuration. - Created Exporter interface and implemented NodeExporter for scraping metrics. - Implemented Registry for managing exporters and added methods for registration and retrieval. - Updated buffer handling to save Prometheus data in exporter-specific directories. - Modified Sender to handle multiple exporters and send data grouped by exporter. - Updated configuration file (nodepulse.yml) to reflect new exporter settings.
1 parent 5120c07 commit 7803337

9 files changed

Lines changed: 2375 additions & 135 deletions

File tree

cmd/start.go

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/node-pulse/agent/internal/config"
13+
"github.com/node-pulse/agent/internal/exporters"
1314
"github.com/node-pulse/agent/internal/logger"
1415
"github.com/node-pulse/agent/internal/pidfile"
1516
"github.com/node-pulse/agent/internal/prometheus"
@@ -80,16 +81,49 @@ func runAgent(cmd *cobra.Command, args []string) error {
8081
}
8182
}()
8283

83-
// Create Prometheus scraper
84-
scraper := prometheus.NewScraper(&prometheus.ScraperConfig{
85-
Endpoint: cfg.Prometheus.Endpoint,
86-
Timeout: cfg.Prometheus.Timeout,
87-
})
84+
// Create exporter registry
85+
registry := exporters.NewRegistry()
8886

89-
// Verify node_exporter is accessible on startup
90-
if err := scraper.Verify(); err != nil {
91-
logger.Error("Failed to verify Prometheus exporter - is node_exporter running?", logger.Err(err))
92-
return fmt.Errorf("prometheus exporter verification failed: %w\nPlease ensure node_exporter is running on %s", err, cfg.Prometheus.Endpoint)
87+
// Register built-in exporters
88+
registry.Register(exporters.NewNodeExporter("", 0))
89+
// Future: register other exporters here
90+
// registry.Register(exporters.NewPostgresExporter("", 0))
91+
// registry.Register(exporters.NewMysqlExporter("", 0))
92+
93+
// Initialize enabled exporters from config
94+
activeExporters := []exporters.Exporter{}
95+
for _, exporterCfg := range cfg.Exporters {
96+
if !exporterCfg.Enabled {
97+
continue
98+
}
99+
100+
// Create exporter instance with configured endpoint and timeout
101+
var exp exporters.Exporter
102+
switch exporterCfg.Name {
103+
case "node_exporter":
104+
exp = exporters.NewNodeExporter(exporterCfg.Endpoint, exporterCfg.Timeout)
105+
default:
106+
logger.Warn("Unknown exporter type, skipping", logger.String("name", exporterCfg.Name))
107+
continue
108+
}
109+
110+
// Verify exporter is accessible
111+
if err := exp.Verify(); err != nil {
112+
logger.Warn("Exporter verification failed, skipping",
113+
logger.String("name", exporterCfg.Name),
114+
logger.String("endpoint", exporterCfg.Endpoint),
115+
logger.Err(err))
116+
continue
117+
}
118+
119+
activeExporters = append(activeExporters, exp)
120+
logger.Info("Exporter initialized",
121+
logger.String("name", exporterCfg.Name),
122+
logger.String("endpoint", exporterCfg.Endpoint))
123+
}
124+
125+
if len(activeExporters) == 0 {
126+
return fmt.Errorf("no active exporters configured - please configure at least one exporter")
93127
}
94128

95129
// Create report sender
@@ -123,14 +157,12 @@ func runAgent(cmd *cobra.Command, args []string) error {
123157
logger.Info("Agent started",
124158
logger.String("server_id", cfg.Agent.ServerID),
125159
logger.Duration("interval", cfg.Agent.Interval),
126-
logger.String("prometheus_endpoint", cfg.Prometheus.Endpoint),
160+
logger.Int("exporters", len(activeExporters)),
127161
logger.String("server_endpoint", cfg.Server.Endpoint))
128162

129163
// Scrape immediately on start with aligned timestamp (UTC)
130164
collectionTime := time.Now().UTC().Truncate(cfg.Agent.Interval)
131-
if err := scrapeAndBuffer(scraper, sender, cfg.Agent.ServerID, collectionTime); err != nil {
132-
logger.Error("Initial scrape failed", logger.Err(err))
133-
}
165+
scrapeAllExporters(ctx, activeExporters, sender, cfg.Agent.ServerID, collectionTime)
134166

135167
// Continue with ticker
136168
for {
@@ -140,34 +172,44 @@ func runAgent(cmd *cobra.Command, args []string) error {
140172
case tickTime := <-ticker.C:
141173
// Align collection time to interval boundary (UTC)
142174
collectionTime := tickTime.UTC().Truncate(cfg.Agent.Interval)
143-
if err := scrapeAndBuffer(scraper, sender, cfg.Agent.ServerID, collectionTime); err != nil {
144-
logger.Error("Scrape failed", logger.Err(err))
145-
}
175+
scrapeAllExporters(ctx, activeExporters, sender, cfg.Agent.ServerID, collectionTime)
146176
}
147177
}
148178
}
149179

150-
// scrapeAndBuffer scrapes metrics and saves raw Prometheus text to buffer
151-
func scrapeAndBuffer(scraper *prometheus.Scraper, sender *report.Sender, serverID string, collectionTime time.Time) error {
152-
// Scrape Prometheus exporter
153-
data, err := scraper.Scrape()
154-
if err != nil {
155-
return fmt.Errorf("failed to scrape prometheus: %w", err)
156-
}
180+
// scrapeAllExporters scrapes all exporters sequentially
181+
func scrapeAllExporters(ctx context.Context, exportersList []exporters.Exporter,
182+
sender *report.Sender, serverID string, collectionTime time.Time) {
157183

158-
// Add explicit timestamps to metrics (aligned to collection time)
159-
// This ensures all agents report metrics at the same logical time boundaries
160-
dataWithTimestamp := prometheus.AddTimestamps(data, collectionTime)
184+
for _, exp := range exportersList {
185+
// Scrape with timeout
186+
scrapeCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
187+
data, err := exp.Scrape(scrapeCtx)
188+
cancel()
161189

162-
// Save raw Prometheus text to buffer (WAL pattern - parsing happens during drain)
163-
if err := sender.BufferPrometheus(dataWithTimestamp, serverID); err != nil {
164-
return fmt.Errorf("failed to buffer prometheus data: %w", err)
165-
}
190+
if err != nil {
191+
logger.Warn("Failed to scrape exporter",
192+
logger.String("exporter", exp.Name()),
193+
logger.Err(err))
194+
continue
195+
}
166196

167-
logger.Debug("Prometheus data scraped and buffered",
168-
logger.Int("bytes", len(dataWithTimestamp)),
169-
logger.String("collection_time", collectionTime.Format(time.RFC3339)))
170-
return nil
197+
// Add explicit timestamps to metrics (aligned to collection time)
198+
dataWithTimestamp := prometheus.AddTimestamps(data, collectionTime)
199+
200+
// Save raw Prometheus text to buffer (WAL pattern)
201+
if err := sender.BufferPrometheus(dataWithTimestamp, serverID, exp.Name()); err != nil {
202+
logger.Error("Failed to buffer metrics",
203+
logger.String("exporter", exp.Name()),
204+
logger.Err(err))
205+
continue
206+
}
207+
208+
logger.Debug("Exporter scraped and buffered",
209+
logger.String("exporter", exp.Name()),
210+
logger.Int("bytes", len(dataWithTimestamp)),
211+
logger.String("collection_time", collectionTime.Format(time.RFC3339)))
212+
}
171213
}
172214

173215
func runInBackground() error {

0 commit comments

Comments
 (0)