import (
"encoding/json"
- "fmt"
"log"
"math"
"net/http"
"runtime"
"sort"
"strconv"
- "strings"
"sync"
"sync/atomic"
+ "unicode/utf8"
)
// Var is an abstract type for all exported variables.
String() string
}
+type jsonVar interface {
+ // appendJSON appends the JSON representation of the receiver to b.
+ appendJSON(b []byte) []byte
+}
+
// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
i int64
}
func (v *Int) String() string {
- return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
+ return string(v.appendJSON(nil))
+}
+
+func (v *Int) appendJSON(b []byte) []byte {
+ return strconv.AppendInt(b, atomic.LoadInt64(&v.i), 10)
}
func (v *Int) Add(delta int64) {
}
func (v *Float) String() string {
- return strconv.FormatFloat(
- math.Float64frombits(v.f.Load()), 'g', -1, 64)
+ return string(v.appendJSON(nil))
+}
+
+func (v *Float) appendJSON(b []byte) []byte {
+ return strconv.AppendFloat(b, math.Float64frombits(v.f.Load()), 'g', -1, 64)
}
// Add adds delta to v.
}
func (v *Map) String() string {
- var b strings.Builder
- fmt.Fprintf(&b, "{")
+ return string(v.appendJSON(nil))
+}
+
+func (v *Map) appendJSON(b []byte) []byte {
+ return v.appendJSONMayExpand(b, false)
+}
+
+func (v *Map) appendJSONMayExpand(b []byte, expand bool) []byte {
+ afterCommaDelim := byte(' ')
+ mayAppendNewline := func(b []byte) []byte { return b }
+ if expand {
+ afterCommaDelim = '\n'
+ mayAppendNewline = func(b []byte) []byte { return append(b, '\n') }
+ }
+
+ b = append(b, '{')
+ b = mayAppendNewline(b)
first := true
v.Do(func(kv KeyValue) {
if !first {
- fmt.Fprintf(&b, ", ")
- }
- fmt.Fprintf(&b, "%q: ", kv.Key)
- if kv.Value != nil {
- fmt.Fprintf(&b, "%v", kv.Value)
- } else {
- fmt.Fprint(&b, "null")
+ b = append(b, ',', afterCommaDelim)
}
first = false
+ b = appendJSONQuote(b, kv.Key)
+ b = append(b, ':', ' ')
+ switch v := kv.Value.(type) {
+ case nil:
+ b = append(b, "null"...)
+ case jsonVar:
+ b = v.appendJSON(b)
+ default:
+ b = append(b, v.String()...)
+ }
})
- fmt.Fprintf(&b, "}")
- return b.String()
+ b = mayAppendNewline(b)
+ b = append(b, '}')
+ b = mayAppendNewline(b)
+ return b
}
// Init removes all keys from the map.
// String implements the Var interface. To get the unquoted string
// use Value.
func (v *String) String() string {
- s := v.Value()
- b, _ := json.Marshal(s)
- return string(b)
+ return string(v.appendJSON(nil))
+}
+
+func (v *String) appendJSON(b []byte) []byte {
+ return appendJSONQuote(b, v.Value())
}
func (v *String) Set(value string) {
}
// All published variables.
-var (
- vars sync.Map // map[string]Var
- varKeysMu sync.RWMutex
- varKeys []string // sorted
-)
+var vars Map
// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
- if _, dup := vars.LoadOrStore(name, v); dup {
+ if _, dup := vars.m.LoadOrStore(name, v); dup {
log.Panicln("Reuse of exported var name:", name)
}
- varKeysMu.Lock()
- defer varKeysMu.Unlock()
- varKeys = append(varKeys, name)
- sort.Strings(varKeys)
+ vars.keysMu.Lock()
+ defer vars.keysMu.Unlock()
+ vars.keys = append(vars.keys, name)
+ sort.Strings(vars.keys)
}
// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
- i, _ := vars.Load(name)
- v, _ := i.(Var)
- return v
+ return vars.Get(name)
}
// Convenience functions for creating new exported variables.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
- varKeysMu.RLock()
- defer varKeysMu.RUnlock()
- for _, k := range varKeys {
- val, _ := vars.Load(k)
- f(KeyValue{k, val.(Var)})
- }
+ vars.Do(f)
}
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")
+ w.Write(vars.appendJSONMayExpand(nil, true))
}
// Handler returns the expvar HTTP Handler.
Publish("cmdline", Func(cmdline))
Publish("memstats", Func(memstats))
}
+
+// TODO: Use json.appendString instead.
+func appendJSONQuote(b []byte, s string) []byte {
+ const hex = "0123456789abcdef"
+ b = append(b, '"')
+ for _, r := range s {
+ switch {
+ case r < ' ' || r == '\\' || r == '"' || r == '<' || r == '>' || r == '&' || r == '\u2028' || r == '\u2029':
+ switch r {
+ case '\\', '"':
+ b = append(b, '\\', byte(r))
+ case '\n':
+ b = append(b, '\\', 'n')
+ case '\r':
+ b = append(b, '\\', 'r')
+ case '\t':
+ b = append(b, '\\', 't')
+ default:
+ b = append(b, '\\', 'u', hex[(r>>12)&0xf], hex[(r>>8)&0xf], hex[(r>>4)&0xf], hex[(r>>0)&0xf])
+ }
+ case r < utf8.RuneSelf:
+ b = append(b, byte(r))
+ default:
+ b = utf8.AppendRune(b, r)
+ }
+ }
+ b = append(b, '"')
+ return b
+}
// RemoveAll removes all exported variables.
// This is for tests only.
func RemoveAll() {
- varKeysMu.Lock()
- defer varKeysMu.Unlock()
- for _, k := range varKeys {
- vars.Delete(k)
+ vars.keysMu.Lock()
+ defer vars.keysMu.Unlock()
+ for _, k := range vars.keys {
+ vars.m.Delete(k)
}
- varKeys = nil
+ vars.keys = nil
}
func TestNil(t *testing.T) {
}
}
+func BenchmarkMapString(b *testing.B) {
+ var m, m1, m2 Map
+ m.Set("map1", &m1)
+ m1.Add("a", 1)
+ m1.Add("z", 2)
+ m.Set("map2", &m2)
+ for i := 0; i < 9; i++ {
+ m2.Add(strconv.Itoa(i), int64(i))
+ }
+ var s1, s2 String
+ m.Set("str1", &s1)
+ s1.Set("hello, world!")
+ m.Set("str2", &s2)
+ s2.Set("fizz buzz")
+ b.ResetTimer()
+
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ _ = m.String()
+ }
+}
+
func BenchmarkRealworldExpvarUsage(b *testing.B) {
var (
bytesSent Int
}
wg.Wait()
}
+
+func TestAppendJSONQuote(t *testing.T) {
+ var b []byte
+ for i := 0; i < 128; i++ {
+ b = append(b, byte(i))
+ }
+ b = append(b, "\u2028\u2029"...)
+ got := string(appendJSONQuote(nil, string(b[:])))
+ want := `"` +
+ `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\t\n\u000b\u000c\r\u000e\u000f` +
+ `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
+ ` !\"#$%\u0026'()*+,-./0123456789:;\u003c=\u003e?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_` +
+ "`" + `abcdefghijklmnopqrstuvwxyz{|}~` + "\x7f" + `\u2028\u2029"`
+ if got != want {
+ t.Errorf("appendJSONQuote mismatch:\ngot %v\nwant %v", got, want)
+ }
+}