pax_global_header00006660000000000000000000000064125445114560014521gustar00rootroot0000000000000052 comment=4439385f7574c8b90bf05f2f0017816dfcc1f540 golang-collectd-0.0~git20150630/000077500000000000000000000000001254451145600161415ustar00rootroot00000000000000golang-collectd-0.0~git20150630/.gitignore000066400000000000000000000004121254451145600201260ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof golang-collectd-0.0~git20150630/LICENSE000066400000000000000000000013771254451145600171560ustar00rootroot00000000000000Copyright (c) 2014 Kimo Rosenbaum Copyright (c) 2015 Florian Forster Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. golang-collectd-0.0~git20150630/README.md000066400000000000000000000035101254451145600174170ustar00rootroot00000000000000# go-collectd Utilities for using [collectd](https://collectd.org/) together with [Go](http://golang.org/). # Synopsis package main import ( "time" "collectd.org/api" "collectd.org/exec" ) func main() { vl := api.ValueList{ Identifier: api.Identifier{ Host: exec.Hostname(), Plugin: "golang", Type: "gauge", }, Time: time.Now(), Interval: exec.Interval(), Values: []api.Value{api.Gauge(42)}, } exec.Putval.Write(vl) } # Description This is a very simple package and very much a *Work in Progress*, so expect things to move around and be renamed a lot. The reposiroty is organized as follows: * Package `collectd.org/api` declares data structures you may already know from the *collectd* source code itself, such as `ValueList`. * Package `collectd.org/exec` declares some utilities for writing binaries to be executed with the *exec plugin*. It provides some utilities (getting the hostname, e.g.) and an executor which you may use to easily schedule function calls. * Package `collectd.org/format` declares functions for formatting *ValueLists* in other format. Right now, only `PUTVAL` is implemented. Eventually I plan to add parsers for some formats, such as the JSON export. * Package `collectd.org/network` implements collectd's [binary network protocol](https://collectd.org/wiki/index.php/Binary_protocol). It offers client and server implementations, see `network.Client` and `network.ListenAndWrite()` for more detials. # Install To use this package in your own programs, simply use `go get` to fetch the packages you need, for example: go get collectd.org/api # Author Florian "octo" Forster <ff at octo.it> golang-collectd-0.0~git20150630/api/000077500000000000000000000000001254451145600167125ustar00rootroot00000000000000golang-collectd-0.0~git20150630/api/json.go000066400000000000000000000063301254451145600202140ustar00rootroot00000000000000package api import ( "encoding/json" "fmt" "collectd.org/cdtime" ) // jsonValueList represents the format used by collectd's JSON export. type jsonValueList struct { Values []json.Number `json:"values"` DSTypes []string `json:"dstypes"` DSNames []string `json:"dsnames,omitempty"` Time cdtime.Time `json:"time"` Interval cdtime.Time `json:"interval"` Host string `json:"host"` Plugin string `json:"plugin"` PluginInstance string `json:"plugin_instance,omitempty"` Type string `json:"type"` TypeInstance string `json:"type_instance,omitempty"` } // MarshalJSON implements the "encoding/json".Marshaler interface for // ValueList. func (vl ValueList) MarshalJSON() ([]byte, error) { jvl := jsonValueList{ Values: make([]json.Number, len(vl.Values)), DSTypes: make([]string, len(vl.Values)), DSNames: make([]string, len(vl.Values)), Time: cdtime.New(vl.Time), Interval: cdtime.NewDuration(vl.Interval), Host: vl.Host, Plugin: vl.Plugin, PluginInstance: vl.PluginInstance, Type: vl.Type, TypeInstance: vl.TypeInstance, } for i, v := range vl.Values { switch v := v.(type) { case Gauge: jvl.Values[i] = json.Number(fmt.Sprintf("%.15g", v)) case Derive: jvl.Values[i] = json.Number(fmt.Sprintf("%d", v)) case Counter: jvl.Values[i] = json.Number(fmt.Sprintf("%d", v)) default: return nil, fmt.Errorf("unexpected data source type: %T", v) } jvl.DSTypes[i] = v.Type() jvl.DSNames[i] = vl.DSName(i) } return json.Marshal(jvl) } // UnmarshalJSON implements the "encoding/json".Unmarshaler interface for // ValueList. // // Please note that this function is currently not compatible with write_http's // "StoreRates" setting: if enabled, write_http converts derives and counters // to a rate (a floating point number), but still puts "derive" or "counter" in // the "dstypes" array. UnmarshalJSON will try to parse such values as // integers, which will fail in many cases. func (vl *ValueList) UnmarshalJSON(data []byte) error { var jvl jsonValueList if err := json.Unmarshal(data, &jvl); err != nil { return err } vl.Host = jvl.Host vl.Plugin = jvl.Plugin vl.PluginInstance = jvl.PluginInstance vl.Type = jvl.Type vl.TypeInstance = jvl.TypeInstance vl.Time = jvl.Time.Time() vl.Interval = jvl.Interval.Duration() vl.Values = make([]Value, len(jvl.Values)) if len(jvl.Values) != len(jvl.DSTypes) { return fmt.Errorf("invalid data: %d value(s), %d data source type(s)", len(jvl.Values), len(jvl.DSTypes)) } for i, n := range jvl.Values { switch jvl.DSTypes[i] { case "gauge": v, err := n.Float64() if err != nil { return err } vl.Values[i] = Gauge(v) case "derive": v, err := n.Int64() if err != nil { return err } vl.Values[i] = Derive(v) case "counter": v, err := n.Int64() if err != nil { return err } vl.Values[i] = Counter(v) default: return fmt.Errorf("unexpected data source type: %q", jvl.DSTypes[i]) } } if len(jvl.DSNames) >= len(vl.Values) { vl.DSNames = make([]string, len(vl.Values)) copy(vl.DSNames, jvl.DSNames) } return nil } golang-collectd-0.0~git20150630/api/json_test.go000066400000000000000000000034671254451145600212630ustar00rootroot00000000000000package api import ( "encoding/json" "io/ioutil" "log" "net/http" "reflect" "testing" "time" ) func TestValueList(t *testing.T) { vlWant := ValueList{ Identifier: Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, Time: time.Unix(1426585562, 999000000), Interval: 10 * time.Second, Values: []Value{Gauge(42)}, DSNames: []string{"legacy"}, } want := `{"values":[42],"dstypes":["gauge"],"dsnames":["legacy"],"time":1426585562.999,"interval":10.000,"host":"example.com","plugin":"golang","type":"gauge"}` got, err := vlWant.MarshalJSON() if err != nil || string(got) != want { t.Errorf("got (%s, %v), want (%s, nil)", got, err, want) } var vlGot ValueList if err := vlGot.UnmarshalJSON([]byte(want)); err != nil { t.Errorf("got %v, want nil)", err) } // Conversion to float64 and back takes its toll -- the conversion is // very accurate, but not bit-perfect. vlGot.Time = vlGot.Time.Round(time.Millisecond) if !reflect.DeepEqual(vlWant, vlGot) { t.Errorf("got %#v, want %#v)", vlGot, vlWant) } } func ExampleValueList_UnmarshalJSON() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { data, err := ioutil.ReadAll(r.Body) if err != nil { log.Printf("while reading body: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } var vls []ValueList if err := json.Unmarshal(data, &vls); err != nil { log.Printf("while parsing JSON: %v", err) http.Error(w, err.Error(), http.StatusBadRequest) return } for _, vl := range vls { var w Writer w.Write(vl) // "w" is a placeholder to avoid cyclic dependencies. // In real live, you'd do something like this here: // exec.Putval.Write(vl) } w.WriteHeader(http.StatusNoContent) }) log.Fatal(http.ListenAndServe(":8080", nil)) } golang-collectd-0.0~git20150630/api/main.go000066400000000000000000000101251254451145600201640ustar00rootroot00000000000000// Package api defines data types representing core collectd data types. package api // import "collectd.org/api" import ( "fmt" "log" "strconv" "strings" "time" ) // Value represents either a Gauge or a Derive. It is Go's equivalent to the C // union value_t. If a function accepts a Value, you may pass in either a Gauge // or a Derive. Passing in any other type may or may not panic. type Value interface { Type() string } // Gauge represents a gauge metric value, such as a temperature. // This is Go's equivalent to the C type "gauge_t". type Gauge float64 // Type returns "gauge". func (v Gauge) Type() string { return "gauge" } // Derive represents a counter metric value, such as bytes sent over the // network. When the counter wraps around (overflows) or is reset, this is // interpreted as a (huge) negative rate, which is discarded. // This is Go's equivalent to the C type "derive_t". type Derive int64 // Type returns "derive". func (v Derive) Type() string { return "derive" } // Counter represents a counter metric value, such as bytes sent over the // network. When a counter value is smaller than the previous value, a wrap // around (overflow) is assumed. This causes huge spikes in case a counter is // reset. Only use Counter for very specific cases. If in doubt, use Derive // instead. // This is Go's equivalent to the C type "counter_t". type Counter uint64 // Type returns "counter". func (v Counter) Type() string { return "counter" } // Identifier identifies one metric. type Identifier struct { Host string Plugin, PluginInstance string Type, TypeInstance string } // ParseIdentifier parses the identifier encoded in s and returns it. func ParseIdentifier(s string) (Identifier, error) { fields := strings.Split(s, "/") if len(fields) != 3 { return Identifier{}, fmt.Errorf("not a valid identifier: %q", s) } id := Identifier{ Host: fields[0], Plugin: fields[1], Type: fields[2], } if i := strings.Index(id.Plugin, "-"); i != -1 { id.PluginInstance = id.Plugin[i+1:] id.Plugin = id.Plugin[:i] } if i := strings.Index(id.Type, "-"); i != -1 { id.TypeInstance = id.Type[i+1:] id.Type = id.Type[:i] } return id, nil } // ValueList represents one (set of) data point(s) of one metric. It is Go's // equivalent of the C type value_list_t. type ValueList struct { Identifier Time time.Time Interval time.Duration Values []Value DSNames []string } // DSName returns the name of the data source at the given index. If vl.DSNames // is nil, returns "value" if there is a single value and a string // representation of index otherwise. func (vl ValueList) DSName(index int) string { if vl.DSNames != nil { return vl.DSNames[index] } else if len(vl.Values) != 1 { return strconv.FormatInt(int64(index), 10) } return "value" } // Writer are objects accepting a ValueList for writing, for example to the // network. type Writer interface { Write(vl ValueList) error } // String returns a string representation of the Identifier. func (id Identifier) String() string { str := id.Host + "/" + id.Plugin if id.PluginInstance != "" { str += "-" + id.PluginInstance } str += "/" + id.Type if id.TypeInstance != "" { str += "-" + id.TypeInstance } return str } // Dispatcher implements a multiplexer for Writer, i.e. each ValueList // written to it is copied and written to each registered Writer. type Dispatcher struct { writers []Writer } // Add adds a Writer to the Dispatcher. func (d *Dispatcher) Add(w Writer) { d.writers = append(d.writers, w) } // Len returns the number of Writers belonging to the Dispatcher. func (d *Dispatcher) Len() int { return len(d.writers) } // Write starts a new Goroutine for each Writer which creates a copy of the // ValueList and then calls the Writer with the copy. It returns nil // immediately. func (d *Dispatcher) Write(vl ValueList) error { for _, w := range d.writers { go func(w Writer) { vlCopy := vl vlCopy.Values = make([]Value, len(vl.Values)) copy(vlCopy.Values, vl.Values) if err := w.Write(vlCopy); err != nil { log.Printf("%T.Write(): %v", w, err) } }(w) } return nil } golang-collectd-0.0~git20150630/api/main_test.go000066400000000000000000000034441254451145600212310ustar00rootroot00000000000000package api // import "collectd.org/api" import ( "testing" ) func TestParseIdentifier(t *testing.T) { cases := []struct { Input string Want Identifier }{ { Input: "example.com/golang/gauge", Want: Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, }, { Input: "example.com/golang-foo/gauge-bar", Want: Identifier{ Host: "example.com", Plugin: "golang", PluginInstance: "foo", Type: "gauge", TypeInstance: "bar", }, }, { Input: "example.com/golang-a-b/gauge-b-c", Want: Identifier{ Host: "example.com", Plugin: "golang", PluginInstance: "a-b", Type: "gauge", TypeInstance: "b-c", }, }, } for i, c := range cases { if got, err := ParseIdentifier(c.Input); got != c.Want || err != nil { t.Errorf("case %d: got (%v, %v), want (%v, %v)", i, got, err, c.Want, nil) } } failures := []string{ "example.com/golang", "example.com/golang/gauge/extra", } for _, c := range failures { if got, err := ParseIdentifier(c); err == nil { t.Errorf("got (%v, %v), want (%v, !%v)", got, err, Identifier{}, nil) } } } func TestIdentifierString(t *testing.T) { id := Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", } cases := []struct { PluginInstance, TypeInstance string Want string }{ {"", "", "example.com/golang/gauge"}, {"foo", "", "example.com/golang-foo/gauge"}, {"", "foo", "example.com/golang/gauge-foo"}, {"foo", "bar", "example.com/golang-foo/gauge-bar"}, } for _, c := range cases { id.PluginInstance = c.PluginInstance id.TypeInstance = c.TypeInstance got := id.String() if got != c.Want { t.Errorf("got %q, want %q", got, c.Want) } } } golang-collectd-0.0~git20150630/cdtime/000077500000000000000000000000001254451145600174065ustar00rootroot00000000000000golang-collectd-0.0~git20150630/cdtime/cdtime.go000066400000000000000000000043471254451145600212120ustar00rootroot00000000000000/* Package cdtime implements methods to convert from and to collectd's internal time representation, cdtime_t. */ package cdtime // import "collectd.org/cdtime" import ( "strconv" "time" ) // Time represens a time in collectd's internal representation. type Time uint64 // New returns a new Time representing time t. func New(t time.Time) Time { return newNano(uint64(t.UnixNano())) } // NewDuration returns a new Time representing duration d. func NewDuration(d time.Duration) Time { return newNano(uint64(d.Nanoseconds())) } // Time converts and returns the time as time.Time. func (t Time) Time() time.Time { s, ns := t.decompose() return time.Unix(s, ns) } // Duration converts and returns the duration as time.Duration. func (t Time) Duration() time.Duration { s, ns := t.decompose() return time.Duration(1000000000*s+ns) * time.Nanosecond } // String returns the string representation of Time. The format used is seconds // since the epoch with millisecond precision, e.g. "1426588900.328". func (t Time) String() string { f := t.Float() return strconv.FormatFloat(f /* format */, 'f' /* precision */, 3 /* bits */, 64) } // Float returns the time as seocnds since epoch. This is a lossy conversion, // which will lose up to 11 bits. This means that the returned value should be // considered to have roughly microsecond precision. func (t Time) Float() float64 { s, ns := t.decompose() return float64(s) + float64(ns)/1000000000.0 } // MarshalJSON implements the "encoding/json".Marshaler interface for Time. func (t Time) MarshalJSON() ([]byte, error) { return []byte(t.String()), nil } // UnmarshalJSON implements the "encoding/json".Unmarshaler interface for Time. func (t *Time) UnmarshalJSON(data []byte) error { f, err := strconv.ParseFloat(string(data) /* bits */, 64) if err != nil { return err } s := uint64(f) ns := uint64((f - float64(s)) * 1000000000.0) *t = newNano(1000000000*s + ns) return nil } func (t Time) decompose() (s, ns int64) { s = int64(t >> 30) ns = (int64(t&0x3fffffff) * 1000000000) >> 30 return } func newNano(ns uint64) Time { // break into seconds and nano-seconds so the left-shift doesn't overflow. s := ns / 1000000000 ns = ns % 1000000000 return Time((s << 30) | ((ns << 30) / 1000000000)) } golang-collectd-0.0~git20150630/exec/000077500000000000000000000000001254451145600170655ustar00rootroot00000000000000golang-collectd-0.0~git20150630/exec/exec.go000066400000000000000000000074401254451145600203450ustar00rootroot00000000000000// Package exec implements tools to write plugins for collectd's "exec plugin" // in Go. package exec // import "collectd.org/exec" import ( "log" "os" "strconv" "sync" "time" "collectd.org/api" "collectd.org/format" ) // Putval is the dispatcher used by the exec package to print ValueLists. var Putval = format.NewPutval(os.Stdout) type valueCallback struct { callback func() api.Value vl api.ValueList done chan bool } type voidCallback struct { callback func(time.Duration) interval time.Duration done chan bool } type callback interface { run(*sync.WaitGroup) stop() } // Executor holds one or more callbacks which are called periodically. type Executor struct { cb []callback group sync.WaitGroup } // NewExecutor returns a pointer to a new Executor object. func NewExecutor() *Executor { return &Executor{ group: sync.WaitGroup{}, } } // ValueCallback adds a simple "value" callback to the Executor. The callback // only returns a Number, i.e. either a api.Gauge or api.Derive, and formatting // and printing is done by the executor. func (e *Executor) ValueCallback(callback func() api.Value, vl api.ValueList) { e.cb = append(e.cb, valueCallback{ callback: callback, vl: vl, done: make(chan bool), }) } // VoidCallback adds a "complex" callback to the Executor. While the functions // prototype is simpler, all the work has to be done by the callback, i.e. the // callback needs to format and print the appropriate lines to "STDOUT". // However, this allows cases in which the number of values reported varies, // e.g. depending on the system the code is running on. func (e *Executor) VoidCallback(callback func(time.Duration), interval time.Duration) { e.cb = append(e.cb, voidCallback{ callback: callback, interval: interval, done: make(chan bool), }) } // Run starts calling all callbacks periodically and blocks. func (e *Executor) Run() { for _, cb := range e.cb { e.group.Add(1) go cb.run(&e.group) } e.group.Wait() } // Stop sends a signal to all callbacks to exit and returns. This unblocks // "Run()" but does not block itself. func (e *Executor) Stop() { for _, cb := range e.cb { cb.stop() } } func (cb valueCallback) run(g *sync.WaitGroup) { if cb.vl.Host == "" { cb.vl.Host = Hostname() } cb.vl.Interval = sanitizeInterval(cb.vl.Interval) cb.vl.Values = make([]api.Value, 1) ticker := time.NewTicker(cb.vl.Interval) for { select { case _ = <-ticker.C: cb.vl.Values[0] = cb.callback() cb.vl.Time = time.Now() Putval.Write(cb.vl) case _ = <-cb.done: g.Done() return } } } func (cb valueCallback) stop() { cb.done <- true } func (cb voidCallback) run(g *sync.WaitGroup) { ticker := time.NewTicker(sanitizeInterval(cb.interval)) for { select { case _ = <-ticker.C: cb.callback(cb.interval) case _ = <-cb.done: g.Done() return } } } func (cb voidCallback) stop() { cb.done <- true } // Interval determines the default interval from the "COLLECTD_INTERVAL" // environment variable. It falls back to 10s if the environment variable is // unset or cannot be parsed. func Interval() time.Duration { i, err := strconv.ParseFloat(os.Getenv("COLLECTD_INTERVAL"), 64) if err != nil { log.Printf("unable to determine default interval: %v", err) return time.Second * 10 } return time.Duration(i * float64(time.Second)) } // Hostname determines the hostname to use from the "COLLECTD_HOSTNAME" // environment variable and falls back to os.Hostname() if it is unset. If that // also fails an empty string is returned. func Hostname() string { if h := os.Getenv("COLLECTD_HOSTNAME"); h != "" { return h } if h, err := os.Hostname(); err == nil { return h } return "" } func sanitizeInterval(in time.Duration) time.Duration { if in == time.Duration(0) { return Interval() } return in } golang-collectd-0.0~git20150630/exec/exec_test.go000066400000000000000000000033561254451145600214060ustar00rootroot00000000000000package exec // import "collectd.org/exec" import ( "os" "testing" "time" "collectd.org/api" ) func TestSanitizeInterval(t *testing.T) { var got, want time.Duration got = sanitizeInterval(10 * time.Second) want = 10 * time.Second if got != want { t.Errorf("got %v, want %v", got, want) } // Environment with seconds if err := os.Setenv("COLLECTD_INTERVAL", "42"); err != nil { t.Fatalf("os.Setenv: %v", err) } got = sanitizeInterval(0) want = 42 * time.Second if got != want { t.Errorf("got %v, want %v", got, want) } // Environment with milliseconds if err := os.Setenv("COLLECTD_INTERVAL", "31.337"); err != nil { t.Fatalf("os.Setenv: %v", err) } got = sanitizeInterval(0) want = 31337 * time.Millisecond if got != want { t.Errorf("got %v, want %v", got, want) } } func Example() { e := NewExecutor() // simple "value" callback answer := func() api.Value { return api.Gauge(42) } e.ValueCallback(answer, api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "answer", TypeInstance: "live_universe_and_everything", }, Interval: time.Second, }) // "complex" void callback bicycles := func(interval time.Duration) { vl := api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "bicycles", }, Interval: interval, Time: time.Now(), Values: make([]api.Value, 1), } data := []struct { TypeInstance string Value api.Gauge }{ {"beijing", api.Gauge(9000000)}, } for _, d := range data { vl.Values[0] = d.Value vl.Identifier.TypeInstance = d.TypeInstance Putval.Write(vl) } } e.VoidCallback(bicycles, time.Second) // blocks forever e.Run() } golang-collectd-0.0~git20150630/export/000077500000000000000000000000001254451145600174625ustar00rootroot00000000000000golang-collectd-0.0~git20150630/export/export.go000066400000000000000000000122701254451145600213340ustar00rootroot00000000000000/* Package export provides an interface to instrument Go code. Instrumenting Go code with this package is very similar to the "expvar" package in the vanilla Go distribution. In fact, the variables exported with this package are also registered with the "expvar" package, so that you can also use other existing metric collection frameworks with it. This package differs in that it has an explicitly cumulative type, Derive. The intended usage pattern of this package is as follows: First, global variables are initialized with NewDerive(), NewGauge(), NewDeriveString() or NewGaugeString(). The Run() function is called as a separate goroutine as part of your program's initialization and, last but not least, the variables are updated with their respective update functions, Add() for Derive and Set() for Gauge. // Initialize global variable. var requestCounter = export.NewDeriveString("example.com/golang/total_requests") // Call Run() in its own goroutine. func main() { client, err := network.Dial( net.JoinHostPort(network.DefaultIPv6Address, network.DefaultService), network.ClientOptions{}) if err != nil { log.Fatal(err) } go export.Run(client, export.Options{ Interval: 10 * time.Second, }) // … } // Update variable. func requestHandler(w http.ResponseWriter, req *http.Request) { defer requestCounter.Add(1) // … } */ package export // import "collectd.org/export" import ( "expvar" "log" "math" "strconv" "sync" "time" "collectd.org/api" ) var ( mutex sync.RWMutex vars []Var ) // Var is an abstract type for metrics exported by this package. type Var interface { ValueList() api.ValueList } // Publish adds v to the internal list of exported metrics. func Publish(v Var) { mutex.Lock() defer mutex.Unlock() vars = append(vars, v) } // Options holds options for the Run() function. type Options struct { Interval time.Duration } // Run periodically calls the ValueList function of each Var, sets the Time and // Interval fields and passes it w.Write(). This function blocks indefinitely. func Run(w api.Writer, opts Options) error { ticker := time.NewTicker(opts.Interval) for { select { case _ = <-ticker.C: mutex.RLock() for _, v := range vars { vl := v.ValueList() vl.Time = time.Now() vl.Interval = opts.Interval if err := w.Write(vl); err != nil { mutex.RUnlock() return err } } mutex.RUnlock() } } } // Derive represents a cumulative integer data type, for example "requests // served since server start". It implements the Var and expvar.Var interfaces. type Derive struct { mu sync.RWMutex id api.Identifier value api.Derive } // NewDerive initializes a new Derive, registers it with the "expvar" package // and returns it. The initial value is zero. func NewDerive(id api.Identifier) *Derive { d := &Derive{ id: id, value: 0, } Publish(d) expvar.Publish(id.String(), d) return d } // NewDeriveString parses s as an Identifier and returns a new Derive. If // parsing s fails, it will panic. This simplifies initializing global // variables. func NewDeriveString(s string) *Derive { id, err := api.ParseIdentifier(s) if err != nil { log.Fatal(err) } return NewDerive(id) } // Add adds diff to d. func (d *Derive) Add(diff int) { d.mu.Lock() defer d.mu.Unlock() d.value += api.Derive(diff) } // String returns the string representation of d. func (d *Derive) String() string { d.mu.RLock() defer d.mu.RUnlock() return strconv.FormatInt(int64(d.value), 10) } // ValueList returns the ValueList representation of d. Both, Time and Interval // are set to zero. func (d *Derive) ValueList() api.ValueList { d.mu.RLock() defer d.mu.RUnlock() return api.ValueList{ Identifier: d.id, Values: []api.Value{d.value}, } } // Gauge represents an absolute floating point data type, for example "heap // memory used". It implements the Var and expvar.Var interfaces. type Gauge struct { mu sync.RWMutex id api.Identifier value api.Gauge } // NewGauge initializes a new Gauge, registers it with the "expvar" package and // returns it. The initial value is NaN. func NewGauge(id api.Identifier) *Gauge { g := &Gauge{ id: id, value: api.Gauge(math.NaN()), } Publish(g) expvar.Publish(id.String(), g) return g } // NewGaugeString parses s as an Identifier and returns a new Gauge. If parsing // s fails, it will panic. This simplifies initializing global variables. func NewGaugeString(s string) *Gauge { id, err := api.ParseIdentifier(s) if err != nil { log.Fatal(err) } return NewGauge(id) } // Set sets g to v. func (g *Gauge) Set(v float64) { g.mu.Lock() defer g.mu.Unlock() g.value = api.Gauge(v) } // String returns the string representation of g. func (g *Gauge) String() string { g.mu.RLock() defer g.mu.RUnlock() return strconv.FormatFloat(float64(g.value), 'g', -1, 64) } // ValueList returns the ValueList representation of g. Both, Time and Interval // are set to zero. func (g *Gauge) ValueList() api.ValueList { g.mu.RLock() defer g.mu.RUnlock() return api.ValueList{ Identifier: g.id, Values: []api.Value{g.value}, } } golang-collectd-0.0~git20150630/export/export_test.go000066400000000000000000000022741254451145600223760ustar00rootroot00000000000000package export // import "collectd.org/export" import ( "expvar" "reflect" "testing" "collectd.org/api" ) func TestDerive(t *testing.T) { d := NewDerive(api.Identifier{ Host: "example.com", Plugin: "golang", Type: "derive", }) for i := 0; i < 10; i++ { d.Add(i) } want := api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "derive", }, Values: []api.Value{api.Derive(45)}, } got := d.ValueList() if !reflect.DeepEqual(got, want) { t.Errorf("got %#v, want %#v", got, want) } s := expvar.Get("example.com/golang/derive").String() if s != "45" { t.Errorf("got %q, want %q", s, "45") } } func TestGauge(t *testing.T) { g := NewGauge(api.Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }) g.Set(42.0) want := api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, Values: []api.Value{api.Gauge(42)}, } got := g.ValueList() if !reflect.DeepEqual(got, want) { t.Errorf("got %#v, want %#v", got, want) } s := expvar.Get("example.com/golang/gauge").String() if s != "42" { t.Errorf("got %q, want %q", s, "42") } } golang-collectd-0.0~git20150630/format/000077500000000000000000000000001254451145600174315ustar00rootroot00000000000000golang-collectd-0.0~git20150630/format/graphite.go000066400000000000000000000041601254451145600215640ustar00rootroot00000000000000package format // import "collectd.org/format" import ( "fmt" "io" "strings" "time" "collectd.org/api" ) // Graphite implements the Writer interface and writes ValueLists in Graphite // format to W. type Graphite struct { W io.Writer Prefix, Suffix string EscapeChar string SeparateInstances bool AlwaysAppendDS bool // TODO(octo): Implement support. replacer *strings.Replacer } func (g *Graphite) escape(in string) string { if g.replacer == nil { g.replacer = strings.NewReplacer( ".", g.EscapeChar, "\t", g.EscapeChar, "\"", g.EscapeChar, "\\", g.EscapeChar, ":", g.EscapeChar, "!", g.EscapeChar, "/", g.EscapeChar, "(", g.EscapeChar, ")", g.EscapeChar, "\n", g.EscapeChar, "\r", g.EscapeChar) } return g.replacer.Replace(in) } func (g *Graphite) formatName(id api.Identifier, dsName string) string { var instanceSeparator = "-" if g.SeparateInstances { instanceSeparator = "." } host := g.escape(id.Host) plugin := g.escape(id.Plugin) if id.PluginInstance != "" { plugin += instanceSeparator + g.escape(id.PluginInstance) } typ := id.Type if id.TypeInstance != "" { typ += instanceSeparator + g.escape(id.TypeInstance) } name := g.Prefix + host + g.Suffix + "." + plugin + "." + typ if dsName != "" { name += "." + g.escape(dsName) } return name } func (g *Graphite) formatValue(v api.Value) (string, error) { switch v := v.(type) { case api.Gauge: return fmt.Sprintf("%.15g", v), nil case api.Derive, api.Counter: return fmt.Sprintf("%d", v), nil default: return "", fmt.Errorf("unexpected type %T", v) } } // Write formats the ValueList in the PUTVAL format and writes it to the // assiciated io.Writer. func (g *Graphite) Write(vl api.ValueList) error { for i, v := range vl.Values { dsName := "" if g.AlwaysAppendDS || len(vl.Values) != 1 { dsName = vl.DSName(i) } name := g.formatName(vl.Identifier, dsName) val, err := g.formatValue(v) if err != nil { return err } t := vl.Time if t.IsZero() { t = time.Now() } fmt.Fprintf(g.W, "%s %s %d\r\n", name, val, t.Unix()) } return nil } golang-collectd-0.0~git20150630/format/graphite_test.go000066400000000000000000000044611254451145600226270ustar00rootroot00000000000000package format import ( "bytes" "testing" "time" "collectd.org/api" ) func TestWrite(t *testing.T) { cases := []struct { ValueList api.ValueList Graphite *Graphite Want string }{ { // case 0 ValueList: api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", PluginInstance: "example", Type: "gauge", TypeInstance: "answer", }, Time: time.Unix(1426975989, 1), Interval: 10 * time.Second, Values: []api.Value{api.Gauge(42)}, }, Graphite: &Graphite{ Prefix: "-->", Suffix: "<--", EscapeChar: "_", SeparateInstances: false, AlwaysAppendDS: true, }, Want: "-->example_com<--.golang-example.gauge-answer.value 42 1426975989\r\n", }, { // case 1 ValueList: api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", PluginInstance: "example", Type: "gauge", TypeInstance: "answer", }, Time: time.Unix(1426975989, 1), Interval: 10 * time.Second, Values: []api.Value{api.Derive(1337)}, }, Graphite: &Graphite{ Prefix: "collectd.", Suffix: "", EscapeChar: "@", SeparateInstances: true, AlwaysAppendDS: false, }, Want: "collectd.example@com.golang.example.gauge.answer 1337 1426975989\r\n", }, { // case 2 ValueList: api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, Time: time.Unix(1426975989, 1), Interval: 10 * time.Second, Values: []api.Value{api.Gauge(42), api.Derive(1337)}, }, Graphite: &Graphite{ Prefix: "collectd.", Suffix: "", EscapeChar: "_", SeparateInstances: true, AlwaysAppendDS: false, }, Want: "collectd.example_com.golang.gauge.0 42 1426975989\r\n" + "collectd.example_com.golang.gauge.1 1337 1426975989\r\n", }, } for i, c := range cases { buf := &bytes.Buffer{} c.Graphite.W = buf if err := c.Graphite.Write(c.ValueList); err != nil { t.Errorf("case %d: got %v, want %v", i, err, nil) } got := buf.String() if got != c.Want { t.Errorf("got %q, want %q", got, c.Want) } } } golang-collectd-0.0~git20150630/format/putval.go000066400000000000000000000025071254451145600212770ustar00rootroot00000000000000// Package format provides utilities to format metrics and notifications in // various formats. package format // import "collectd.org/format" import ( "fmt" "io" "strings" "time" "collectd.org/api" ) // Putval implements the Writer interface for PUTVAL formatted output. type Putval struct { w io.Writer } // NewPutval returns a new Putval object writing to the provided io.Writer. func NewPutval(w io.Writer) *Putval { return &Putval{ w: w, } } // Write formats the ValueList in the PUTVAL format and writes it to the // assiciated io.Writer. func (p *Putval) Write(vl api.ValueList) error { s, err := formatValues(vl) if err != nil { return err } _, err = fmt.Fprintf(p.w, "PUTVAL %q interval=%.3f %s\n", vl.Identifier.String(), vl.Interval.Seconds(), s) return err } func formatValues(vl api.ValueList) (string, error) { fields := make([]string, 1+len(vl.Values)) fields[0] = formatTime(vl.Time) for i, v := range vl.Values { switch v := v.(type) { case api.Gauge: fields[i+1] = fmt.Sprintf("%.15g", v) case api.Derive: fields[i+1] = fmt.Sprintf("%d", v) default: return "", fmt.Errorf("unexpected type %T", v) } } return strings.Join(fields, ":"), nil } func formatTime(t time.Time) string { if t.IsZero() { return "N" } return fmt.Sprintf("%.3f", float64(t.UnixNano())/1000000000.0) } golang-collectd-0.0~git20150630/network/000077500000000000000000000000001254451145600176325ustar00rootroot00000000000000golang-collectd-0.0~git20150630/network/buffer.go000066400000000000000000000165371254451145600214460ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "bytes" "encoding/binary" "errors" "io" "math" "sync" "time" "collectd.org/api" "collectd.org/cdtime" ) // ErrNotEnoughSpace is returned when adding a ValueList would exeed the buffer // size. var ErrNotEnoughSpace = errors.New("not enough space") // Buffer contains the binary representation of multiple ValueLists and state // optimally write the next ValueList. type Buffer struct { lock *sync.Mutex buffer *bytes.Buffer output io.Writer state api.ValueList size int username, password string securityLevel SecurityLevel } // NewBuffer initializes a new Buffer. If "size" is 0, DefaultBufferSize will // be used. func NewBuffer(size int) *Buffer { if size <= 0 { size = DefaultBufferSize } return &Buffer{ lock: new(sync.Mutex), buffer: new(bytes.Buffer), size: size, } } // Sign enables cryptographic signing of data. func (b *Buffer) Sign(username, password string) { b.username = username b.password = password b.securityLevel = Sign } // Encrypt enables encryption of data. func (b *Buffer) Encrypt(username, password string) { b.username = username b.password = password b.securityLevel = Encrypt } // Available returns the number of bytes still available in the buffer. func (b *Buffer) Available() int { var overhead int switch b.securityLevel { case Sign: overhead = 36 + len(b.username) case Encrypt: overhead = 42 + len(b.username) } unavail := overhead + b.buffer.Len() if b.size < unavail { return 0 } return b.size - unavail } // Read reads the buffer into "out". If signing or encryption is enabled, data // will be signed / encrypted before writing it to "out". Returns // ErrNotEnoughSpace if the provided buffer is too small to hold the entire // packet data. func (b *Buffer) Read(out []byte) (int, error) { b.lock.Lock() defer b.lock.Unlock() switch b.securityLevel { case Sign: return b.readSigned(out) case Encrypt: return b.readEncrypted(out) } if len(out) < b.buffer.Len() { return 0, ErrNotEnoughSpace } n := copy(out, b.buffer.Bytes()) b.reset() return n, nil } func (b *Buffer) readSigned(out []byte) (int, error) { if len(out) < 36+len(b.username)+b.buffer.Len() { return 0, ErrNotEnoughSpace } signed := signSHA256(b.buffer.Bytes(), b.username, b.password) b.reset() return copy(out, signed), nil } func (b *Buffer) readEncrypted(out []byte) (int, error) { if len(out) < 42+len(b.username)+b.buffer.Len() { return 0, ErrNotEnoughSpace } ciphertext, err := encryptAES256(b.buffer.Bytes(), b.username, b.password) if err != nil { return 0, err } b.reset() return copy(out, ciphertext), nil } // WriteTo writes the buffer contents to "w". It implements the io.WriteTo // interface. func (b *Buffer) WriteTo(w io.Writer) (int64, error) { tmp := make([]byte, b.size) n, err := b.Read(tmp) if err != nil { return 0, err } n, err = w.Write(tmp[:n]) return int64(n), err } // Write adds a ValueList to the buffer. Returns ErrNotEnoughSpace if not // enough space in the buffer is available to add this value list. In that // case, call Read() to empty the buffer and try again. func (b *Buffer) Write(vl api.ValueList) error { b.lock.Lock() defer b.lock.Unlock() // remember the original buffer size so we can truncate all potentially // written data in case of an error. l := b.buffer.Len() if err := b.writeValueList(vl); err != nil { if l != 0 { b.buffer.Truncate(l) } return err } return nil } func (b *Buffer) writeValueList(vl api.ValueList) error { if err := b.writeIdentifier(vl.Identifier); err != nil { return err } if err := b.writeTime(vl.Time); err != nil { return err } if err := b.writeInterval(vl.Interval); err != nil { return err } if err := b.writeValues(vl.Values); err != nil { return err } return nil } func (b *Buffer) writeIdentifier(id api.Identifier) error { if id.Host != b.state.Host { if err := b.writeString(typeHost, id.Host); err != nil { return err } b.state.Host = id.Host } if id.Plugin != b.state.Plugin { if err := b.writeString(typePlugin, id.Plugin); err != nil { return err } b.state.Plugin = id.Plugin } if id.PluginInstance != b.state.PluginInstance { if err := b.writeString(typePluginInstance, id.PluginInstance); err != nil { return err } b.state.PluginInstance = id.PluginInstance } if id.Type != b.state.Type { if err := b.writeString(typeType, id.Type); err != nil { return err } b.state.Type = id.Type } if id.TypeInstance != b.state.TypeInstance { if err := b.writeString(typeTypeInstance, id.TypeInstance); err != nil { return err } b.state.TypeInstance = id.TypeInstance } return nil } func (b *Buffer) writeTime(t time.Time) error { if b.state.Time == t { return nil } b.state.Time = t return b.writeInt(typeTimeHR, uint64(cdtime.New(t))) } func (b *Buffer) writeInterval(d time.Duration) error { if b.state.Interval == d { return nil } b.state.Interval = d return b.writeInt(typeIntervalHR, uint64(cdtime.NewDuration(d))) } func (b *Buffer) writeValues(values []api.Value) error { size := 6 + 9*len(values) if size > b.Available() { return ErrNotEnoughSpace } binary.Write(b.buffer, binary.BigEndian, uint16(typeValues)) binary.Write(b.buffer, binary.BigEndian, uint16(size)) binary.Write(b.buffer, binary.BigEndian, uint16(len(values))) for _, v := range values { switch v.(type) { case api.Gauge: binary.Write(b.buffer, binary.BigEndian, uint8(dsTypeGauge)) case api.Derive: binary.Write(b.buffer, binary.BigEndian, uint8(dsTypeDerive)) default: panic("unexpected type") } } for _, v := range values { switch v := v.(type) { case api.Gauge: if math.IsNaN(float64(v)) { b.buffer.Write([]byte{0, 0, 0, 0, 0, 0, 0xf8, 0x7f}) } else { // sic: floats are encoded in little endian. binary.Write(b.buffer, binary.LittleEndian, float64(v)) } case api.Derive: binary.Write(b.buffer, binary.BigEndian, int64(v)) default: panic("unexpected type") } } return nil } func (b *Buffer) writeString(typ uint16, s string) error { encoded := bytes.NewBufferString(s) encoded.Write([]byte{0}) // Because s is a Unicode string, encoded.Len() may be larger than // len(s). size := 4 + encoded.Len() if size > b.Available() { return ErrNotEnoughSpace } binary.Write(b.buffer, binary.BigEndian, typ) binary.Write(b.buffer, binary.BigEndian, uint16(size)) b.buffer.Write(encoded.Bytes()) return nil } func (b *Buffer) writeInt(typ uint16, n uint64) error { size := 12 if size > b.Available() { return ErrNotEnoughSpace } binary.Write(b.buffer, binary.BigEndian, typ) binary.Write(b.buffer, binary.BigEndian, uint16(size)) binary.Write(b.buffer, binary.BigEndian, n) return nil } func (b *Buffer) reset() { b.buffer.Reset() b.state = api.ValueList{} } /* func (b *Buffer) flush() error { if b.buffer.Len() == 0 { return nil } buf := make([]byte, b.buffer.Len()) if _, err := b.buffer.Read(buf); err != nil { return err } if b.username != "" && b.password != "" { if b.encrypt { var err error if buf, err = encryptAES256(buf, b.username, b.password); err != nil { return err } } else { buf = signSHA256(buf, b.username, b.password) } } if _, err := b.output.Write(buf); err != nil { return err } // zero state b.state = api.ValueList{} return nil } */ golang-collectd-0.0~git20150630/network/buffer_test.go000066400000000000000000000066371254451145600225050ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "bytes" "math" "reflect" "testing" "time" "collectd.org/api" ) func TestWriteValueList(t *testing.T) { b := NewBuffer(0) vl := api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, Time: time.Unix(1426076671, 123000000), // Wed Mar 11 13:24:31 CET 2015 Interval: 10 * time.Second, Values: []api.Value{api.Derive(1)}, } if err := b.Write(vl); err != nil { t.Errorf("Write got %v, want nil", err) return } // ValueList with much the same fields, to test compression. vl = api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", PluginInstance: "test", Type: "gauge", }, Time: time.Unix(1426076681, 234000000), // Wed Mar 11 13:24:41 CET 2015 Interval: 10 * time.Second, Values: []api.Value{api.Derive(2)}, } if err := b.Write(vl); err != nil { t.Errorf("Write got %v, want nil", err) return } want := []byte{ // vl1 0, 0, 0, 16, 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0, 0, 2, 0, 11, 'g', 'o', 'l', 'a', 'n', 'g', 0, 0, 4, 0, 10, 'g', 'a', 'u', 'g', 'e', 0, 0, 8, 0, 12, 0x15, 0x40, 0x0c, 0xff, 0xc7, 0xdf, 0x3b, 0x64, 0, 9, 0, 12, 0, 0, 0, 0x02, 0x80, 0, 0, 0, 0, 6, 0, 15, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 1, // vl2 0, 3, 0, 9, 't', 'e', 's', 't', 0, 0, 8, 0, 12, 0x15, 0x40, 0x0d, 0x02, 0x4e, 0xf9, 0xdb, 0x22, 0, 6, 0, 15, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 2, } got := b.buffer.Bytes() if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } func TestWriteTime(t *testing.T) { b := &Buffer{buffer: new(bytes.Buffer), size: DefaultBufferSize} b.writeTime(time.Unix(1426083986, 314000000)) // Wed Mar 11 15:26:26 CET 2015 // 1426083986.314 * 2^30 -> 1531246020641985396.736 // 1531246020641985396 -> 0x1540142494189374 want := []byte{0, 8, // pkg type 0, 12, // pkg len 0x15, 0x40, 0x14, 0x24, 0x94, 0x18, 0x93, 0x74, } got := b.buffer.Bytes() if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } func TestWriteValues(t *testing.T) { b := &Buffer{buffer: new(bytes.Buffer), size: DefaultBufferSize} b.writeValues([]api.Value{ api.Gauge(42), api.Derive(31337), api.Gauge(math.NaN()), }) want := []byte{0, 6, // pkg type 0, 33, // pkg len 0, 3, // num values 1, 2, 1, // gauge, derive, gauge 0, 0, 0, 0, 0, 0, 0x45, 0x40, // 42.0 0, 0, 0, 0, 0, 0, 0x7a, 0x69, // 31337 0, 0, 0, 0, 0, 0, 0xf8, 0x7f, // NaN } got := b.buffer.Bytes() if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } func TestWriteString(t *testing.T) { b := &Buffer{buffer: new(bytes.Buffer), size: DefaultBufferSize} if err := b.writeString(0xf007, "foo"); err != nil { t.Errorf("got %v, want nil", err) } want := []byte{0xf0, 0x07, // pkg type 0, 8, // pkg len 'f', 'o', 'o', 0, // "foo\0" } got := b.buffer.Bytes() if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } func TestWriteInt(t *testing.T) { b := &Buffer{buffer: new(bytes.Buffer), size: DefaultBufferSize} if err := b.writeInt(23, uint64(384)); err != nil { t.Errorf("got %v, want nil", err) } want := []byte{0, 23, // pkg type 0, 12, // pkg len 0, 0, 0, 0, 0, 0, 1, 128, // 384 } got := b.buffer.Bytes() if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } golang-collectd-0.0~git20150630/network/client.go000066400000000000000000000036211254451145600214410ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "net" "collectd.org/api" ) // ClientOptions holds configuration options for Client. type ClientOptions struct { // SecurityLevel determines whether data is signed, encrypted or sent // in plain text. SecurityLevel SecurityLevel // Username and password for the "Sign" and "Encrypt" security levels. Username, Password string // Size of the send buffer. When zero, DefaultBufferSize is used. BufferSize int } // Client is a connection to a collectd server. It implements the // api.Writer interface. type Client struct { udp net.Conn buffer *Buffer opts ClientOptions } // Dial connects to the collectd server at address. "address" must be a network // address accepted by net.Dial(). func Dial(address string, opts ClientOptions) (*Client, error) { c, err := net.Dial("udp", address) if err != nil { return nil, err } b := NewBuffer(opts.BufferSize) if opts.SecurityLevel == Sign { b.Sign(opts.Username, opts.Password) } else if opts.SecurityLevel == Encrypt { b.Encrypt(opts.Username, opts.Password) } return &Client{ udp: c, buffer: b, opts: opts, }, nil } // Write adds a ValueList to the internal buffer. Data is only written to // the network when the buffer is full. func (c *Client) Write(vl api.ValueList) error { if err := c.buffer.Write(vl); err != ErrNotEnoughSpace { return err } if err := c.Flush(); err != nil { return err } return c.buffer.Write(vl) } // Flush writes the contents of the underlying buffer to the network // immediately. func (c *Client) Flush() error { _, err := c.buffer.WriteTo(c.udp) return err } // Close writes remaining data to the network and closes the socket. You must // not use "c" after this call. func (c *Client) Close() error { if err := c.Flush(); err != nil { return err } if err := c.udp.Close(); err != nil { return err } c.buffer = nil return nil } golang-collectd-0.0~git20150630/network/client_test.go000066400000000000000000000010551254451145600224770ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "log" "net" "time" "collectd.org/api" ) func ExampleClient() { conn, err := Dial(net.JoinHostPort("example.com", DefaultService), ClientOptions{}) if err != nil { log.Fatal(err) } defer conn.Close() vl := api.ValueList{ Identifier: api.Identifier{ Host: "example.com", Plugin: "golang", Type: "gauge", }, Time: time.Now(), Interval: 10 * time.Second, Values: []api.Value{api.Gauge(42.0)}, } if err := conn.Write(vl); err != nil { log.Fatal(err) } } golang-collectd-0.0~git20150630/network/crypto.go000066400000000000000000000127761254451145600215160ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "bufio" "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "encoding/binary" "errors" "fmt" "io" "log" "os" "strings" "sync" "time" ) // PasswordLookup is used when parsing signed and encrypted network traffic to // look up the password associated with a given username. type PasswordLookup interface { Password(user string) (string, error) } // AuthFile implements the PasswordLookup interface in the same way the // collectd network plugin implements it, i.e. by stat'ing and reading a file. // // The file has a very simple syntax with one username / password mapping per // line, separated by a colon. For example: // // alice: w0nderl4nd // bob: bu1|der type AuthFile struct { name string last time.Time data map[string]string lock *sync.Mutex } // NewAuthFile initializes and returns a new AuthFile. func NewAuthFile(name string) *AuthFile { return &AuthFile{ name: name, lock: &sync.Mutex{}, } } // Password looks up a user in the file and returns the associated password. func (a *AuthFile) Password(user string) (string, error) { if a == nil { return "", fmt.Errorf("no AuthFile") } a.lock.Lock() defer a.lock.Unlock() if err := a.update(); err != nil { return "", err } pwd, ok := a.data[user] if !ok { return "", fmt.Errorf("no such user: %q", user) } return pwd, nil } func (a *AuthFile) update() error { fi, err := os.Stat(a.name) if err != nil { return err } if !fi.ModTime().After(a.last) { // up to date return nil } file, err := os.Open(a.name) if err != nil { return err } defer file.Close() newData := make(map[string]string) r := bufio.NewReader(file) for { line, err := r.ReadString('\n') if err != nil && err != io.EOF { return err } else if err == io.EOF { break } line = strings.Trim(line, " \r\n\t\v") fields := strings.SplitN(line, ":", 2) if len(fields) != 2 { continue } user := strings.TrimSpace(fields[0]) pass := strings.TrimSpace(fields[1]) if strings.HasPrefix(user, "#") { continue } newData[user] = pass } a.data = newData a.last = fi.ModTime() return nil } func signSHA256(payload []byte, username, password string) []byte { mac := hmac.New(sha256.New, bytes.NewBufferString(password).Bytes()) usernameBuffer := bytes.NewBufferString(username) size := uint16(36 + usernameBuffer.Len()) mac.Write(usernameBuffer.Bytes()) mac.Write(payload) out := new(bytes.Buffer) binary.Write(out, binary.BigEndian, uint16(typeSignSHA256)) binary.Write(out, binary.BigEndian, size) out.Write(mac.Sum(nil)) out.Write(usernameBuffer.Bytes()) out.Write(payload) return out.Bytes() } func verifySHA256(part, payload []byte, lookup PasswordLookup) (bool, error) { if lookup == nil { return false, errors.New("no PasswordLookup available") } if len(part) <= 32 { return false, fmt.Errorf("part too small (%d bytes)", len(part)) } hash := part[:32] user := bytes.NewBuffer(part[32:]).String() password, err := lookup.Password(user) if err != nil { return false, err } mac := hmac.New(sha256.New, bytes.NewBufferString(password).Bytes()) mac.Write(part[32:]) mac.Write(payload) return bytes.Equal(hash, mac.Sum(nil)), nil } func createCipher(password string, iv []byte) (cipher.Stream, error) { passwordHash := sha256.Sum256(bytes.NewBufferString(password).Bytes()) blockCipher, err := aes.NewCipher(passwordHash[:]) if err != nil { return nil, err } streamCipher := cipher.NewOFB(blockCipher, iv) return streamCipher, nil } func encryptAES256(plaintext []byte, username, password string) ([]byte, error) { iv := make([]byte, 16) if _, err := rand.Read(iv); err != nil { log.Printf("rand.Read: %v", err) return nil, err } streamCipher, err := createCipher(password, iv) if err != nil { return nil, err } usernameBuffer := bytes.NewBufferString(username) size := uint16(42 + usernameBuffer.Len() + len(plaintext)) checksum := sha1.Sum(plaintext) out := new(bytes.Buffer) binary.Write(out, binary.BigEndian, uint16(typeEncryptAES256)) binary.Write(out, binary.BigEndian, size) binary.Write(out, binary.BigEndian, uint16(usernameBuffer.Len())) out.Write(usernameBuffer.Bytes()) out.Write(iv) w := &cipher.StreamWriter{S: streamCipher, W: out} w.Write(checksum[:]) w.Write(plaintext) return out.Bytes(), nil } func decryptAES256(ciphertext []byte, lookup PasswordLookup) ([]byte, error) { if lookup == nil { return nil, errors.New("no PasswordLookup available") } buf := bytes.NewBuffer(ciphertext) userLen := int(binary.BigEndian.Uint16(buf.Next(2))) if 42+userLen >= buf.Len() { return nil, fmt.Errorf("invalid username length %d", userLen) } user := bytes.NewBuffer(buf.Next(userLen)).String() password, err := lookup.Password(user) if err != nil { return nil, err } iv := make([]byte, 16) if n, err := buf.Read(iv); n != 16 || err != nil { return nil, fmt.Errorf("reading IV failed: %v", err) } streamCipher, err := createCipher(password, iv) if err != nil { return nil, err } r := &cipher.StreamReader{S: streamCipher, R: buf} plaintext := make([]byte, buf.Len()) if n, err := r.Read(plaintext); n != len(plaintext) || err != nil { return nil, fmt.Errorf("decryption failure: got (%d, %v), want (%d, nil)", n, err, len(plaintext)) } checksumWant := plaintext[:20] plaintext = plaintext[20:] checksumGot := sha1.Sum(plaintext) if !bytes.Equal(checksumGot[:], checksumWant[:]) { return nil, errors.New("checksum mismatch") } return plaintext, nil } golang-collectd-0.0~git20150630/network/crypto_test.go000066400000000000000000000053551254451145600225500ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "bytes" "errors" "reflect" "testing" ) type mockPasswordLookup map[string]string func (l mockPasswordLookup) Password(user string) (string, error) { pass, ok := l[user] if !ok { return "", errors.New("not found") } return pass, nil } func TestSign(t *testing.T) { want := []byte{ 2, 0, 0, 41, 0xcd, 0xa5, 0x9a, 0x37, 0xb0, 0x81, 0xc2, 0x31, 0x24, 0x2a, 0x6d, 0xbd, 0xfb, 0x44, 0xdb, 0xd7, 0x41, 0x2a, 0xf4, 0x29, 0x83, 0xde, 0xa5, 0x11, 0x96, 0xd2, 0xe9, 0x30, 0x21, 0xae, 0xc5, 0x45, 'a', 'd', 'm', 'i', 'n', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'd', } got := signSHA256([]byte{'c', 'o', 'l', 'l', 'e', 'c', 't', 'd'}, "admin", "admin") if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } passwords := mockPasswordLookup{ "admin": "admin", } ok, err := verifySHA256(want[4:41], want[41:], passwords) if !ok || err != nil { t.Errorf("got (%v, %v), want (true, nil)", ok, err) } want[41], want[42] = want[42], want[41] // corrupt data ok, err = verifySHA256(want[4:41], want[41:], passwords) if ok || err != nil { t.Errorf("got (%v, %v), want (false, nil)", ok, err) } want[41], want[42] = want[42], want[41] // fix data passwords["admin"] = "test123" // different password ok, err = verifySHA256(want[4:41], want[41:], passwords) if ok || err != nil { t.Errorf("got (%v, %v), want (false, nil)", ok, err) } } func TestEncrypt(t *testing.T) { plaintext := []byte{'c', 'o', 'l', 'l', 'e', 'c', 't', 'd'} // actual ciphertext depends on IV -- only check the first part want := []byte{ 0x02, 0x10, // part type 0x00, 0x37, // part length 0x00, 0x05, // username length 0x61, 0x64, 0x6d, 0x69, 0x6e, // username // IV // SHA1 // encrypted data } ciphertext, err := encryptAES256(plaintext, "admin", "admin") if !bytes.Equal(want, ciphertext[:11]) || err != nil { t.Errorf("got (%v, %v), want (%v, nil)", ciphertext[:11], err, want) } passwords := mockPasswordLookup{ "admin": "admin", } if got, err := decryptAES256(ciphertext[4:], passwords); !bytes.Equal(got, plaintext) || err != nil { t.Errorf("got (%v, %v), want (%v, nil)", got, err, plaintext) } ciphertext[47], ciphertext[48] = ciphertext[48], ciphertext[47] // corrupt data if got, err := decryptAES256(ciphertext[4:], passwords); got != nil || err == nil { t.Errorf("got (%v, %v), want (nil, \"checksum mismatch\")", got, err) } ciphertext[47], ciphertext[48] = ciphertext[48], ciphertext[47] // fix data passwords["admin"] = "test123" // different password if got, err := decryptAES256(ciphertext[4:], passwords); got != nil || err == nil { t.Errorf("got (%v, %v), want (nil, \"no such user\")", got, err) } } golang-collectd-0.0~git20150630/network/main.go000066400000000000000000000023701254451145600211070ustar00rootroot00000000000000/* Package network implements collectd's binary network protocol. */ package network // import "collectd.org/network" // Well-known addresses and port. const ( DefaultIPv4Address = "239.192.74.66" DefaultIPv6Address = "ff18::efc0:4a42" DefaultService = "25826" ) // Default size of "Buffer". This is based on the maximum bytes that fit into // an Ethernet frame without fragmentation: // - ( + ) = 1500 - (40 + 8) = 1452 const DefaultBufferSize = 1452 // Numeric data source type identifiers. const ( dsTypeCounter = 0 dsTypeGauge = 1 dsTypeDerive = 2 ) // IDs of the various "parts", i.e. subcomponents of a packet. const ( typeHost = 0x0000 typeTime = 0x0001 typeTimeHR = 0x0008 typePlugin = 0x0002 typePluginInstance = 0x0003 typeType = 0x0004 typeTypeInstance = 0x0005 typeValues = 0x0006 typeInterval = 0x0007 typeIntervalHR = 0x0009 typeSignSHA256 = 0x0200 typeEncryptAES256 = 0x0210 ) // SecurityLevel determines whether data is signed, encrypted or used without // any protection. type SecurityLevel int // Predefined security levels. "None" is used for plain text. const ( None SecurityLevel = iota Sign Encrypt ) golang-collectd-0.0~git20150630/network/parse.go000066400000000000000000000126661254451145600213060ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "bytes" "encoding/binary" "errors" "fmt" "log" "time" "collectd.org/api" "collectd.org/cdtime" ) // ErrInvalid is returned when parsing the network data was aborted due to // illegal data format. var ErrInvalid = errors.New("invalid data") // ParseOpts holds confiruation options for "Parse()". type ParseOpts struct { // PasswordLookup is used lookup passwords to verify signed data and // decrypt encrypted data. PasswordLookup PasswordLookup // SecurityLevel determines the minimum security level expected by the // caller. If set to "Sign", only signed and encrypted data is returned // by Parse(), if set to "Encrypt", only encrypted data is returned. SecurityLevel SecurityLevel } // Parse parses the binary network format and returns a slice of ValueLists. If // a parse error is encountered, all ValueLists parsed to this point are // returned as well as the error. Unknown "parts" are silently ignored. func Parse(b []byte, opts ParseOpts) ([]api.ValueList, error) { return parse(b, None, opts) } func parse(b []byte, sl SecurityLevel, opts ParseOpts) ([]api.ValueList, error) { var valueLists []api.ValueList var state api.ValueList buf := bytes.NewBuffer(b) for buf.Len() > 0 { partType := binary.BigEndian.Uint16(buf.Next(2)) partLength := int(binary.BigEndian.Uint16(buf.Next(2))) if partLength < 5 || partLength-4 > buf.Len() { return valueLists, fmt.Errorf("invalid length %d", partLength) } // First 4 bytes were already read partLength -= 4 payload := buf.Next(partLength) if len(payload) != partLength { return valueLists, fmt.Errorf("invalid length: want %d, got %d", partLength, len(payload)) } switch partType { case typeHost, typePlugin, typePluginInstance, typeType, typeTypeInstance: if err := parseIdentifier(partType, payload, &state); err != nil { return valueLists, err } case typeInterval, typeIntervalHR, typeTime, typeTimeHR: if err := parseTime(partType, payload, &state); err != nil { return valueLists, err } case typeValues: vl := state var err error if vl.Values, err = parseValues(payload); err != nil { return valueLists, err } if opts.SecurityLevel <= sl { valueLists = append(valueLists, vl) } case typeSignSHA256: vls, err := parseSignSHA256(payload, buf.Bytes(), opts) if err != nil { return valueLists, err } valueLists = append(valueLists, vls...) case typeEncryptAES256: vls, err := parseEncryptAES256(payload, opts) if err != nil { return valueLists, err } valueLists = append(valueLists, vls...) default: log.Printf("ignoring field of type %#x", partType) } } return valueLists, nil } func parseIdentifier(partType uint16, payload []byte, state *api.ValueList) error { str, err := parseString(payload) if err != nil { return err } switch partType { case typeHost: state.Identifier.Host = str case typePlugin: state.Identifier.Plugin = str case typePluginInstance: state.Identifier.PluginInstance = str case typeType: state.Identifier.Type = str case typeTypeInstance: state.Identifier.TypeInstance = str } return nil } func parseTime(partType uint16, payload []byte, state *api.ValueList) error { v, err := parseInt(payload) if err != nil { return err } switch partType { case typeInterval: state.Interval = time.Duration(v) * time.Second case typeIntervalHR: state.Interval = cdtime.Time(v).Duration() case typeTime: state.Time = time.Unix(int64(v), 0) case typeTimeHR: state.Time = cdtime.Time(v).Time() } return nil } func parseValues(b []byte) ([]api.Value, error) { buffer := bytes.NewBuffer(b) var n uint16 if err := binary.Read(buffer, binary.BigEndian, &n); err != nil { return nil, err } if int(n*9) != buffer.Len() { return nil, ErrInvalid } types := make([]byte, n) values := make([]api.Value, n) if _, err := buffer.Read(types); err != nil { return nil, err } for i, typ := range types { switch typ { case dsTypeGauge: var v float64 if err := binary.Read(buffer, binary.LittleEndian, &v); err != nil { return nil, err } values[i] = api.Gauge(v) case dsTypeDerive: var v int64 if err := binary.Read(buffer, binary.BigEndian, &v); err != nil { return nil, err } values[i] = api.Derive(v) case dsTypeCounter: var v uint64 if err := binary.Read(buffer, binary.BigEndian, &v); err != nil { return nil, err } values[i] = api.Counter(v) default: return nil, ErrInvalid } } return values, nil } func parseSignSHA256(pkg, payload []byte, opts ParseOpts) ([]api.ValueList, error) { ok, err := verifySHA256(pkg, payload, opts.PasswordLookup) if err != nil { return nil, err } else if !ok { return nil, errors.New("SHA256 verification failure") } return parse(payload, Sign, opts) } func parseEncryptAES256(payload []byte, opts ParseOpts) ([]api.ValueList, error) { plaintext, err := decryptAES256(payload, opts.PasswordLookup) if err != nil { return nil, errors.New("AES256 decryption failure") } return parse(plaintext, Encrypt, opts) } func parseInt(b []byte) (uint64, error) { if len(b) != 8 { return 0, ErrInvalid } var i uint64 buf := bytes.NewBuffer(b) if err := binary.Read(buf, binary.BigEndian, &i); err != nil { return 0, err } return i, nil } func parseString(b []byte) (string, error) { if b[len(b)-1] != 0 { return "", ErrInvalid } buf := bytes.NewBuffer(b[:len(b)-1]) return buf.String(), nil } golang-collectd-0.0~git20150630/network/parse_test.go000066400000000000000000000347611254451145600223450ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "encoding/hex" "testing" ) func TestParse(t *testing.T) { for i, raw := range rawPacketData { vl, err := Parse(raw, ParseOpts{}) if err != nil { t.Errorf("%d: got (%v, %v), want nil", i, vl, err) } } } func BenchmarkPackets(b *testing.B) { for i := 0; i < b.N; i++ { _, err := Parse(rawPacketData[i%len(rawPacketData)], ParseOpts{}) if err != nil { b.Error(err) } } } // Collectd packets captured using Wireshark var rawPacketData = func() [][]byte { strs := []string{ "0000000e6c6f63616c686f7374000008000c1513676ac3a6e0970009000c00000002800000000002000973776170000004000973776170000005000966726565000006000f00010100000080ff610f420008000c1513676ac3a8fc120004000c737761705f696f0000050007696e000006000f00010200000000000000000008000c1513676ac3a9077d000500086f7574000006000f00010200000000000000000008000c1513676ac3bd2a8c0002000e696e74657266616365000003000965746830000004000e69665f6f63746574730000050005000006001800020202000000000000000000000000000000000008000c1513676ac3bd5a970004000e69665f6572726f7273000006001800020202000000000000000000000000000000000008000c1513676ac3bd7fea000300076c6f000004000e69665f6f6374657473000006001800020202000000000009e79c000000000009e79c0008000c1513676ac3bdaae60003000a776c616e30000006001800020202000000001009fa5400000000011cf6670008000c1513676ac3bdb0e00004000e69665f6572726f7273000006001800020202000000000000000000000000000000000008000c1513676ac3bd3d6d0003000965746830000004000f69665f7061636b657473000006001800020202000000000000000000000000000000000008000c1513676ac3bdae290003000a776c616e300000060018000202020000000000032f8f00000000000205e50008000c1513676ac3bdbb7b0003000c646f636b657230000006001800020202000000000000000000000000000000000008000c1513676ac3bda0db000300076c6f000004000e69665f6572726f7273000006001800020202000000000000000000000000000000000008000c1513676ac3bdbde80003000c646f636b657230000006001800020202000000000000000000000000000000000008000c1513676ac3bd8d8e000300076c6f000004000f69665f7061636b6574730000060018000202020000000000000c9c0000000000000c9c0008000c1513676ac3bdb90b0003000c646f636b657230000004000e69665f6f6374657473000006001800020202000000000000000000000000000000000008000c1513676ac469b10f0002000e70726f6365737365730000030005000004000d70735f7374617465000005000c7a6f6d62696573000006000f00010100000000000000000008000c1513676ac469a4a30005000d736c656570696e67000006000f0001010000000000006e400008000c1513676ac469c6320005000b706167696e67000006000f00010100000000000000000008000c1513676ac469f06e0005000c626c6f636b6564000006000f00010100000000000000000008000c1513676ac4698af40005000c72756e6e696e67000006000f00010100000000000000000008000c1513676ac469bbe10005000c73746f70706564000006000f00010100000000000000000008000c1513676ac46b8e710004000e666f726b5f726174650000050005000006000f0001020000000000001bcf0008000c1513676d437f1296000200086370750000030006300000040008637075000005000b73797374656d000006000f00010200000000000021870008000c1513676d437f36020005000969646c65000006000f000102000000000005847a0008000c1513676d437f979b0005000977616974000006000f00010200000000000005210008000c1513676d43802ff60005000c736f6674697271000006000f000102000000000000001f0008000c1513676d43803b3a0005000a737465616c000006000f0001020000000000000000", "0000000e6c6f63616c686f7374000008000c1513676d4380551f0009000c0000000280000000000200086370750000030006310000040008637075000005000975736572000006000f0001020000000000007cad0008000c1513676d43805dbe000500096e696365000006000f00010200000000000001de0008000c1513676d4380697d0005000b73797374656d000006000f0001020000000000001ce80008000c1513676d438072bd0005000969646c65000006000f000102000000000005931c0008000c1513676d43807c430005000977616974000006000f000102000000000000094b0008000c1513676d43808cee0005000c736f6674697271000006000f00010200000000000000120008000c1513676d4380843a0005000e696e74657272757074000006000f00010200000000000000000008000c1513676d438096230005000a737465616c000006000f00010200000000000000000008000c1513676d4380aa9c0003000632000005000975736572000006000f00010200000000000089580008000c1513676d4380b29f000500096e696365000006000f00010200000000000003610008000c1513676d4380c44c0005000969646c65000006000f000102000000000005873d0008000c1513676d4380bc0f0005000b73797374656d000006000f000102000000000000201d0008000c1513676d4380cea40005000977616974000006000f00010200000000000005810008000c1513676d4380d7370005000e696e74657272757074000006000f00010200000000000000000008000c1513676d4380ea830005000a737465616c000006000f00010200000000000000000008000c1513676d437eef62000300063000000500096e696365000006000f00010200000000000003920008000c1513676d4380e0260003000632000005000c736f6674697271000006000f00010200000000000000160008000c1513676d438101410003000633000005000975736572000006000f0001020000000000007d8a0008000c1513676d438109f5000500096e696365000006000f00010200000000000004350008000c1513676d4380244b0003000630000005000e696e74657272757074000006000f00010200000000000000000008000c1513676d438122070003000633000005000969646c65000006000f0001020000000000058eb60008000c1513676d43812e830005000977616974000006000f0001020000000000000ca80008000c1513676d438141480005000c736f6674697271000006000f000102000000000000001e0008000c1513676d43814a5d0005000a737465616c000006000f00010200000000000000000008000c1513676d4381149e0005000b73797374656d000006000f0001020000000000001b9a0008000c1513676d437ea8600003000630000005000975736572000006000f00010200000000000089a80008000c1513676d438138190003000633000005000e696e74657272757074000006000f00010200000000000000000008000c1513676d438a9ca00002000e696e74657266616365000003000965746830000004000e69665f6f63746574730000050005000006001800020202000000000000000000000000000000000008000c1513676d438aea760004000f69665f7061636b657473000006001800020202000000000000000000000000000000000008000c1513676d438b214d0004000e69665f6572726f727300000600180002020200000000000000000000000000000000", "0000000e6c6f63616c686f7374000008000c1513676d438aac590009000c00000002800000000002000764660000030009726f6f74000004000f64665f636f6d706c6578000005000966726565000006000f0001010000004c077e57420008000c1513676d438b6ada0005000d7265736572766564000006000f00010100000000338116420008000c1513676d438b7a170002000e696e7465726661636500000300076c6f000004000e69665f6f63746574730000050005000006001800020202000000000009ecf5000000000009ecf50008000c1513676d438b75780002000764660000030009726f6f74000004000f64665f636f6d706c6578000005000975736564000006000f000101000000e0a41b26420008000c1513676d438b8ed20002000e696e7465726661636500000300076c6f000004000e69665f6572726f72730000050005000006001800020202000000000000000000000000000000000008000c1513676d438b86bf0004000f69665f7061636b6574730000060018000202020000000000000c9d0000000000000c9d0008000c1513676d438bb3e60003000a776c616e300000060018000202020000000000032fab00000000000205ed0008000c1513676d438bd62e0003000c646f636b657230000004000e69665f6f6374657473000006001800020202000000000000000000000000000000000008000c1513676d438bbc8f0003000a776c616e30000004000e69665f6572726f7273000006001800020202000000000000000000000000000000000008000c1513676d438bdf030003000c646f636b657230000004000f69665f7061636b657473000006001800020202000000000000000000000000000000000008000c1513676d438baaf10003000a776c616e30000004000e69665f6f637465747300000600180002020200000000100a042300000000011cfa460008000c1513676d438c5f100002000764660000030009626f6f74000004000f64665f636f6d706c6578000005000966726565000006000f0001010000000010e198410008000c1513676d438c689c0005000d7265736572766564000006000f00010100000000804c68410008000c1513676d438c70ce0005000975736564000006000f0001010000000020ea9e410008000c1513676d438be7bc0002000e696e74657266616365000003000c646f636b657230000004000e69665f6572726f72730000050005000006001800020202000000000000000000000000000000000008000c1513676d43beca8c0002000c656e74726f70790000030005000004000c656e74726f7079000006000f0001010000000000088f400008000c1513676d43bf1d13000200096c6f616400000400096c6f6164000006002100030101019a9999999999a93f666666666666d63f5c8fc2f5285cdf3f0008000c1513676d43c02b85000200096469736b000003000873646100000400106469736b5f6f63746574730000060018000202020000000075887800000000005b6f3c000008000c1513676d43c06d1f0004000d6469736b5f6f7073000006001800020202000000000003cbbd000000000001c0510008000c1513676d43c08b6a0004000e6469736b5f74696d65000006001800020202000000000000003f00000000000001720008000c1513676d43c0a5fb000400106469736b5f6d65726765640000060018000202020000000000001285000000000000f8010008000c1513676d43c0c8b4000300097364613100000400106469736b5f6f63746574730000060018000202020000000001107c000000000000003c00", "0000000e6c6f63616c686f7374000008000c1513676d43c0d00a0009000c0000000280000000000200096469736b000003000973646131000004000d6469736b5f6f7073000006001800020202000000000000029b00000000000000080008000c1513676d43c0d7b20004000e6469736b5f74696d650000060018000202020000000000000004000000000000000f0008000c1513676d43c0df73000400106469736b5f6d65726765640000060018000202020000000000000fb400000000000000010008000c1513676d43c0f87c000300097364613200000400106469736b5f6f6374657473000006001800020202000000000000080000000000000000000008000c1513676d43c1003e0004000d6469736b5f6f7073000006001800020202000000000000000200000000000000000008000c1513676d43c107bf000400106469736b5f6d6572676564000006001800020202000000000000000000000000000000000008000c1513676d43c12fa40003000973646135000004000d6469736b5f6f7073000006001800020202000000000003c867000000000001aef20008000c1513676d43c13d5e000400106469736b5f6d657267656400000600180002020200000000000002d1000000000000f8000008000c1513676d43c136a90004000e6469736b5f74696d65000006001800020202000000000000003f000000000000011c0008000c1513676d43c1740500030009646d2d3000000400106469736b5f6f63746574730000060018000202020000000074596400000000005b6f00000008000c1513676d43c179c70004000d6469736b5f6f7073000006001800020202000000000003cae4000000000002b0f30008000c1513676d43c18abe000400106469736b5f6d6572676564000006001800020202000000000000000000000000000000000008000c1513676d43c181b90004000e6469736b5f74696d650000060018000202020000000000000040000000000000013e0008000c1513676d43c1a95e00030009646d2d3100000400106469736b5f6f637465747300000600180002020200000000000e000000000000000000000008000c1513676d43c1b7ea0004000e6469736b5f74696d65000006001800020202000000000000000200000000000000000008000c1513676d43c1b03e0004000d6469736b5f6f707300000600180002020200000000000000e000000000000000000008000c1513676d43c1c00d000400106469736b5f6d6572676564000006001800020202000000000000000000000000000000000008000c1513676d43c12818000300097364613500000400106469736b5f6f637465747300000600180002020200000000746c6400000000005b6f00000008000c1513676d43d320a80002000c62617474657279000003000630000004000b636861726765000006000f0001018fc2f5285c2f58400008000c1513676d43d36fd60004000c63757272656e74000006000f00010100000000000000800008000c1513676d43d3cdb60004000c766f6c74616765000006000f000101736891ed7cbf28400008000c1513676d43d59dd60002000869727100000300050000040008697271000005000630000006000f00010200000000000000110008000c1513676d43d5d2cf0005000631000006000f00010200000000000000100008000c1513676d43d5fe820005000638000006000f00010200000000000000010008000c1513676d43d635440005000639000006000f00010200000000000035210008000c1513676d43d66265000500073132000006000f0001020000000000000790", "0000000e6c6f63616c686f7374000008000c1513676d43d68e940009000c000000028000000000020008697271000004000869727100000500073136000006000f00010200000000000000210008000c1513676d43d69be20002000a7573657273000004000a75736572730000050005000006000f00010100000000000010400008000c1513676d43d6aa5d00020008697271000004000869727100000500073233000006000f00010200000000000000250008000c1513676d43d6c7dc000500073431000006000f000102000000000000ff7d0008000c1513676d43d6e23d000500073432000006000f00010200000000000008070008000c1513676d43d9aa3a000500073437000006000f0001020000000000079a260008000c1513676d43d9cca9000500073438000006000f00010200000000000000c70008000c1513676d43d9ea5d000500073439000006000f00010200000000000004c20008000c1513676d43da050e000500073530000006000f000102000000000000001c0008000c1513676d43da1efa000500084e4d49000006000f00010200000000000000000008000c1513676d43da3c82000500084c4f43000006000f000102000000000018d3080008000c1513676d43da544e00050008535055000006000f00010200000000000000000008000c1513676d43da6cca00050008504d49000006000f00010200000000000000000008000c1513676d43da885400050008495749000006000f000102000000000000a9da0008000c1513676d43daa23a00050008525452000006000f00010200000000000000030008000c1513676d43dabaed00050008524553000006000f00010200000000000ac8360008000c1513676d43dad4150005000843414c000006000f000102000000000000191f0008000c1513676d43daeef300050008544c42000006000f000102000000000003dbdc0008000c1513676d43db11410005000854524d000006000f00010200000000000000000008000c1513676d43db292c00050008544852000006000f00010200000000000000000008000c1513676d43db411d000500084d4345000006000f00010200000000000000000008000c1513676d43db5b59000500084d4350000006000f000102000000000000003c0008000c1513676d43db680100050008455252000006000f00010200000000000000000008000c1513676d43db758a000500084d4953000006000f00010200000000000000000008000c1513676d43dd2e800002000b6d656d6f7279000004000b6d656d6f7279000005000975736564000006000f00010100000000febbe0410008000c1513676d43dd3f4b0005000d6275666665726564000006000f0001010000000070fbc8410008000c1513676d43dd48700005000b636163686564000006000f00010100000000c008df410008000c1513676d43dd51c60005000966726565000006000f00010100000080481d05420008000c1513676d43dec7e30002000973776170000004000973776170000005000975736564000006000f00010100000000000000000008000c1513676d43ded4490005000966726565000006000f00010100000080ff610f420008000c1513676d43dedcfd0005000b636163686564000006000f00010100000000000000000008000c1513676d43d715e300020008697271000004000869727100000500073434000006000f0001020000000000031b610008000c1513676d43d73116000500073435000006000f00010200000000000000180008000c1513676d43ee00150002000973776170000004000c737761705f696f0000050007696e000006000f0001020000000000000000", } var rawData [][]byte for _, str := range strs { buf, err := hex.DecodeString(str) if err != nil { panic(err) } rawData = append(rawData, buf) } return rawData }() func TestParseInt(t *testing.T) { want := uint64(8231) got, err := parseInt([]byte{0, 0, 0, 0, 0, 0, 0x20, 0x27}) if err != nil { t.Error(err) } else if got != want { t.Errorf("got %d, want %d", got, want) } got, err = parseInt([]byte{0, 0, 0, 0, 0, 0x20, 0x27}) if err == nil { t.Errorf("got (%d, nil), want (0, ErrorInvalid)", got) } got, err = parseInt([]byte{0, 0, 0, 0, 0, 0, 0, 0x20, 0x27}) if err == nil { t.Errorf("got (%d, nil), want (0, ErrorInvalid)", got) } } func TestParseString(t *testing.T) { want := "test" got, err := parseString([]byte{'t', 'e', 's', 't', 0}) if err != nil { t.Error(err) } else if got != want { t.Errorf("got %q, want %q", got, want) } got, err = parseString([]byte{'t', 'e', 's', 't'}) if err == nil { t.Errorf("got (%q, nil), want (\"\", ErrorInvalid)", got) } } golang-collectd-0.0~git20150630/network/server.go000066400000000000000000000045021254451145600214700ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "log" "net" "collectd.org/api" ) // ListenAndWrite listens on the provided UDP address, parses the received // packets and writes them to the provided api.Writer. // This is a convenience function for a minimally configured server. If you // need more control, see the "Server" type below. func ListenAndWrite(address string, d api.Writer) error { srv := &Server{ Addr: address, Writer: d, } return srv.ListenAndWrite() } // Server holds parameters for running a collectd server. type Server struct { Addr string // UDP address to listen on. Writer api.Writer // Object used to send incoming ValueLists to. BufferSize uint16 // Maximum packet size to accept. PasswordLookup PasswordLookup // User to password lookup. SecurityLevel SecurityLevel // Minimal required security level // Interface is the name of the interface to use when subscribing to a // multicast group. Has no effect when using unicast. Interface string } // ListenAndWrite listens on the provided UDP address, parses the received // packets and writes them to the provided api.Writer. func (srv *Server) ListenAndWrite() error { addr := srv.Addr if addr == "" { addr = ":" + DefaultService } laddr, err := net.ResolveUDPAddr("udp", srv.Addr) if err != nil { return err } var sock *net.UDPConn if laddr.IP.IsMulticast() { var ifi *net.Interface if srv.Interface != "" { if ifi, err = net.InterfaceByName(srv.Interface); err != nil { return err } } sock, err = net.ListenMulticastUDP("udp", ifi, laddr) } else { sock, err = net.ListenUDP("udp", laddr) } if err != nil { return err } defer sock.Close() if srv.BufferSize <= 0 { srv.BufferSize = DefaultBufferSize } buf := make([]byte, srv.BufferSize) popts := ParseOpts{ PasswordLookup: srv.PasswordLookup, SecurityLevel: srv.SecurityLevel, } for { n, err := sock.Read(buf) if err != nil { return err } valueLists, err := Parse(buf[:n], popts) if err != nil { log.Printf("error while parsing: %v", err) continue } go dispatch(valueLists, srv.Writer) } } func dispatch(valueLists []api.ValueList, d api.Writer) { for _, vl := range valueLists { if err := d.Write(vl); err != nil { log.Printf("error while dispatching: %v", err) } } } golang-collectd-0.0~git20150630/network/server_test.go000066400000000000000000000016761254451145600225400ustar00rootroot00000000000000package network // import "collectd.org/network" import ( "log" "net" "os" "collectd.org/format" ) // This example demonstrates how to listen to encrypted network traffic and // dump it to STDOUT using format.Putval. func ExampleServer_ListenAndWrite() { srv := &Server{ Addr: net.JoinHostPort("::", DefaultService), Writer: format.NewPutval(os.Stdout), PasswordLookup: NewAuthFile("/etc/collectd/users"), } // blocks log.Fatal(srv.ListenAndWrite()) } // This example demonstrates how to forward received IPv6 multicast traffic to // a unicast address, using PSK encryption. func ExampleListenAndWrite() { opts := ClientOptions{ SecurityLevel: Encrypt, Username: "collectd", Password: "dieXah7e", } client, err := Dial(net.JoinHostPort("example.com", DefaultService), opts) if err != nil { log.Fatal(err) } defer client.Close() // blocks log.Fatal(ListenAndWrite(":"+DefaultService, client)) }