s9s Architecture Guide
Comprehensive architecture documentation for s9s, covering system design, patterns, and component interactions.
Table of Contents
- Overview
- Architecture Diagram
- Core Components
- Key Design Patterns
- Data Flow
- Configuration Management
- State Management
- Concurrency Model
- Error Handling
- Security Considerations
- Performance Optimization
- Testing Architecture
- Extension Points
- Development Guidelines
- Debugging
Overview
s9s follows a modular, layered architecture designed for maintainability, testability, and extensibility. The application is built using Go and the tview terminal UI framework.
Architecture Diagram
┌─────────────────────────────────────────────────────────────┐ │ Terminal UI (tview) │ ├─────────────────────────────────────────────────────────────┤ │ Views Layer │ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │ │ │ Jobs │ │ Nodes │ │Dashboard │ │ Others │ │ │ └─────────┘ └─────────┘ └──────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ UI Components │ │ ┌──────────┐ ┌───────────┐ ┌────────┐ ┌─────────────┐ │ │ │ Table │ │StatusBar │ │ Modal │ │ FilterBar │ │ │ └──────────┘ └───────────┘ └────────┘ └─────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ Data Access Layer │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ DAO (Data Access Objects) │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │ │ │ │ │JobMgr │ │NodeMgr │ │PartMgr │ │Others │ │ │ │ │ └─────────┘ └─────────┘ └──────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ SLURM Adapter │ │ ┌─────────────────┐ ┌──────────────────────┐ │ │ │ SLURM Client │ │ Mock Client │ │ │ │ (Production) │ │ (Development) │ │ │ └─────────────────┘ └──────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ External Services │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ │ SLURM REST │ │ SSH Service │ │ Export Service │ │ │ │ API │ │ │ │ │ │ │ └─────────────┘ └──────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────┘
Core Components
1. Application Layer (cmd/s9s/)
The entry point of the application that:
- Parses command-line arguments
- Initializes configuration
- Sets up the application context
- Handles graceful shutdown
// cmd/s9s/main.go func main() { app := app.New() if err := app.Run(); err != nil { log.Fatal(err) } }
2. Views Layer (internal/views/)
Views implement the UI for different resources:
- BaseView: Common functionality for all views
- JobsView: Job listing and management
- NodesView: Node monitoring and control
- DashboardView: Cluster overview
- PartitionsView: Partition information
- Additional Views: Users, QoS, Reservations, etc.
Key interfaces:
type View interface { Name() string Title() string SetupView() tview.Primitive Refresh() error }
3. UI Components (internal/ui/)
Reusable UI components:
- MultiSelectTable: Table with multi-selection support
- StatusBar: Application-wide status messages
- FilterBar: Advanced filtering interface
- LoadingManager: Loading state management
- Modal dialogs: Confirmations, forms, etc.
4. Data Access Objects (internal/dao/)
Abstracts SLURM operations with clean interfaces:
type SlurmClient interface { Jobs() JobManager Nodes() NodeManager Partitions() PartitionManager // ... other managers } type JobManager interface { List(opts *ListJobsOptions) (*JobList, error) Get(id string) (*Job, error) Cancel(id string) error Hold(id string) error Release(id string) error Submit(job *JobSubmission) (string, error) }
5. SLURM Adapter (internal/dao/slurm_adapter.go)
Bridges the DAO interfaces with the actual SLURM client:
- Handles API version compatibility
- Manages authentication
- Implements retry logic
- Provides error translation
6. Mock Implementation (pkg/slurm/mock.go)
Full SLURM simulator for development:
- Simulates job lifecycle
- Provides realistic test data
- Supports all SLURM operations
- Configurable delays and behaviors
Key Design Patterns
Interface Segregation
Each manager interface is focused on a single resource type:
type NodeManager interface { List(opts *ListNodesOptions) (*NodeList, error) Get(name string) (*Node, error) Drain(name string, reason string) error Resume(name string) error }
Dependency Injection
Views receive dependencies through constructors:
func NewJobsView(client dao.SlurmClient) *JobsView { return &JobsView{ client: client, // ... other initialization } }
Observer Pattern
Views can subscribe to refresh events:
type RefreshObserver interface { OnRefresh() error }
Command Pattern
User actions are encapsulated as commands:
type Command interface { Execute() error Undo() error }
Data Flow
User Input Flow
User Input -> View Handler -> Validation -> DAO Call -> SLURM API -> Response -> UI Update
Refresh Flow
Timer/Manual Trigger -> View.Refresh() -> DAO.List() -> Parse Response -> Update Table -> Render
Error Handling Flow
Error Occurs -> Log Error -> User-Friendly Message -> Status Bar Display -> Optional Retry
Configuration Management
Configuration Hierarchy
- Default Configuration (built-in)
- System Configuration ()
/etc/s9s/config.yaml - User Configuration ()
~/.s9s/config.yaml - Environment Variables ()
S9S_* - Command-line Flags
Configuration Structure
type Config struct { Clusters map[string]*ClusterConfig Preferences *UserPreferences Debug bool LogFile string } type ClusterConfig struct { URL string Auth AuthConfig Timeout time.Duration RetryAttempts int }
State Management
View State
Each view maintains its own state:
type JobsView struct { jobs []*dao.Job // Current job list filter string // Active filter selectedJobs map[string]bool // Multi-selection state autoRefresh bool // Auto-refresh enabled mu sync.RWMutex // Thread safety }
Global State
Application-wide state is managed by:
- Configuration manager
- Theme manager
- Key binding registry
- Status bar coordinator
Concurrency Model
Goroutine Usage
- Main UI Thread: Handles all tview rendering
- Refresh Workers: Background data fetching
- SSH Sessions: Separate goroutine per session
- Export Operations: Async file operations
Synchronization
// Mutex for data protection v.mu.Lock() v.jobs = newJobs v.mu.Unlock() // Channels for communication done := make(chan bool) go func() { // Long operation done <- true }()
Error Handling
Error Types
- Network Errors: Retry with backoff
- Authentication Errors: Prompt for credentials
- Permission Errors: Clear user message
- Data Errors: Log and skip invalid entries
Error Propagation
// DAO layer adds context if err != nil { return fmt.Errorf("failed to list jobs: %w", err) } // View layer shows user-friendly message if err != nil { v.mainStatusBar.Error("Unable to refresh job list") debug.Logger.Printf("Refresh error: %v", err) }
Security Considerations
Authentication
- Token-based authentication preferred
- Credentials stored securely (keyring integration planned)
- Session management with timeout
- No credentials in logs or debug output
Input Validation
- All user input sanitized
- Command injection prevention
- Path traversal protection
- Size limits on data operations
Performance Optimization
Caching Strategy
- View-level caching: Recent data retained
- API response caching: Configurable TTL
- Lazy loading: Load data on demand
- Incremental updates: Refresh only changed data
Resource Management
// Limit concurrent operations sem := make(chan struct{}, 5) for _, job := range jobs { sem <- struct{}{} go func(j *Job) { defer func() { <-sem }() // Process job }(job) }
Testing Architecture
Test Layers
- Unit Tests: Individual component testing
- Integration Tests: Component interaction
- End-to-End Tests: Full workflow validation
- Performance Tests: Benchmarking
Mock Strategy
// Interface for easy mocking type TimeProvider interface { Now() time.Time } // Mock implementation type MockTimeProvider struct { CurrentTime time.Time }
Extension Points
Plugin System (Planned)
type Plugin interface { Name() string Version() string Init(api PluginAPI) error RegisterCommands() []Command RegisterViews() []View }
Custom Views
Developers can add custom views by:
- Implementing the View interface
- Registering with the view manager
- Adding key bindings
Export Formats
New export formats can be added by implementing:
type Exporter interface { Export(data interface{}, writer io.Writer) error FileExtension() string MimeType() string }
Future Architectural Considerations
Planned Improvements
- Plugin System: Dynamic loading of extensions
- Event Bus: Decoupled component communication
- State Store: Centralized state management
- API Gateway: Multiple backend support
- Metrics Collection: Performance monitoring
Scalability
- Pagination for large datasets
- Virtual scrolling for tables
- Progressive data loading
- Background prefetching
Development Guidelines
Adding a New View
- Create view file in
internal/views/ - Implement View interface
- Add to view registry
- Define key bindings
- Add tests
Adding a New Command
- Define command in view
- Add key binding
- Implement business logic
- Update help text
- Add tests
Code Organization
internal/views/newfeature.go # View implementation internal/views/newfeature_test.go # Unit tests internal/dao/types.go # Add data types docs/ # Update documentation
Debugging and Diagnostics
Debug Mode
Enable with
--debug- Detailed logging to file
- Performance metrics
- API request/response logging
- State change tracking
Health Checks
Built-in diagnostics:
- Connection status
- API availability
- Performance metrics
- Error rate monitoring
Related Documentation
- Setup Guide - Development environment setup
- Testing Guide - Testing strategies and practices
- Contributing Guide - Contribution process
- Linting Standards - Code quality requirements
- CI/CD Setup - Continuous integration configuration
This architecture provides a solid foundation for a maintainable, extensible terminal UI application while keeping the codebase organized and testable.