├── .gitignore ├── LICENSE ├── Make.protobuf ├── Makefile ├── README.md ├── client.go ├── event.go ├── marshal.go ├── proto ├── Makefile ├── proto.pb.go └── proto.proto ├── state.go └── transport.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Christopher Gilbert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Make.protobuf: -------------------------------------------------------------------------------- 1 | %.pb.go: %.proto 2 | protoc --go_out=. $< -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: install 2 | 3 | install: 4 | go install 5 | go install ./proto 6 | 7 | test: 8 | go test 9 | 10 | clean: 11 | go clean ./... 12 | 13 | nuke: 14 | go clean -i ./... 15 | 16 | regenerate: 17 | make -C proto regenerate 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Riemann client (Golang) 2 | 3 | [![GoDoc](https://godoc.org/github.com/bigdatadev/goryman?status.png)](http://godoc.org/github.com/bigdatadev/goryman) 4 | 5 | ## Introduction 6 | 7 | Go client library for [Riemann](https://github.com/aphyr/riemann). 8 | 9 | This client library was inspired by [Raidman](https://github.com/amir/raidman), and [Riemann NodeJS Client](https://github.com/perezd/riemann-nodejs-client). 10 | 11 | Features: 12 | * Idiomatic concurrency 13 | * Sending events, state updates, queries. 14 | * Feature parity with the reference implementation written in Ruby. 15 | 16 | ## Installation 17 | 18 | To install the package for use in your own programs: 19 | 20 | ``` 21 | go get github.com/bigdatadev/goryman 22 | ``` 23 | 24 | If you're a developer, Riemann uses [Google Protocol Buffers](https://github.com/golang/protobuf), so make sure that's installed and available on your PATH. 25 | 26 | ``` 27 | go get github.com/golang/protobuf/{proto,protoc-gen-go} 28 | ``` 29 | 30 | ## Getting Started 31 | 32 | First we'll need to import the library: 33 | 34 | ```go 35 | import ( 36 | "github.com/bigdatadev/goryman" 37 | ) 38 | ``` 39 | 40 | Next we'll need to establish a new client: 41 | 42 | ```go 43 | c := goryman.NewGorymanClient("localhost:5555") 44 | err := c.Connect() 45 | if err != nil { 46 | panic(err) 47 | } 48 | ``` 49 | 50 | Don't forget to close the client connection when you're done: 51 | 52 | ```go 53 | defer c.Close() 54 | ``` 55 | 56 | Just like the Riemann Ruby client, the client sends small events over UDP by default. TCP is used for queries, and large events. There is no acknowledgement of UDP packets, but they are roughly an order of magnitude faster than TCP. We assume both TCP and UDP are listening on the same port. 57 | 58 | Sending events is easy ([list of valid event properties](http://aphyr.github.com/riemann/concepts.html)): 59 | 60 | ```go 61 | err = c.SendEvent(&goryman.Event{ 62 | Service: "moargore", 63 | Metric: 100, 64 | Tags: []string{"nonblocking"}, 65 | }) 66 | if err != nil { 67 | panic(err) 68 | } 69 | ``` 70 | 71 | You can also query events: 72 | 73 | ```go 74 | events, err := c.QueryEvents("host = \"goryman\"") 75 | if err != nil { 76 | panic(err) 77 | } 78 | ``` 79 | 80 | The Hostname and Time in events will automatically be replaced with the hostname of the server and the current time if none is specified. 81 | 82 | ## Integrations 83 | 84 | Martini: [GoryMartini](http://github.com/bigdatadev/gorymartini) 85 | 86 | ## Contributing 87 | 88 | Just send me a pull request. Please take a look at the project issues and see how you can help. Here are some tips: 89 | - please add more tests. 90 | - please check your syntax. 91 | 92 | ## Author 93 | 94 | Christopher Gilbert 95 | 96 | * Web: [http://cjgilbert.me](http://cjgilbert.me) 97 | * Twitter: [@bigdatadev](https://twitter.com/bigdatadev) 98 | * Linkedin: [/in/christophergilbert](https://www.linkedin.com/in/christophergilbert) 99 | 100 | ## Copyright 101 | 102 | See [LICENSE](LICENSE) document -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // A Riemann client for Go, featuring concurrency, sending events and state updates, queries, 2 | // and feature parity with the reference implementation written in Ruby. 3 | // 4 | // Copyright (C) 2014 by Christopher Gilbert 5 | package goryman 6 | 7 | import ( 8 | "net" 9 | "time" 10 | 11 | pb "github.com/golang/protobuf/proto" 12 | "github.com/bigdatadev/goryman/proto" 13 | ) 14 | 15 | // GorymanClient is a client library to send events to Riemann 16 | type GorymanClient struct { 17 | udp *UdpTransport 18 | tcp *TcpTransport 19 | addr string 20 | } 21 | 22 | // NewGorymanClient - Factory 23 | func NewGorymanClient(addr string) *GorymanClient { 24 | return &GorymanClient{ 25 | addr: addr, 26 | } 27 | } 28 | 29 | // Connect creates a UDP and TCP connection to a Riemann server 30 | func (c *GorymanClient) Connect() error { 31 | udp, err := net.DialTimeout("udp", c.addr, time.Second*5) 32 | if err != nil { 33 | return err 34 | } 35 | tcp, err := net.DialTimeout("tcp", c.addr, time.Second*5) 36 | if err != nil { 37 | return err 38 | } 39 | c.udp = NewUdpTransport(udp) 40 | c.tcp = NewTcpTransport(tcp) 41 | return nil 42 | } 43 | 44 | // Close the connection to Riemann 45 | func (c *GorymanClient) Close() error { 46 | if nil == c.udp && nil == c.tcp { 47 | return nil 48 | } 49 | err := c.udp.Close() 50 | if err != nil { 51 | return err 52 | } 53 | return c.tcp.Close() 54 | } 55 | 56 | // Send an event 57 | func (c *GorymanClient) SendEvent(e *Event) error { 58 | epb, err := EventToProtocolBuffer(e) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | message := &proto.Msg{} 64 | message.Events = append(message.Events, epb) 65 | 66 | _, err = c.sendMaybeRecv(message) 67 | return err 68 | } 69 | 70 | // Send a state update 71 | func (c *GorymanClient) SendState(s *State) error { 72 | spb, err := StateToProtocolBuffer(s) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | message := &proto.Msg{} 78 | message.States = append(message.States, spb) 79 | 80 | _, err = c.sendMaybeRecv(message) 81 | return err 82 | } 83 | 84 | // Query the server for events 85 | func (c *GorymanClient) QueryEvents(q string) ([]Event, error) { 86 | query := &proto.Query{} 87 | query.String_ = pb.String(q) 88 | 89 | message := &proto.Msg{} 90 | message.Query = query 91 | 92 | response, err := c.sendRecv(message) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | return ProtocolBuffersToEvents(response.GetEvents()), nil 98 | } 99 | 100 | // Send and receive data from Riemann 101 | func (c *GorymanClient) sendRecv(m *proto.Msg) (*proto.Msg, error) { 102 | return c.tcp.SendRecv(m) 103 | } 104 | 105 | // Send and maybe receive data from Riemann 106 | func (c *GorymanClient) sendMaybeRecv(m *proto.Msg) (*proto.Msg, error) { 107 | _, err := c.udp.SendMaybeRecv(m) 108 | if err != nil { 109 | return c.tcp.SendMaybeRecv(m) 110 | } 111 | return nil, nil 112 | } 113 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package goryman 2 | 3 | import () 4 | 5 | // Event is a wrapper for Riemann events 6 | type Event struct { 7 | Ttl float32 8 | Time int64 9 | Tags []string 10 | Host string // Defaults to os.Hostname() 11 | State string 12 | Service string 13 | Metric interface{} // Could be Int, Float32, Float64 14 | Description string 15 | Attributes map[string]string 16 | } 17 | -------------------------------------------------------------------------------- /marshal.go: -------------------------------------------------------------------------------- 1 | package goryman 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "time" 8 | 9 | pb "github.com/golang/protobuf/proto" 10 | "github.com/bigdatadev/goryman/proto" 11 | ) 12 | 13 | // EventToProtocolBuffer converts an Event type to a proto.Event 14 | func EventToProtocolBuffer(event *Event) (*proto.Event, error) { 15 | if event.Host == "" { 16 | event.Host, _ = os.Hostname() 17 | } 18 | if event.Time == 0 { 19 | event.Time = time.Now().Unix() 20 | } 21 | 22 | var e proto.Event 23 | t := reflect.ValueOf(&e).Elem() 24 | s := reflect.ValueOf(event).Elem() 25 | typeOfEvent := s.Type() 26 | 27 | for i := 0; i < s.NumField(); i++ { 28 | f := s.Field(i) 29 | value := reflect.ValueOf(f.Interface()) 30 | if reflect.Zero(f.Type()) != value && f.Interface() != nil { 31 | name := typeOfEvent.Field(i).Name 32 | switch name { 33 | case "State", "Service", "Host", "Description": 34 | tmp := reflect.ValueOf(pb.String(value.String())) 35 | t.FieldByName(name).Set(tmp) 36 | case "Ttl": 37 | tmp := reflect.ValueOf(pb.Float32(float32(value.Float()))) 38 | t.FieldByName(name).Set(tmp) 39 | case "Time": 40 | tmp := reflect.ValueOf(pb.Int64(value.Int())) 41 | t.FieldByName(name).Set(tmp) 42 | case "Tags": 43 | tmp := reflect.ValueOf(value.Interface().([]string)) 44 | t.FieldByName(name).Set(tmp) 45 | case "Metric": 46 | switch reflect.TypeOf(f.Interface()).Kind() { 47 | case reflect.Int: 48 | tmp := reflect.ValueOf(pb.Int64(int64(value.Int()))) 49 | t.FieldByName("MetricSint64").Set(tmp) 50 | case reflect.Float32: 51 | tmp := reflect.ValueOf(pb.Float32(float32(value.Float()))) 52 | t.FieldByName("MetricF").Set(tmp) 53 | case reflect.Float64: 54 | tmp := reflect.ValueOf(pb.Float64(value.Float())) 55 | t.FieldByName("MetricD").Set(tmp) 56 | default: 57 | return nil, fmt.Errorf("Metric of invalid type (type %v)", 58 | reflect.TypeOf(f.Interface()).Kind()) 59 | } 60 | case "Attributes": 61 | var attrs []*proto.Attribute 62 | for k, v := range value.Interface().(map[string]string) { 63 | k_, v_ := k, v 64 | attrs = append(attrs, &proto.Attribute{ 65 | Key: &k_, 66 | Value: &v_, 67 | }) 68 | } 69 | t.FieldByName(name).Set(reflect.ValueOf(attrs)) 70 | } 71 | } 72 | } 73 | return &e, nil 74 | } 75 | 76 | // StateToProtocolBuffer converts a State type to a proto.State 77 | func StateToProtocolBuffer(state *State) (*proto.State, error) { 78 | if state.Host == "" { 79 | state.Host, _ = os.Hostname() 80 | } 81 | if state.Time == 0 { 82 | state.Time = time.Now().Unix() 83 | } 84 | 85 | var e proto.State 86 | t := reflect.ValueOf(&e).Elem() 87 | s := reflect.ValueOf(state).Elem() 88 | typeOfEvent := s.Type() 89 | 90 | for i := 0; i < s.NumField(); i++ { 91 | f := s.Field(i) 92 | value := reflect.ValueOf(f.Interface()) 93 | if reflect.Zero(f.Type()) != value && f.Interface() != nil { 94 | name := typeOfEvent.Field(i).Name 95 | switch name { 96 | case "State", "Service", "Host", "Description": 97 | tmp := reflect.ValueOf(pb.String(value.String())) 98 | t.FieldByName(name).Set(tmp) 99 | case "Once": 100 | tmp := reflect.ValueOf(pb.Bool(bool(value.Bool()))) 101 | t.FieldByName(name).Set(tmp) 102 | case "Ttl": 103 | tmp := reflect.ValueOf(pb.Float32(float32(value.Float()))) 104 | t.FieldByName(name).Set(tmp) 105 | case "Time": 106 | tmp := reflect.ValueOf(pb.Int64(value.Int())) 107 | t.FieldByName(name).Set(tmp) 108 | case "Tags": 109 | tmp := reflect.ValueOf(value.Interface().([]string)) 110 | t.FieldByName(name).Set(tmp) 111 | case "Metric": 112 | switch reflect.TypeOf(f.Interface()).Kind() { 113 | case reflect.Int: 114 | tmp := reflect.ValueOf(pb.Int64(int64(value.Int()))) 115 | t.FieldByName("MetricSint64").Set(tmp) 116 | case reflect.Float32: 117 | tmp := reflect.ValueOf(pb.Float32(float32(value.Float()))) 118 | t.FieldByName("MetricF").Set(tmp) 119 | case reflect.Float64: 120 | tmp := reflect.ValueOf(pb.Float64(value.Float())) 121 | t.FieldByName("MetricD").Set(tmp) 122 | default: 123 | return nil, fmt.Errorf("Metric of invalid type (type %v)", 124 | reflect.TypeOf(f.Interface()).Kind()) 125 | } 126 | case "Attributes": 127 | var attrs []*proto.Attribute 128 | for k, v := range value.Interface().(map[string]string) { 129 | k_, v_ := k, v 130 | attrs = append(attrs, &proto.Attribute{ 131 | Key: &k_, 132 | Value: &v_, 133 | }) 134 | } 135 | t.FieldByName(name).Set(reflect.ValueOf(attrs)) 136 | } 137 | } 138 | } 139 | return &e, nil 140 | } 141 | 142 | // ProtocolBuffersToEvents converts an array of proto.Event to an array of Event 143 | func ProtocolBuffersToEvents(pbEvents []*proto.Event) []Event { 144 | var events []Event 145 | for _, event := range pbEvents { 146 | e := Event{ 147 | State: event.GetState(), 148 | Service: event.GetService(), 149 | Host: event.GetHost(), 150 | Description: event.GetDescription(), 151 | Ttl: event.GetTtl(), 152 | Time: event.GetTime(), 153 | Tags: event.GetTags(), 154 | } 155 | if event.MetricF != nil { 156 | e.Metric = event.GetMetricF() 157 | } else if event.MetricD != nil { 158 | e.Metric = event.GetMetricD() 159 | } else { 160 | e.Metric = event.GetMetricSint64() 161 | } 162 | if event.Attributes != nil { 163 | e.Attributes = make(map[string]string, len(event.GetAttributes())) 164 | for _, attr := range event.GetAttributes() { 165 | e.Attributes[attr.GetKey()] = attr.GetValue() 166 | } 167 | } 168 | events = append(events, e) 169 | } 170 | return events 171 | } 172 | -------------------------------------------------------------------------------- /proto/Makefile: -------------------------------------------------------------------------------- 1 | include ../Make.protobuf 2 | 3 | all: regenerate 4 | 5 | regenerate: 6 | rm -f proto.pb.go 7 | make proto.pb.go 8 | -------------------------------------------------------------------------------- /proto/proto.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: proto.proto 3 | // DO NOT EDIT! 4 | 5 | package proto 6 | 7 | import proto1 "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto1.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type State struct { 17 | Time *int64 `protobuf:"varint,1,opt,name=time" json:"time,omitempty"` 18 | State *string `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` 19 | Service *string `protobuf:"bytes,3,opt,name=service" json:"service,omitempty"` 20 | Host *string `protobuf:"bytes,4,opt,name=host" json:"host,omitempty"` 21 | Description *string `protobuf:"bytes,5,opt,name=description" json:"description,omitempty"` 22 | Once *bool `protobuf:"varint,6,opt,name=once" json:"once,omitempty"` 23 | Tags []string `protobuf:"bytes,7,rep,name=tags" json:"tags,omitempty"` 24 | Ttl *float32 `protobuf:"fixed32,8,opt,name=ttl" json:"ttl,omitempty"` 25 | XXX_unrecognized []byte `json:"-"` 26 | } 27 | 28 | func (this *State) Reset() { *this = State{} } 29 | func (this *State) String() string { return proto1.CompactTextString(this) } 30 | func (*State) ProtoMessage() {} 31 | 32 | func (this *State) GetTime() int64 { 33 | if this != nil && this.Time != nil { 34 | return *this.Time 35 | } 36 | return 0 37 | } 38 | 39 | func (this *State) GetState() string { 40 | if this != nil && this.State != nil { 41 | return *this.State 42 | } 43 | return "" 44 | } 45 | 46 | func (this *State) GetService() string { 47 | if this != nil && this.Service != nil { 48 | return *this.Service 49 | } 50 | return "" 51 | } 52 | 53 | func (this *State) GetHost() string { 54 | if this != nil && this.Host != nil { 55 | return *this.Host 56 | } 57 | return "" 58 | } 59 | 60 | func (this *State) GetDescription() string { 61 | if this != nil && this.Description != nil { 62 | return *this.Description 63 | } 64 | return "" 65 | } 66 | 67 | func (this *State) GetOnce() bool { 68 | if this != nil && this.Once != nil { 69 | return *this.Once 70 | } 71 | return false 72 | } 73 | 74 | func (this *State) GetTags() []string { 75 | if this != nil { 76 | return this.Tags 77 | } 78 | return nil 79 | } 80 | 81 | func (this *State) GetTtl() float32 { 82 | if this != nil && this.Ttl != nil { 83 | return *this.Ttl 84 | } 85 | return 0 86 | } 87 | 88 | type Event struct { 89 | Time *int64 `protobuf:"varint,1,opt,name=time" json:"time,omitempty"` 90 | State *string `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` 91 | Service *string `protobuf:"bytes,3,opt,name=service" json:"service,omitempty"` 92 | Host *string `protobuf:"bytes,4,opt,name=host" json:"host,omitempty"` 93 | Description *string `protobuf:"bytes,5,opt,name=description" json:"description,omitempty"` 94 | Tags []string `protobuf:"bytes,7,rep,name=tags" json:"tags,omitempty"` 95 | Ttl *float32 `protobuf:"fixed32,8,opt,name=ttl" json:"ttl,omitempty"` 96 | Attributes []*Attribute `protobuf:"bytes,9,rep,name=attributes" json:"attributes,omitempty"` 97 | MetricSint64 *int64 `protobuf:"zigzag64,13,opt,name=metric_sint64" json:"metric_sint64,omitempty"` 98 | MetricD *float64 `protobuf:"fixed64,14,opt,name=metric_d" json:"metric_d,omitempty"` 99 | MetricF *float32 `protobuf:"fixed32,15,opt,name=metric_f" json:"metric_f,omitempty"` 100 | XXX_unrecognized []byte `json:"-"` 101 | } 102 | 103 | func (this *Event) Reset() { *this = Event{} } 104 | func (this *Event) String() string { return proto1.CompactTextString(this) } 105 | func (*Event) ProtoMessage() {} 106 | 107 | func (this *Event) GetTime() int64 { 108 | if this != nil && this.Time != nil { 109 | return *this.Time 110 | } 111 | return 0 112 | } 113 | 114 | func (this *Event) GetState() string { 115 | if this != nil && this.State != nil { 116 | return *this.State 117 | } 118 | return "" 119 | } 120 | 121 | func (this *Event) GetService() string { 122 | if this != nil && this.Service != nil { 123 | return *this.Service 124 | } 125 | return "" 126 | } 127 | 128 | func (this *Event) GetHost() string { 129 | if this != nil && this.Host != nil { 130 | return *this.Host 131 | } 132 | return "" 133 | } 134 | 135 | func (this *Event) GetDescription() string { 136 | if this != nil && this.Description != nil { 137 | return *this.Description 138 | } 139 | return "" 140 | } 141 | 142 | func (this *Event) GetTags() []string { 143 | if this != nil { 144 | return this.Tags 145 | } 146 | return nil 147 | } 148 | 149 | func (this *Event) GetTtl() float32 { 150 | if this != nil && this.Ttl != nil { 151 | return *this.Ttl 152 | } 153 | return 0 154 | } 155 | 156 | func (this *Event) GetAttributes() []*Attribute { 157 | if this != nil { 158 | return this.Attributes 159 | } 160 | return nil 161 | } 162 | 163 | func (this *Event) GetMetricSint64() int64 { 164 | if this != nil && this.MetricSint64 != nil { 165 | return *this.MetricSint64 166 | } 167 | return 0 168 | } 169 | 170 | func (this *Event) GetMetricD() float64 { 171 | if this != nil && this.MetricD != nil { 172 | return *this.MetricD 173 | } 174 | return 0 175 | } 176 | 177 | func (this *Event) GetMetricF() float32 { 178 | if this != nil && this.MetricF != nil { 179 | return *this.MetricF 180 | } 181 | return 0 182 | } 183 | 184 | type Query struct { 185 | String_ *string `protobuf:"bytes,1,opt,name=string" json:"string,omitempty"` 186 | XXX_unrecognized []byte `json:"-"` 187 | } 188 | 189 | func (this *Query) Reset() { *this = Query{} } 190 | func (this *Query) String() string { return proto1.CompactTextString(this) } 191 | func (*Query) ProtoMessage() {} 192 | 193 | func (this *Query) GetString_() string { 194 | if this != nil && this.String_ != nil { 195 | return *this.String_ 196 | } 197 | return "" 198 | } 199 | 200 | type Msg struct { 201 | Ok *bool `protobuf:"varint,2,opt,name=ok" json:"ok,omitempty"` 202 | Error *string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"` 203 | States []*State `protobuf:"bytes,4,rep,name=states" json:"states,omitempty"` 204 | Query *Query `protobuf:"bytes,5,opt,name=query" json:"query,omitempty"` 205 | Events []*Event `protobuf:"bytes,6,rep,name=events" json:"events,omitempty"` 206 | XXX_unrecognized []byte `json:"-"` 207 | } 208 | 209 | func (this *Msg) Reset() { *this = Msg{} } 210 | func (this *Msg) String() string { return proto1.CompactTextString(this) } 211 | func (*Msg) ProtoMessage() {} 212 | 213 | func (this *Msg) GetOk() bool { 214 | if this != nil && this.Ok != nil { 215 | return *this.Ok 216 | } 217 | return false 218 | } 219 | 220 | func (this *Msg) GetError() string { 221 | if this != nil && this.Error != nil { 222 | return *this.Error 223 | } 224 | return "" 225 | } 226 | 227 | func (this *Msg) GetStates() []*State { 228 | if this != nil { 229 | return this.States 230 | } 231 | return nil 232 | } 233 | 234 | func (this *Msg) GetQuery() *Query { 235 | if this != nil { 236 | return this.Query 237 | } 238 | return nil 239 | } 240 | 241 | func (this *Msg) GetEvents() []*Event { 242 | if this != nil { 243 | return this.Events 244 | } 245 | return nil 246 | } 247 | 248 | type Attribute struct { 249 | Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` 250 | Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` 251 | XXX_unrecognized []byte `json:"-"` 252 | } 253 | 254 | func (this *Attribute) Reset() { *this = Attribute{} } 255 | func (this *Attribute) String() string { return proto1.CompactTextString(this) } 256 | func (*Attribute) ProtoMessage() {} 257 | 258 | func (this *Attribute) GetKey() string { 259 | if this != nil && this.Key != nil { 260 | return *this.Key 261 | } 262 | return "" 263 | } 264 | 265 | func (this *Attribute) GetValue() string { 266 | if this != nil && this.Value != nil { 267 | return *this.Value 268 | } 269 | return "" 270 | } 271 | 272 | func init() { 273 | } -------------------------------------------------------------------------------- /proto/proto.proto: -------------------------------------------------------------------------------- 1 | option java_package = "com.aphyr.riemann"; 2 | option java_outer_classname = "Proto"; 3 | 4 | message State { 5 | optional int64 time = 1; 6 | optional string state = 2; 7 | optional string service = 3; 8 | optional string host = 4; 9 | optional string description = 5; 10 | optional bool once = 6; 11 | repeated string tags = 7; 12 | optional float ttl = 8; 13 | optional float metric_f = 15; 14 | } 15 | 16 | message Event { 17 | optional int64 time = 1; 18 | optional string state = 2; 19 | optional string service = 3; 20 | optional string host = 4; 21 | optional string description = 5; 22 | repeated string tags = 7; 23 | optional float ttl = 8; 24 | optional float metric_f = 15; 25 | } 26 | 27 | message Query { 28 | optional string string = 1; 29 | } 30 | 31 | message Msg { 32 | optional bool ok = 2; 33 | optional string error = 3; 34 | repeated State states = 4; 35 | optional Query query = 5; 36 | repeated Event events = 6; 37 | } -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package goryman 2 | 3 | import () 4 | 5 | // State is a wrapper for Riemann states 6 | type State struct { 7 | Ttl float32 8 | Time int64 9 | Tags []string 10 | Host string // Defaults to os.Hostname() 11 | State string 12 | Service string 13 | Once bool 14 | Metric interface{} // Could be Int, Float32, Float64 15 | Description string 16 | Attributes map[string]string 17 | } 18 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | package goryman 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | 11 | pb "github.com/golang/protobuf/proto" 12 | "github.com/bigdatadev/goryman/proto" 13 | ) 14 | 15 | // Transport is an interface to a generic transport used by the client 16 | type Transport interface { 17 | SendRecv(message *proto.Msg) (*proto.Msg, error) 18 | SendMaybeRecv(message *proto.Msg) (*proto.Msg, error) 19 | } 20 | 21 | // TcpTransport is a type that implements the Transport interface 22 | type TcpTransport struct { 23 | conn net.Conn 24 | requestQueue chan request 25 | } 26 | 27 | // UdpTransport is a type that implements the Transport interface 28 | type UdpTransport struct { 29 | conn net.Conn 30 | requestQueue chan request 31 | } 32 | 33 | // request encapsulates a request to send to the Riemann server 34 | type request struct { 35 | message *proto.Msg 36 | response_ch chan response 37 | } 38 | 39 | // response encapsulates a response from the Riemann server 40 | type response struct { 41 | message *proto.Msg 42 | err error 43 | } 44 | 45 | // MAX_UDP_SIZE is the maximum allowed size of a UDP packet before automatically failing the send 46 | const MAX_UDP_SIZE = 16384 47 | 48 | // NewTcpTransport - Factory 49 | func NewTcpTransport(conn net.Conn) *TcpTransport { 50 | t := &TcpTransport{ 51 | conn: conn, 52 | requestQueue: make(chan request), 53 | } 54 | go t.runRequestQueue() 55 | return t 56 | } 57 | 58 | // NewUdpTransport - Factory 59 | func NewUdpTransport(conn net.Conn) *UdpTransport { 60 | t := &UdpTransport{ 61 | conn: conn, 62 | requestQueue: make(chan request), 63 | } 64 | go t.runRequestQueue() 65 | return t 66 | } 67 | 68 | // TcpTransport implementation of SendRecv, queues a request to send a message to the server 69 | func (t *TcpTransport) SendRecv(message *proto.Msg) (*proto.Msg, error) { 70 | response_ch := make(chan response) 71 | t.requestQueue <- request{message, response_ch} 72 | r := <-response_ch 73 | return r.message, r.err 74 | } 75 | 76 | // TcpTransport implementation of SendMaybeRecv, queues a request to send a message to the server 77 | func (t *TcpTransport) SendMaybeRecv(message *proto.Msg) (*proto.Msg, error) { 78 | return t.SendRecv(message) 79 | } 80 | 81 | // Close will close the TcpTransport 82 | func (t *TcpTransport) Close() error { 83 | close(t.requestQueue) 84 | err := t.conn.Close() 85 | if err != nil { 86 | return err 87 | } 88 | return nil 89 | } 90 | 91 | // runRequestQueue services the TcpTransport request queue 92 | func (t *TcpTransport) runRequestQueue() { 93 | for req := range t.requestQueue { 94 | message := req.message 95 | response_ch := req.response_ch 96 | 97 | msg, err := t.execRequest(message) 98 | 99 | response_ch <- response{msg, err} 100 | } 101 | } 102 | 103 | // execRequest will send a TCP message to Riemann 104 | func (t *TcpTransport) execRequest(message *proto.Msg) (*proto.Msg, error) { 105 | msg := &proto.Msg{} 106 | data, err := pb.Marshal(message) 107 | if err != nil { 108 | return msg, err 109 | } 110 | b := new(bytes.Buffer) 111 | if err = binary.Write(b, binary.BigEndian, uint32(len(data))); err != nil { 112 | return msg, err 113 | } 114 | if _, err = t.conn.Write(b.Bytes()); err != nil { 115 | return msg, err 116 | } 117 | if _, err = t.conn.Write(data); err != nil { 118 | return msg, err 119 | } 120 | var header uint32 121 | if err = binary.Read(t.conn, binary.BigEndian, &header); err != nil { 122 | return msg, err 123 | } 124 | response := make([]byte, header) 125 | if err = readMessages(t.conn, response); err != nil { 126 | return msg, err 127 | } 128 | if err = pb.Unmarshal(response, msg); err != nil { 129 | return msg, err 130 | } 131 | if msg.GetOk() != true { 132 | return msg, errors.New(msg.GetError()) 133 | } 134 | return msg, nil 135 | } 136 | 137 | // UdpTransport implementation of SendRecv, will automatically fail if called 138 | func (t *UdpTransport) SendRecv(message *proto.Msg) (*proto.Msg, error) { 139 | return nil, fmt.Errorf("udp doesn't support receiving acknowledgements") 140 | } 141 | 142 | // UdpTransport implementation of SendMaybeRecv, queues a request to send a message to the server 143 | func (t *UdpTransport) SendMaybeRecv(message *proto.Msg) (*proto.Msg, error) { 144 | response_ch := make(chan response) 145 | t.requestQueue <- request{message, response_ch} 146 | r := <-response_ch 147 | return r.message, r.err 148 | } 149 | 150 | // Close will close the UdpTransport 151 | func (t *UdpTransport) Close() error { 152 | close(t.requestQueue) 153 | err := t.conn.Close() 154 | if err != nil { 155 | return err 156 | } 157 | return nil 158 | } 159 | 160 | // runRequestQueue services the UdpTransport request queue 161 | func (t *UdpTransport) runRequestQueue() { 162 | for req := range t.requestQueue { 163 | message := req.message 164 | response_ch := req.response_ch 165 | 166 | msg, err := t.execRequest(message) 167 | 168 | response_ch <- response{msg, err} 169 | } 170 | } 171 | 172 | // execRequest will send a UDP message to Riemann 173 | func (t *UdpTransport) execRequest(message *proto.Msg) (*proto.Msg, error) { 174 | data, err := pb.Marshal(message) 175 | if err != nil { 176 | return nil, err 177 | } 178 | if len(data) > MAX_UDP_SIZE { 179 | return nil, fmt.Errorf("unable to send message, too large for udp") 180 | } 181 | if _, err = t.conn.Write(data); err != nil { 182 | return nil, err 183 | } 184 | return nil, nil 185 | } 186 | 187 | // readMessages will read Riemann messages from the TCP connection 188 | func readMessages(r io.Reader, p []byte) error { 189 | for len(p) > 0 { 190 | n, err := r.Read(p) 191 | p = p[n:] 192 | if err != nil { 193 | return err 194 | } 195 | } 196 | return nil 197 | } 198 | --------------------------------------------------------------------------------