Skip to content

Commit

Permalink
Add GET /api/about that returns useful system info. Closes #1354.
Browse files Browse the repository at this point in the history
  • Loading branch information
knadh committed Jun 24, 2023
1 parent 5b40461 commit c581fe2
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
g.POST("/api/settings/smtp/test", handleTestSMTPSettings)
g.POST("/api/admin/reload", handleReloadApp)
g.GET("/api/logs", handleGetLogs)
g.GET("/api/about", handleGetAboutInfo)

g.GET("/api/subscribers/:id", handleGetSubscriber)
g.GET("/api/subscribers/:id/export", handleExportSubscriberData)
Expand Down
40 changes: 40 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -697,6 +698,45 @@ func initBounceManager(app *App) *bounce.Manager {
return b
}

func initAbout(q *models.Queries, db *sqlx.DB) about {
var (
mem runtime.MemStats
utsname syscall.Utsname
)

// Memory / alloc stats.
runtime.ReadMemStats(&mem)

// OS info.
if err := syscall.Uname(&utsname); err != nil {
lo.Printf("WARNING: error getting system info: %v", err)
}

// DB dbv.
info := types.JSONText(`{}`)
if err := db.QueryRow(q.GetDBInfo).Scan(&info); err != nil {
lo.Printf("WARNING: error getting database version: %v", err)
}

return about{
Version: versionString,
Build: buildString,
GoArch: runtime.GOARCH,
GoVersion: runtime.Version(),
Database: info,
System: aboutSystem{
NumCPU: runtime.NumCPU(),
},
Host: aboutHost{
OS: int8ToStr(utsname.Sysname[:]),
OSRelease: int8ToStr(utsname.Release[:]),
Machine: int8ToStr(utsname.Machine[:]),
Hostname: int8ToStr(utsname.Nodename[:]),
},
}

}

// initHTTPServer sets up and runs the app's main HTTP server and blocks forever.
func initHTTPServer(app *App) *echo.Echo {
// Initialize the HTTP server.
Expand Down
4 changes: 4 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type App struct {
captcha *captcha.Captcha
events *events.Events
notifTpls *notifTpls
about about
log *log.Logger
bufLog *buflog.BufLog

Expand Down Expand Up @@ -229,6 +230,9 @@ func main() {
app.manager.AddMessenger(m)
}

// Load system information.
app.about = initAbout(queries, db)

// Start the campaign workers. The campaign batches (fetch from DB, push out
// messages) get processed at the specified interval.
go app.manager.Run()
Expand Down
45 changes: 45 additions & 0 deletions cmd/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package main

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"runtime"
"strings"
"syscall"
"time"

"github.com/gofrs/uuid"
"github.com/jmoiron/sqlx/types"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/rawbytes"
"github.com/knadh/koanf/v2"
Expand All @@ -18,6 +21,27 @@ import (
"github.com/labstack/echo/v4"
)

type aboutHost struct {
OS string `json:"os"`
OSRelease string `json:"os_release"`
Machine string `json:"arch"`
Hostname string `json:"hostname"`
}
type aboutSystem struct {
NumCPU int `json:"num_cpu"`
AllocMB uint64 `json:"memory_alloc_mb"`
OSMB uint64 `json:"memory_from_os_mb"`
}
type about struct {
Version string `json:"version"`
Build string `json:"build"`
GoVersion string `json:"go_version"`
GoArch string `json:"go_arch"`
Database types.JSONText `json:"database"`
System aboutSystem `json:"system"`
Host aboutHost `json:"host"`
}

var (
reAlphaNum = regexp.MustCompile(`[^a-z0-9\-]`)
)
Expand Down Expand Up @@ -266,3 +290,24 @@ func handleTestSMTPSettings(c echo.Context) error {

return c.JSON(http.StatusOK, okResp{app.bufLog.Lines()})
}

func handleGetAboutInfo(c echo.Context) error {
app := c.Get("app").(*App)

var (
mem runtime.MemStats
utsname syscall.Utsname
)

runtime.ReadMemStats(&mem)

if err := syscall.Uname(&utsname); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("error getting system info: %v", err))
}

out := app.about
out.System.AllocMB = mem.Alloc / 1024 / 1024
out.System.OSMB = mem.Sys / 1024 / 1024

return c.JSON(http.StatusOK, out)
}
10 changes: 10 additions & 0 deletions cmd/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"crypto/rand"
"fmt"
"path/filepath"
Expand Down Expand Up @@ -99,3 +100,12 @@ func strSliceContains(str string, sl []string) bool {

return false
}

func int8ToStr(bs []int8) string {
b := make([]byte, len(bs))
for i, v := range bs {
b[i] = byte(v)
}

return string(bytes.Trim(b, "\x00"))
}
1 change: 1 addition & 0 deletions models/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ type Queries struct {
QueryBounces string `query:"query-bounces"`
DeleteBounces *sqlx.Stmt `query:"delete-bounces"`
DeleteBouncesBySubscriber *sqlx.Stmt `query:"delete-bounces-by-subscriber"`
GetDBInfo string `query:"get-db-info"`
}

// CompileSubscriberQueryTpl takes an arbitrary WHERE expressions
Expand Down
4 changes: 4 additions & 0 deletions queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1067,3 +1067,7 @@ WITH sub AS (
)
DELETE FROM bounces WHERE subscriber_id = (SELECT id FROM sub);


-- name: get-db-info
SELECT JSON_BUILD_OBJECT('version', (SELECT VERSION()),
'size_mb', (SELECT ROUND(pg_database_size('listmonk')/(1024^2)))) AS info;

0 comments on commit c581fe2

Please sign in to comment.