diff --git a/.drone.jsonnet b/.drone.jsonnet index fe6cf764..fea23fb1 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -10,7 +10,7 @@ local bootstrap = '25.02'; local nginx = '1.24.0'; local python = '3.12-slim-bookworm'; local alpine = '3.21'; -local visual_diff_skip_build = '2786'; +local visual_diff_skip_build = '2837'; local build(arch, testUI) = [{ kind: 'pipeline', diff --git a/backend/cmd/stability/main.go b/backend/cmd/stability/main.go index c955ad54..9b3b5539 100644 --- a/backend/cmd/stability/main.go +++ b/backend/cmd/stability/main.go @@ -1,38 +1,29 @@ package main import ( - "context" - "os" - "os/signal" + "path" "syscall" + "github.com/syncloud/platform/hook" "github.com/syncloud/platform/log" "github.com/syncloud/platform/stability" ) func main() { logger := log.Default() - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer cancel() - mem := stability.NewMemInfo("/proc") - commonDir := os.Getenv("SNAP_COMMON") - if commonDir == "" { - commonDir = "/var/snap/platform/common" - } - events := stability.NewEventLog(commonDir + "/stability-events.jsonl") - z := stability.NewZram(mem, stability.SwaponSyscall, stability.SwapoffSyscall, events, logger) - if err := z.EnsureConfigured(); err != nil { + eventsPath := path.Join(hook.DataDir, "stability-events.jsonl") + stability.MigrateEventLog(path.Join(hook.CommonDir, "stability-events.jsonl"), eventsPath, logger) + events := stability.NewEventLog(eventsPath) + zram := stability.NewZram(mem, stability.SwaponSyscall, stability.SwapoffSyscall, events, logger) + if err := zram.EnsureConfigured(); err != nil { logger.Sugar().Warnf("stability: zram setup failed (continuing): %v", err) } - scan := stability.NewProcScanner("/proc") - w := stability.NewWatcher(mem, scan, func(pid int, sig syscall.Signal) error { + scanner := stability.NewProcScanner("/proc") + watcher := stability.NewWatcher(mem, scanner, func(pid int, sig syscall.Signal) error { return syscall.Kill(pid, sig) }, events, logger) - if err := w.Run(ctx); err != nil && err != context.Canceled { - logger.Sugar().Errorf("stability: watcher exited: %v", err) - os.Exit(1) - } + watcher.Run() } diff --git a/backend/ioc/common.go b/backend/ioc/common.go index 274aa376..22fa69d0 100644 --- a/backend/ioc/common.go +++ b/backend/ioc/common.go @@ -572,8 +572,8 @@ func Init(userConfig string, systemConfig string, backupDir string, varDir strin return nil, err } - err = c.Singleton(func() *stability.EventLog { - return stability.NewEventLog("/var/snap/platform/common/stability-events.jsonl") + err = c.Singleton(func(systemConfig *config.SystemConfig) *stability.EventLog { + return stability.NewEventLog(path.Join(systemConfig.DataDir(), "stability-events.jsonl")) }) if err != nil { return nil, err diff --git a/backend/stability/migrate.go b/backend/stability/migrate.go new file mode 100644 index 00000000..474052c2 --- /dev/null +++ b/backend/stability/migrate.go @@ -0,0 +1,25 @@ +package stability + +import ( + "errors" + "os" + + "go.uber.org/zap" +) + +func MigrateEventLog(oldPath, newPath string, logger *zap.Logger) { + if _, err := os.Stat(newPath); err == nil { + return + } else if !errors.Is(err, os.ErrNotExist) { + logger.Warn("stability: stat new event log failed", zap.Error(err)) + return + } + if _, err := os.Stat(oldPath); err != nil { + return + } + if err := os.Rename(oldPath, newPath); err != nil { + logger.Warn("stability: migrate event log failed", zap.String("from", oldPath), zap.String("to", newPath), zap.Error(err)) + return + } + logger.Info("stability: migrated event log", zap.String("from", oldPath), zap.String("to", newPath)) +} diff --git a/backend/stability/migrate_test.go b/backend/stability/migrate_test.go new file mode 100644 index 00000000..aa4a2920 --- /dev/null +++ b/backend/stability/migrate_test.go @@ -0,0 +1,54 @@ +package stability + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestMigrateMovesWhenOnlyOldExists(t *testing.T) { + dir := t.TempDir() + old := filepath.Join(dir, "common", "events.jsonl") + newP := filepath.Join(dir, "data", "events.jsonl") + require.NoError(t, os.MkdirAll(filepath.Dir(old), 0755)) + require.NoError(t, os.MkdirAll(filepath.Dir(newP), 0755)) + require.NoError(t, os.WriteFile(old, []byte("{\"kind\":\"x\"}\n"), 0644)) + + MigrateEventLog(old, newP, zap.NewNop()) + + _, errOld := os.Stat(old) + assert.True(t, os.IsNotExist(errOld)) + body, err := os.ReadFile(newP) + require.NoError(t, err) + assert.Equal(t, "{\"kind\":\"x\"}\n", string(body)) +} + +func TestMigrateSkipsWhenNewAlreadyExists(t *testing.T) { + dir := t.TempDir() + old := filepath.Join(dir, "events.jsonl") + newP := filepath.Join(dir, "new.jsonl") + require.NoError(t, os.WriteFile(old, []byte("old"), 0644)) + require.NoError(t, os.WriteFile(newP, []byte("new"), 0644)) + + MigrateEventLog(old, newP, zap.NewNop()) + + oldBody, _ := os.ReadFile(old) + newBody, _ := os.ReadFile(newP) + assert.Equal(t, "old", string(oldBody), "old file untouched if new exists") + assert.Equal(t, "new", string(newBody), "new file untouched if new exists") +} + +func TestMigrateNoopWhenOldMissing(t *testing.T) { + dir := t.TempDir() + old := filepath.Join(dir, "missing.jsonl") + newP := filepath.Join(dir, "new.jsonl") + + MigrateEventLog(old, newP, zap.NewNop()) + + _, err := os.Stat(newP) + assert.True(t, os.IsNotExist(err)) +} diff --git a/backend/stability/oom.go b/backend/stability/oom.go index be38079c..9e476bcd 100644 --- a/backend/stability/oom.go +++ b/backend/stability/oom.go @@ -1,7 +1,6 @@ package stability import ( - "context" "errors" "os" "syscall" @@ -42,7 +41,7 @@ func NewWatcher(mem *MemInfo, scan *ProcScanner, kill KillFn, events *EventLog, } } -func (w *Watcher) Run(ctx context.Context) error { +func (w *Watcher) Run() { t := time.NewTicker(w.interval) defer t.Stop() w.log.Info("oom-watcher: started", @@ -50,14 +49,9 @@ func (w *Watcher) Run(ctx context.Context) error { zap.Float64("avail_min", w.availMin), zap.Float64("psi_max", w.psiMax), ) - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-t.C: - if err := w.tick(); err != nil { - w.log.Warn("oom-watcher: tick error", zap.Error(err)) - } + for range t.C { + if err := w.tick(); err != nil { + w.log.Warn("oom-watcher: tick error", zap.Error(err)) } } }