1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package expvar provides a standardized interface to public variables, such 6// as operation counters in servers. It exposes these variables via HTTP at 7// /debug/vars in JSON format. As of Go 1.22, the /debug/vars request must 8// use GET. 9// 10// Operations to set or modify these public variables are atomic. 11// 12// In addition to adding the HTTP handler, this package registers the 13// following variables: 14// 15// cmdline os.Args 16// memstats runtime.Memstats 17// 18// The package is sometimes only imported for the side effect of 19// registering its HTTP handler and the above variables. To use it 20// this way, link this package into your program: 21// 22// import _ "expvar" 23package expvar 24 25import ( 26 "encoding/json" 27 "internal/godebug" 28 "log" 29 "math" 30 "net/http" 31 "os" 32 "runtime" 33 "slices" 34 "strconv" 35 "sync" 36 "sync/atomic" 37 "unicode/utf8" 38) 39 40// Var is an abstract type for all exported variables. 41type Var interface { 42 // String returns a valid JSON value for the variable. 43 // Types with String methods that do not return valid JSON 44 // (such as time.Time) must not be used as a Var. 45 String() string 46} 47 48type jsonVar interface { 49 // appendJSON appends the JSON representation of the receiver to b. 50 appendJSON(b []byte) []byte 51} 52 53// Int is a 64-bit integer variable that satisfies the [Var] interface. 54type Int struct { 55 i atomic.Int64 56} 57 58func (v *Int) Value() int64 { 59 return v.i.Load() 60} 61 62func (v *Int) String() string { 63 return string(v.appendJSON(nil)) 64} 65 66func (v *Int) appendJSON(b []byte) []byte { 67 return strconv.AppendInt(b, v.i.Load(), 10) 68} 69 70func (v *Int) Add(delta int64) { 71 v.i.Add(delta) 72} 73 74func (v *Int) Set(value int64) { 75 v.i.Store(value) 76} 77 78// Float is a 64-bit float variable that satisfies the [Var] interface. 79type Float struct { 80 f atomic.Uint64 81} 82 83func (v *Float) Value() float64 { 84 return math.Float64frombits(v.f.Load()) 85} 86 87func (v *Float) String() string { 88 return string(v.appendJSON(nil)) 89} 90 91func (v *Float) appendJSON(b []byte) []byte { 92 return strconv.AppendFloat(b, math.Float64frombits(v.f.Load()), 'g', -1, 64) 93} 94 95// Add adds delta to v. 96func (v *Float) Add(delta float64) { 97 for { 98 cur := v.f.Load() 99 curVal := math.Float64frombits(cur) 100 nxtVal := curVal + delta 101 nxt := math.Float64bits(nxtVal) 102 if v.f.CompareAndSwap(cur, nxt) { 103 return 104 } 105 } 106} 107 108// Set sets v to value. 109func (v *Float) Set(value float64) { 110 v.f.Store(math.Float64bits(value)) 111} 112 113// Map is a string-to-Var map variable that satisfies the [Var] interface. 114type Map struct { 115 m sync.Map // map[string]Var 116 keysMu sync.RWMutex 117 keys []string // sorted 118} 119 120// KeyValue represents a single entry in a [Map]. 121type KeyValue struct { 122 Key string 123 Value Var 124} 125 126func (v *Map) String() string { 127 return string(v.appendJSON(nil)) 128} 129 130func (v *Map) appendJSON(b []byte) []byte { 131 return v.appendJSONMayExpand(b, false) 132} 133 134func (v *Map) appendJSONMayExpand(b []byte, expand bool) []byte { 135 afterCommaDelim := byte(' ') 136 mayAppendNewline := func(b []byte) []byte { return b } 137 if expand { 138 afterCommaDelim = '\n' 139 mayAppendNewline = func(b []byte) []byte { return append(b, '\n') } 140 } 141 142 b = append(b, '{') 143 b = mayAppendNewline(b) 144 first := true 145 v.Do(func(kv KeyValue) { 146 if !first { 147 b = append(b, ',', afterCommaDelim) 148 } 149 first = false 150 b = appendJSONQuote(b, kv.Key) 151 b = append(b, ':', ' ') 152 switch v := kv.Value.(type) { 153 case nil: 154 b = append(b, "null"...) 155 case jsonVar: 156 b = v.appendJSON(b) 157 default: 158 b = append(b, v.String()...) 159 } 160 }) 161 b = mayAppendNewline(b) 162 b = append(b, '}') 163 b = mayAppendNewline(b) 164 return b 165} 166 167// Init removes all keys from the map. 168func (v *Map) Init() *Map { 169 v.keysMu.Lock() 170 defer v.keysMu.Unlock() 171 v.keys = v.keys[:0] 172 v.m.Clear() 173 return v 174} 175 176// addKey updates the sorted list of keys in v.keys. 177func (v *Map) addKey(key string) { 178 v.keysMu.Lock() 179 defer v.keysMu.Unlock() 180 // Using insertion sort to place key into the already-sorted v.keys. 181 i, found := slices.BinarySearch(v.keys, key) 182 if found { 183 return 184 } 185 v.keys = slices.Insert(v.keys, i, key) 186} 187 188func (v *Map) Get(key string) Var { 189 i, _ := v.m.Load(key) 190 av, _ := i.(Var) 191 return av 192} 193 194func (v *Map) Set(key string, av Var) { 195 // Before we store the value, check to see whether the key is new. Try a Load 196 // before LoadOrStore: LoadOrStore causes the key interface to escape even on 197 // the Load path. 198 if _, ok := v.m.Load(key); !ok { 199 if _, dup := v.m.LoadOrStore(key, av); !dup { 200 v.addKey(key) 201 return 202 } 203 } 204 205 v.m.Store(key, av) 206} 207 208// Add adds delta to the *[Int] value stored under the given map key. 209func (v *Map) Add(key string, delta int64) { 210 i, ok := v.m.Load(key) 211 if !ok { 212 var dup bool 213 i, dup = v.m.LoadOrStore(key, new(Int)) 214 if !dup { 215 v.addKey(key) 216 } 217 } 218 219 // Add to Int; ignore otherwise. 220 if iv, ok := i.(*Int); ok { 221 iv.Add(delta) 222 } 223} 224 225// AddFloat adds delta to the *[Float] value stored under the given map key. 226func (v *Map) AddFloat(key string, delta float64) { 227 i, ok := v.m.Load(key) 228 if !ok { 229 var dup bool 230 i, dup = v.m.LoadOrStore(key, new(Float)) 231 if !dup { 232 v.addKey(key) 233 } 234 } 235 236 // Add to Float; ignore otherwise. 237 if iv, ok := i.(*Float); ok { 238 iv.Add(delta) 239 } 240} 241 242// Delete deletes the given key from the map. 243func (v *Map) Delete(key string) { 244 v.keysMu.Lock() 245 defer v.keysMu.Unlock() 246 i, found := slices.BinarySearch(v.keys, key) 247 if found { 248 v.keys = slices.Delete(v.keys, i, i+1) 249 v.m.Delete(key) 250 } 251} 252 253// Do calls f for each entry in the map. 254// The map is locked during the iteration, 255// but existing entries may be concurrently updated. 256func (v *Map) Do(f func(KeyValue)) { 257 v.keysMu.RLock() 258 defer v.keysMu.RUnlock() 259 for _, k := range v.keys { 260 i, _ := v.m.Load(k) 261 val, _ := i.(Var) 262 f(KeyValue{k, val}) 263 } 264} 265 266// String is a string variable, and satisfies the [Var] interface. 267type String struct { 268 s atomic.Value // string 269} 270 271func (v *String) Value() string { 272 p, _ := v.s.Load().(string) 273 return p 274} 275 276// String implements the [Var] interface. To get the unquoted string 277// use [String.Value]. 278func (v *String) String() string { 279 return string(v.appendJSON(nil)) 280} 281 282func (v *String) appendJSON(b []byte) []byte { 283 return appendJSONQuote(b, v.Value()) 284} 285 286func (v *String) Set(value string) { 287 v.s.Store(value) 288} 289 290// Func implements [Var] by calling the function 291// and formatting the returned value using JSON. 292type Func func() any 293 294func (f Func) Value() any { 295 return f() 296} 297 298func (f Func) String() string { 299 v, _ := json.Marshal(f()) 300 return string(v) 301} 302 303// All published variables. 304var vars Map 305 306// Publish declares a named exported variable. This should be called from a 307// package's init function when it creates its Vars. If the name is already 308// registered then this will log.Panic. 309func Publish(name string, v Var) { 310 if _, dup := vars.m.LoadOrStore(name, v); dup { 311 log.Panicln("Reuse of exported var name:", name) 312 } 313 vars.keysMu.Lock() 314 defer vars.keysMu.Unlock() 315 vars.keys = append(vars.keys, name) 316 slices.Sort(vars.keys) 317} 318 319// Get retrieves a named exported variable. It returns nil if the name has 320// not been registered. 321func Get(name string) Var { 322 return vars.Get(name) 323} 324 325// Convenience functions for creating new exported variables. 326 327func NewInt(name string) *Int { 328 v := new(Int) 329 Publish(name, v) 330 return v 331} 332 333func NewFloat(name string) *Float { 334 v := new(Float) 335 Publish(name, v) 336 return v 337} 338 339func NewMap(name string) *Map { 340 v := new(Map).Init() 341 Publish(name, v) 342 return v 343} 344 345func NewString(name string) *String { 346 v := new(String) 347 Publish(name, v) 348 return v 349} 350 351// Do calls f for each exported variable. 352// The global variable map is locked during the iteration, 353// but existing entries may be concurrently updated. 354func Do(f func(KeyValue)) { 355 vars.Do(f) 356} 357 358func expvarHandler(w http.ResponseWriter, r *http.Request) { 359 w.Header().Set("Content-Type", "application/json; charset=utf-8") 360 w.Write(vars.appendJSONMayExpand(nil, true)) 361} 362 363// Handler returns the expvar HTTP Handler. 364// 365// This is only needed to install the handler in a non-standard location. 366func Handler() http.Handler { 367 return http.HandlerFunc(expvarHandler) 368} 369 370func cmdline() any { 371 return os.Args 372} 373 374func memstats() any { 375 stats := new(runtime.MemStats) 376 runtime.ReadMemStats(stats) 377 return *stats 378} 379 380func init() { 381 if godebug.New("httpmuxgo121").Value() == "1" { 382 http.HandleFunc("/debug/vars", expvarHandler) 383 } else { 384 http.HandleFunc("GET /debug/vars", expvarHandler) 385 } 386 Publish("cmdline", Func(cmdline)) 387 Publish("memstats", Func(memstats)) 388} 389 390// TODO: Use json.appendString instead. 391func appendJSONQuote(b []byte, s string) []byte { 392 const hex = "0123456789abcdef" 393 b = append(b, '"') 394 for _, r := range s { 395 switch { 396 case r < ' ' || r == '\\' || r == '"' || r == '<' || r == '>' || r == '&' || r == '\u2028' || r == '\u2029': 397 switch r { 398 case '\\', '"': 399 b = append(b, '\\', byte(r)) 400 case '\n': 401 b = append(b, '\\', 'n') 402 case '\r': 403 b = append(b, '\\', 'r') 404 case '\t': 405 b = append(b, '\\', 't') 406 default: 407 b = append(b, '\\', 'u', hex[(r>>12)&0xf], hex[(r>>8)&0xf], hex[(r>>4)&0xf], hex[(r>>0)&0xf]) 408 } 409 case r < utf8.RuneSelf: 410 b = append(b, byte(r)) 411 default: 412 b = utf8.AppendRune(b, r) 413 } 414 } 415 b = append(b, '"') 416 return b 417} 418