forked from cerc-io/plugeth
e7067be94f
* cmd/geth, mobile: add memsize to pprof server This is a temporary change, to be reverted before the next release. * cmd/geth: fix variable name
154 lines
3.3 KiB
Go
154 lines
3.3 KiB
Go
package memsizeui
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fjl/memsize"
|
|
)
|
|
|
|
type Handler struct {
|
|
init sync.Once
|
|
mux http.ServeMux
|
|
mu sync.Mutex
|
|
reports map[int]Report
|
|
roots map[string]interface{}
|
|
reportID int
|
|
}
|
|
|
|
type Report struct {
|
|
ID int
|
|
Date time.Time
|
|
Duration time.Duration
|
|
RootName string
|
|
Sizes memsize.Sizes
|
|
}
|
|
|
|
type templateInfo struct {
|
|
Roots []string
|
|
Reports map[int]Report
|
|
PathDepth int
|
|
Data interface{}
|
|
}
|
|
|
|
func (ti *templateInfo) Link(path ...string) string {
|
|
prefix := strings.Repeat("../", ti.PathDepth)
|
|
return prefix + strings.Join(path, "")
|
|
}
|
|
|
|
func (h *Handler) Add(name string, v interface{}) {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
|
panic("root must be non-nil pointer")
|
|
}
|
|
h.mu.Lock()
|
|
if h.roots == nil {
|
|
h.roots = make(map[string]interface{})
|
|
}
|
|
h.roots[name] = v
|
|
h.mu.Unlock()
|
|
}
|
|
|
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
h.init.Do(func() {
|
|
h.reports = make(map[int]Report)
|
|
h.mux.HandleFunc("/", h.handleRoot)
|
|
h.mux.HandleFunc("/scan", h.handleScan)
|
|
h.mux.HandleFunc("/report/", h.handleReport)
|
|
})
|
|
h.mux.ServeHTTP(w, r)
|
|
}
|
|
|
|
func (h *Handler) templateInfo(r *http.Request, data interface{}) *templateInfo {
|
|
h.mu.Lock()
|
|
roots := make([]string, 0, len(h.roots))
|
|
for name := range h.roots {
|
|
roots = append(roots, name)
|
|
}
|
|
h.mu.Unlock()
|
|
sort.Strings(roots)
|
|
|
|
return &templateInfo{
|
|
Roots: roots,
|
|
Reports: h.reports,
|
|
PathDepth: strings.Count(r.URL.Path, "/") - 1,
|
|
Data: data,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
serveHTML(w, rootTemplate, http.StatusOK, h.templateInfo(r, nil))
|
|
}
|
|
|
|
func (h *Handler) handleScan(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "invalid HTTP method, want POST", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
ti := h.templateInfo(r, "Unknown root")
|
|
id, ok := h.scan(r.URL.Query().Get("root"))
|
|
if !ok {
|
|
serveHTML(w, notFoundTemplate, http.StatusNotFound, ti)
|
|
return
|
|
}
|
|
w.Header().Add("Location", ti.Link(fmt.Sprintf("report/%d", id)))
|
|
w.WriteHeader(http.StatusSeeOther)
|
|
}
|
|
|
|
func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
|
|
var id int
|
|
fmt.Sscan(strings.TrimPrefix(r.URL.Path, "/report/"), &id)
|
|
h.mu.Lock()
|
|
report, ok := h.reports[id]
|
|
h.mu.Unlock()
|
|
|
|
if !ok {
|
|
serveHTML(w, notFoundTemplate, http.StatusNotFound, h.templateInfo(r, "Report not found"))
|
|
} else {
|
|
serveHTML(w, reportTemplate, http.StatusOK, h.templateInfo(r, report))
|
|
}
|
|
}
|
|
|
|
func (h *Handler) scan(root string) (int, bool) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
val, ok := h.roots[root]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
id := h.reportID
|
|
start := time.Now()
|
|
sizes := memsize.Scan(val)
|
|
h.reports[id] = Report{
|
|
ID: id,
|
|
RootName: root,
|
|
Date: start.Truncate(1 * time.Second),
|
|
Duration: time.Since(start),
|
|
Sizes: sizes,
|
|
}
|
|
h.reportID++
|
|
return id, true
|
|
}
|
|
|
|
func serveHTML(w http.ResponseWriter, tpl *template.Template, status int, ti *templateInfo) {
|
|
w.Header().Set("content-type", "text/html")
|
|
var buf bytes.Buffer
|
|
if err := tpl.Execute(&buf, ti); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
buf.WriteTo(w)
|
|
}
|