expvarmon/service.go

177 lines
3.6 KiB
Go

package main
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"github.com/antonholmquist/jason"
)
var (
// uptimeCounter is a variable used for tracking uptime status.
// It should be always incrementing and included into default expvar vars.
// Could be replaced with something different or made configurable in
// the future.
uptimeCounter = VarName("memstats.PauseTotalNs").ToSlice()
)
// Service represents constantly updating info about single service.
type Service struct {
Port string
Name string
Cmdline string
stacks map[VarName]*Stack
Err error
Restarted bool
UptimeCounter int64
}
// NewService returns new Service object.
func NewService(port string, vars []VarName) *Service {
values := make(map[VarName]*Stack)
for _, name := range vars {
values[VarName(name)] = NewStack()
}
return &Service{
Name: port, // we have only port on start, so use it as name until resolved
Port: port,
stacks: values,
}
}
// Update updates Service info from Expvar variable.
func (s *Service) Update(wg *sync.WaitGroup) {
defer wg.Done()
expvar, err := FetchExpvar(s.Addr())
// check for restart
if s.Err != nil && err == nil {
s.Restarted = true
}
s.Err = err
// if memstat.PauseTotalNs less than s.UptimeCounter
// then service was restarted
c, err := expvar.GetInt64(uptimeCounter...)
if err != nil {
s.Err = err
} else {
if s.UptimeCounter > c {
s.Restarted = true
}
s.UptimeCounter = c
}
// Update Cmdline & Name only once
if len(s.Cmdline) == 0 {
cmdline, err := expvar.GetStringArray("cmdline")
if err != nil {
s.Err = err
} else {
s.Cmdline = strings.Join(cmdline, " ")
s.Name = BaseCommand(cmdline)
}
}
// For all vars, fetch desired value from Json and push to it's own stack.
for name, stack := range s.stacks {
value, err := expvar.GetValue(name.ToSlice()...)
if err != nil {
stack.Push(nil)
continue
}
v := guessValue(value)
if v != nil {
stack.Push(v)
}
}
}
// guessValue attemtps to bruteforce all supported types.
func guessValue(value *jason.Value) interface{} {
if v, err := value.Int64(); err == nil {
return v
} else if v, err := value.Float64(); err == nil {
return v
} else if v, err := value.Boolean(); err == nil {
return v
} else if v, err := value.String(); err == nil {
return v
}
return nil
}
// Addr returns fully qualified host:port pair for service.
//
// If host is not specified, 'localhost' is used.
func (s Service) Addr() string {
if strings.HasPrefix(s.Port, "https://") {
return fmt.Sprintf("%s%s", s.Port, ExpvarsURL)
}
// Try as port only
_, err := strconv.Atoi(s.Port)
if err == nil {
return fmt.Sprintf("http://localhost:%s%s", s.Port, ExpvarsURL)
}
host, port, err := net.SplitHostPort(s.Port)
if err == nil {
return fmt.Sprintf("http://%s:%s%s", host, port, ExpvarsURL)
}
return ""
}
// Value returns current value for the given var of this service.
//
// It also formats value, if kind is specified.
func (s Service) Value(name VarName) string {
if s.Err != nil {
return "N/A"
}
val, ok := s.stacks[name]
if !ok {
return "N/A"
}
v := val.Front()
if v == nil {
return "N/A"
}
return Format(v, name.Kind())
}
// Values returns slice of ints with recent
// values of the given var, to be used with sparkline.
func (s Service) Values(name VarName) []int {
stack, ok := s.stacks[name]
if !ok {
return nil
}
return stack.IntValues()
}
// Max returns maximum recorded value for given service and var.
func (s Service) Max(name VarName) interface{} {
val, ok := s.stacks[name]
if !ok {
return nil
}
v := val.Max
if v == nil {
return nil
}
return Format(v, name.Kind())
}