diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-02 08:39:35 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-02 08:39:35 +0200 |
| commit | b3327d17f9f5c7080b05d6d5457cf25550d40ad9 (patch) | |
| tree | 090e66047c14c9183058bd68a8d4afc12b48f399 /internal/flamegraph/liveserver.go | |
| parent | 38620359537d77ab10acb888d266d3c6eb16fc9b (diff) | |
Add --open support for live flamegraph browser launch
Diffstat (limited to 'internal/flamegraph/liveserver.go')
| -rw-r--r-- | internal/flamegraph/liveserver.go | 79 |
1 files changed, 78 insertions, 1 deletions
diff --git a/internal/flamegraph/liveserver.go b/internal/flamegraph/liveserver.go index 801b841..dda1af1 100644 --- a/internal/flamegraph/liveserver.go +++ b/internal/flamegraph/liveserver.go @@ -3,8 +3,12 @@ package flamegraph import ( "context" "encoding/json" + "errors" "fmt" "net/http" + "os/exec" + "runtime" + "strings" "time" ) @@ -14,18 +18,91 @@ var liveServerTimeouts = serverTimeouts{ idleTimeout: 60 * time.Second, } +type LiveServerOptions struct { + AutoOpenBrowser bool + OpenCommand string +} + +var openBrowserURLFn = openBrowserURL + // ServeLive starts the live flamegraph HTTP server and blocks until ctx is canceled. func ServeLive(ctx context.Context, lt *LiveTrie, interval time.Duration) error { + return ServeLiveWithOptions(ctx, lt, interval, LiveServerOptions{}) +} + +// ServeLiveWithOptions starts the live flamegraph server with runtime options. +func ServeLiveWithOptions(ctx context.Context, lt *LiveTrie, interval time.Duration, options LiveServerOptions) error { mux := http.NewServeMux() mux.HandleFunc("/", handleLivePage()) mux.HandleFunc("/events", handleSSE(lt, interval)) mux.HandleFunc("/reset", handleReset(lt)) mux.HandleFunc("/order", handleOrder(lt)) return runServer(ctx, mux, liveServerTimeouts, func(hostname string, port int) { - fmt.Printf("Live flamegraph available at http://%s:%d/\n", hostname, port) + url := fmt.Sprintf("http://%s:%d/", hostname, port) + fmt.Printf("Live flamegraph available at %s\n", url) + if err := maybeOpenLiveBrowser(url, options); err != nil { + fmt.Printf("Live flamegraph browser auto-open failed: %v\n", err) + } }) } +func maybeOpenLiveBrowser(url string, options LiveServerOptions) error { + if !options.AutoOpenBrowser { + return nil + } + return openBrowserURLFn(url, options.OpenCommand) +} + +func openBrowserURL(url, openCommand string) error { + parts, err := browserOpenCommandParts(runtime.GOOS, openCommand, url) + if err != nil { + return err + } + cmd := exec.Command(parts[0], parts[1:]...) + if err := cmd.Start(); err != nil { + return err + } + go func() { _ = cmd.Wait() }() + return nil +} + +func browserOpenCommandParts(goos, openCommand, url string) ([]string, error) { + var parts []string + if trimmed := strings.TrimSpace(openCommand); trimmed != "" { + parts = strings.Fields(trimmed) + } else { + parts = defaultBrowserCommand(goos) + } + if len(parts) == 0 { + return nil, errors.New("empty browser open command") + } + + containsURL := false + for i := range parts { + if strings.Contains(parts[i], "{url}") { + parts[i] = strings.ReplaceAll(parts[i], "{url}", url) + containsURL = true + } + } + if !containsURL { + parts = append(parts, url) + } + return parts, nil +} + +func defaultBrowserCommand(goos string) []string { + switch goos { + case "darwin": + return []string{"open", "-a", "Firefox"} + case "linux": + return []string{"firefox"} + case "windows": + return []string{"cmd", "/c", "start"} + default: + return []string{"firefox"} + } +} + func handleLivePage() http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") |
