1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package flamegraph
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
)
// 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)
srv := &http.Server{Handler: buildSVGHandler(absPath, urlPath)}
listener, err := listenRandomPort()
if err != nil {
return err
}
defer listener.Close()
hostname, port := serverHostPort(listener)
printServerURL(hostname, port, urlPath)
errCh := make(chan error, 1)
go func() {
errCh <- srv.Serve(listener)
}()
return waitForStop(srv, errCh)
}
func buildURLPath(absPath string) string {
urlPath := filepath.ToSlash(absPath)
if !strings.HasPrefix(urlPath, "/") {
return "/" + urlPath
}
return urlPath
}
func buildSVGHandler(absPath, urlPath string) http.Handler {
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 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 waitForStop(srv *http.Server, errCh <-chan error) error {
stopCh := make(chan os.Signal, 1)
signal.Notify(stopCh, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(stopCh)
select {
case sig := <-stopCh:
_ = sig
case serveErr := <-errCh:
if serveErr != nil && serveErr != http.ErrServerClosed {
return serveErr
}
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
return fmt.Errorf("shutdown web server: %w", err)
}
serveErr := <-errCh
if serveErr != nil && serveErr != http.ErrServerClosed {
return serveErr
}
return nil
}
|