Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
68 changes: 47 additions & 21 deletions backend/health/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions backend/health/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"+
Expand Down Expand Up @@ -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()
Expand Down
7 changes: 1 addition & 6 deletions backend/stability/zram.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
16 changes: 9 additions & 7 deletions backend/stability/zram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "كتابة",
"netRx": "استقبال",
"netTx": "إرسال",
"swapIn": "داخل",
"swapOut": "خارج",
"kindZramEnabled": "تم تمكين zram",
"kindSwapoffFile": "تم تعطيل ملف التبديل",
"kindPressure": "تم اكتشاف ضغط على الذاكرة",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "schreiben",
"netRx": "Empfang",
"netTx": "Senden",
"swapIn": "rein",
"swapOut": "raus",
"kindZramEnabled": "zram aktiviert",
"kindSwapoffFile": "Auslagerungsdatei deaktiviert",
"kindPressure": "Speicherengpass erkannt",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "लिखना",
"netRx": "डाउनलोड",
"netTx": "अपलोड",
"swapIn": "अंदर",
"swapOut": "बाहर",
"kindZramEnabled": "zram सक्षम",
"kindSwapoffFile": "फ़ाइल स्वैप अक्षम",
"kindPressure": "मेमोरी दबाव का पता चला",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "書き込み",
"netRx": "受信",
"netTx": "送信",
"swapIn": "入",
"swapOut": "出",
"kindZramEnabled": "zram を有効化",
"kindSwapoffFile": "ファイルスワップを無効化",
"kindPressure": "メモリ圧迫を検出",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "запись",
"netRx": "приём",
"netTx": "передача",
"swapIn": "вход",
"swapOut": "выход",
"kindZramEnabled": "zram включён",
"kindSwapoffFile": "файл подкачки отключён",
"kindPressure": "обнаружена нехватка памяти",
Expand Down
2 changes: 2 additions & 0 deletions web/platform/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"ioWrite": "写入",
"netRx": "下载",
"netTx": "上传",
"swapIn": "换入",
"swapOut": "换出",
"kindZramEnabled": "已启用 zram",
"kindSwapoffFile": "已禁用文件交换",
"kindPressure": "检测到内存压力",
Expand Down
8 changes: 8 additions & 0 deletions web/platform/src/views/Health.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<div class="setline" v-if="swapTotalMb > 0">
<h3>{{ $t('health.swap') }} — {{ swapUsedMb }} / {{ swapTotalMb }} MB</h3>
<s-progress :percentage="swapPct" :stroke-width="14" :show-text="false" :status="pctStatus(swapPct)" data-testid="health-swap-bar" />
<div class="muted" data-testid="health-swap-rate">{{ $t('health.swapIn') }} {{ swapRate.inKBs }} · {{ $t('health.swapOut') }} {{ swapRate.outKBs }} KB/s</div>
</div>
</div>

Expand Down Expand Up @@ -86,6 +87,7 @@ export default {
prevMetrics: null,
events: [],
cpuPct: 0,
swapRate: { inKBs: 0, outKBs: 0 },
diskRates: [],
netRates: [],
metricsTimer: null,
Expand Down Expand Up @@ -177,6 +179,7 @@ export default {
computeDeltas (prev, next) {
if (!prev) {
this.cpuPct = 0
this.swapRate = { inKBs: 0, outKBs: 0 }
this.diskRates = (next.disks || []).map(d => ({ name: d.name, readKBs: 0, writeKBs: 0 }))
this.netRates = (next.net || []).map(n => ({ name: n.name, rxKBs: 0, txKBs: 0 }))
return
Expand All @@ -187,6 +190,11 @@ export default {
this.cpuPct = totalDelta > 0 ? Math.max(0, Math.min(100, ((totalDelta - idleDelta) / totalDelta) * 100)) : 0

const secs = METRICS_INTERVAL_MS / 1000
const pageKb = 4
this.swapRate = {
inKBs: Math.max(0, Math.round(((next.memory.swap_in_pages || 0) - (prev.memory.swap_in_pages || 0)) * pageKb / secs)),
outKBs: Math.max(0, Math.round(((next.memory.swap_out_pages || 0) - (prev.memory.swap_out_pages || 0)) * pageKb / secs))
}
const prevDisks = {}
;(prev.disks || []).forEach(d => { prevDisks[d.name] = d })
this.diskRates = (next.disks || []).map(d => {
Expand Down