add rabbit_mq_parser plugin
This commit is contained in:
parent
4fb9bf3c9e
commit
37450f125e
|
@ -55,6 +55,7 @@ import (
|
|||
_ "github.com/influxdata/telegraf/plugins/inputs/procstat"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/prometheus"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/puppetagent"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/rabbit_mq_parser"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/rabbitmq"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/raindrops"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/redis"
|
||||
|
|
|
@ -0,0 +1,663 @@
|
|||
package statsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/streadway/amqp"
|
||||
)
|
||||
|
||||
// RabbitMQParser is the top level struct for this plugin
|
||||
type RabbitMQParser struct {
|
||||
RabbitmqAddress string
|
||||
QueueName string
|
||||
Prefetch int
|
||||
|
||||
conn *amqp.Connection
|
||||
ch *amqp.Channel
|
||||
q amqp.Queue
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Description satisfies the telegraf.ServiceInput interface
|
||||
func (rmq *RabbitMQParser) Description() string {
|
||||
return "RabbitMQ client with specialized parser"
|
||||
}
|
||||
|
||||
// SampleConfig satisfies the telegraf.ServiceInput interface
|
||||
func (rmq *RabbitMQParser) SampleConfig() string {
|
||||
return `
|
||||
## Address and port for the rabbitmq server to pull from
|
||||
rabbitmq_address = "amqp://guest:guest@localhost:5672/"
|
||||
queue_name = "task_queue"
|
||||
prefetch = 1000
|
||||
`
|
||||
}
|
||||
|
||||
// Gather satisfies the telegraf.ServiceInput interface
|
||||
// All gathering is done in the Start function
|
||||
func (rmq *RabbitMQParser) Gather(_ telegraf.Accumulator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start satisfies the telegraf.ServiceInput interface
|
||||
// Yanked from "https://www.rabbitmq.com/tutorials/tutorial-two-go.html"
|
||||
func (rmq *RabbitMQParser) Start(acc telegraf.Accumulator) error {
|
||||
|
||||
// Create queue connection and assign it to RabbitMQParser
|
||||
conn, err := amqp.Dial(rmq.RabbitmqAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: Failed to connect to RabbitMQ", err)
|
||||
}
|
||||
rmq.conn = conn
|
||||
|
||||
// Create channel and assign it to RabbitMQParser
|
||||
ch, err := conn.Channel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: Failed to open a channel", err)
|
||||
}
|
||||
rmq.ch = ch
|
||||
|
||||
// Declare a queue and assign it to RabbitMQParser
|
||||
q, err := ch.QueueDeclare(rmq.QueueName, true, false, false, false, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: Failed to declare a queue", err)
|
||||
}
|
||||
rmq.q = q
|
||||
|
||||
// Declare QoS on queue
|
||||
err = ch.Qos(rmq.Prefetch, 0, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: failed to set Qos", err)
|
||||
}
|
||||
|
||||
// Register the RabbitMQ parser as a consumer of the queue
|
||||
// And start the lister passing in the Accumulator
|
||||
msgs := rmq.registerConsumer()
|
||||
go listen(msgs, acc)
|
||||
|
||||
// Log that service has started
|
||||
log.Println("Starting RabbitMQ service...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Yanked from "https://www.rabbitmq.com/tutorials/tutorial-two-go.html"
|
||||
func (rmq *RabbitMQParser) registerConsumer() <-chan amqp.Delivery {
|
||||
messages, err := rmq.ch.Consume(rmq.QueueName, "", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%v: failed establishing connection to queue", err))
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
// Iterate over messages as they are coming in
|
||||
// and launch new goroutine to handle load
|
||||
func listen(msgs <-chan amqp.Delivery, acc telegraf.Accumulator) {
|
||||
for d := range msgs {
|
||||
go handleMessage(d, acc)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessage parses the incoming messages into *client.Point
|
||||
// and then adds them to the Accumulator
|
||||
func handleMessage(d amqp.Delivery, acc telegraf.Accumulator) {
|
||||
msg := sanitizeMsg(d)
|
||||
switch msg.Name() {
|
||||
case "proc":
|
||||
if _, ok := msg.Fields()["num"].(float64); !ok {
|
||||
d.Ack(false)
|
||||
log.Printf("string field instead of float field %v\n", msg)
|
||||
return
|
||||
}
|
||||
d.Ack(false)
|
||||
acc.AddFields(msg.Name(), msg.Fields(), msg.Tags(), msg.Time())
|
||||
default:
|
||||
d.Ack(false)
|
||||
acc.AddFields(msg.Name(), msg.Fields(), msg.Tags(), msg.Time())
|
||||
}
|
||||
}
|
||||
|
||||
// sanitizeMsg breaks message cleanly into the different parts
|
||||
// turns them into an IR and returns a point
|
||||
func sanitizeMsg(msg amqp.Delivery) *client.Point {
|
||||
ir := &irMessage{}
|
||||
m := string(msg.Body)
|
||||
switch {
|
||||
case strings.Contains(m, "'severity'"):
|
||||
text := strings.Split(m, "'severity'")
|
||||
clockSplit := strings.Split(text[1], "'clock'")
|
||||
ir.severity = clockSplit[0]
|
||||
tsSplit := strings.Split(clockSplit[1], "'timestamp'")
|
||||
ir.clock = tsSplit[0]
|
||||
valueSplit := strings.Split(tsSplit[1], "'value'")
|
||||
ir.ts = valueSplit[0]
|
||||
serverSplit := strings.Split(valueSplit[1], "'server'")
|
||||
ir.value = serverSplit[0]
|
||||
sourceSplit := strings.Split(serverSplit[1], "'source'")
|
||||
ir.server = sourceSplit[0]
|
||||
hostSplit := strings.Split(sourceSplit[1], "'host'")
|
||||
ir.source = hostSplit[0]
|
||||
keySplit := strings.Split(hostSplit[1], "'key'")
|
||||
ir.host = keySplit[0]
|
||||
logEventSplit := strings.Split(keySplit[1], "'logeventid'")
|
||||
ir.key = logEventSplit[0]
|
||||
ir.logeventid = logEventSplit[1]
|
||||
case strings.Contains(m, `"host"`):
|
||||
text := strings.Split(m, "\"host\"")
|
||||
hostSplit := strings.Split(text[1], "\"clock\"")
|
||||
ir.host = hostSplit[0]
|
||||
clockSplit := strings.Split(hostSplit[1], "\"value\"")
|
||||
ir.clock = clockSplit[0]
|
||||
valueSplit := strings.Split(clockSplit[1], "\"key\"")
|
||||
ir.value = valueSplit[0]
|
||||
keySplit := strings.Split(valueSplit[1], "\"server\"")
|
||||
ir.key = keySplit[0]
|
||||
ir.server = keySplit[1]
|
||||
ir.doubleQuoted = true
|
||||
case strings.Contains(m, "'host'"):
|
||||
text := strings.Split(m, "'host'")
|
||||
hostSplit := strings.Split(text[1], "'clock'")
|
||||
ir.host = hostSplit[0]
|
||||
clockSplit := strings.Split(hostSplit[1], "'value'")
|
||||
ir.clock = clockSplit[0]
|
||||
valueSplit := strings.Split(clockSplit[1], "'key'")
|
||||
ir.value = valueSplit[0]
|
||||
keySplit := strings.Split(valueSplit[1], "'server'")
|
||||
ir.key = keySplit[0]
|
||||
ir.server = keySplit[1]
|
||||
ir.doubleQuoted = false
|
||||
}
|
||||
return ir.message().point()
|
||||
}
|
||||
|
||||
// Takes the intermediate representation and turns it into a message
|
||||
func (ir *irMessage) message() message {
|
||||
var msg message
|
||||
// trim trailing chars from value
|
||||
ir.value = string(ir.value[2 : len(ir.value)-2])
|
||||
|
||||
// trim trailing chars from key
|
||||
ir.key = string(ir.key[3 : len(ir.key)-3])
|
||||
|
||||
// check what type of value is to be stored
|
||||
// "'" indicates string messages
|
||||
if strings.ContainsAny(ir.value, "'") {
|
||||
msg = ir.toStringMessage()
|
||||
} else {
|
||||
msg = ir.toFloatMessage()
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
// irMessage is an intermediate representation of the
|
||||
// point as it moves through the parser
|
||||
type irMessage struct {
|
||||
host string
|
||||
clock string
|
||||
value string
|
||||
key string
|
||||
server string
|
||||
severity string
|
||||
ts string
|
||||
source string
|
||||
logeventid string
|
||||
doubleQuoted bool
|
||||
}
|
||||
|
||||
// cleans host and server names
|
||||
func cleanHost(str string) string {
|
||||
c := strings.Split(str, "'")
|
||||
return c[1]
|
||||
}
|
||||
|
||||
// takes a dirty timestamp string and turns it into time.Time
|
||||
func cleanClock(str string) time.Time {
|
||||
c := string(str[2 : len(str)-2])
|
||||
i, err := strconv.ParseInt(c, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%v: parsing integer", err))
|
||||
}
|
||||
return time.Unix(i, 0)
|
||||
}
|
||||
|
||||
// irMessage -> *strMessage
|
||||
func (ir *irMessage) toStringMessage() *strMessage {
|
||||
sm := &strMessage{}
|
||||
if ir.doubleQuoted {
|
||||
sm.host = cleanHost(strings.Replace(ir.host, "\"", "'", -1))
|
||||
sm.clock = cleanClock(strings.Replace(ir.clock, "\"", "'", -1))
|
||||
sm.server = cleanHost(strings.Replace(ir.host, "\"", "'", -1))
|
||||
sm.value = ir.value
|
||||
sm.key = ir.key
|
||||
} else {
|
||||
sm.host = cleanHost(ir.host)
|
||||
sm.clock = cleanClock(ir.clock)
|
||||
sm.server = cleanHost(ir.server)
|
||||
sm.value = ir.value
|
||||
sm.key = ir.key
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
// irMessage -> *floatMessage
|
||||
func (ir *irMessage) toFloatMessage() *floatMessage {
|
||||
fm := &floatMessage{}
|
||||
if ir.doubleQuoted {
|
||||
fm.host = cleanHost(strings.Replace(ir.host, "\"", "'", -1))
|
||||
fm.clock = cleanClock(strings.Replace(ir.clock, "\"", "'", -1))
|
||||
fm.server = cleanHost(strings.Replace(ir.host, "\"", "'", -1))
|
||||
i, err := strconv.ParseFloat(ir.value, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%v: parsing float", err))
|
||||
}
|
||||
fm.value = i
|
||||
fm.key = ir.key
|
||||
} else {
|
||||
fm.host = cleanHost(ir.host)
|
||||
fm.clock = cleanClock(ir.clock)
|
||||
fm.server = cleanHost(ir.server)
|
||||
i, err := strconv.ParseFloat(ir.value, 64)
|
||||
if err != nil {
|
||||
j, err := strconv.ParseInt(ir.value, 10, 64)
|
||||
if err != nil {
|
||||
// if we fail to parse a value out of the string we return 0
|
||||
log.Printf("Error parsing %v with key %v setting value to 0\n", ir.value, ir.key)
|
||||
}
|
||||
i = float64(j)
|
||||
}
|
||||
fm.value = i
|
||||
fm.key = ir.key
|
||||
}
|
||||
return fm
|
||||
}
|
||||
|
||||
// This is awful decision tree parsing, but it works...
|
||||
// Layout:
|
||||
// I've split all of the keys on the "[" and then [0] of that split on "."
|
||||
// Then I walk down all the different combinations there
|
||||
// The first switch statement is on length of the bracket split
|
||||
// within each case of the bracket switch statement there is a switch
|
||||
// on the length of the period split.
|
||||
func structureKey(key string, value interface{}) (string, map[string]string, map[string]interface{}) {
|
||||
// Beginning of Influx point
|
||||
meas := ""
|
||||
tags := make(map[string]string, 0)
|
||||
fields := make(map[string]interface{}, 0)
|
||||
|
||||
// BracketSplit splits the metics on the "["
|
||||
bs := strings.Split(key, "[")
|
||||
// PeriodSplit splits the first part of the metric on "."s
|
||||
ps := strings.Split(bs[0], ".")
|
||||
|
||||
// Switch on the results of the bracket split
|
||||
switch len(bs) {
|
||||
|
||||
// No brackets so len(split) == 1
|
||||
case 1:
|
||||
meas = ps[0]
|
||||
// Switch on the results of the period split
|
||||
switch len(ps) {
|
||||
// meas.field
|
||||
case 2:
|
||||
fields[ps[1]] = value
|
||||
|
||||
// meas.field*
|
||||
case 3:
|
||||
switch {
|
||||
case ps[1] == "lbv":
|
||||
meas = jwp(ps[0], ps[1])
|
||||
fields[ps[2]] = value
|
||||
default:
|
||||
fields[fmt.Sprintf("%v.%v", ps[1], ps[2])] = value
|
||||
}
|
||||
// meas.field.field.context
|
||||
case 4:
|
||||
switch {
|
||||
case strings.Contains(ps[3], "-"):
|
||||
meas = jwp(ps[0], ps[1])
|
||||
fields[ps[2]] = value
|
||||
tags["context"] = ps[3]
|
||||
case ps[1] == "lbv" && ps[2] == "cs":
|
||||
meas = jwp(ps[0], ps[1])
|
||||
fields[jwp(ps[2], ps[3])] = value
|
||||
default:
|
||||
fields[jw2p(ps[1], ps[2], ps[3])] = value
|
||||
|
||||
}
|
||||
// netscaler.lbv.(rps|srv).(rack)
|
||||
case 6:
|
||||
meas = jwp(ps[0], ps[1])
|
||||
tags["rack"] = jw2p(ps[3], ps[4], ps[5])
|
||||
fields[ps[2]] = value
|
||||
// Default - Deal with "CPU-", "Memory-", "Incoming-", "Outgoing-"
|
||||
default:
|
||||
switch {
|
||||
// "CPU-"
|
||||
case strings.Contains(key, "CPU-"):
|
||||
s := strings.Split(key, "CPU-")
|
||||
meas = "CPU"
|
||||
tags["host"] = s[1]
|
||||
fields["value"] = value
|
||||
// "Memory-"
|
||||
case strings.Contains(key, "Memory-"):
|
||||
s := strings.Split(key, "Memory-")
|
||||
meas = "Memory"
|
||||
tags["host"] = s[1]
|
||||
// "Incoming-"
|
||||
fields["value"] = value
|
||||
case strings.Contains(key, "Incoming-"):
|
||||
s := strings.Split(key, "Incoming-")
|
||||
meas = "Incoming"
|
||||
tags["host"] = s[1]
|
||||
fields["value"] = value
|
||||
// "Outgoing-"
|
||||
case strings.Contains(key, "Outgoing-"):
|
||||
s := strings.Split(key, "Outgoing-")
|
||||
meas = "Outgoing"
|
||||
tags["host"] = s[1]
|
||||
fields["value"] = value
|
||||
// Default!
|
||||
default:
|
||||
meas = key
|
||||
fields["value"] = value
|
||||
}
|
||||
}
|
||||
// Brackets so len(split) == 2
|
||||
// longest case
|
||||
case 2:
|
||||
|
||||
// Switch on the results of the period split
|
||||
switch len(ps) {
|
||||
|
||||
// period split only contains measurement
|
||||
case 1:
|
||||
meas = ps[0]
|
||||
bracket := trim(bs[1])
|
||||
// Arcane parsing rules
|
||||
slash := strings.Contains(bs[1], "/")
|
||||
comma := strings.Contains(bs[1], ",")
|
||||
dash := strings.Contains(bs[1], "-")
|
||||
vlan := strings.Contains(bs[1], "Vlan")
|
||||
inter := strings.Contains(meas, "if")
|
||||
switch {
|
||||
|
||||
// Bracket contains something like 1/40 -> ignore
|
||||
case slash:
|
||||
fields["value"] = value
|
||||
// bracket is field name with some changes
|
||||
case comma:
|
||||
// switch "," and " " to "."
|
||||
bracket = rp(rp(bracket, ",", "."), " ", ".")
|
||||
fields[bracket] = value
|
||||
// bracket contains a port number
|
||||
case dash:
|
||||
ds := strings.Split(bracket, "-")
|
||||
tags[ds[0]] = ds[1]
|
||||
fields["value"] = value
|
||||
// Bracket contains a Vlan number
|
||||
case vlan:
|
||||
s := strings.Split(bracket, "Vlan")
|
||||
tags["Vlan"] = s[1]
|
||||
fields["value"] = value
|
||||
// Bracket contains an interface name
|
||||
case inter:
|
||||
tags["interface"] = bracket
|
||||
fields["value"] = value
|
||||
// Default
|
||||
default:
|
||||
meas = key
|
||||
fields["value"] = value
|
||||
}
|
||||
|
||||
// period split contains more information as well as brackets
|
||||
case 2:
|
||||
meas = ps[0]
|
||||
bracket := trim(bs[1])
|
||||
// Switch on length of bracket
|
||||
switch {
|
||||
|
||||
// short brakets
|
||||
case len(bracket) < 10:
|
||||
bracket = rp(bracket, ",", "")
|
||||
if bracket != "" {
|
||||
tags["process"] = bracket
|
||||
}
|
||||
fields[ps[1]] = value
|
||||
|
||||
// medium brakets
|
||||
case len(bracket) < 25:
|
||||
// remove all {,}," from bracket
|
||||
bracket = rp(rp(rp(bracket, "\"", ""), "{", ""), "}", "")
|
||||
fields[bracket] = value
|
||||
|
||||
// long brackets are system.run[curl ....]
|
||||
case len(bracket) > 25 && len(bracket) < 150:
|
||||
fields[ps[1]] = bracket
|
||||
tags["status_code"] = fmt.Sprint(value)
|
||||
|
||||
// Default
|
||||
default:
|
||||
meas = ps[0]
|
||||
f := strings.Split(bracket, "FailureStatus")
|
||||
tags["ps"] = f[0]
|
||||
fields["powersupply.failurestatus"] = value
|
||||
}
|
||||
|
||||
// len(period_split) == 3 and contains more information
|
||||
case 3:
|
||||
meas = ps[0]
|
||||
bracket := trim(bs[1])
|
||||
// Switch on bracket content
|
||||
switch {
|
||||
// netscaler.lbv.*
|
||||
case ps[0] == "netscaler" && ps[1] == "lbv":
|
||||
meas = jwp(ps[0], ps[1])
|
||||
tags["context"] = bracket
|
||||
fields[ps[2]] = value
|
||||
|
||||
// bracket contains context
|
||||
case strings.Contains(bracket, "-"):
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
tags["context"] = bracket
|
||||
|
||||
// bracket contains file system info
|
||||
case strings.Contains(bracket, "/"):
|
||||
t := strings.Split(bracket, ",")
|
||||
tags["path"] = t[0]
|
||||
if len(t) > 1 {
|
||||
fields[jw2p(ps[1], ps[2], t[1])] = value
|
||||
} else {
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
}
|
||||
|
||||
// TODO: find a non default case that fits all "net","system","vm" mess down here
|
||||
default:
|
||||
bracketCommaSplit := strings.Split(bracket, ",")
|
||||
|
||||
// Switch on bracket contents then measurement (set on line 119)
|
||||
switch {
|
||||
|
||||
// system cpu and swap meas
|
||||
case bracketCommaSplit[0] == "":
|
||||
fields[jwp(ps[1], bracketCommaSplit[1])] = value
|
||||
|
||||
// net meas
|
||||
case meas == "net":
|
||||
tags["interface"] = bracketCommaSplit[0]
|
||||
if len(bracketCommaSplit) > 1 {
|
||||
fields[jw2p(ps[1], ps[2], bracketCommaSplit[1])] = value
|
||||
} else {
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
}
|
||||
|
||||
// vm measurement
|
||||
case meas == "vm":
|
||||
fields[jw2p(ps[1], ps[2], bracketCommaSplit[0])] = value
|
||||
|
||||
// system measurment
|
||||
case meas == "system":
|
||||
// for per-cpu metrics we need to pull out cpu as tag
|
||||
if ps[1] == "cpu" {
|
||||
fields[jw2p(ps[1], ps[2], bracketCommaSplit[0])] = value
|
||||
tags["cpu"] = bracketCommaSplit[1]
|
||||
} else {
|
||||
// For system health checks we need to store system checked (mem, disk, cpu, etc...) with diff tags
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
tags["system"] = bracketCommaSplit[0]
|
||||
}
|
||||
|
||||
// web measurement
|
||||
case meas == "web":
|
||||
if ps[2] == "time" {
|
||||
fields["value"] = value
|
||||
} else {
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
}
|
||||
tags["system"] = "ZabbixGUI"
|
||||
// app measurement
|
||||
case meas == "app":
|
||||
fields[jwp(ps[1], ps[2])] = value
|
||||
tags["provider"] = bracket
|
||||
// Default
|
||||
default:
|
||||
meas = key
|
||||
fields["value"] = value
|
||||
}
|
||||
}
|
||||
|
||||
// len(period_split) == 5 and contains most of the metadata
|
||||
case 5:
|
||||
meas = ps[0]
|
||||
bracket := trim(bs[1])
|
||||
// Switch on measurement name
|
||||
switch {
|
||||
|
||||
// custom measurement -> custom.vfs.dev
|
||||
case meas == "custom":
|
||||
meas = jw2p(ps[0], ps[1], ps[2])
|
||||
tags["drive"] = bracket
|
||||
fields[jwp(ps[3], ps[4])] = value
|
||||
|
||||
// app measurement
|
||||
case meas == "app":
|
||||
tags["name"] = jwp(ps[1], ps[2])
|
||||
fields[jwp(ps[3], ps[4])] = value
|
||||
|
||||
// default
|
||||
default:
|
||||
meas = key
|
||||
fields["value"] = value
|
||||
}
|
||||
|
||||
// Default case for len(period_split) == 5
|
||||
default:
|
||||
meas = key
|
||||
fields["value"] = value
|
||||
}
|
||||
|
||||
// Multiple brackets -> grpavg["app-searchautocomplete","system.cpu.util[,user]",last,0]
|
||||
default:
|
||||
sp := strings.Split(strings.Split(key, "grpavg[")[1], ",")
|
||||
tags["app"] = trimS(sp[0])
|
||||
s := strings.Split(trimS(sp[1]), ".")
|
||||
meas = s[0]
|
||||
field := fmt.Sprintf("%v.%v.%v.%v", s[1], s[2], sp[3], sp[4])
|
||||
fields[field] = value
|
||||
}
|
||||
// Return the start of a point
|
||||
return meas, tags, fields
|
||||
}
|
||||
|
||||
// join with period
|
||||
func jwp(s1, s2 string) string {
|
||||
return fmt.Sprintf("%v.%v", s1, s2)
|
||||
}
|
||||
|
||||
// join with 2 period
|
||||
func jw2p(s1, s2, s3 string) string {
|
||||
return fmt.Sprintf("%v.%v.%v", s1, s2, s3)
|
||||
}
|
||||
|
||||
// replace
|
||||
func rp(s, old, new string) string {
|
||||
return strings.Replace(s, old, new, -1)
|
||||
}
|
||||
|
||||
// trims last char from string
|
||||
func trim(s string) string {
|
||||
return s[0 : len(s)-1]
|
||||
}
|
||||
|
||||
// common interface for different datatypes
|
||||
type message interface {
|
||||
point() *client.Point
|
||||
}
|
||||
|
||||
// takes an irMessage -> float field
|
||||
type floatMessage struct {
|
||||
host string
|
||||
clock time.Time
|
||||
value float64
|
||||
key string
|
||||
server string
|
||||
}
|
||||
|
||||
func trimS(s string) string {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
|
||||
// satisfies the message interface
|
||||
func (fm *floatMessage) point() *client.Point {
|
||||
meas, tags, fields := structureKey(fm.key, fm.value)
|
||||
tags["host"] = fm.host
|
||||
tags["server"] = fm.server
|
||||
pt, err := client.NewPoint(meas, tags, fields, fm.clock)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%v: creating float point", err))
|
||||
}
|
||||
return pt
|
||||
}
|
||||
|
||||
// takes an irMessage -> string field
|
||||
type strMessage struct {
|
||||
host string
|
||||
clock time.Time
|
||||
value string
|
||||
key string
|
||||
server string
|
||||
}
|
||||
|
||||
// satisfies the message interface
|
||||
func (sm *strMessage) point() *client.Point {
|
||||
meas, tags, fields := structureKey(sm.key, sm.value)
|
||||
tags["host"] = sm.host
|
||||
tags["server"] = sm.server
|
||||
pt, err := client.NewPoint(meas, tags, fields, sm.clock)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%v: creating string point", err))
|
||||
}
|
||||
return pt
|
||||
}
|
||||
|
||||
// Stop satisfies the telegraf.ServiceInput interface
|
||||
func (rmq *RabbitMQParser) Stop() {
|
||||
rmq.Lock()
|
||||
defer rmq.Unlock()
|
||||
rmq.conn.Close()
|
||||
rmq.ch.Close()
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("rabbit_mq_parser", func() telegraf.Input {
|
||||
return &RabbitMQParser{}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/streadway/amqp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Open file with zabbix data
|
||||
t := time.Now()
|
||||
f, err := os.Open("zabbix.txt")
|
||||
defer f.Close()
|
||||
logErr(err, "error opening file")
|
||||
log.Printf("opened file in %v\n", time.Now().Sub(t))
|
||||
// Make a new scanner to feed points into Queue
|
||||
scan := bufio.NewScanner(f)
|
||||
|
||||
t = time.Now()
|
||||
// Create producer to publish to Queue
|
||||
p := newProducer()
|
||||
defer p.conn.Close()
|
||||
defer p.ch.Close()
|
||||
log.Printf("established connection in %v\n", time.Now().Sub(t))
|
||||
cntr := 0
|
||||
cl := NewConcurrencyLimiter(1000)
|
||||
t = time.Now()
|
||||
for scan.Scan() {
|
||||
body := scan.Text()
|
||||
cl.Increment()
|
||||
go p.publish(body, cl)
|
||||
cntr++
|
||||
if cntr%10000 == 0 {
|
||||
log.Printf("sent %v lines in %v", cntr, time.Now().Sub(t))
|
||||
}
|
||||
}
|
||||
log.Printf("sent %v lines in %v", cntr, time.Now().Sub(t))
|
||||
}
|
||||
|
||||
type producer struct {
|
||||
conn *amqp.Connection
|
||||
ch *amqp.Channel
|
||||
q amqp.Queue
|
||||
}
|
||||
|
||||
func (p producer) publish(body string, cl *ConcurrencyLimiter) {
|
||||
err := p.ch.Publish("", p.q.Name, false, false, amqp.Publishing{
|
||||
DeliveryMode: amqp.Persistent,
|
||||
ContentType: "text/plain",
|
||||
Body: []byte(body),
|
||||
})
|
||||
if err != nil {
|
||||
logErr(err, "Failed to publish a message")
|
||||
}
|
||||
cl.Decrement()
|
||||
}
|
||||
|
||||
func newProducer() producer {
|
||||
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
logErr(err, "Failed to connect to RabbitMQ")
|
||||
|
||||
ch, err := conn.Channel()
|
||||
logErr(err, "Failed to open a channel")
|
||||
|
||||
q, err := ch.QueueDeclare(
|
||||
"task_queue", // name
|
||||
true, // durable
|
||||
false, // delete when unused
|
||||
false, // exclusive
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
logErr(err, "Failed to declare a queue")
|
||||
return producer{
|
||||
conn: conn,
|
||||
ch: ch,
|
||||
q: q,
|
||||
}
|
||||
}
|
||||
|
||||
func logErr(err error, msg string) {
|
||||
if err != nil {
|
||||
log.Printf("%s: %s\n", msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ConcurrencyLimiter is a go routine safe struct that can be used to
|
||||
// ensure that no more than a specifid max number of goroutines are
|
||||
// executing.
|
||||
type ConcurrencyLimiter struct {
|
||||
inc chan chan struct{}
|
||||
dec chan struct{}
|
||||
max int
|
||||
count int
|
||||
}
|
||||
|
||||
// NewConcurrencyLimiter returns a configured limiter that will
|
||||
// ensure that calls to Increment will block if the max is hit.
|
||||
func NewConcurrencyLimiter(max int) *ConcurrencyLimiter {
|
||||
c := &ConcurrencyLimiter{
|
||||
inc: make(chan chan struct{}),
|
||||
dec: make(chan struct{}, max),
|
||||
max: max,
|
||||
}
|
||||
go c.handleLimits()
|
||||
return c
|
||||
}
|
||||
|
||||
// Increment will increase the count of running goroutines by 1.
|
||||
// if the number is currently at the max, the call to Increment
|
||||
// will block until another goroutine decrements.
|
||||
func (c *ConcurrencyLimiter) Increment() {
|
||||
r := make(chan struct{})
|
||||
c.inc <- r
|
||||
<-r
|
||||
}
|
||||
|
||||
// Decrement will reduce the count of running goroutines by 1
|
||||
func (c *ConcurrencyLimiter) Decrement() {
|
||||
c.dec <- struct{}{}
|
||||
}
|
||||
|
||||
// handleLimits runs in a goroutine to manage the count of
|
||||
// running goroutines.
|
||||
func (c *ConcurrencyLimiter) handleLimits() {
|
||||
for {
|
||||
r := <-c.inc
|
||||
if c.count >= c.max {
|
||||
<-c.dec
|
||||
c.count--
|
||||
}
|
||||
c.count++
|
||||
r <- struct{}{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
[agent]
|
||||
interval = "1s"
|
||||
round_interval = true
|
||||
metric_batch_size = 10000
|
||||
metric_buffer_limit = 100000
|
||||
collection_jitter = "0s"
|
||||
flush_interval = "10s"
|
||||
flush_jitter = "0s"
|
||||
precision = "s"
|
||||
debug = false
|
||||
quiet = false
|
||||
hostname = ""
|
||||
omit_hostname = false
|
||||
|
||||
[[outputs.influxdb]]
|
||||
urls = ["http://localhost:8086"] # required
|
||||
database = "telegraf" # required
|
||||
retention_policy = ""
|
||||
write_consistency = "any"
|
||||
timeout = "5s"
|
||||
|
||||
[[inputs.rabbit_mq_parser]]
|
||||
rabbitmq_address = "amqp://guest:guest@localhost:5672/"
|
||||
queue_name = "task_queue"
|
||||
prefetch = 1000
|
||||
|
Loading…
Reference in New Issue