package expvar
import (
"encoding/json"
"fmt"
"log"
"math"
"net/http"
"os"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
)
type Var interface {
String () string
}
type Int struct {
i int64
}
func (v *Int ) Value () int64 {
return atomic .LoadInt64 (&v .i )
}
func (v *Int ) String () string {
return strconv .FormatInt (atomic .LoadInt64 (&v .i ), 10 )
}
func (v *Int ) Add (delta int64 ) {
atomic .AddInt64 (&v .i , delta )
}
func (v *Int ) Set (value int64 ) {
atomic .StoreInt64 (&v .i , value )
}
type Float struct {
f uint64
}
func (v *Float ) Value () float64 {
return math .Float64frombits (atomic .LoadUint64 (&v .f ))
}
func (v *Float ) String () string {
return strconv .FormatFloat (
math .Float64frombits (atomic .LoadUint64 (&v .f )), 'g' , -1 , 64 )
}
func (v *Float ) Add (delta float64 ) {
for {
cur := atomic .LoadUint64 (&v .f )
curVal := math .Float64frombits (cur )
nxtVal := curVal + delta
nxt := math .Float64bits (nxtVal )
if atomic .CompareAndSwapUint64 (&v .f , cur , nxt ) {
return
}
}
}
func (v *Float ) Set (value float64 ) {
atomic .StoreUint64 (&v .f , math .Float64bits (value ))
}
type Map struct {
m sync .Map
keysMu sync .RWMutex
keys []string
}
type KeyValue struct {
Key string
Value Var
}
func (v *Map ) String () string {
var b strings .Builder
fmt .Fprintf (&b , "{" )
first := true
v .Do (func (kv KeyValue ) {
if !first {
fmt .Fprintf (&b , ", " )
}
fmt .Fprintf (&b , "%q: %v" , kv .Key , kv .Value )
first = false
})
fmt .Fprintf (&b , "}" )
return b .String ()
}
func (v *Map ) Init () *Map {
v .keysMu .Lock ()
defer v .keysMu .Unlock ()
v .keys = v .keys [:0 ]
v .m .Range (func (k , _ any ) bool {
v .m .Delete (k )
return true
})
return v
}
func (v *Map ) addKey (key string ) {
v .keysMu .Lock ()
defer v .keysMu .Unlock ()
if i := sort .SearchStrings (v .keys , key ); i >= len (v .keys ) {
v .keys = append (v .keys , key )
} else if v .keys [i ] != key {
v .keys = append (v .keys , "" )
copy (v .keys [i +1 :], v .keys [i :])
v .keys [i ] = key
}
}
func (v *Map ) Get (key string ) Var {
i , _ := v .m .Load (key )
av , _ := i .(Var )
return av
}
func (v *Map ) Set (key string , av Var ) {
if _ , ok := v .m .Load (key ); !ok {
if _ , dup := v .m .LoadOrStore (key , av ); !dup {
v .addKey (key )
return
}
}
v .m .Store (key , av )
}
func (v *Map ) Add (key string , delta int64 ) {
i , ok := v .m .Load (key )
if !ok {
var dup bool
i , dup = v .m .LoadOrStore (key , new (Int ))
if !dup {
v .addKey (key )
}
}
if iv , ok := i .(*Int ); ok {
iv .Add (delta )
}
}
func (v *Map ) AddFloat (key string , delta float64 ) {
i , ok := v .m .Load (key )
if !ok {
var dup bool
i , dup = v .m .LoadOrStore (key , new (Float ))
if !dup {
v .addKey (key )
}
}
if iv , ok := i .(*Float ); ok {
iv .Add (delta )
}
}
func (v *Map ) Delete (key string ) {
v .keysMu .Lock ()
defer v .keysMu .Unlock ()
i := sort .SearchStrings (v .keys , key )
if i < len (v .keys ) && key == v .keys [i ] {
v .keys = append (v .keys [:i ], v .keys [i +1 :]...)
v .m .Delete (key )
}
}
func (v *Map ) Do (f func (KeyValue )) {
v .keysMu .RLock ()
defer v .keysMu .RUnlock ()
for _ , k := range v .keys {
i , _ := v .m .Load (k )
f (KeyValue {k , i .(Var )})
}
}
type String struct {
s atomic .Value
}
func (v *String ) Value () string {
p , _ := v .s .Load ().(string )
return p
}
func (v *String ) String () string {
s := v .Value ()
b , _ := json .Marshal (s )
return string (b )
}
func (v *String ) Set (value string ) {
v .s .Store (value )
}
type Func func () any
func (f Func ) Value () any {
return f ()
}
func (f Func ) String () string {
v , _ := json .Marshal (f ())
return string (v )
}
var (
vars sync .Map
varKeysMu sync .RWMutex
varKeys []string
)
func Publish (name string , v Var ) {
if _ , dup := vars .LoadOrStore (name , v ); dup {
log .Panicln ("Reuse of exported var name:" , name )
}
varKeysMu .Lock ()
defer varKeysMu .Unlock ()
varKeys = append (varKeys , name )
sort .Strings (varKeys )
}
func Get (name string ) Var {
i , _ := vars .Load (name )
v , _ := i .(Var )
return v
}
func NewInt (name string ) *Int {
v := new (Int )
Publish (name , v )
return v
}
func NewFloat (name string ) *Float {
v := new (Float )
Publish (name , v )
return v
}
func NewMap (name string ) *Map {
v := new (Map ).Init ()
Publish (name , v )
return v
}
func NewString (name string ) *String {
v := new (String )
Publish (name , v )
return v
}
func Do (f func (KeyValue )) {
varKeysMu .RLock ()
defer varKeysMu .RUnlock ()
for _ , k := range varKeys {
val , _ := vars .Load (k )
f (KeyValue {k , val .(Var )})
}
}
func expvarHandler (w http .ResponseWriter , r *http .Request ) {
w .Header ().Set ("Content-Type" , "application/json; charset=utf-8" )
fmt .Fprintf (w , "{\n" )
first := true
Do (func (kv KeyValue ) {
if !first {
fmt .Fprintf (w , ",\n" )
}
first = false
fmt .Fprintf (w , "%q: %s" , kv .Key , kv .Value )
})
fmt .Fprintf (w , "\n}\n" )
}
func Handler () http .Handler {
return http .HandlerFunc (expvarHandler )
}
func cmdline () any {
return os .Args
}
func memstats () any {
stats := new (runtime .MemStats )
runtime .ReadMemStats (stats )
return *stats
}
func init () {
http .HandleFunc ("/debug/vars" , expvarHandler )
Publish ("cmdline" , Func (cmdline ))
Publish ("memstats" , Func (memstats ))
}