summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-27 17:11:58 +0200
committerPaul Buetow <paul@buetow.org>2026-02-27 17:11:58 +0200
commit25ecf971f695e80ab41d08ab204dcc5bf1214147 (patch)
treecdffb8af4423934f0f2203788b0132db752234bc /internal
parent5f3d3c71650c23b66b347e748263c9759d3be2b5 (diff)
flamegraph: add live HTTP server entrypoint
Diffstat (limited to 'internal')
-rw-r--r--internal/flamegraph/liveserver.go51
1 files changed, 51 insertions, 0 deletions
diff --git a/internal/flamegraph/liveserver.go b/internal/flamegraph/liveserver.go
index d7a48cf..6b964a1 100644
--- a/internal/flamegraph/liveserver.go
+++ b/internal/flamegraph/liveserver.go
@@ -1,11 +1,62 @@
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)