diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 15:35:24 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 15:35:24 +0200 |
| commit | 99b02bf8c389a793df5d5986db05eed7e459f7b1 (patch) | |
| tree | bc4e36cfcd3c9ef9b067beed2eb5b68a75a45aa2 /internal/flamegraph/webserver.go | |
| parent | 4ff17c30120d657b966f8a55188ba167dc875e64 (diff) | |
refactor: remove web flamegrapher and keep TUI-only
Diffstat (limited to 'internal/flamegraph/webserver.go')
| -rw-r--r-- | internal/flamegraph/webserver.go | 199 |
1 files changed, 0 insertions, 199 deletions
diff --git a/internal/flamegraph/webserver.go b/internal/flamegraph/webserver.go deleted file mode 100644 index c472dfb..0000000 --- a/internal/flamegraph/webserver.go +++ /dev/null @@ -1,199 +0,0 @@ -package flamegraph - -import ( - "context" - "fmt" - "net" - "net/http" - "os" - "os/signal" - "path/filepath" - "strings" - "syscall" - "time" -) - -type serverTimeouts struct { - readTimeout time.Duration - writeTimeout time.Duration - idleTimeout time.Duration -} - -var defaultServerTimeouts = serverTimeouts{ - readTimeout: 10 * time.Second, - writeTimeout: 30 * time.Second, - idleTimeout: 60 * time.Second, -} - -// ServeSVG starts a small HTTP server that serves a single flamegraph SVG. -// -// It prints a URL of the form http://HOSTNAME:PORT/abs/path/to.svg and blocks until -// the user presses Ctrl+C or the process receives SIGTERM, at which point the server -// is shut down gracefully. -func ServeSVG(svgFile string) error { - absPath, err := filepath.Abs(svgFile) - if err != nil { - return fmt.Errorf("resolve svg path: %w", err) - } - urlPath := buildURLPath(absPath) - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - return runServer(ctx, buildSVGHandler(absPath, urlPath), defaultServerTimeouts, func(hostname string, port int) { - printServerURL(hostname, port, urlPath) - }) -} - -// ServeSVGAutoReload serves an SVG viewer page that periodically reloads the SVG. -// -// The SVG file itself is still served directly at its absolute URL path, while "/" -// serves a small HTML wrapper that appends a cache-busting query parameter on each -// refresh interval to pick up newly written SVG content. -func ServeSVGAutoReload(svgFile string, refreshInterval time.Duration) error { - if refreshInterval <= 0 { - return fmt.Errorf("refresh interval must be > 0") - } - - absPath, err := filepath.Abs(svgFile) - if err != nil { - return fmt.Errorf("resolve svg path: %w", err) - } - urlPath := buildURLPath(absPath) - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - - mux := buildSVGAutoReloadHandler(absPath, urlPath, refreshInterval) - return runServer(ctx, mux, defaultServerTimeouts, func(hostname string, port int) { - printServerURL(hostname, port, "/") - }) -} - -func buildURLPath(absPath string) string { - urlPath := filepath.ToSlash(absPath) - if !strings.HasPrefix(urlPath, "/") { - return "/" + urlPath - } - return urlPath -} - -func buildSVGHandler(absPath, urlPath string) *http.ServeMux { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, urlPath, http.StatusFound) - }) - mux.HandleFunc(urlPath, func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, absPath) - }) - return mux -} - -func buildSVGAutoReloadHandler(absPath, urlPath string, refreshInterval time.Duration) *http.ServeMux { - intervalMs := refreshInterval.Milliseconds() - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, _ = fmt.Fprintf(w, `<!doctype html> -<html> -<head> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <title>I/O Flamegraph (Auto-Reload)</title> - <style> - body { margin: 0; font-family: monospace; } - .bar { padding: 8px 12px; border-bottom: 1px solid #ddd; } - .viewer { width: 100%%; height: calc(100vh - 42px); border: 0; display: block; } - </style> -</head> -<body> - <div class="bar"> - Auto-refresh every %d ms. - <button type="button" onclick="refreshNow()">Refresh now</button> - </div> - <iframe id="fg" class="viewer" src="%s"></iframe> - <script> - const base = %q; - function refreshNow() { - document.getElementById("fg").src = base + "?t=" + Date.now(); - } - setInterval(refreshNow, %d); - </script> -</body> -</html> -`, intervalMs, urlPath, urlPath, intervalMs) - }) - mux.HandleFunc(urlPath, func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, absPath) - }) - return mux -} - -func listenRandomPort() (net.Listener, error) { - listener, err := net.Listen("tcp", ":0") - if err != nil { - return nil, fmt.Errorf("start web server: %w", err) - } - return listener, nil -} - -func serverHostPort(listener net.Listener) (string, int) { - hostname, err := os.Hostname() - if err != nil { - hostname = "localhost" - } - port := listener.Addr().(*net.TCPAddr).Port - return hostname, port -} - -func printServerURL(hostname string, port int, urlPath string) { - fmt.Printf("Flamegraph available at http://%s:%d%s\n", hostname, port, urlPath) - fmt.Println("Press Ctrl+C to stop the web server.") -} - -func newHTTPServer(mux *http.ServeMux, timeouts serverTimeouts) *http.Server { - return &http.Server{ - Handler: mux, - ReadTimeout: timeouts.readTimeout, - WriteTimeout: timeouts.writeTimeout, - IdleTimeout: timeouts.idleTimeout, - } -} - -func runServer(ctx context.Context, mux *http.ServeMux, timeouts serverTimeouts, printURL func(hostname string, port int)) error { - srv := newHTTPServer(mux, timeouts) - - listener, err := listenRandomPort() - if err != nil { - return err - } - defer listener.Close() - - hostname, port := serverHostPort(listener) - if printURL != nil { - printURL(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 web server: %w", err) - } - - serveErr := <-errCh - if serveErr != nil && serveErr != http.ErrServerClosed { - return serveErr - } - return nil -} |
