diff --git a/TODO.md b/TODO.md index 2088bca1..2a84833e 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,6 @@ repo with its own release workflow. platform CI pipeline (similar to how nginx and authelia are already built in-tree), so upgrading a dependency is just a version bump in this repo. -**Current downloads in `package.sh`:** +**Remaining downloads in `package.sh`:** - `https://github.com/syncloud/3rdparty/releases/download/openldap/openldap-${ARCH}.tar.gz` - `https://github.com/syncloud/3rdparty/releases/download/btrfs/btrfs-${ARCH}.tar.gz` -- `https://github.com/syncloud/3rdparty/releases/download/gptfdisk/gptfdisk-${ARCH}.tar.gz` diff --git a/backend/health/metrics.go b/backend/health/metrics.go index cbe04c19..cf2ac6c6 100644 --- a/backend/health/metrics.go +++ b/backend/health/metrics.go @@ -10,14 +10,14 @@ import ( ) type CPU struct { - User uint64 `json:"user"` - Nice uint64 `json:"nice"` - System uint64 `json:"system"` - Idle uint64 `json:"idle"` - IOWait uint64 `json:"iowait"` - IRQ uint64 `json:"irq"` + User uint64 `json:"user"` + Nice uint64 `json:"nice"` + System uint64 `json:"system"` + Idle uint64 `json:"idle"` + IOWait uint64 `json:"iowait"` + IRQ uint64 `json:"irq"` SoftIRQ uint64 `json:"softirq"` - Steal uint64 `json:"steal"` + Steal uint64 `json:"steal"` } func (c CPU) Total() uint64 { @@ -29,13 +29,15 @@ func (c CPU) Busy() uint64 { } type Memory struct { - TotalKB uint64 `json:"total_kb"` - AvailableKB uint64 `json:"available_kb"` - FreeKB uint64 `json:"free_kb"` - BuffersKB uint64 `json:"buffers_kb"` - CachedKB uint64 `json:"cached_kb"` - SwapTotalKB uint64 `json:"swap_total_kb"` - SwapFreeKB uint64 `json:"swap_free_kb"` + TotalKB uint64 `json:"total_kb"` + AvailableKB uint64 `json:"available_kb"` + FreeKB uint64 `json:"free_kb"` + BuffersKB uint64 `json:"buffers_kb"` + CachedKB uint64 `json:"cached_kb"` + SwapTotalKB uint64 `json:"swap_total_kb"` + SwapFreeKB uint64 `json:"swap_free_kb"` + SwapInPages uint64 `json:"swap_in_pages"` + SwapOutPages uint64 `json:"swap_out_pages"` } type Disk struct { @@ -47,9 +49,9 @@ type Disk struct { } type Mount struct { - Path string `json:"path"` - TotalKB uint64 `json:"total_kb"` - UsedKB uint64 `json:"used_kb"` + Path string `json:"path"` + TotalKB uint64 `json:"total_kb"` + UsedKB uint64 `json:"used_kb"` } type Net struct { @@ -59,11 +61,11 @@ type Net struct { } type Snapshot struct { - CPU CPU `json:"cpu"` - Memory Memory `json:"memory"` - Disks []Disk `json:"disks"` + CPU CPU `json:"cpu"` + Memory Memory `json:"memory"` + Disks []Disk `json:"disks"` Mounts []Mount `json:"mounts"` - Net []Net `json:"net"` + Net []Net `json:"net"` } type Collector struct { @@ -85,6 +87,7 @@ func (c *Collector) Snapshot() (Snapshot, error) { if err != nil { return s, err } + mem.SwapInPages, mem.SwapOutPages = c.readSwapCounters() s.Memory = mem s.Disks, _ = c.readDisks() s.Net, _ = c.readNet() @@ -155,6 +158,29 @@ func (c *Collector) readMemory() (Memory, error) { return m, sc.Err() } +func (c *Collector) readSwapCounters() (in, out uint64) { + f, err := os.Open(filepath.Join(c.procDir, "vmstat")) + if err != nil { + return 0, 0 + } + defer f.Close() + sc := bufio.NewScanner(f) + for sc.Scan() { + fields := strings.Fields(sc.Text()) + if len(fields) != 2 { + continue + } + v, _ := strconv.ParseUint(fields[1], 10, 64) + switch fields[0] { + case "pswpin": + in = v + case "pswpout": + out = v + } + } + return in, out +} + func (c *Collector) readDisks() ([]Disk, error) { f, err := os.Open(filepath.Join(c.procDir, "diskstats")) if err != nil { diff --git a/backend/health/metrics_test.go b/backend/health/metrics_test.go index 5adb2077..93718b8b 100644 --- a/backend/health/metrics_test.go +++ b/backend/health/metrics_test.go @@ -21,6 +21,7 @@ func newTestCollector(t *testing.T) (*Collector, string) { dir := t.TempDir() writeProc(t, dir, "stat", "cpu 1000 50 200 5000 30 0 10 0\ncpu0 ...\n") writeProc(t, dir, "meminfo", "MemTotal: 3700000 kB\nMemAvailable: 1500000 kB\nMemFree: 200000 kB\nBuffers: 50000 kB\nCached: 900000 kB\nSwapTotal: 2000000 kB\nSwapFree: 1500000 kB\n") + writeProc(t, dir, "vmstat", "nr_free_pages 50000\npswpin 1234\npswpout 5678\npgfault 999\n") writeProc(t, dir, "diskstats", " 8 0 sda 100 0 200 0 10 0 20 0 0 0 0 0 0 0\n"+ " 8 1 sda1 50 0 100 0 5 0 10 0 0 0 0 0 0 0\n"+ " 179 0 mmcblk0 1000 0 2000 0 100 0 200 0 0 0 0 0 0 0\n"+ @@ -52,6 +53,20 @@ func TestReadMemory(t *testing.T) { assert.Equal(t, uint64(2000000), mem.SwapTotalKB) } +func TestReadSwapCounters(t *testing.T) { + c, _ := newTestCollector(t) + in, out := c.readSwapCounters() + assert.Equal(t, uint64(1234), in) + assert.Equal(t, uint64(5678), out) +} + +func TestReadSwapCountersMissingVmstat(t *testing.T) { + c := NewCollector(t.TempDir()) + in, out := c.readSwapCounters() + assert.Equal(t, uint64(0), in) + assert.Equal(t, uint64(0), out) +} + func TestReadDisksFiltersPartitionsAndLoops(t *testing.T) { c, _ := newTestCollector(t) disks, err := c.readDisks() diff --git a/backend/stability/zram.go b/backend/stability/zram.go index 4919d2df..07f4e3bb 100644 --- a/backend/stability/zram.go +++ b/backend/stability/zram.go @@ -21,7 +21,6 @@ const ( zramSysBlockDefault = "/sys/block/zram0" zramHotAddDefault = "/sys/class/zram-control/hot_add" procSwapsDefault = "/proc/swaps" - memThresholdKB = 6 * 1024 * 1024 zramMaxSizeBytes = uint64(2 * 1024 * 1024 * 1024) zramPriority = 10 swapMagicV1 = "SWAPSPACE2" @@ -54,10 +53,6 @@ func (z *Zram) EnsureConfigured() error { if err != nil { return fmt.Errorf("zram: meminfo: %w", err) } - if snap.TotalKB > memThresholdKB { - z.log.Info("zram: skipping; memory above threshold", zap.Uint64("total_kb", snap.TotalKB)) - return nil - } on, err := z.alreadyOn() if err != nil { return fmt.Errorf("zram: check swaps: %w", err) @@ -175,7 +170,7 @@ func (z *Zram) sizeBytes(totalBytes uint64) uint64 { func (z *Zram) configureSysfs(sizeBytes uint64) error { if err := os.WriteFile(filepath.Join(z.sysBlock, "comp_algorithm"), []byte("zstd"), 0644); err != nil { - return err + z.log.Warn("zram: zstd unsupported, keeping kernel default algorithm", zap.Error(err)) } return os.WriteFile(filepath.Join(z.sysBlock, "disksize"), []byte(fmt.Sprintf("%d", sizeBytes)), 0644) } diff --git a/backend/stability/zram_test.go b/backend/stability/zram_test.go index d8d8dcfc..9f365f77 100644 --- a/backend/stability/zram_test.go +++ b/backend/stability/zram_test.go @@ -54,13 +54,6 @@ func strconvUint(v uint64) string { return string(digits) } -func TestEnsureSkipsAboveMemThreshold(t *testing.T) { - z, sysBlock := newTestZram(t, 8*1024*1024, "Filename...\n") - require.NoError(t, z.EnsureConfigured()) - size, _ := os.ReadFile(filepath.Join(sysBlock, "disksize")) - assert.Equal(t, "0", string(size)) -} - func TestEnsureSkipsIfAlreadyOn(t *testing.T) { z, sysBlock := newTestZram(t, 4*1024*1024, "Filename\t\tType\tSize\tUsed\tPriority\n") sc, err := os.ReadFile(z.procSwaps) @@ -93,6 +86,15 @@ func TestConfigureSysfsWritesAlgoAndSize(t *testing.T) { assert.Equal(t, "123456", string(size)) } +func TestConfigureSysfsToleratesUnsupportedAlgo(t *testing.T) { + z, sysBlock := newTestZram(t, 4*1024*1024, "") + require.NoError(t, os.Remove(filepath.Join(sysBlock, "comp_algorithm"))) + require.NoError(t, os.Mkdir(filepath.Join(sysBlock, "comp_algorithm"), 0755)) + require.NoError(t, z.configureSysfs(123456)) + size, _ := os.ReadFile(filepath.Join(sysBlock, "disksize")) + assert.Equal(t, "123456", string(size)) +} + func TestSizeBytesCapped(t *testing.T) { z, _ := newTestZram(t, 4*1024*1024, "") assert.Equal(t, uint64(2*1024*1024*1024), z.sizeBytes(8*1024*1024*1024)) diff --git a/web/platform/src/locales/ar.json b/web/platform/src/locales/ar.json index d68b485b..b40ae3ef 100644 --- a/web/platform/src/locales/ar.json +++ b/web/platform/src/locales/ar.json @@ -322,6 +322,8 @@ "ioWrite": "كتابة", "netRx": "استقبال", "netTx": "إرسال", + "swapIn": "داخل", + "swapOut": "خارج", "kindZramEnabled": "تم تمكين zram", "kindSwapoffFile": "تم تعطيل ملف التبديل", "kindPressure": "تم اكتشاف ضغط على الذاكرة", diff --git a/web/platform/src/locales/de.json b/web/platform/src/locales/de.json index 27390a4a..bf3abde5 100644 --- a/web/platform/src/locales/de.json +++ b/web/platform/src/locales/de.json @@ -322,6 +322,8 @@ "ioWrite": "schreiben", "netRx": "Empfang", "netTx": "Senden", + "swapIn": "rein", + "swapOut": "raus", "kindZramEnabled": "zram aktiviert", "kindSwapoffFile": "Auslagerungsdatei deaktiviert", "kindPressure": "Speicherengpass erkannt", diff --git a/web/platform/src/locales/en.json b/web/platform/src/locales/en.json index 2679f10d..bd880648 100644 --- a/web/platform/src/locales/en.json +++ b/web/platform/src/locales/en.json @@ -78,6 +78,8 @@ "ioWrite": "write", "netRx": "down", "netTx": "up", + "swapIn": "in", + "swapOut": "out", "kindZramEnabled": "zram enabled", "kindSwapoffFile": "file swap disabled", "kindPressure": "memory pressure detected", diff --git a/web/platform/src/locales/es.json b/web/platform/src/locales/es.json index 7c421104..7da21719 100644 --- a/web/platform/src/locales/es.json +++ b/web/platform/src/locales/es.json @@ -322,6 +322,8 @@ "ioWrite": "escritura", "netRx": "recibido", "netTx": "enviado", + "swapIn": "entra", + "swapOut": "sale", "kindZramEnabled": "zram activado", "kindSwapoffFile": "archivo de intercambio desactivado", "kindPressure": "presión de memoria detectada", diff --git a/web/platform/src/locales/fr.json b/web/platform/src/locales/fr.json index e57496b3..0e15a7a1 100644 --- a/web/platform/src/locales/fr.json +++ b/web/platform/src/locales/fr.json @@ -322,6 +322,8 @@ "ioWrite": "écriture", "netRx": "reçu", "netTx": "envoyé", + "swapIn": "entrée", + "swapOut": "sortie", "kindZramEnabled": "zram activé", "kindSwapoffFile": "fichier swap désactivé", "kindPressure": "pression mémoire détectée", diff --git a/web/platform/src/locales/hi.json b/web/platform/src/locales/hi.json index 21043eef..4428e453 100644 --- a/web/platform/src/locales/hi.json +++ b/web/platform/src/locales/hi.json @@ -322,6 +322,8 @@ "ioWrite": "लिखना", "netRx": "डाउनलोड", "netTx": "अपलोड", + "swapIn": "अंदर", + "swapOut": "बाहर", "kindZramEnabled": "zram सक्षम", "kindSwapoffFile": "फ़ाइल स्वैप अक्षम", "kindPressure": "मेमोरी दबाव का पता चला", diff --git a/web/platform/src/locales/ja.json b/web/platform/src/locales/ja.json index 61f60980..1a5ce431 100644 --- a/web/platform/src/locales/ja.json +++ b/web/platform/src/locales/ja.json @@ -322,6 +322,8 @@ "ioWrite": "書き込み", "netRx": "受信", "netTx": "送信", + "swapIn": "入", + "swapOut": "出", "kindZramEnabled": "zram を有効化", "kindSwapoffFile": "ファイルスワップを無効化", "kindPressure": "メモリ圧迫を検出", diff --git a/web/platform/src/locales/pt.json b/web/platform/src/locales/pt.json index 6c2bca0f..d44eb891 100644 --- a/web/platform/src/locales/pt.json +++ b/web/platform/src/locales/pt.json @@ -322,6 +322,8 @@ "ioWrite": "escrita", "netRx": "recebido", "netTx": "enviado", + "swapIn": "entra", + "swapOut": "sai", "kindZramEnabled": "zram ativado", "kindSwapoffFile": "arquivo de swap desativado", "kindPressure": "pressão de memória detectada", diff --git a/web/platform/src/locales/ru.json b/web/platform/src/locales/ru.json index 2bae25c6..6053039a 100644 --- a/web/platform/src/locales/ru.json +++ b/web/platform/src/locales/ru.json @@ -322,6 +322,8 @@ "ioWrite": "запись", "netRx": "приём", "netTx": "передача", + "swapIn": "вход", + "swapOut": "выход", "kindZramEnabled": "zram включён", "kindSwapoffFile": "файл подкачки отключён", "kindPressure": "обнаружена нехватка памяти", diff --git a/web/platform/src/locales/zh-CN.json b/web/platform/src/locales/zh-CN.json index fc28db05..e1c5a1fc 100644 --- a/web/platform/src/locales/zh-CN.json +++ b/web/platform/src/locales/zh-CN.json @@ -322,6 +322,8 @@ "ioWrite": "写入", "netRx": "下载", "netTx": "上传", + "swapIn": "换入", + "swapOut": "换出", "kindZramEnabled": "已启用 zram", "kindSwapoffFile": "已禁用文件交换", "kindPressure": "检测到内存压力", diff --git a/web/platform/src/views/Health.vue b/web/platform/src/views/Health.vue index 0127a217..b20404db 100644 --- a/web/platform/src/views/Health.vue +++ b/web/platform/src/views/Health.vue @@ -19,6 +19,7 @@