Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
expvarmon
*.swp
.idea/
2 changes: 1 addition & 1 deletion average.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

func averageJason(array []*jason.Value) float64 {
var arr []float64
arr := make([]float64, 0, len(array))
for _, v := range array {
val, _ := v.Float64()
arr = append(arr, val)
Expand Down
8 changes: 6 additions & 2 deletions expvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func getBasicAuthEnv() (user, password string) {
return os.Getenv("HTTP_USER"), os.Getenv("HTTP_PASSWORD")
}

// FetchExpvar fetches expvar by http for the given addr (host:port)
func FetchExpvar(u url.URL) (*Expvar, error) {
// FetchExpvar fetches expvar by http for the given addr (host:port).
func FetchExpvar(u url.URL, headers map[string]string) (*Expvar, error) {
e := &Expvar{&jason.Object{}}
client := &http.Client{
Timeout: 1 * time.Second, // TODO: make it configurable or left default?
Expand All @@ -34,6 +34,10 @@ func FetchExpvar(u url.URL) (*Expvar, error) {
req.URL = &u
req.Host = u.Host

for k, v := range headers {
req.Header.Add(k, v)
}

if user, pass := getBasicAuthEnv(); user != "" && pass != "" {
req.SetBasicAuth(user, pass)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/divan/expvarmon

go 1.15

require (
github.com/antonholmquist/jason v1.0.0
github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63
Expand Down
10 changes: 3 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ github.com/antonholmquist/jason v1.0.0 h1:Ytg94Bcf1Bfi965K2q0s22mig/n4eGqEij/atE
github.com/antonholmquist/jason v1.0.0/go.mod h1:+GxMEKI0Va2U8h3os6oiUAetHAlGMvxjdpAH/9uvUMA=
github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 h1:FxdkNGQyRwwk94rJ+IMNrTjv864XzT93/cvk7UpMM38=
github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63/go.mod h1:8z71/aZjDHLs4ihK/5nD5wZVQxm/W4eRDnxQZcJmVD4=
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM=
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1 h1:TCe+sQ3zOPa8KNePfjrb5Pix5FPa44WsiiCzsYv0Yts=
github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1/go.mod h1:W48Llfv3G9PIK3TQjqynxzC9mP4HUcAn/6vlDWekq9k=
github.com/gizak/termui v0.0.0-20190202061602-5ece2b7178ff h1:md8xjZ5dOJsdTnDXsnbu2W9WLBpZ3yh8ebRad+i2bQY=
github.com/gizak/termui v0.0.0-20190202061602-5ece2b7178ff/go.mod h1:TISPFiAGKVhyNxElRxDknJ+HzcUyO7sAJCsc8dfAe+M=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
Expand All @@ -17,11 +14,10 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d h1:/0nqYrqVyPTVDT2jPx9z9BZ+ItJWlWA9VITUGvNaZkI=
github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d/go.mod h1:El8LdwAxb76Ih0mRmpu9uMVq+ajiqGSxmYiUe+nSr+w=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
golang.org/x/net v0.0.0-20180801234040-f4c29de78a2a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
19 changes: 17 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,29 @@ import (
"github.com/gizak/termui"
)

type arrayFlags []string

func (i *arrayFlags) String() string {
return "list of custom headers to pass to the endpoint"
}

func (i *arrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
}

var (
interval = flag.Duration("i", 5*time.Second, "Polling interval")
urls = flag.String("ports", "", "Ports/URLs for accessing services expvars (start-end,port2,port3,https://host:port)")
varsArg = flag.String("vars", "mem:memstats.Alloc,mem:memstats.Sys,mem:memstats.HeapAlloc,mem:memstats.HeapInuse,duration:memstats.PauseNs,duration:memstats.PauseTotalNs", "Vars to monitor (comma-separated)")
dummy = flag.Bool("dummy", false, "Use dummy (console) output")
self = flag.Bool("self", false, "Monitor itself")
endpoint = flag.String("endpoint", DefaultEndpoint, "URL endpoint for expvars")
headers arrayFlags
)

func main() {
flag.Var(&headers, "headers", "Custom headers to pass onto application")
flag.Usage = Usage
flag.Parse()

Expand Down Expand Up @@ -55,6 +68,7 @@ func main() {
data := NewUIData(vars)
for _, port := range ports {
service := NewService(port, vars)
service.CustomHeaders = ParseHeaders(headers)
data.Services = append(data.Services, service)
}

Expand Down Expand Up @@ -106,18 +120,19 @@ func UpdateAll(ui UI, data *UIData) {
ui.Update(*data)
}

// Usage reimplements flag.Usage
// Usage reimplements flag.Usage.
func Usage() {
progname := os.Args[0]
fmt.Fprintf(os.Stderr, "Usage of %s:\n", progname)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
Examples:
%s -ports="80"
%s -headers="X-Custom-Header: Foo" -headers="X-Another-Header: Bar" -ports="80"
%s -ports="23000-23010,http://example.com:80-81" -i=1m
%s -ports="80,remoteapp:80" -vars="mem:memstats.Alloc,duration:Response.Mean,Counter"
%s -ports="1234-1236" -vars="Goroutines" -self

For more details and docs, see README: http://github.com/divan/expvarmon
`, progname, progname, progname, progname)
`, progname, progname, progname, progname, progname)
}
2 changes: 1 addition & 1 deletion self.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func uptime() interface{} {
return int64(uptime)
}

// startPort defines lower port for bind
// startPort defines lower port for bind.
const startPort = 32768

// StartSelfMonitor starts http server on random port and exports expvars.
Expand Down
25 changes: 12 additions & 13 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@ import (
"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()
)
// 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.
var uptimeCounter = VarName("memstats.PauseTotalNs").ToSlice()

// Service represents constantly updating info about single service.
type Service struct {
URL url.URL
Name string
Cmdline string
URL url.URL
CustomHeaders map[string]string
Name string
Cmdline string

stacks map[VarName]*Stack

Expand All @@ -33,7 +32,7 @@ type Service struct {
func NewService(url url.URL, vars []VarName) *Service {
values := make(map[VarName]*Stack)
for _, name := range vars {
values[VarName(name)] = NewStack()
values[name] = NewStack()
}

return &Service{
Expand All @@ -47,7 +46,7 @@ func NewService(url url.URL, vars []VarName) *Service {
// Update updates Service info from Expvar variable.
func (s *Service) Update(wg *sync.WaitGroup) {
defer wg.Done()
expvar, err := FetchExpvar(s.URL)
expvar, err := FetchExpvar(s.URL, s.CustomHeaders)
// check for restart
if s.Err != nil && err == nil {
s.Restarted = true
Expand Down Expand Up @@ -91,7 +90,7 @@ func (s *Service) Update(wg *sync.WaitGroup) {
}
}

// guessValue attemtps to bruteforce all supported types.
// guessValue attempts to bruteforce all supported types.
func guessValue(value *jason.Value) interface{} {
if v, err := value.Int64(); err == nil {
return v
Expand Down
6 changes: 2 additions & 4 deletions ui_multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (t *TermUI) Update(data UIData) {
t.Status.Text = fmt.Sprintf("Last update: %v", data.LastTimestamp.Format(time.Stamp))

// List with service names
var services []string
services := make([]string, 0, len(data.Services))
for _, service := range data.Services {
services = append(services, StatusLine(service))
}
Expand Down Expand Up @@ -128,8 +128,7 @@ func (t *TermUI) Update(data UIData) {

t.Relayout()

var widgets []termui.Bufferer
widgets = append(widgets, t.Title, t.Status, t.Services, t.Sparkline1)
widgets := []termui.Bufferer{t.Title, t.Status, t.Services, t.Sparkline1}
for _, list := range t.Lists {
widgets = append(widgets, list)
}
Expand Down Expand Up @@ -200,7 +199,6 @@ func (t *TermUI) Relayout() {
t.Sparkline2.Height = h
t.Sparkline2.Y = th - h
}

}

// Close shuts down UI module.
Expand Down
7 changes: 3 additions & 4 deletions ui_single.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (t *TermUISingle) Init(data UIData) error {
t.Pars[i] = par
}

var sparklines []termui.Sparkline
sparklines := make([]termui.Sparkline, 0, len(data.Vars))
for _, name := range data.Vars {
spl := termui.NewSparkline()
spl.Height = 1
Expand All @@ -69,7 +69,7 @@ func (t *TermUISingle) Init(data UIData) error {
s := termui.NewSparklines(sparklines...)
s.Height = 2*len(sparklines) + 2
s.Border = true
s.BorderLabel = fmt.Sprintf("Monitoring")
s.BorderLabel = "Monitoring"
return s
}()

Expand Down Expand Up @@ -108,8 +108,7 @@ func (t *TermUISingle) Update(data UIData) {

t.Relayout()

var widgets []termui.Bufferer
widgets = append(widgets, t.Title, t.Status, t.Sparkline)
widgets := []termui.Bufferer{t.Title, t.Status, t.Sparkline}
for _, par := range t.Pars {
widgets = append(widgets, par)
}
Expand Down
29 changes: 22 additions & 7 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,29 @@ func ParseVars(vars string) ([]VarName, error) {
}

ss := strings.FieldsFunc(vars, func(r rune) bool { return r == ',' })
var ret []VarName
ret := make([]VarName, 0, len(ss))
for _, s := range ss {
ret = append(ret, VarName(s))
}
return ret, nil
}

func ParseHeaders(headers []string) map[string]string {
ret := make(map[string]string, len(headers))
for _, header := range headers {
tokens := strings.Split(header, ":")
if len(tokens) < 2 {
continue
}

k := strings.TrimSpace(tokens[0])
v := strings.TrimSpace(strings.Join(tokens[1:], ":"))

ret[k] = v
}
return ret
}

// BaseCommand returns cleaned command name from Cmdline array.
//
// I.e. "./some.service/binary.name -arg 1 -arg" will be "binary.name".
Expand All @@ -41,8 +57,6 @@ func BaseCommand(cmdline []string) string {
//
// Note, rawurl shouldn't contain port, as port will be appended.
func flattenURLs(rawurl string, ports []string) ([]url.URL, error) {
var urls []url.URL

// Add http by default
if !strings.HasPrefix(rawurl, "http") {
rawurl = fmt.Sprintf("http://%s", rawurl)
Expand All @@ -58,6 +72,7 @@ func flattenURLs(rawurl string, ports []string) ([]url.URL, error) {
}

// Create new URL for each port
urls := make([]url.URL, 0, len(ports))
for _, port := range ports {
u := *baseURL
u.Host = fmt.Sprintf("%s:%s", u.Host, port)
Expand All @@ -66,7 +81,7 @@ func flattenURLs(rawurl string, ports []string) ([]url.URL, error) {
return urls, nil
}

// ParsePorts parses and flattens comma-separated ports/urls into URLs slice
// ParsePorts parses and flattens comma-separated ports/urls into URLs slice.
func ParsePorts(s string) ([]url.URL, error) {
var urls []url.URL
fields := strings.FieldsFunc(s, func(r rune) bool { return r == ',' })
Expand All @@ -93,7 +108,7 @@ func ParsePorts(s string) ([]url.URL, error) {
// for the single port and range of ports to parse.
//
// i.e. "http://name:1234-1236/_endpoint" would return "http://name/_endpoint" and
// "1234-1236"
// "1234-1236".
func extractURLAndPorts(s string) (string, string) {
var rawurl, ports string
parts := strings.Split(s, ":")
Expand Down Expand Up @@ -125,14 +140,14 @@ func extractURLAndPorts(s string) (string, string) {
return rawurl, ports
}

// parseRange flattens port ranges, such as "1234-1240,1333"
// parseRange flattens port ranges, such as "1234-1240,1333".
func parseRange(s string) ([]string, error) {
portsInt, err := ranges.Parse(s)
if err != nil {
return nil, err
}

var ports []string
ports := make([]string, 0, len(portsInt))
for _, port := range portsInt {
ports = append(ports, fmt.Sprintf("%d", port))
}
Expand Down
26 changes: 26 additions & 0 deletions utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ func TestExtractUrlAndPorts(t *testing.T) {
}
}

func TestParseHeaders(t *testing.T) {
hdrs := []string{
"X-MyCustom-Auth: abcd12345",
"Accept-encoding: application/json",
"X-Multi-Colon: foo:bar",
"X-Spaces-Trimmed: foobar",
}

parsedHeaders := ParseHeaders(hdrs)
if parsedHeaders["X-MyCustom-Auth"] != "abcd12345" {
t.Fatalf("Header parsing failed: %v\n", parsedHeaders)
}

if parsedHeaders["Accept-encoding"] != "application/json" {
t.Fatalf("Header parsing failed: %v\n", parsedHeaders)
}

if parsedHeaders["X-Multi-Colon"] != "foo:bar" {
t.Fatalf("Header parsing failed: %v\n", parsedHeaders)
}

if parsedHeaders["X-Spaces-Trimmed"] != "foobar" {
t.Fatalf("Header parsing failed: %v\n", parsedHeaders)
}
}

func TestPorts(t *testing.T) {
arg := "1234,1235"
ports, err := ParsePorts(arg)
Expand Down
Loading