diff --git a/go.mod b/go.mod index 9c4e119..eee051f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.erbosoft.com/amy/amsterdam -go 1.25.0 +go 1.26 require ( github.com/CloudyKit/jet/v6 v6.3.1 @@ -9,6 +9,7 @@ require ( github.com/bits-and-blooms/bitset v1.24.0 github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d github.com/disintegration/imaging v1.6.2 + github.com/dustin/go-humanize v1.0.1 github.com/go-sql-driver/mysql v1.9.3 github.com/hashicorp/golang-lru v1.0.2 github.com/jmoiron/sqlx v1.4.0 diff --git a/go.sum b/go.sum index 6528a70..6e7f7a1 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xC github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= diff --git a/main.go b/main.go index 96a92d9..f1208bb 100644 --- a/main.go +++ b/main.go @@ -113,6 +113,7 @@ func setupEcho() *echo.Echo { sysGroup.POST("/ipban/add", ui.AmWrap(AddIPBan)) sysGroup.Match(GetAndPost, "/audit", ui.AmWrap(SystemAudit)) sysGroup.Match(GetAndPost, "/import", ui.AmWrap(UserImport)) + sysGroup.GET("/sysstat", ui.AmWrap(SysStats)) // community group uiset2 := make([]echo.MiddlewareFunc, len(uiset), len(uiset)+1) @@ -206,9 +207,12 @@ func setupEcho() *echo.Echo { // ampool is the worker pool for one-shot background tasks. var ampool *util.WorkerPool +// SystemStartTime records the time since the system was started. +var SystemStartTime time.Time + // main is Ye Olde Main Function. func main() { - start := time.Now() + SystemStartTime = time.Now() // Configure the system. config.SetupConfig() closer, err := database.SetupDb() @@ -250,7 +254,7 @@ func main() { database.AmStoreAudit(database.AmNewAudit(database.AuditShutdown, 0, myIP.String())) }() - stime := time.Since(start) + stime := time.Since(SystemStartTime) log.Infof("Amsterdam %s startup sequence completed in %v", config.AMSTERDAM_VERSION, stime) // Start server diff --git a/sysadmin.go b/sysadmin.go index e377c84..89a0ba0 100644 --- a/sysadmin.go +++ b/sysadmin.go @@ -16,6 +16,7 @@ import ( "fmt" "net" "reflect" + "runtime" "strconv" "strings" "time" @@ -25,6 +26,7 @@ import ( "git.erbosoft.com/amy/amsterdam/ui" "git.erbosoft.com/amy/amsterdam/util" "github.com/CloudyKit/jet/v6" + "github.com/dustin/go-humanize" log "github.com/sirupsen/logrus" ) @@ -809,3 +811,35 @@ func UserImport(ctxt ui.AmContext) (string, any) { ctxt.SetFrameTitle("Import Results") return "framed", "import_results.jet" } + +/* SysStats displays system statistics. + * Parameters: + * ctxt - The AmContext for the request. + * Returns: + * Command string dictating what to be rendered. + * Data as a parameter for the command string. + */ +func SysStats(ctxt ui.AmContext) (string, any) { + if !database.AmTestPermission("Global.SysAdminAccess", ctxt.CurrentUser().BaseLevel) { + return "error", ENOACCESS + } + + mstat := new(runtime.MemStats) + runtime.ReadMemStats(mstat) + ctxt.VarMap().Set("mstat", mstat) + ctxt.VarMap().Set("numgo", runtime.NumGoroutine()) + ctxt.VarMap().Set("uptime", time.Since(SystemStartTime).String()) + ctxt.VarMap().Set("memAlloc", humanize.IBytes(mstat.Alloc)) + ctxt.VarMap().Set("memTotalAlloc", humanize.IBytes(mstat.TotalAlloc)) + ctxt.VarMap().Set("memSys", humanize.IBytes(mstat.Sys)) + ctxt.VarMap().Set("memHeapAlloc", humanize.IBytes(mstat.HeapAlloc)) + ctxt.VarMap().Set("memHeapSys", humanize.IBytes(mstat.HeapSys)) + ctxt.VarMap().Set("memHeapIdle", humanize.IBytes(mstat.HeapIdle)) + ctxt.VarMap().Set("memHeapInuse", humanize.IBytes(mstat.HeapInuse)) + ctxt.VarMap().Set("memHeapReleased", humanize.IBytes(mstat.HeapReleased)) + ctxt.VarMap().Set("memLastGCTime", time.Unix(0, int64(mstat.LastGC)).Format(time.RFC3339)) + ctxt.VarMap().Set("memTotalPause", fmt.Sprintf("%.3f ms", float64(mstat.PauseTotalNs)/1000/1000)) + ctxt.VarMap().Set("memGCPercent", fmt.Sprintf("%.5f%%", float64(mstat.GCCPUFraction)*100)) + ctxt.SetFrameTitle("System Statistics") + return "framed", "sysstat.jet" +} diff --git a/ui/menudefs.yaml b/ui/menudefs.yaml index 5a0f523..3b9c973 100644 --- a/ui/menudefs.yaml +++ b/ui/menudefs.yaml @@ -54,6 +54,9 @@ menudefs: - text: "Import User Accounts" link: "/sysadmin/import" permission: "Global.SysAdminAccess" + - text: "System Statistics" + link: "/sysadmin/sysstat" + permission: "Global.SysAdminAccess" - id: "communityadmin" title: "Community Administration:" subtitle: "[CNAME]" diff --git a/ui/views/sysstat.jet b/ui/views/sysstat.jet new file mode 100644 index 0000000..88ed390 --- /dev/null +++ b/ui/views/sysstat.jet @@ -0,0 +1,134 @@ +{* + * Amsterdam Web Communities System + * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + *} +
+ +
+

System Statistics

+
+
+ + +
+ + Return to System Administration Menu + +
+ + +

General Statistics

+
+
+ + + + + + + + + + + + + + + + + + + + + +
Amsterdam Uptime{{ uptime }}
Active Goroutines{{ numgo }}
Memory Usage{{ memAlloc }} allocated of {{ memTotalAlloc }} ({{ memSys }} from system)
Pointer Lookups{{ mstat.Lookups }}
Memory Allocations{{ mstat.Mallocs }} mallocs, {{ mstat.Frees }} frees
+
+
+ + +

Heap Statistics

+
+
+ + + + + + + + + + + + + +
Heap Usage{{ memHeapAlloc }} allocated ({{ memHeapSys }} from system)
Heap Activity{{ memHeapIdle }} idle, {{ memHeapInuse }} in use, {{ memHeapReleased }} released to OS
Heap Objects{{ mstat.HeapObjects }}
+
+
+ + +

System Allocations

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Stack Usage{{ mstat.StackInuse }} in use ({{ mstat.StackSys }} from system)
MSpan Usage{{ mstat.MSpanInuse }} in use ({{ mstat.MSpanSys }} from system)
MCache Usage{{ mstat.MCacheInuse }} in use ({{ mstat.MCacheSys }} from system)
Bucket Hashtable Usage{{ mstat.BuckHashSys }}
GC Usage{{ mstat.GCSys }}
Miscellaneous Usage{{ mstat.OtherSys }}
+
+
+ + +

GC Statistics

+
+
+ + + + + + + + + + + + + + + + + + + + + +
Next GC Run At{{ mstat.NextGC }} bytes heap
Last GC Run At{{ memLastGCTime }}
Total GC Pause Time{{ memTotalPause }}
Total GCs{{ mstat.NumGC }}
GC CPU Percent{{ memGCPercent }}
+
+
+