2015-04-21 12:51:01 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2015-07-10 19:29:22 +03:00
|
|
|
"net/url"
|
2015-04-25 15:54:17 +03:00
|
|
|
"strings"
|
2015-05-01 20:12:23 +03:00
|
|
|
"sync"
|
2015-05-02 01:21:52 +03:00
|
|
|
|
|
|
|
"github.com/antonholmquist/jason"
|
2015-04-21 12:51:01 +03:00
|
|
|
)
|
|
|
|
|
2015-05-09 12:40:10 -04:00
|
|
|
var (
|
2015-05-12 12:50:25 +03:00
|
|
|
// 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.
|
2015-05-09 12:40:10 -04:00
|
|
|
uptimeCounter = VarName("memstats.PauseTotalNs").ToSlice()
|
|
|
|
)
|
|
|
|
|
2015-04-21 12:51:01 +03:00
|
|
|
// Service represents constantly updating info about single service.
|
|
|
|
type Service struct {
|
2015-07-10 19:29:22 +03:00
|
|
|
URL url.URL
|
2015-04-30 23:54:54 +03:00
|
|
|
Name string
|
|
|
|
Cmdline string
|
2015-04-25 15:54:17 +03:00
|
|
|
|
2015-05-02 10:22:49 +03:00
|
|
|
stacks map[VarName]*Stack
|
2015-04-21 12:51:01 +03:00
|
|
|
|
2015-05-09 12:40:10 -04:00
|
|
|
Err error
|
|
|
|
Restarted bool
|
|
|
|
UptimeCounter int64
|
2015-04-21 12:51:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewService returns new Service object.
|
2015-07-10 19:29:22 +03:00
|
|
|
func NewService(url url.URL, vars []VarName) *Service {
|
2015-05-01 18:48:34 +03:00
|
|
|
values := make(map[VarName]*Stack)
|
2015-05-01 16:49:19 +03:00
|
|
|
for _, name := range vars {
|
2015-05-01 18:48:34 +03:00
|
|
|
values[VarName(name)] = NewStack()
|
2015-05-01 16:49:19 +03:00
|
|
|
}
|
2015-05-02 10:22:49 +03:00
|
|
|
|
2015-04-21 12:51:01 +03:00
|
|
|
return &Service{
|
2015-07-10 19:29:22 +03:00
|
|
|
Name: url.Host, // we have only port on start, so use it as name until resolved
|
|
|
|
URL: url,
|
2015-04-25 16:29:19 +03:00
|
|
|
|
2015-05-02 10:22:49 +03:00
|
|
|
stacks: values,
|
2015-04-21 12:51:01 +03:00
|
|
|
}
|
2015-04-29 00:07:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update updates Service info from Expvar variable.
|
2015-05-01 20:12:23 +03:00
|
|
|
func (s *Service) Update(wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
2015-07-10 19:29:22 +03:00
|
|
|
expvar, err := FetchExpvar(s.URL)
|
2015-05-03 15:37:52 +03:00
|
|
|
// check for restart
|
|
|
|
if s.Err != nil && err == nil {
|
|
|
|
s.Restarted = true
|
|
|
|
}
|
2015-05-01 13:20:06 +03:00
|
|
|
s.Err = err
|
2015-04-21 12:51:01 +03:00
|
|
|
|
2015-05-09 12:40:10 -04:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-05-01 19:26:28 +03:00
|
|
|
// 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)
|
|
|
|
}
|
2015-05-01 00:47:11 +03:00
|
|
|
}
|
2015-04-25 15:54:17 +03:00
|
|
|
|
2015-05-01 19:26:28 +03:00
|
|
|
// For all vars, fetch desired value from Json and push to it's own stack.
|
2015-05-02 10:22:49 +03:00
|
|
|
for name, stack := range s.stacks {
|
2015-05-02 01:21:52 +03:00
|
|
|
value, err := expvar.GetValue(name.ToSlice()...)
|
2015-05-01 16:49:19 +03:00
|
|
|
if err != nil {
|
2015-05-03 15:37:52 +03:00
|
|
|
stack.Push(nil)
|
2015-05-01 16:49:19 +03:00
|
|
|
continue
|
|
|
|
}
|
2015-05-02 01:21:52 +03:00
|
|
|
v := guessValue(value)
|
|
|
|
if v != nil {
|
|
|
|
stack.Push(v)
|
|
|
|
}
|
2015-05-01 00:47:11 +03:00
|
|
|
}
|
2015-04-30 23:54:54 +03:00
|
|
|
}
|
|
|
|
|
2015-05-02 10:22:49 +03:00
|
|
|
// guessValue attemtps to bruteforce all supported types.
|
2015-05-02 01:21:52 +03:00
|
|
|
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
|
2015-08-20 21:38:04 -04:00
|
|
|
} else if v, err := value.Array(); err == nil {
|
|
|
|
// if we get an array, calculate average
|
|
|
|
|
|
|
|
// empty array, treat as zero
|
|
|
|
if len(v) == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
avg := averageJason(v)
|
|
|
|
|
|
|
|
// cast to int64 for Int64 values
|
|
|
|
if _, err := v[0].Int64(); err == nil {
|
|
|
|
return int64(avg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return avg
|
2015-05-02 01:21:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-01 19:13:23 +03:00
|
|
|
// Value returns current value for the given var of this service.
|
2015-05-02 10:12:38 +03:00
|
|
|
//
|
|
|
|
// It also formats value, if kind is specified.
|
2015-05-01 18:48:34 +03:00
|
|
|
func (s Service) Value(name VarName) string {
|
2015-04-30 23:54:54 +03:00
|
|
|
if s.Err != nil {
|
2015-04-25 22:46:16 +03:00
|
|
|
return "N/A"
|
|
|
|
}
|
2015-05-02 10:22:49 +03:00
|
|
|
val, ok := s.stacks[name]
|
2015-04-30 23:54:54 +03:00
|
|
|
if !ok {
|
|
|
|
return "N/A"
|
|
|
|
}
|
2015-05-02 10:12:38 +03:00
|
|
|
|
|
|
|
v := val.Front()
|
|
|
|
if v == nil {
|
2015-04-30 23:54:54 +03:00
|
|
|
return "N/A"
|
|
|
|
}
|
|
|
|
|
2015-05-02 22:29:02 +03:00
|
|
|
return Format(v, name.Kind())
|
2015-04-30 23:54:54 +03:00
|
|
|
}
|
|
|
|
|
2015-05-02 01:21:52 +03:00
|
|
|
// Values returns slice of ints with recent
|
|
|
|
// values of the given var, to be used with sparkline.
|
2015-05-01 18:48:34 +03:00
|
|
|
func (s Service) Values(name VarName) []int {
|
2015-05-02 10:22:49 +03:00
|
|
|
stack, ok := s.stacks[name]
|
2015-04-30 23:54:54 +03:00
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
2015-04-25 22:46:16 +03:00
|
|
|
|
2015-05-02 01:21:52 +03:00
|
|
|
return stack.IntValues()
|
2015-04-25 22:46:16 +03:00
|
|
|
}
|
2015-05-02 22:29:02 +03:00
|
|
|
|
2015-05-03 18:03:49 +03:00
|
|
|
// Max returns maximum recorded value for given service and var.
|
2015-05-02 22:29:02 +03:00
|
|
|
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())
|
|
|
|
}
|