Phase 1: Fix Redis Schema Leak - Create internal/storage/dataset.go with DatasetStore abstraction - Remove all direct Redis calls from cmd/data_manager/data_sync.go - data_manager now uses DatasetStore for transfer tracking and metadata Phase 2: Simplify TUI Services - Embed *queue.TaskQueue directly in services.TaskQueue - Eliminate 60% of wrapper boilerplate (203 -> ~100 lines) - Keep only TUI-specific methods (EnqueueTask, GetJobStatus, experiment methods) Phase 5: Clean go.mod Dependencies - Remove duplicate go-redis/redis/v8 dependency - Migrate internal/storage/migrate.go to redis/go-redis/v9 - Separate test-only deps (miniredis, testify) into own block Results: - Zero direct Redis calls in cmd/ - 60% fewer lines in TUI services - Cleaner dependency structure
163 lines
4.5 KiB
Go
163 lines
4.5 KiB
Go
// Package services provides TUI service implementations
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jfraeys/fetch_ml/cmd/tui/internal/config"
|
|
"github.com/jfraeys/fetch_ml/internal/domain"
|
|
"github.com/jfraeys/fetch_ml/internal/experiment"
|
|
"github.com/jfraeys/fetch_ml/internal/network"
|
|
"github.com/jfraeys/fetch_ml/internal/queue"
|
|
)
|
|
|
|
// Task is an alias for domain.Task for TUI compatibility
|
|
type Task = domain.Task
|
|
|
|
// TaskQueue provides TUI-specific task operations by embedding queue.TaskQueue
|
|
// and extending it with experiment management capabilities.
|
|
type TaskQueue struct {
|
|
*queue.TaskQueue // Embed to inherit all queue methods directly
|
|
expManager *experiment.Manager
|
|
ctx context.Context
|
|
}
|
|
|
|
// NewTaskQueue creates a new task queue service
|
|
func NewTaskQueue(cfg *config.Config) (*TaskQueue, error) {
|
|
// Create internal queue config
|
|
queueCfg := queue.Config{
|
|
RedisAddr: cfg.RedisAddr,
|
|
RedisPassword: cfg.RedisPassword,
|
|
RedisDB: cfg.RedisDB,
|
|
}
|
|
|
|
internalQueue, err := queue.NewTaskQueue(queueCfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create task queue: %w", err)
|
|
}
|
|
|
|
// Initialize experiment manager
|
|
// TODO: Get base path from config
|
|
expManager := experiment.NewManager("./experiments")
|
|
|
|
return &TaskQueue{
|
|
TaskQueue: internalQueue,
|
|
expManager: expManager,
|
|
ctx: context.Background(),
|
|
}, nil
|
|
}
|
|
|
|
// EnqueueTask adds a new task to the queue (TUI-specific: creates task with proper defaults)
|
|
func (tq *TaskQueue) EnqueueTask(jobName, args string, priority int64) (*Task, error) {
|
|
// Create internal task
|
|
internalTask := &queue.Task{
|
|
JobName: jobName,
|
|
Args: args,
|
|
Priority: priority,
|
|
}
|
|
|
|
// Use embedded queue to enqueue
|
|
err := tq.TaskQueue.AddTask(internalTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return domain.Task directly (no conversion needed)
|
|
return internalTask, nil
|
|
}
|
|
|
|
// GetQueuedTasks retrieves all queued tasks (TUI-specific alias for GetAllTasks)
|
|
func (tq *TaskQueue) GetQueuedTasks() ([]*Task, error) {
|
|
return tq.TaskQueue.GetAllTasks()
|
|
}
|
|
|
|
// GetJobStatus gets the status of a job by name (TUI-specific convenience method)
|
|
func (tq *TaskQueue) GetJobStatus(jobName string) (map[string]string, error) {
|
|
task, err := tq.TaskQueue.GetTaskByName(jobName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if task == nil {
|
|
return map[string]string{"status": "not_found"}, nil
|
|
}
|
|
|
|
return map[string]string{
|
|
"status": task.Status,
|
|
"task_id": task.ID,
|
|
}, nil
|
|
}
|
|
|
|
// GetMetrics retrieves metrics for a job (TUI-specific: currently returns empty)
|
|
func (tq *TaskQueue) GetMetrics(_ string) (map[string]string, error) {
|
|
// This method doesn't exist in internal queue, return empty for now
|
|
return map[string]string{}, nil
|
|
}
|
|
|
|
// ListDatasets retrieves available datasets (TUI-specific: currently returns empty)
|
|
func (tq *TaskQueue) ListDatasets() ([]struct {
|
|
Name string
|
|
SizeBytes int64
|
|
Location string
|
|
LastAccess string
|
|
}, error) {
|
|
// This method doesn't exist in internal queue, return empty for now
|
|
return []struct {
|
|
Name string
|
|
SizeBytes int64
|
|
Location string
|
|
LastAccess string
|
|
}{}, nil
|
|
}
|
|
|
|
// ListExperiments retrieves experiment list
|
|
func (tq *TaskQueue) ListExperiments() ([]string, error) {
|
|
return tq.expManager.ListExperiments()
|
|
}
|
|
|
|
// GetExperimentDetails retrieves formatted experiment details
|
|
func (tq *TaskQueue) GetExperimentDetails(commitID string) (string, error) {
|
|
meta, err := tq.expManager.ReadMetadata(commitID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
metrics, err := tq.expManager.GetMetrics(commitID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
output := fmt.Sprintf("Experiment: %s\n", meta.JobName)
|
|
output += fmt.Sprintf("Commit ID: %s\n", meta.CommitID)
|
|
output += fmt.Sprintf("User: %s\n", meta.User)
|
|
output += fmt.Sprintf("Timestamp: %d\n\n", meta.Timestamp)
|
|
output += "Metrics:\n"
|
|
|
|
if len(metrics) == 0 {
|
|
output += " No metrics logged.\n"
|
|
} else {
|
|
for _, m := range metrics {
|
|
output += fmt.Sprintf(" %s: %.4f (Step: %d)\n", m.Name, m.Value, m.Step)
|
|
}
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// Close closes the task queue
|
|
func (tq *TaskQueue) Close() error {
|
|
return tq.TaskQueue.Close()
|
|
}
|
|
|
|
// MLServer is an alias for network.MLServer for backward compatibility
|
|
type MLServer = network.MLServer
|
|
|
|
// NewMLServer creates a new ML server connection
|
|
func NewMLServer(cfg *config.Config) (*MLServer, error) {
|
|
// Local mode: skip SSH entirely
|
|
if cfg.Host == "" {
|
|
return network.NewMLServer("", "", "", 0, "")
|
|
}
|
|
|
|
return network.NewMLServer(cfg.Host, cfg.User, cfg.SSHKey, cfg.Port, cfg.KnownHosts)
|
|
}
|