0.2.78
This commit is contained in:
@@ -5,10 +5,8 @@ import (
|
||||
"crypto/subtle"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -16,6 +14,7 @@ import (
|
||||
"github.com/ponzischeme89/memby/server/internal/appupdate"
|
||||
"github.com/ponzischeme89/memby/server/internal/buildinfo"
|
||||
"github.com/ponzischeme89/memby/server/internal/library"
|
||||
"github.com/ponzischeme89/memby/server/internal/runtimestats"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -54,6 +53,9 @@ func (s *Server) adminRoutes() http.Handler {
|
||||
mux.Handle("GET /admin/api/searches", s.adminAuth(s.handleAdminSearches))
|
||||
mux.Handle("GET /admin/api/events", s.adminAuth(s.handleAdminEvents))
|
||||
mux.Handle("GET /admin/api/runtime", s.adminAuth(s.handleAdminRuntime))
|
||||
// The breakdown is its own route because it costs its own money: walking every
|
||||
// goroutine's stack stops the world, so it is asked for rather than polled.
|
||||
mux.Handle("GET /admin/api/runtime/goroutines", s.adminAuth(s.handleAdminGoroutines))
|
||||
mux.Handle("POST /admin/api/sync", s.adminAuth(s.handleAdminSync))
|
||||
mux.Handle("GET /admin/api/ingest", s.adminAuth(s.handleAdminIngest))
|
||||
mux.Handle("POST /admin/api/for-you", s.adminAuth(s.handleAdminForYou))
|
||||
@@ -101,6 +103,18 @@ func (s *Server) adminRoutes() http.Handler {
|
||||
// abandoned tab's session alive.
|
||||
mux.Handle("GET /admin/api/notifications/stream", s.adminAuth(s.handleAdminNotificationStream))
|
||||
|
||||
// The Integrations area. Deliberately a longer path than /admin/api/integrations,
|
||||
// which is the older, narrower question — the webhook destinations administrative
|
||||
// events are posted to. These are the external services Memby depends on.
|
||||
mux.Handle("GET /admin/api/integrations/services",
|
||||
s.adminAuth(s.handleAdminIntegrationServices))
|
||||
mux.Handle("GET /admin/api/integrations/services/{integrationID}",
|
||||
s.adminAuth(s.handleAdminIntegrationService))
|
||||
mux.Handle("POST /admin/api/integrations/services/{integrationID}/enabled",
|
||||
s.adminAuth(s.handleAdminIntegrationEnabled))
|
||||
mux.Handle("POST /admin/api/integrations/services/{integrationID}/test",
|
||||
s.adminAuth(s.handleAdminIntegrationTest))
|
||||
|
||||
mux.Handle("GET /admin/api/integrations", s.adminAuth(s.handleAdminIntegrations))
|
||||
mux.Handle("POST /admin/api/integrations", s.adminAuth(s.handleAdminSaveIntegration))
|
||||
mux.Handle("DELETE /admin/api/integrations/{integrationID}", s.adminAuth(s.handleAdminDeleteIntegration))
|
||||
@@ -134,33 +148,34 @@ func (s *Server) adminRoutes() http.Handler {
|
||||
return mux
|
||||
}
|
||||
|
||||
type adminRuntimeStatus struct {
|
||||
Goroutines int `json:"goroutines"`
|
||||
GOMAXPROCS int `json:"gomaxprocs"`
|
||||
HeapAlloc uint64 `json:"heapAlloc"`
|
||||
HeapInuse uint64 `json:"heapInuse"`
|
||||
HeapIdle uint64 `json:"heapIdle"`
|
||||
HeapReleased uint64 `json:"heapReleased"`
|
||||
StackInuse uint64 `json:"stackInuse"`
|
||||
Sys uint64 `json:"sys"`
|
||||
NextGC uint64 `json:"nextGc"`
|
||||
NumGC uint32 `json:"numGc"`
|
||||
MemoryLimit int64 `json:"memoryLimit"`
|
||||
ConfiguredLim string `json:"configuredLimit,omitempty"`
|
||||
// The console's Process card used to be handed a bare goroutine count, which is a number
|
||||
// nobody can act on: it says nothing about what those goroutines are doing, which part of
|
||||
// Memby they belong to, or whether it has been climbing all week. Everything behind these
|
||||
// two routes lives in internal/runtimestats, including the health verdict, so the
|
||||
// thresholds and the sentences explaining them sit beside each other and can be tested.
|
||||
func (s *Server) handleAdminRuntime(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
writeJSON(w, http.StatusOK, runtimestats.Read())
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminRuntime(w http.ResponseWriter, _ *http.Request) {
|
||||
var memory runtime.MemStats
|
||||
runtime.ReadMemStats(&memory)
|
||||
// handleAdminGoroutines is the on-demand snapshot. `?format=text` returns the raw dump for
|
||||
// an operator who wants the whole thing — which is the alternative to leaving a profiling
|
||||
// endpoint permanently mounted, and unlike one it is behind the admin guard and produces
|
||||
// nothing until somebody asks.
|
||||
func (s *Server) handleAdminGoroutines(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
writeJSON(w, http.StatusOK, adminRuntimeStatus{
|
||||
Goroutines: runtime.NumGoroutine(), GOMAXPROCS: runtime.GOMAXPROCS(0),
|
||||
HeapAlloc: memory.HeapAlloc, HeapInuse: memory.HeapInuse,
|
||||
HeapIdle: memory.HeapIdle, HeapReleased: memory.HeapReleased,
|
||||
StackInuse: memory.StackInuse, Sys: memory.Sys,
|
||||
NextGC: memory.NextGC, NumGC: memory.NumGC,
|
||||
MemoryLimit: debug.SetMemoryLimit(-1), ConfiguredLim: os.Getenv("GOMEMLIMIT"),
|
||||
})
|
||||
log := s.loggerFor(r.Context())
|
||||
if r.URL.Query().Get("format") == "text" {
|
||||
log.Info("goroutine dump requested", "component", "admin")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=memby-goroutines.txt")
|
||||
_, _ = io.WriteString(w, runtimestats.StackDump())
|
||||
return
|
||||
}
|
||||
report := runtimestats.CollectGoroutines()
|
||||
log.Info("goroutine breakdown collected",
|
||||
"component", "admin", "goroutines", report.Total, "took_ms", report.CollectedInMs)
|
||||
writeJSON(w, http.StatusOK, report)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminEvents(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -686,7 +701,7 @@ func (s *Server) handleAdminForYou(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := s.forYou.RebuildAll(ctx, true); err != nil {
|
||||
if _, err := s.forYou.RebuildAll(ctx, true); err != nil {
|
||||
s.log.Error("manual For You rebuild failed",
|
||||
"component", "admin", "action", req.Action, "error", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user