add caching

This commit is contained in:
Fran Jurmanović
2025-05-28 19:59:43 +02:00
parent 0ced45ce55
commit 3dfbe77219
8 changed files with 382 additions and 73 deletions

View File

@@ -1,13 +1,98 @@
package model
type ServiceStatus string
import (
"database/sql/driver"
"fmt"
)
type ServiceStatus int
const (
StatusRunning ServiceStatus = "SERVICE_RUNNING\r\n"
StatusStopped ServiceStatus = "SERVICE_STOPPED\r\n"
StatusRestarting ServiceStatus = "SERVICE_RESTARTING\r\n"
StatusUnknown ServiceStatus = iota
StatusStopped
StatusStopping
StatusRestarting
StatusStarting
StatusRunning
)
// String converts the ServiceStatus to its string representation
func (s ServiceStatus) String() string {
switch s {
case StatusRunning:
return "SERVICE_RUNNING"
case StatusStopped:
return "SERVICE_STOPPED"
case StatusStarting:
return "SERVICE_STARTING"
case StatusStopping:
return "SERVICE_STOPPING"
case StatusRestarting:
return "SERVICE_RESTARTING"
default:
return "SERVICE_UNKNOWN"
}
}
// ParseServiceStatus converts a string to ServiceStatus
func ParseServiceStatus(s string) ServiceStatus {
switch s {
case "SERVICE_RUNNING":
return StatusRunning
case "SERVICE_STOPPED":
return StatusStopped
case "SERVICE_STARTING":
return StatusStarting
case "SERVICE_STOPPING":
return StatusStopping
case "SERVICE_RESTARTING":
return StatusRestarting
default:
return StatusUnknown
}
}
// MarshalJSON implements json.Marshaler interface
func (s ServiceStatus) MarshalJSON() ([]byte, error) {
return []byte(`"` + s.String() + `"`), nil
}
// UnmarshalJSON implements json.Unmarshaler interface
func (s *ServiceStatus) UnmarshalJSON(data []byte) error {
str := string(data)
// Remove quotes
str = str[1 : len(str)-1]
*s = ParseServiceStatus(str)
return nil
}
// Scan implements the sql.Scanner interface
func (s *ServiceStatus) Scan(value interface{}) error {
if value == nil {
*s = StatusUnknown
return nil
}
switch v := value.(type) {
case string:
*s = ParseServiceStatus(v)
return nil
case []byte:
*s = ParseServiceStatus(string(v))
return nil
case int64:
*s = ServiceStatus(v)
return nil
default:
return fmt.Errorf("unsupported type for ServiceStatus: %T", value)
}
}
// Value implements the driver.Valuer interface
func (s ServiceStatus) Value() (driver.Value, error) {
return s.String(), nil
}
type ApiModel struct {
Api string `json:"api"`
}

120
local/model/cache.go Normal file
View File

@@ -0,0 +1,120 @@
package model
import (
"sync"
"time"
)
// StatusCache represents a cached server status with expiration
type StatusCache struct {
Status ServiceStatus
UpdatedAt time.Time
}
// CacheConfig holds configuration for cache behavior
type CacheConfig struct {
ExpirationTime time.Duration // How long before a cache entry expires
ThrottleTime time.Duration // Minimum time between status checks
DefaultStatus ServiceStatus // Default status to return when throttled
}
// ServerStatusCache manages cached server statuses
type ServerStatusCache struct {
sync.RWMutex
cache map[string]*StatusCache
config CacheConfig
lastChecked map[string]time.Time
}
// NewServerStatusCache creates a new server status cache
func NewServerStatusCache(config CacheConfig) *ServerStatusCache {
return &ServerStatusCache{
cache: make(map[string]*StatusCache),
lastChecked: make(map[string]time.Time),
config: config,
}
}
// GetStatus retrieves the cached status or indicates if a fresh check is needed
func (c *ServerStatusCache) GetStatus(serviceName string) (ServiceStatus, bool) {
c.RLock()
defer c.RUnlock()
// Check if we're being throttled
if lastCheck, exists := c.lastChecked[serviceName]; exists {
if time.Since(lastCheck) < c.config.ThrottleTime {
if cached, ok := c.cache[serviceName]; ok {
return cached.Status, false
}
return c.config.DefaultStatus, false
}
}
// Check if we have a valid cached entry
if cached, ok := c.cache[serviceName]; ok {
if time.Since(cached.UpdatedAt) < c.config.ExpirationTime {
return cached.Status, false
}
}
return StatusUnknown, true
}
// UpdateStatus updates the cache with a new status
func (c *ServerStatusCache) UpdateStatus(serviceName string, status ServiceStatus) {
c.Lock()
defer c.Unlock()
c.cache[serviceName] = &StatusCache{
Status: status,
UpdatedAt: time.Now(),
}
c.lastChecked[serviceName] = time.Now()
}
// Clear removes all entries from the cache
func (c *ServerStatusCache) Clear() {
c.Lock()
defer c.Unlock()
c.cache = make(map[string]*StatusCache)
c.lastChecked = make(map[string]time.Time)
}
// LookupCache provides a generic cache for lookup data
type LookupCache struct {
sync.RWMutex
data map[string]interface{}
}
// NewLookupCache creates a new lookup cache
func NewLookupCache() *LookupCache {
return &LookupCache{
data: make(map[string]interface{}),
}
}
// Get retrieves a cached value by key
func (c *LookupCache) Get(key string) (interface{}, bool) {
c.RLock()
defer c.RUnlock()
value, exists := c.data[key]
return value, exists
}
// Set stores a value in the cache
func (c *LookupCache) Set(key string, value interface{}) {
c.Lock()
defer c.Unlock()
c.data[key] = value
}
// Clear removes all entries from the cache
func (c *LookupCache) Clear() {
c.Lock()
defer c.Unlock()
c.data = make(map[string]interface{})
}

View File

@@ -9,7 +9,7 @@ import (
type Server struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"`
Status ServiceStatus `json:"status"`
Status ServiceStatus `json:"status" gorm:"-"`
IP string `gorm:"not null" json:"-"`
Port int `gorm:"not null" json:"-"`
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"