@@ -14,6 +14,8 @@ import (
1414 "time"
1515)
1616
17+ // from pkg.go.dev/runtime/metrics#Read
18+
1719const (
1820 // ex: /cgo/go-to-c-calls:calls
1921 MetCgo = "/cgo"
@@ -33,18 +35,24 @@ const (
3335 memoizationThreshold = 10 * time .Second
3436)
3537
36- var (
37- // Get descriptions for all supported metrics.
38- descs = metrics .All ()
38+ type metricUnit = int
3939
40- // Create a sample for each metric.
41- allsamples = make ([]metrics.Sample , len (descs ))
42-
43- sb strings.Builder
40+ const (
41+ unitUnknown = metricUnit (iota )
42+ unitSeconds
43+ unitCount
44+ unitPercent
45+ unitBytes
46+ unitGcTime
47+ )
4448
45- mu sync.Mutex // protects allsamples, last, sb
49+ var (
50+ descs = metrics .All ()
51+ allsamples = make ([]metrics.Sample , len (descs ))
4652
53+ sb strings.Builder
4754 lastcall time.Time
55+ mu sync.Mutex // protects allsamples, last, sb
4856)
4957
5058func init () {
@@ -54,7 +62,6 @@ func init() {
5462 sb .Grow (len (allsamples ) * 100 )
5563}
5664
57- // from pkg.go.dev/runtime/metrics#Read
5865func Metrics () string {
5966 mu .Lock ()
6067 defer mu .Unlock ()
@@ -78,17 +85,45 @@ func Metrics() string {
7885 continue
7986 }
8087
88+ unit := ""
89+ u := unitUnknown
90+ namesplit := strings .Split (name , ":" )
91+ if len (namesplit ) >= 2 {
92+ name = namesplit [0 ]
93+ unit = namesplit [1 ]
94+ }
95+
96+ if unit == "cpu-seconds" || unit == "seconds" {
97+ u = unitSeconds
98+ } else if unit == "count" ||
99+ unit == "cleanups" ||
100+ unit == "calls" ||
101+ unit == "gc-cycles" ||
102+ unit == "finalizers" ||
103+ unit == "objects" ||
104+ unit == "events" ||
105+ unit == "threads" ||
106+ unit == "goroutines" {
107+ u = unitCount
108+ } else if unit == "percent" {
109+ u = unitPercent
110+ } else if unit == "bytes" {
111+ u = unitBytes
112+ } else if unit == "gc-cycle" {
113+ u = unitGcTime
114+ }
115+
81116 switch value .Kind () {
82117 case metrics .KindUint64 :
83- s := fmt .Sprintf ("%s: %d \n " , name , value .Uint64 ())
118+ s := fmt .Sprintf ("%s: %s \n " , name , unit4int ( value .Uint64 (), u ))
84119 sb .WriteString (s )
85120 case metrics .KindFloat64 :
86- s := fmt .Sprintf ("%s: %f \n " , name , value .Float64 ())
121+ s := fmt .Sprintf ("%s: %s \n " , name , unit4float ( value .Float64 (), u ))
87122 sb .WriteString (s )
88123 case metrics .KindFloat64Histogram :
89124 // The histogram may be quite large, so let's just pull out
90125 // a crude estimate for the median.
91- s := fmt .Sprintf ("%s: hist(%s)\n " , name , histoCsv (value .Float64Histogram ()))
126+ s := fmt .Sprintf ("%s: hist(%s)\n " , name , histoCsv (value .Float64Histogram (), u ))
92127 sb .WriteString (s )
93128 case metrics .KindBad :
94129 fallthrough
@@ -101,15 +136,72 @@ func Metrics() string {
101136 return sb .String ()
102137}
103138
104- func histoCsv (h * metrics.Float64Histogram ) string {
139+ func histoCsv (h * metrics.Float64Histogram , u metricUnit ) string {
105140 var sb strings.Builder
106141 sb .Grow (20 * len (h .Buckets ))
107142 for i , b := range h .Buckets {
108- s := fmt .Sprintf ("%f:%d" , b , h .Counts [i ])
109- sb .WriteString (s )
110- if i < len (h .Buckets )- 1 {
143+ if i >= len (h .Buckets )- 1 {
144+ break
145+ }
146+ if i > 0 {
111147 sb .WriteString ("," )
112148 }
149+ s := fmt .Sprintf ("%s:%s" , unit4float (b , u ), unit4int (h .Counts [i ], u ))
150+ sb .WriteString (s )
113151 }
114152 return sb .String ()
115153}
154+
155+ func unit4int (v uint64 , u metricUnit ) string {
156+ switch u {
157+ case unitSeconds :
158+ return FmtSecs (int64 (v )) // may wrap?
159+ case unitPercent :
160+ return fmt .Sprintf ("%d%" , v )
161+ case unitBytes :
162+ return FmtBytes (v )
163+ case unitGcTime :
164+ return fmt .Sprintf ("%d" , v )
165+ case unitCount :
166+ return FmtWithCommas (v )
167+ default :
168+ return fmt .Sprintf ("%d" , v )
169+ }
170+ }
171+
172+ // FmtWithCommas formats a uint64 with comma separators every 3 digits (e.g. 1234567 -> "1,234,567").
173+ func FmtWithCommas (v uint64 ) string {
174+ s := fmt .Sprintf ("%d" , v )
175+ n := len (s )
176+ if n <= 3 {
177+ return s
178+ }
179+ // pre-allocate exact size: n digits + (n-1)/3 commas
180+ b := make ([]byte , n + (n - 1 )/ 3 )
181+ for i , j , k := n - 1 , len (b )- 1 , 0 ; i >= 0 ; i , k = i - 1 , k + 1 {
182+ if k > 0 && k % 3 == 0 {
183+ b [j ] = ','
184+ j --
185+ }
186+ b [j ] = s [i ]
187+ j --
188+ }
189+ return string (b )
190+ }
191+
192+ func unit4float (v float64 , u metricUnit ) string {
193+ switch u {
194+ case unitSeconds :
195+ return FmtSecsFloat (v )
196+ case unitPercent :
197+ return fmt .Sprintf ("%.2f%%" , v )
198+ case unitBytes :
199+ return FmtBytes (uint64 (v ))
200+ case unitGcTime :
201+ return fmt .Sprintf ("%.2f" , v )
202+ case unitCount :
203+ return FmtWithCommas (uint64 (v ))
204+ default :
205+ return fmt .Sprintf ("%.3g" , v )
206+ }
207+ }
0 commit comments