forked from arl/statsviz
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplots_list.go
More file actions
180 lines (152 loc) 路 3.92 KB
/
Copy pathplots_list.go
File metadata and controls
180 lines (152 loc) 路 3.92 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package plot
import (
"encoding/json"
"fmt"
"io"
"runtime/debug"
"runtime/metrics"
"strings"
"sync"
"time"
)
var (
names map[string]bool
usedMetrics map[string]struct{}
)
type plotFunc func(idxs map[string]int) runtimeMetric
var plotFuncs []plotFunc
func registerPlotFunc(f plotFunc) {
plotFuncs = append(plotFuncs, f)
}
func registerRuntimePlot(name string, metrics ...string) bool {
if names == nil {
names = make(map[string]bool)
usedMetrics = make(map[string]struct{})
}
if names[name] {
panic(name + " is an already reserved plot name")
}
names[name] = true
// Record the metrics we use.
for _, m := range metrics {
usedMetrics[m] = struct{}{}
}
return true
}
func IsReservedPlotName(name string) bool {
return names[name]
}
type runtimeMetric interface {
name() string
isEnabled() bool
layout([]metrics.Sample) any
values([]metrics.Sample) any
}
// List holds all the plots that statsviz knows about. Some plots might be
// disabled, if they rely on metrics that are unknown to the current Go version.
type List struct {
rtPlots []runtimeMetric
userPlots []UserPlot
once sync.Once // ensure Config is called once
cfg *Config
idxs map[string]int // map metrics name to idx in samples and descs
descs []metrics.Description
mu sync.Mutex // protects samples in case of concurrent calls to WriteValues
samples []metrics.Sample
}
func NewList(userPlots []UserPlot) (*List, error) {
if name := hasDuplicatePlotNames(userPlots); name != "" {
return nil, fmt.Errorf("duplicate plot name %s", name)
}
descs := metrics.All()
pl := &List{
idxs: make(map[string]int),
descs: descs,
samples: make([]metrics.Sample, len(descs)),
userPlots: userPlots,
}
for i := range pl.samples {
pl.samples[i].Name = pl.descs[i].Name
pl.idxs[pl.samples[i].Name] = i
}
metrics.Read(pl.samples)
return pl, nil
}
func (pl *List) Config() *Config {
pl.once.Do(func() {
pl.rtPlots = make([]runtimeMetric, 0, len(plotFuncs))
for _, f := range plotFuncs {
pl.rtPlots = append(pl.rtPlots, f(pl.idxs))
}
layouts := make([]any, 0, len(pl.rtPlots))
for i := range pl.rtPlots {
if pl.rtPlots[i].isEnabled() {
layouts = append(layouts, pl.rtPlots[i].layout(pl.samples))
}
}
pl.cfg = &Config{
Events: []string{"lastgc"},
Series: layouts,
}
// User plots go at the back of the list for now.
for i := range pl.userPlots {
pl.cfg.Series = append(pl.cfg.Series, pl.userPlots[i].Layout())
}
})
return pl.cfg
}
// WriteValues writes into w a JSON object containing the data points for all
// plots at the current instant.
func (pl *List) WriteValues(w io.Writer) error {
pl.mu.Lock()
defer pl.mu.Unlock()
metrics.Read(pl.samples)
// lastgc time series is used as source to represent garbage collection
// timestamps as vertical bars on certain plots.
gcStats := debug.GCStats{}
debug.ReadGCStats(&gcStats)
m := map[string]any{
// javascript timestampts are in milliseconds
"lastgc": []int64{gcStats.LastGC.UnixMilli()},
"timestamp": time.Now().UnixMilli(),
}
for _, p := range pl.rtPlots {
if p.isEnabled() {
m[p.name()] = p.values(pl.samples)
}
}
for i := range pl.userPlots {
up := &pl.userPlots[i]
switch {
case up.Scatter != nil:
vals := make([]float64, len(up.Scatter.Funcs))
for i := range up.Scatter.Funcs {
vals[i] = up.Scatter.Funcs[i]()
}
m[up.Scatter.Plot.Name] = vals
case up.Heatmap != nil:
panic("unimplemented")
}
}
if err := json.NewEncoder(w).Encode(m); err != nil {
// If we fail to encode the metrics values, it's probably because
if strings.Contains(err.Error(), "NaN") {
for s, a := range m {
if v, ok := a.([]float64); ok {
for i := range v {
if v[i] != v[i] {
v[i] = 0
}
}
m[s] = v
}
}
err = json.NewEncoder(w).Encode(m)
if err == nil {
return nil
}
}
return fmt.Errorf("failed to write/convert metrics values to json: %v", err)
}
return nil
}