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(`
` + `
` + `
`)
}