Merge pull request #105 from hobbyistlabs-coder/feat/etl-pipeline-5135729988445987487
feat: implement ETL framework for Ultimate Visibility
This commit is contained in:
commit
2a072c7405
3 changed files with 118 additions and 0 deletions
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"jane/pkg/config"
|
"jane/pkg/config"
|
||||||
"jane/pkg/cron"
|
"jane/pkg/cron"
|
||||||
"jane/pkg/devices"
|
"jane/pkg/devices"
|
||||||
|
"jane/pkg/etl"
|
||||||
"jane/pkg/health"
|
"jane/pkg/health"
|
||||||
"jane/pkg/heartbeat"
|
"jane/pkg/heartbeat"
|
||||||
"jane/pkg/logger"
|
"jane/pkg/logger"
|
||||||
|
|
@ -190,6 +191,11 @@ func gatewayCmd(debug bool) error {
|
||||||
resourceTracker.Start(ctx)
|
resourceTracker.Start(ctx)
|
||||||
fmt.Println("✓ Resource tracker started")
|
fmt.Println("✓ Resource tracker started")
|
||||||
|
|
||||||
|
// Start ETL Pipeline for Ultimate Visibility
|
||||||
|
etlPipeline := etl.NewPipeline(cfg.WorkspacePath(), 1*time.Minute)
|
||||||
|
etlPipeline.Start(ctx)
|
||||||
|
fmt.Println("✓ ETL Pipeline started")
|
||||||
|
|
||||||
if err := channelManager.StartAll(ctx); err != nil {
|
if err := channelManager.StartAll(ctx); err != nil {
|
||||||
fmt.Printf("Error starting channels: %v\n", err)
|
fmt.Printf("Error starting channels: %v\n", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -220,6 +226,7 @@ func gatewayCmd(debug bool) error {
|
||||||
heartbeatService.Stop()
|
heartbeatService.Stop()
|
||||||
cronService.Stop()
|
cronService.Stop()
|
||||||
resourceTracker.Stop()
|
resourceTracker.Stop()
|
||||||
|
etlPipeline.Stop()
|
||||||
mediaStore.Stop()
|
mediaStore.Stop()
|
||||||
agentLoop.Stop()
|
agentLoop.Stop()
|
||||||
agentLoop.Close()
|
agentLoop.Close()
|
||||||
|
|
|
||||||
111
pkg/etl/pipeline.go
Normal file
111
pkg/etl/pipeline.go
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package etl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"jane/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pipeline manages the ETL process for Ultimate Visibility metrics
|
||||||
|
type Pipeline struct {
|
||||||
|
workspacePath string
|
||||||
|
interval time.Duration
|
||||||
|
stopCh chan struct{}
|
||||||
|
stopOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemMetrics represents the extracted system KPIs
|
||||||
|
type SystemMetrics struct {
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Goroutines int `json:"goroutines"`
|
||||||
|
MemoryAllocMB float64 `json:"memory_alloc_mb"`
|
||||||
|
MemoryTotalMB float64 `json:"memory_total_mb"`
|
||||||
|
MemorySysMB float64 `json:"memory_sys_mb"`
|
||||||
|
NumGC uint32 `json:"num_gc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPipeline creates a new ETL pipeline
|
||||||
|
func NewPipeline(workspacePath string, interval time.Duration) *Pipeline {
|
||||||
|
if interval == 0 {
|
||||||
|
interval = 1 * time.Minute
|
||||||
|
}
|
||||||
|
return &Pipeline{
|
||||||
|
workspacePath: workspacePath,
|
||||||
|
interval: interval,
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins the ETL extraction loop
|
||||||
|
func (p *Pipeline) Start(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(p.interval)
|
||||||
|
|
||||||
|
// Ensure log directory exists
|
||||||
|
logDir := filepath.Join(p.workspacePath, "logs", "etl")
|
||||||
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||||
|
logger.ErrorCF("ETL", "Failed to create ETL log directory", map[string]any{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-p.stopCh:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
p.extractAndLoad()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
logger.InfoCF("ETL", "Pipeline started", map[string]any{"interval": p.interval.String()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop halts the ETL pipeline
|
||||||
|
func (p *Pipeline) Stop() {
|
||||||
|
p.stopOnce.Do(func() {
|
||||||
|
close(p.stopCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pipeline) extractAndLoad() {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
|
metrics := SystemMetrics{
|
||||||
|
Timestamp: time.Now().UTC(),
|
||||||
|
Goroutines: runtime.NumGoroutine(),
|
||||||
|
MemoryAllocMB: float64(m.Alloc) / 1024 / 1024,
|
||||||
|
MemoryTotalMB: float64(m.TotalAlloc) / 1024 / 1024,
|
||||||
|
MemorySysMB: float64(m.Sys) / 1024 / 1024,
|
||||||
|
NumGC: m.NumGC,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform (JSON serialization)
|
||||||
|
data, err := json.Marshal(metrics)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("ETL", "Failed to marshal metrics", map[string]any{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load (Write to JSONL file)
|
||||||
|
logFile := filepath.Join(p.workspacePath, "logs", "etl", "system_metrics.jsonl")
|
||||||
|
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("ETL", "Failed to open metrics file", map[string]any{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Write(append(data, '\n')); err != nil {
|
||||||
|
logger.ErrorCF("ETL", "Failed to write metrics", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue