Pārlūkot izejas kodu

Display performance stats of the current page in a bar on top

Lucas Stadler 8 gadi atpakaļ
vecāks
revīzija
5eb1e01e82
2 mainītis faili ar 212 papildinājumiem un 3 dzēšanām
  1. 195 0
      perf/perf.go
  2. 17 3
      quit.go

+ 195 - 0
perf/perf.go

@ -0,0 +1,195 @@
1
package perf
2
3
import (
4
	"context"
5
	"encoding/json"
6
	"fmt"
7
	"html/template"
8
	"log"
9
	"math/rand"
10
	"net/http"
11
	"strings"
12
	"sync"
13
	"time"
14
)
15
16
func init() {
17
	rand.Seed(time.Now().UnixNano())
18
}
19
20
type Perf struct {
21
	mu           sync.Mutex
22
	measurements map[string]map[string]interface{}
23
}
24
25
func NewPerf() *Perf {
26
	return &Perf{
27
		measurements: make(map[string]map[string]interface{}),
28
	}
29
}
30
31
type key int
32
33
var perfKey key = 0
34
35
func (p *Perf) MeasureRequest(req *http.Request) {
36
	id := p.RequestID(req)
37
	start := time.Now()
38
	ctx := req.Context()
39
	go func() {
40
		select {
41
		case <-ctx.Done():
42
			p.measure(id, "request-time", time.Since(start).String())
43
		}
44
45
		time.Sleep(1 * time.Second)
46
		p.Delete(id)
47
	}()
48
}
49
50
func (p *Perf) Measure(req *http.Request, key string, val interface{}) {
51
	p.measure(p.RequestID(req), key, val)
52
}
53
54
func (p *Perf) measure(id string, key string, val interface{}) {
55
	p.mu.Lock()
56
	data, ok := p.measurements[id]
57
	if !ok {
58
		data = make(map[string]interface{})
59
		p.measurements[id] = data
60
	}
61
	data[key] = val
62
	p.mu.Unlock()
63
}
64
65
func (p *Perf) RequestID(req *http.Request) string {
66
	id, ok := req.Context().Value(perfKey).(string)
67
	if !ok {
68
		data := make([]byte, 10)
69
		_, err := rand.Read(data)
70
		if err != nil {
71
			panic(err)
72
		}
73
		id = fmt.Sprintf("%x", data)
74
		*req = *req.WithContext(context.WithValue(req.Context(), perfKey, id))
75
	}
76
	return id
77
}
78
79
func (p *Perf) Measurements(id string) map[string]interface{} {
80
	p.mu.Lock()
81
	ms := p.measurements[id]
82
	p.mu.Unlock()
83
	return ms
84
}
85
86
func (p *Perf) Delete(id string) {
87
	p.mu.Lock()
88
	delete(p.measurements, id)
89
	p.mu.Unlock()
90
}
91
92
func (p *Perf) Size() int {
93
	p.mu.Lock()
94
	defer p.mu.Unlock()
95
	return len(p.measurements)
96
}
97
98
func (p *Perf) ServeHTTP(w http.ResponseWriter, req *http.Request) {
99
	parts := strings.SplitN(req.URL.Path, "/", 3)
100
	if len(parts) < 3 {
101
		http.Error(w, "missing request id", http.StatusBadRequest)
102
		return
103
	}
104
	id := parts[2]
105
	perfData := p.Measurements(id)
106
107
	data, err := json.MarshalIndent(perfData, "", "  ")
108
	if err != nil {
109
		log.Fatal("marshaling perf data: ", err)
110
		http.Error(w, "internal server error", http.StatusInternalServerError)
111
		return
112
	}
113
114
	w.Header().Set("Content-Type", "application/json")
115
	w.Write(data)
116
}
117
118
func (p *Perf) Render(req *http.Request) template.HTML {
119
	return template.HTML(`<style>
120
body {
121
	transform: translate(0, 2.5em);
122
}
123
124
#performance-bar {
125
	display: flex;
126
127
	position: fixed;
128
	width: 100vw;
129
	transform: translate(-0.5em, -3em);
130
131
	padding: 0.75em;
132
	margin-bottom: 1em;
133
134
	background-color: #222;
135
	color: #ddd;
136
}
137
138
.performance-pair {
139
	margin-right: 1em;
140
141
	font-family: sans-serif;
142
}
143
144
.performance-value {
145
	color: #eee;
146
	font-weight: 600;
147
}
148
149
.performance-key {
150
	margin-right: 0.5em;
151
152
	color: #777;
153
}
154
</style>
155
` + `
156
<script>var performanceId = "` + p.RequestID(req) + `";
157
</script>
158
` + `
159
<script>
160
	var performanceBar = document.getElementById("performance-bar");
161
	var xhr = new XMLHttpRequest();
162
	xhr.open("GET", "/__performance__/" + performanceId);
163
	xhr.responseType = "json";
164
	xhr.onreadystatechange = ev => {
165
		if (xhr.readyState != XMLHttpRequest.DONE || xhr.status != 200) {
166
			return
167
		}
168
169
		var performanceData = xhr.response;
170
171
		if (performanceData == null) {
172
			performanceBar.style = "color: red;";
173
			performanceBar.textContent = "Could not load performance data";
174
		}
175
176
		Object.keys(performanceData).forEach(k => {
177
			var kv = document.createElement("div");
178
			kv.className = "performance-pair";
179
180
			var ke = document.createElement("span");
181
			ke.className = "performance-key";
182
			ke.textContent = k;
183
			kv.appendChild(ke);
184
185
			var ve = document.createElement("span");
186
			ve.className = "performance-value";
187
			ve.textContent = performanceData[k];
188
			kv.appendChild(ve);
189
190
			performanceBar.appendChild(kv);
191
		})
192
	};
193
	xhr.send();
194
</script>`)
195
}

+ 17 - 3
quit.go

@ -17,8 +17,12 @@ import (
17 17
18 18
	"github.com/libgit2/git2go"
19 19
	"github.com/russross/blackfriday"
20
21
	"github.com/heyLu/quit/perf"
20 22
)
21 23
24
var performance = perf.NewPerf()
25
22 26
func main() {
23 27
	repoPath := "."
24 28
	if len(os.Args) > 1 {
@ -36,12 +40,17 @@ func main() {
36 40
	}
37 41
38 42
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
43
		performance.MeasureRequest(req)
44
39 45
		buf := new(bytes.Buffer)
46
		start := time.Now()
40 47
		err = repoTmpl.Execute(buf, map[string]interface{}{
41
			"RepoPath": path.Base(repoPath),
42
			"Repo":     NewFancyRepo(repo),
43
			"Style":    template.CSS(repoStyle),
48
			"RepoPath":    path.Base(repoPath),
49
			"Repo":        NewFancyRepo(repo),
50
			"Style":       template.CSS(repoStyle),
51
			"Performance": performance.Render(req),
44 52
		})
53
		performance.Measure(req, "template", time.Since(start).String())
45 54
		if err != nil {
46 55
			log.Println("rendering repo: ", err)
47 56
			http.Error(w, "internal server error", http.StatusInternalServerError)
@ -49,6 +58,7 @@ func main() {
49 58
		}
50 59
		io.Copy(w, buf)
51 60
	})
61
	http.Handle("/__performance__/", performance)
52 62
53 63
	log.Fatal(http.ListenAndServe("localhost:12345", nil))
54 64
}
@ -484,6 +494,8 @@ var repoTmpl = template.Must(template.New("").Funcs(repoFuncs).Parse(`<!doctype
484 494
	</head>
485 495
486 496
	<body>
497
		<section id="performance-bar"></section>
498
487 499
		<div id="container">
488 500
489 501
		<section id="repo" class="repo-info">
@ -535,6 +547,8 @@ var repoTmpl = template.Must(template.New("").Funcs(repoFuncs).Parse(`<!doctype
535 547
		{{ end }}
536 548
537 549
		</div>
550
551
		{{ .Performance }}
538 552
	</body>
539 553
</html>
540 554
`))