Нет описания

perf.go 3.7KB

    package perf import ( "context" "encoding/json" "fmt" "html/template" "log" "math/rand" "net/http" "strings" "sync" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } type Perf struct { mu sync.Mutex measurements map[string]map[string]interface{} } func NewPerf() *Perf { return &Perf{ measurements: make(map[string]map[string]interface{}), } } type key int var perfKey key = 0 func (p *Perf) MeasureRequest(req *http.Request) { id := p.RequestID(req) start := time.Now() ctx := req.Context() go func() { select { case <-ctx.Done(): p.measure(id, "request-time", time.Since(start).String()) } time.Sleep(1 * time.Second) p.Delete(id) }() } func (p *Perf) Measure(req *http.Request, key string, val interface{}) { p.measure(p.RequestID(req), key, val) } func (p *Perf) measure(id string, key string, val interface{}) { p.mu.Lock() data, ok := p.measurements[id] if !ok { data = make(map[string]interface{}) p.measurements[id] = data } data[key] = val p.mu.Unlock() } func (p *Perf) RequestID(req *http.Request) string { id, ok := req.Context().Value(perfKey).(string) if !ok { data := make([]byte, 10) _, err := rand.Read(data) if err != nil { panic(err) } id = fmt.Sprintf("%x", data) *req = *req.WithContext(context.WithValue(req.Context(), perfKey, id)) } return id } func (p *Perf) Measurements(id string) map[string]interface{} { p.mu.Lock() ms := p.measurements[id] p.mu.Unlock() return ms } func (p *Perf) Delete(id string) { p.mu.Lock() delete(p.measurements, id) p.mu.Unlock() } func (p *Perf) Size() int { p.mu.Lock() defer p.mu.Unlock() return len(p.measurements) } func (p *Perf) ServeHTTP(w http.ResponseWriter, req *http.Request) { parts := strings.SplitN(req.URL.Path, "/", 3) if len(parts) < 3 { http.Error(w, "missing request id", http.StatusBadRequest) return } id := parts[2] perfData := p.Measurements(id) data, err := json.MarshalIndent(perfData, "", " ") if err != nil { log.Fatal("marshaling perf data: ", err) http.Error(w, "internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(data) } func (p *Perf) Render(req *http.Request) template.HTML { return template.HTML(`<style> body { transform: translate(0, 2.5em); } #performance-bar { display: flex; position: fixed; width: 99.1vw; transform: translate(-0.5em, -3em); padding: 0.75em; background-color: #222; color: #ddd; } .performance-pair { margin-right: 1em; font-family: sans-serif; } .performance-value { color: #eee; font-weight: 600; } .performance-key { margin-right: 0.5em; color: #777; } </style> ` + ` <script>var performanceId = "` + p.RequestID(req) + `"; </script> ` + ` <script> var performanceBar = document.getElementById("performance-bar"); var xhr = new XMLHttpRequest(); xhr.open("GET", "/__performance__/" + performanceId); xhr.responseType = "json"; xhr.onreadystatechange = ev => { if (xhr.readyState != XMLHttpRequest.DONE || xhr.status != 200) { return } var performanceData = xhr.response; if (performanceData == null) { performanceBar.style = "color: red;"; performanceBar.textContent = "Could not load performance data"; } Object.keys(performanceData).forEach(k => { var kv = document.createElement("div"); kv.className = "performance-pair"; var ke = document.createElement("span"); ke.className = "performance-key"; ke.textContent = k; kv.appendChild(ke); var ve = document.createElement("span"); ve.className = "performance-value"; ve.textContent = performanceData[k]; kv.appendChild(ve); performanceBar.appendChild(kv); }) }; xhr.send(); </script>`) }