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