expvarmon/var.go

175 lines
3.5 KiB
Go

package main
import (
"fmt"
"strings"
"time"
"github.com/pyk/byten"
)
// VarName represents variable name.
//
// It has dot-separated format, like "memstats.Alloc",
// but can be used in different forms, hence it's own type.
//
// It also can have optional "kind:" modifier, like "mem:" or "duration:"
type VarName string
// VarKind specifies special kinds of values, affects formatting.
type VarKind int
// VarValue represents arbitrary value for variable.
type VarValue interface{}
const (
KindDefault VarKind = iota
KindMemory
KindDuration
KindString
)
// ToSlice converts "dot-separated" notation into the "slice of strings".
//
// "dot-separated" notation is a human-readable format, passed via args.
// "slice of strings" is used by Jason library.
//
// Example: "memstats.Alloc" => []string{"memstats", "Alloc"}
// Example: "mem:memstats.Alloc" => []string{"memstats", "Alloc"}
func (v VarName) ToSlice() []string {
start := strings.IndexRune(string(v), ':') + 1
slice := DottedFieldsToSliceEscaped(string(v)[start:])
return slice
}
// Short returns short name, which is typically is the last word in the long names.
func (v VarName) Short() string {
if v == "" {
return ""
}
slice := v.ToSlice()
return slice[len(slice)-1]
}
// Long returns long name, without kind: modifier.
func (v VarName) Long() string {
if v == "" {
return ""
}
start := strings.IndexRune(string(v), ':') + 1
return string(v)[start:]
}
// Kind returns kind of variable, based on it's name modifiers ("mem:")
func (v VarName) Kind() VarKind {
start := strings.IndexRune(string(v), ':')
if start == -1 {
return KindDefault
}
switch string(v)[:start] {
case "mem":
return KindMemory
case "duration":
return KindDuration
case "str":
return KindString
}
return KindDefault
}
// Format returns human-readable var value representation.
func Format(v VarValue, kind VarKind) string {
switch kind {
case KindMemory:
if _, ok := v.(int64); !ok {
break
}
return fmt.Sprintf("%s", byten.Size(v.(int64)))
case KindDuration:
if _, ok := v.(int64); !ok {
break
}
return fmt.Sprintf("%s", roundDuration(time.Duration(v.(int64))))
}
if f, ok := v.(float64); ok {
return fmt.Sprintf("%.2f", f)
}
return fmt.Sprintf("%v", v)
}
// roundDuration removes unneeded precision from the String() output for time.Duration.
func roundDuration(d time.Duration) time.Duration {
r := time.Second
if d < time.Second {
r = time.Millisecond
}
if d < time.Millisecond {
r = time.Microsecond
}
if r <= 0 {
return d
}
neg := d < 0
if neg {
d = -d
}
if m := d % r; m+m < r {
d = d - m
} else {
d = d + r - m
}
if neg {
return -d
}
return d
}
func DottedFieldsToSliceEscaped(s string) []string {
rv := make([]string, 0)
lastSlash := false
curr := ""
for _, r := range s {
// base case, dot not after slash
if !lastSlash && r == '.' {
if len(curr) > 0 {
rv = append(rv, curr)
curr = ""
}
continue
} else if !lastSlash {
// any character not after slash
curr += string(r)
if r == '\\' {
lastSlash = true
} else {
lastSlash = false
}
continue
} else if r == '\\' {
// last was slash, and so is this
lastSlash = false // 2 slashes = 0
// we already appended a single slash on first
continue
} else if r == '.' {
// we see \. but already appended \ last time
// replace it with .
curr = curr[:len(curr)-1] + "."
lastSlash = false
} else {
// \ and any other character, ignore
curr += string(r)
lastSlash = false
continue
}
}
if len(curr) > 0 {
rv = append(rv, curr)
}
return rv
}