diff --git a/.gitignore b/.gitignore index cdebd88..ba1e28a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ expvarmon *.swp +.idea/ diff --git a/average.go b/average.go index 7adc0f7..53bd39c 100644 --- a/average.go +++ b/average.go @@ -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) diff --git a/expvars.go b/expvars.go index 59e23fa..90637b7 100644 --- a/expvars.go +++ b/expvars.go @@ -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? @@ -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) } diff --git a/go.mod b/go.mod index 9ae1632..ca6441d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index df539e3..833b7ac 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/main.go b/main.go index 0787d1c..9095d07 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,17 @@ 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)") @@ -18,9 +29,11 @@ var ( 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() @@ -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) } @@ -106,7 +120,7 @@ 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) @@ -114,10 +128,11 @@ func Usage() { 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) } diff --git a/self.go b/self.go index 3d7973e..cee6881 100644 --- a/self.go +++ b/self.go @@ -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. diff --git a/service.go b/service.go index b0c6177..a1a4308 100644 --- a/service.go +++ b/service.go @@ -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 @@ -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{ @@ -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 @@ -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 diff --git a/ui_multi.go b/ui_multi.go index 3390795..341644d 100644 --- a/ui_multi.go +++ b/ui_multi.go @@ -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)) } @@ -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) } @@ -200,7 +199,6 @@ func (t *TermUI) Relayout() { t.Sparkline2.Height = h t.Sparkline2.Y = th - h } - } // Close shuts down UI module. diff --git a/ui_single.go b/ui_single.go index 0d5e48a..3af537d 100644 --- a/ui_single.go +++ b/ui_single.go @@ -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 @@ -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 }() @@ -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) } diff --git a/utils.go b/utils.go index 8e4c935..075c455 100644 --- a/utils.go +++ b/utils.go @@ -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". @@ -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) @@ -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) @@ -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 == ',' }) @@ -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, ":") @@ -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)) } diff --git a/utils_test.go b/utils_test.go index f99214f..af458e0 100644 --- a/utils_test.go +++ b/utils_test.go @@ -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) diff --git a/var.go b/var.go index 8dff7ed..40b707f 100644 --- a/var.go +++ b/var.go @@ -13,7 +13,7 @@ import ( // 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:" +// It also can have optional "kind:" modifier, like "mem:" or "duration:". type VarName string // VarKind specifies special kinds of values, affects formatting. @@ -35,7 +35,7 @@ const ( // "slice of strings" is used by Jason library. // // Example: "memstats.Alloc" => []string{"memstats", "Alloc"} -// Example: "mem: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:]) @@ -62,7 +62,7 @@ func (v VarName) Long() string { return string(v)[start:] } -// Kind returns kind of variable, based on it's name modifiers ("mem:") +// 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 { @@ -87,12 +87,12 @@ func Format(v VarValue, kind VarKind) string { if _, ok := v.(int64); !ok { break } - return fmt.Sprintf("%s", byten.Size(v.(int64))) + return byten.Size(v.(int64)) case KindDuration: if _, ok := v.(int64); !ok { break } - return fmt.Sprintf("%s", roundDuration(time.Duration(v.(int64)))) + return roundDuration(time.Duration(v.(int64))).String() } if f, ok := v.(float64); ok { @@ -119,7 +119,7 @@ func roundDuration(d time.Duration) time.Duration { d = -d } if m := d % r; m+m < r { - d = d - m + d -= m } else { d = d + r - m } diff --git a/vendor/github.com/nsf/termbox-go/syscalls.go b/vendor/github.com/nsf/termbox-go/syscalls.go deleted file mode 100644 index 4f52bb9..0000000 --- a/vendor/github.com/nsf/termbox-go/syscalls.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build ignore - -package termbox - -/* -#include -#include -*/ -import "C" - -type syscall_Termios C.struct_termios - -const ( - syscall_IGNBRK = C.IGNBRK - syscall_BRKINT = C.BRKINT - syscall_PARMRK = C.PARMRK - syscall_ISTRIP = C.ISTRIP - syscall_INLCR = C.INLCR - syscall_IGNCR = C.IGNCR - syscall_ICRNL = C.ICRNL - syscall_IXON = C.IXON - syscall_OPOST = C.OPOST - syscall_ECHO = C.ECHO - syscall_ECHONL = C.ECHONL - syscall_ICANON = C.ICANON - syscall_ISIG = C.ISIG - syscall_IEXTEN = C.IEXTEN - syscall_CSIZE = C.CSIZE - syscall_PARENB = C.PARENB - syscall_CS8 = C.CS8 - syscall_VMIN = C.VMIN - syscall_VTIME = C.VTIME - - // on darwin change these to (on *bsd too?): - // C.TIOCGETA - // C.TIOCSETA - syscall_TCGETS = C.TCGETS - syscall_TCSETS = C.TCSETS -) diff --git a/vendor/modules.txt b/vendor/modules.txt index cc660fd..78e97ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,14 +1,20 @@ # github.com/antonholmquist/jason v1.0.0 +## explicit github.com/antonholmquist/jason # github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 +## explicit github.com/bsiegert/ranges # github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1 +## explicit github.com/gizak/termui # github.com/mattn/go-runewidth v0.0.4 +## explicit github.com/mattn/go-runewidth # github.com/mitchellh/go-wordwrap v1.0.0 +## explicit github.com/mitchellh/go-wordwrap # github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb github.com/nsf/termbox-go # github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d +## explicit github.com/pyk/byten