package flamegraph import ( "context" "fmt" "net/http" "time" ) // ServeLive starts the live flamegraph HTTP server and blocks until ctx is canceled. func ServeLive(ctx context.Context, lt *LiveTrie, interval time.Duration) error { mux := http.NewServeMux() mux.HandleFunc("/", handleLivePage()) mux.HandleFunc("/events", handleSSE(lt, interval)) srv := &http.Server{Handler: mux} listener, err := listenRandomPort() if err != nil { return err } defer listener.Close() hostname, port := serverHostPort(listener) fmt.Printf("Live flamegraph available at http://%s:%d/\n", hostname, port) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() select { case <-ctx.Done(): case serveErr := <-errCh: if serveErr != nil && serveErr != http.ErrServerClosed { return serveErr } return nil } shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { return fmt.Errorf("shutdown live web server: %w", err) } serveErr := <-errCh if serveErr != nil && serveErr != http.ErrServerClosed { return serveErr } return nil } func handleLivePage() http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(liveHTML)) } } func handleSSE(lt *LiveTrie, interval time.Duration) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } if interval <= 0 { interval = time.Second } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") lastVersion, err := sendSnapshot(w, flusher, lt, ^uint64(0)) if err != nil { return } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-r.Context().Done(): return case <-ticker.C: if lt.Version() == lastVersion { continue } lastVersion, err = sendSnapshot(w, flusher, lt, lastVersion) if err != nil { return } } } } } func sendSnapshot(w http.ResponseWriter, flusher http.Flusher, lt *LiveTrie, lastVersion uint64) (uint64, error) { payload, version := lt.SnapshotJSON() if version == lastVersion { return lastVersion, nil } if _, err := fmt.Fprintf(w, "data: %s\n\n", payload); err != nil { return lastVersion, err } flusher.Flush() return version, nil }