Fix supervisord collector (#978)
* Replace supervisord xmlrpc library * Use `github.com/mattn/go-xmlrpc` that doesn't leak goroutines. * Fix uptime metric * Use Prometheus best practices for uptime metric. * Use "start time" rather than "uptime". * Don't emit a start time if the process is down. * Add changelog entry. * Add example compatibility rules. Signed-off-by: Ben Kochie <superq@gmail.com>
This commit is contained in:
parent
2c52b8c761
commit
5d23ad0ca7
16 changed files with 477 additions and 1018 deletions
21
vendor/github.com/mattn/go-xmlrpc/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mattn/go-xmlrpc/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
48
vendor/github.com/mattn/go-xmlrpc/README.md
generated
vendored
Normal file
48
vendor/github.com/mattn/go-xmlrpc/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# go-xmlrpc
|
||||
|
||||
xmlrpc interface for go
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-xmlrpc"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
res, e := xmlrpc.Call(
|
||||
"http://your-blog.example.com/xmlrpc.php",
|
||||
"metaWeblog.getRecentPosts",
|
||||
"blog-id",
|
||||
"user-id",
|
||||
"password",
|
||||
10)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
for _, p := range res.(xmlrpc.Array) {
|
||||
for k, v := range p.(xmlrpc.Struct) {
|
||||
fmt.Printf("%s=%v\n", k, v)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-xmlrpc
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a. mattn)
|
||||
365
vendor/github.com/mattn/go-xmlrpc/xmlrpc.go
generated
vendored
Normal file
365
vendor/github.com/mattn/go-xmlrpc/xmlrpc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
package xmlrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Array []interface{}
|
||||
type Struct map[string]interface{}
|
||||
|
||||
var xmlSpecial = map[byte]string{
|
||||
'<': "<",
|
||||
'>': ">",
|
||||
'"': """,
|
||||
'\'': "'",
|
||||
'&': "&",
|
||||
}
|
||||
|
||||
func xmlEscape(s string) string {
|
||||
var b bytes.Buffer
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if s, ok := xmlSpecial[c]; ok {
|
||||
b.WriteString(s)
|
||||
} else {
|
||||
b.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type valueNode struct {
|
||||
Type string `xml:"attr"`
|
||||
Body string `xml:"chardata"`
|
||||
}
|
||||
|
||||
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
|
||||
se, e := nextStart(p)
|
||||
if e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
|
||||
var nv interface{}
|
||||
switch se.Name.Local {
|
||||
case "string":
|
||||
var s string
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
return xml.Name{}, s, nil
|
||||
case "boolean":
|
||||
var s string
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
var b bool
|
||||
switch s {
|
||||
case "true", "1":
|
||||
b = true
|
||||
case "false", "0":
|
||||
b = false
|
||||
default:
|
||||
e = errors.New("invalid boolean value")
|
||||
}
|
||||
return xml.Name{}, b, e
|
||||
case "int", "i1", "i2", "i4", "i8":
|
||||
var s string
|
||||
var i int
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
i, e = strconv.Atoi(strings.TrimSpace(s))
|
||||
return xml.Name{}, i, e
|
||||
case "double":
|
||||
var s string
|
||||
var f float64
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
f, e = strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
return xml.Name{}, f, e
|
||||
case "dateTime.iso8601":
|
||||
var s string
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
t, e := time.Parse("20060102T15:04:05", s)
|
||||
if e != nil {
|
||||
t, e = time.Parse("2006-01-02T15:04:05-07:00", s)
|
||||
if e != nil {
|
||||
t, e = time.Parse("2006-01-02T15:04:05", s)
|
||||
}
|
||||
}
|
||||
return xml.Name{}, t, e
|
||||
case "base64":
|
||||
var s string
|
||||
if e = p.DecodeElement(&s, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
if b, e := base64.StdEncoding.DecodeString(s); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
} else {
|
||||
return xml.Name{}, b, nil
|
||||
}
|
||||
case "member":
|
||||
nextStart(p)
|
||||
return next(p)
|
||||
case "value":
|
||||
nextStart(p)
|
||||
return next(p)
|
||||
case "name":
|
||||
nextStart(p)
|
||||
return next(p)
|
||||
case "struct":
|
||||
st := Struct{}
|
||||
|
||||
se, e = nextStart(p)
|
||||
for e == nil && se.Name.Local == "member" {
|
||||
// name
|
||||
se, e = nextStart(p)
|
||||
if se.Name.Local != "name" {
|
||||
return xml.Name{}, nil, errors.New("invalid response")
|
||||
}
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
var name string
|
||||
if e = p.DecodeElement(&name, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
se, e = nextStart(p)
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// value
|
||||
_, value, e := next(p)
|
||||
if se.Name.Local != "value" {
|
||||
return xml.Name{}, nil, errors.New("invalid response")
|
||||
}
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
st[name] = value
|
||||
|
||||
se, e = nextStart(p)
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return xml.Name{}, st, nil
|
||||
case "array":
|
||||
var ar Array
|
||||
nextStart(p) // data
|
||||
for {
|
||||
nextStart(p) // top of value
|
||||
_, value, e := next(p)
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
ar = append(ar, value)
|
||||
}
|
||||
return xml.Name{}, ar, nil
|
||||
case "nil":
|
||||
return xml.Name{}, nil, nil
|
||||
}
|
||||
|
||||
if e = p.DecodeElement(nv, &se); e != nil {
|
||||
return xml.Name{}, nil, e
|
||||
}
|
||||
return se.Name, nv, e
|
||||
}
|
||||
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
|
||||
for {
|
||||
t, e := p.Token()
|
||||
if e != nil {
|
||||
return xml.StartElement{}, e
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case xml.StartElement:
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func toXml(v interface{}, typ bool) (s string) {
|
||||
if v == nil {
|
||||
return "<nil/>"
|
||||
}
|
||||
r := reflect.ValueOf(v)
|
||||
t := r.Type()
|
||||
k := t.Kind()
|
||||
|
||||
if b, ok := v.([]byte); ok {
|
||||
return "<base64>" + base64.StdEncoding.EncodeToString(b) + "</base64>"
|
||||
}
|
||||
|
||||
switch k {
|
||||
case reflect.Invalid:
|
||||
panic("unsupported type")
|
||||
case reflect.Bool:
|
||||
return fmt.Sprintf("<boolean>%v</boolean>", v)
|
||||
case reflect.Int,
|
||||
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if typ {
|
||||
return fmt.Sprintf("<int>%v</int>", v)
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
case reflect.Uintptr:
|
||||
panic("unsupported type")
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if typ {
|
||||
return fmt.Sprintf("<double>%v</double>", v)
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
panic("unsupported type")
|
||||
case reflect.Array:
|
||||
s = "<array><data>"
|
||||
for n := 0; n < r.Len(); n++ {
|
||||
s += "<value>"
|
||||
s += toXml(r.Index(n).Interface(), typ)
|
||||
s += "</value>"
|
||||
}
|
||||
s += "</data></array>"
|
||||
return s
|
||||
case reflect.Chan:
|
||||
panic("unsupported type")
|
||||
case reflect.Func:
|
||||
panic("unsupported type")
|
||||
case reflect.Interface:
|
||||
return toXml(r.Elem(), typ)
|
||||
case reflect.Map:
|
||||
s = "<struct>"
|
||||
for _, key := range r.MapKeys() {
|
||||
s += "<member>"
|
||||
s += "<name>" + xmlEscape(key.Interface().(string)) + "</name>"
|
||||
s += "<value>" + toXml(r.MapIndex(key).Interface(), typ) + "</value>"
|
||||
s += "</member>"
|
||||
}
|
||||
s += "</struct>"
|
||||
return s
|
||||
case reflect.Ptr:
|
||||
panic("unsupported type")
|
||||
case reflect.Slice:
|
||||
s = "<array><data>"
|
||||
for n := 0; n < r.Len(); n++ {
|
||||
s += "<value>"
|
||||
s += toXml(r.Index(n).Interface(), typ)
|
||||
s += "</value>"
|
||||
}
|
||||
s += "</data></array>"
|
||||
return s
|
||||
case reflect.String:
|
||||
if typ {
|
||||
return fmt.Sprintf("<string>%v</string>", xmlEscape(v.(string)))
|
||||
}
|
||||
return xmlEscape(v.(string))
|
||||
case reflect.Struct:
|
||||
s = "<struct>"
|
||||
for n := 0; n < r.NumField(); n++ {
|
||||
s += "<member>"
|
||||
s += "<name>" + t.Field(n).Name + "</name>"
|
||||
s += "<value>" + toXml(r.FieldByIndex([]int{n}).Interface(), true) + "</value>"
|
||||
s += "</member>"
|
||||
}
|
||||
s += "</struct>"
|
||||
return s
|
||||
case reflect.UnsafePointer:
|
||||
return toXml(r.Elem(), typ)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Client is client of XMLRPC
|
||||
type Client struct {
|
||||
HttpClient *http.Client
|
||||
url string
|
||||
}
|
||||
|
||||
// NewClient create new Client
|
||||
func NewClient(url string) *Client {
|
||||
return &Client{
|
||||
HttpClient: &http.Client{Transport: http.DefaultTransport, Timeout: 10 * time.Second},
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
||||
func makeRequest(name string, args ...interface{}) *bytes.Buffer {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(`<?xml version="1.0"?><methodCall>`)
|
||||
buf.WriteString("<methodName>" + xmlEscape(name) + "</methodName>")
|
||||
buf.WriteString("<params>")
|
||||
for _, arg := range args {
|
||||
buf.WriteString("<param><value>")
|
||||
buf.WriteString(toXml(arg, true))
|
||||
buf.WriteString("</value></param>")
|
||||
}
|
||||
buf.WriteString("</params></methodCall>")
|
||||
return buf
|
||||
}
|
||||
|
||||
func call(client *http.Client, url, name string, args ...interface{}) (v interface{}, e error) {
|
||||
r, e := httpClient.Post(url, "text/xml", makeRequest(name, args...))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
// Since we do not always read the entire body, discard the rest, which
|
||||
// allows the http transport to reuse the connection.
|
||||
defer io.Copy(ioutil.Discard, r.Body)
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode/100 != 2 {
|
||||
return nil, errors.New(http.StatusText(http.StatusBadRequest))
|
||||
}
|
||||
|
||||
p := xml.NewDecoder(r.Body)
|
||||
se, e := nextStart(p) // methodResponse
|
||||
if se.Name.Local != "methodResponse" {
|
||||
return nil, errors.New("invalid response: missing methodResponse")
|
||||
}
|
||||
se, e = nextStart(p) // params
|
||||
if se.Name.Local != "params" {
|
||||
return nil, errors.New("invalid response: missing params")
|
||||
}
|
||||
se, e = nextStart(p) // param
|
||||
if se.Name.Local != "param" {
|
||||
return nil, errors.New("invalid response: missing param")
|
||||
}
|
||||
se, e = nextStart(p) // value
|
||||
if se.Name.Local != "value" {
|
||||
return nil, errors.New("invalid response: missing value")
|
||||
}
|
||||
_, v, e = next(p)
|
||||
return v, e
|
||||
}
|
||||
|
||||
// Call call remote procedures function name with args
|
||||
func (c *Client) Call(name string, args ...interface{}) (v interface{}, e error) {
|
||||
return call(c.HttpClient, c.url, name, args...)
|
||||
}
|
||||
|
||||
// Global httpClient allows us to pool/reuse connections and not wastefully
|
||||
// re-create transports for each request.
|
||||
var httpClient = &http.Client{Transport: http.DefaultTransport, Timeout: 10 * time.Second}
|
||||
|
||||
// Call call remote procedures function name with args
|
||||
func Call(url, name string, args ...interface{}) (v interface{}, e error) {
|
||||
return call(httpClient, url, name, args...)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue