This document describes the modular architecture of PromViz and how to extend it with new data sources.
promviz/
├── main.go # Entry point
├── internal/
│ ├── app/
│ │ └── app.go # Application orchestration
│ ├── backend/
│ │ ├── types.go # Backend interface and common types
│ │ ├── prom/
│ │ │ └── client.go # Prometheus implementation
│ │ ├── influxdb/
│ │ │ └── client.go # InfluxDB v2 implementation
│ │ ├── influxdb1/
│ │ │ └── client.go # InfluxDB v1 implementation
│ │ └── mock/
│ │ └── client.go # Mock implementation (example)
│ ├── config/
│ │ └── config.go # Configuration management
│ └── ui/
│ └── ui.go # Terminal user interface
├── queries.yaml # Configuration file
└── go.mod # Dependencies
- Minimal entry point that handles CLI flags
- Creates and starts the application
- Handles graceful shutdown
- Orchestrates all components
- Manages the application lifecycle
- Coordinates between backend and UI
- Handles periodic metric updates
- Interface Definition:
Backendinterface intypes.go - Implementations: Each database/source has its own package
prom/: Prometheus implementationinfluxdb/: InfluxDB v2 implementationinfluxdb1/: InfluxDB v1 implementationmock/: Example mock implementation
- Extensible: Easy to add new data sources
- YAML configuration parsing and validation
- Backend-specific configuration management
- Centralized configuration access
- Terminal user interface using
tview - ASCII graph rendering with
asciigraph - Keyboard navigation and event handling
- Query history management
All backends must implement the Backend interface:
type Backend interface {
Connect(ctx context.Context) error
Query(ctx context.Context, expr string) (float64, error)
Close() error
Name() string
}To add a new data source (e.g., InfluxDB, TimescaleDB), follow these steps:
mkdir -p internal/backend/influxdbCreate internal/backend/influxdb/client.go:
package influxdb
import (
"context"
"fmt"
)
// Config holds InfluxDB-specific configuration
type Config struct {
URL string `yaml:"url"`
Token string `yaml:"token"`
Org string `yaml:"org"`
Bucket string `yaml:"bucket"`
}
func (c *Config) GetURL() string {
return c.URL
}
// Client wraps the InfluxDB client
type Client struct {
config *Config
// Add InfluxDB client fields here
}
func NewClient(config *Config) (*Client, error) {
// Initialize InfluxDB client
return &Client{config: config}, nil
}
func (c *Client) Connect(ctx context.Context) error {
// Test connection to InfluxDB
return nil
}
func (c *Client) Query(ctx context.Context, expr string) (float64, error) {
// Execute Flux query and return result
return 0.0, nil
}
func (c *Client) Close() error {
// Close InfluxDB connection
return nil
}
func (c *Client) Name() string {
return "influxdb"
}Add InfluxDB config to your configuration struct in internal/config/config.go:
type Config struct {
Prometheus prom.Config `yaml:"prometheus,omitempty"`
InfluxDB influxdb.Config `yaml:"influxdb,omitempty"`
Queries []backend.Query `yaml:"queries"`
Backend string `yaml:"backend"` // "prometheus", "influxdb", etc.
}Modify createBackend() in internal/app/app.go:
func createBackend(cfg *config.Config) (backend.Backend, error) {
switch cfg.Backend {
case "prometheus", "":
return prom.NewClient(cfg.GetPrometheusConfig())
case "influxdb":
return influxdb.NewClient(cfg.GetInfluxDBConfig())
case "influxdb1":
return influxdb1.NewClient(cfg.GetInfluxDB1Config())
case "timescaledb":
return timescaledb.NewClient(cfg.GetTimescaleDBConfig())
default:
return nil, fmt.Errorf("unsupported backend: %s", cfg.Backend)
}
}Example queries.yaml for InfluxDB:
backend: influxdb
influxdb:
url: "http://localhost:8086"
token: "your-token-here"
org: "your-org"
bucket: "metrics"
queries:
- name: CPU Usage
expr: 'r._measurement == "cpu" and r._field == "usage_percent"'
- name: Memory Usage
expr: 'r._measurement == "mem" and r._field == "used_percent"'PromViz now includes full InfluxDB v2 support with the following features:
backend: influxdb
influxdb:
url: "http://localhost:8086"
token: "your-influxdb-token"
org: "your-organization"
bucket: "telegraf"
queries:
- name: Simple Filter
expr: 'r._measurement == "cpu" and r._field == "usage_idle"'
- name: Complex Flux Query
expr: |
from(bucket: "telegraf")
|> range(start: -5m)
|> filter(fn: (r) => r._measurement == "cpu" and r._field == "usage_active")
|> mean()- Simple Expressions: Use simplified filters for basic queries
- Full Flux Queries: Support for complex multi-line Flux queries
- Automatic Query Wrapping: Simple expressions are automatically wrapped with bucket context
- Error Handling: Comprehensive connection and query error reporting
- Authentication: Token-based authentication with organization/bucket scoping
PromViz also supports InfluxDB v1 with InfluxQL queries:
backend: influxdb1
influxdb1:
url: "http://localhost:8086"
username: "admin"
password: "password"
database: "telegraf"
queries:
- name: Simple Field Query
expr: 'usage_idle'
- name: Full InfluxQL Query
expr: |
SELECT mean("usage_idle")
FROM "cpu"
WHERE time >= now() - 5m
GROUP BY time(1m)
ORDER BY time DESC
LIMIT 1- InfluxQL Support: Full support for InfluxDB v1's SQL-like query language
- HTTP API: Direct HTTP implementation without external dependencies
- Authentication: Username/password authentication
- Simple Expressions: Basic field queries are auto-wrapped in SELECT statements
- Complex Queries: Full InfluxQL syntax support for advanced use cases
- Error Handling: Comprehensive error reporting for connection and query issues
- Separation of Concerns: Each layer has a single responsibility
- Testability: Each component can be tested independently
- Extensibility: Easy to add new data sources without modifying existing code
- Maintainability: Clear boundaries between components
- Reusability: UI and app logic can work with any backend implementation
- Strategy Pattern: Backend interface allows swapping data sources
- Factory Pattern: Backend creation based on configuration
- Observer Pattern: UI updates based on data changes
- Facade Pattern: App package provides simple interface to complex subsystems
This architecture ensures that PromViz can grow to support multiple data sources while maintaining clean, maintainable code.