1
0
mirror of https://github.com/divan/expvarmon.git synced 2025-04-27 13:48:55 +08:00
expvarmon/var.go

338 lines
6.9 KiB
Go
Raw Normal View History

2015-05-01 18:48:34 +03:00
package main
2015-05-02 22:29:02 +03:00
import (
"fmt"
"strings"
"time"
2016-11-10 19:58:10 +01:00
"github.com/antonholmquist/jason"
2015-05-02 22:29:02 +03:00
"github.com/pyk/byten"
)
2015-05-01 18:48:34 +03:00
// VarName represents variable name.
//
// It has dot-separated format, like "memstats.Alloc",
// but can be used in different forms, hence it's own type.
2015-05-02 10:12:38 +03:00
//
// It also can have optional "kind:" modifier, like "mem:" or "duration:"
2015-05-01 18:48:34 +03:00
type VarName string
2015-05-02 22:29:02 +03:00
// VarKind specifies special kinds of values, affects formatting.
2015-05-02 20:12:53 +03:00
type VarKind int
2015-05-02 10:12:38 +03:00
2016-11-12 22:35:51 +01:00
const (
KindDefault VarKind = iota
KindMemory
KindDuration
KindString
2016-11-12 23:59:56 +01:00
KindGCPauses
2016-11-12 22:35:51 +01:00
)
2016-11-10 19:58:10 +01:00
// Var represents arbitrary value for variable.
type Var interface {
Kind() VarKind
String() string
Set(*jason.Value)
}
// IntVar represents variable which value can be represented as integer,
// and suitable for displaying with sparklines.
type IntVar interface {
Var
Value() int
}
2016-11-12 23:59:56 +01:00
// NewVar inits new Var object with the given name.
func NewVar(name VarName) Var {
kind := name.Kind()
switch kind {
case KindDefault:
return &Number{}
case KindMemory:
return &Memory{}
case KindDuration:
return &Duration{}
case KindString:
return &String{}
case KindGCPauses:
return &GCPauses{}
default:
return &Number{}
}
}
2016-11-10 23:59:32 +01:00
// Number is a type for numeric values, obtained from JSON.
// In JSON it's always float64, so there is no straightforward way
// to separate float from int, so let's keep everything as float.
2016-11-10 19:58:10 +01:00
type Number struct {
val float64
}
func (v *Number) Kind() VarKind { return KindDefault }
func (v *Number) String() string {
return fmt.Sprintf("%.02f", v.val)
}
func (v *Number) Set(j *jason.Value) {
2016-11-13 12:05:26 +01:00
if j == nil {
v.val = 0
return
}
2016-11-12 14:02:16 +01:00
if n, err := j.Float64(); err == nil {
2016-11-10 19:58:10 +01:00
v.val = n
2016-11-12 14:02:16 +01:00
} else if n, err := j.Int64(); err == nil {
v.val = float64(n)
2016-11-10 19:58:10 +01:00
} else {
v.val = 0
}
}
// Value implements IntVar for Number type.
func (v *Number) Value() int {
return int(v.val)
}
2016-11-10 23:59:32 +01:00
// Memory represents memory information in bytes.
2016-11-10 19:58:10 +01:00
type Memory struct {
bytes int64
}
func (v *Memory) Kind() VarKind { return KindMemory }
func (v *Memory) String() string {
return fmt.Sprintf("%s", byten.Size(v.bytes))
}
func (v *Memory) Set(j *jason.Value) {
2016-11-13 12:05:26 +01:00
if j == nil {
v.bytes = 0
return
}
2016-11-10 19:58:10 +01:00
if n, err := j.Int64(); err == nil {
v.bytes = n
} else {
v.bytes = 0
}
}
// Value implements IntVar for Memory type.
func (v *Memory) Value() int {
// TODO: check for possible overflows
return int(v.bytes)
}
2016-11-10 23:59:32 +01:00
// Duration represents duration data (in ns)
2016-11-10 19:58:10 +01:00
type Duration struct {
dur time.Duration
}
func (v *Duration) Kind() VarKind { return KindDuration }
func (v *Duration) String() string {
return fmt.Sprintf("%s", roundDuration(time.Duration(v.dur)))
}
func (v *Duration) Set(j *jason.Value) {
2016-11-13 12:05:26 +01:00
if j == nil {
v.dur = 0
return
}
2016-11-10 19:58:10 +01:00
if n, err := j.Int64(); err == nil {
v.dur = time.Duration(n)
} else if n, err := j.Float64(); err == nil {
v.dur = time.Duration(int64(n))
} else {
v.dur = 0
}
}
// Value implements IntVar for Duration type.
func (v *Duration) Value() int {
// TODO: check for possible overflows
return int(v.dur)
}
2016-11-10 23:59:32 +01:00
// Strings represents string data.
2016-11-10 19:58:10 +01:00
type String struct {
str string
}
func (v *String) Kind() VarKind { return KindString }
func (v *String) String() string { return v.str }
func (v *String) Set(j *jason.Value) {
2016-11-13 12:05:26 +01:00
if j == nil {
v.str = "N/A"
return
}
2016-11-10 19:58:10 +01:00
if n, err := j.String(); err == nil {
v.str = n
} else {
v.str = "N/A"
}
}
2016-11-12 23:59:56 +01:00
// GCPauses represents GC pauses data.
//
// It uses memstat.PauseNS circular buffer, but lacks
// NumGC information, so we don't know what the start
// and the end. It's enough for most stats, though.
type GCPauses struct {
pauses [256]uint64
}
2016-11-10 19:58:10 +01:00
2016-11-12 23:59:56 +01:00
func (v *GCPauses) Kind() VarKind { return KindGCPauses }
func (v *GCPauses) String() string { return "" }
func (v *GCPauses) Set(j *jason.Value) {
v.pauses = [256]uint64{}
2016-11-13 12:05:26 +01:00
if j == nil {
return
}
2016-11-12 23:59:56 +01:00
if arr, err := j.Array(); err == nil {
for i := 0; i < len(arr); i++ {
p, _ := arr[i].Int64()
v.pauses[i] = uint64(p)
}
}
}
func (v *GCPauses) Histogram(bins int) *Histogram {
hist := NewHistogram(bins)
for i := 0; i < 256; i++ {
2016-11-13 12:05:26 +01:00
// we ignore zeros, since
// its never the case, but
// we have zeros on the very beginning
if v.pauses[i] > 0 {
hist.Add(v.pauses[i])
}
2016-11-10 19:58:10 +01:00
}
2016-11-12 23:59:56 +01:00
return hist
2016-11-10 19:58:10 +01:00
}
2015-05-02 22:29:02 +03:00
2016-11-12 23:59:56 +01:00
// TODO: add boolean, timestamp, gcpauses, gcendtimes types
2015-05-01 18:48:34 +03:00
// 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"}
2015-05-02 10:12:38 +03:00
// Example: "mem:memstats.Alloc" => []string{"memstats", "Alloc"}
2015-05-01 18:48:34 +03:00
func (v VarName) ToSlice() []string {
2015-05-02 10:12:38 +03:00
start := strings.IndexRune(string(v), ':') + 1
slice := DottedFieldsToSliceEscaped(string(v)[start:])
2015-05-02 10:12:38 +03:00
return slice
2015-05-01 18:48:34 +03:00
}
// 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]
}
2015-05-02 10:12:38 +03:00
2015-05-02 10:22:49 +03:00
// 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:]
}
2016-11-12 23:59:56 +01:00
// Kind returns kind of variable, based on it's name
// modifiers ("mem:") or full names for special cases.
2015-05-02 20:12:53 +03:00
func (v VarName) Kind() VarKind {
2016-11-12 23:59:56 +01:00
if v.Long() == "memstats.PauseNs" {
return KindGCPauses
}
2015-05-02 10:12:38 +03:00
start := strings.IndexRune(string(v), ':')
if start == -1 {
return KindDefault
}
switch string(v)[:start] {
case "mem":
return KindMemory
case "duration":
return KindDuration
2015-05-02 21:43:32 +03:00
case "str":
return KindString
2015-05-02 10:12:38 +03:00
}
return KindDefault
}
2015-05-02 22:29:02 +03:00
2015-05-03 19:04:17 +03:00
// roundDuration removes unneeded precision from the String() output for time.Duration.
2015-05-03 00:00:05 +03:00
func roundDuration(d time.Duration) time.Duration {
r := time.Second
if d < time.Second {
r = time.Millisecond
}
if d < time.Millisecond {
r = time.Microsecond
}
2016-11-12 14:02:16 +01:00
if d < time.Microsecond {
r = time.Nanosecond
}
2015-05-03 00:00:05 +03:00
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
}