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
+
+
+
+
+
+
+
+
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 }} |
+
+
+
+
+