diff --git a/cmd/telegraf/ipmi.conf b/cmd/telegraf/ipmi.conf new file mode 100644 index 000000000..bfe130058 --- /dev/null +++ b/cmd/telegraf/ipmi.conf @@ -0,0 +1,106 @@ +# Telegraf Configuration + +# Telegraf is entirely plugin driven. All metrics are gathered from the +# declared inputs, and sent to the declared outputs. + +# Plugins must be declared in here to be active. +# To deactivate a plugin, comment out the name and any variables. + +# Use 'telegraf -config telegraf.conf -test' to see what metrics a config +# file would generate. + +# Global tags can be specified here in key="value" format. +[global_tags] + # dc = "us-east-1" # will tag all metrics with dc=us-east-1 + # rack = "1a" + +# Configuration for telegraf agent +[agent] + ## Default data collection interval for all inputs + interval = "10s" + ## Rounds collection interval to 'interval' + ## ie, if interval="10s" then always collect on :00, :10, :20, etc. + round_interval = true + + ## Telegraf will cache metric_buffer_limit metrics for each output, and will + ## flush this buffer on a successful write. + metric_buffer_limit = 1000 + ## Flush the buffer whenever full, regardless of flush_interval. + flush_buffer_when_full = true + + ## Collection jitter is used to jitter the collection by a random amount. + ## Each plugin will sleep for a random time within jitter before collecting. + ## This can be used to avoid many plugins querying things like sysfs at the + ## same time, which can have a measurable effect on the system. + collection_jitter = "0s" + + ## Default flushing interval for all outputs. You shouldn't set this below + ## interval. Maximum flush_interval will be flush_interval + flush_jitter + flush_interval = "10s" + ## Jitter the flush interval by a random amount. This is primarily to avoid + ## large write spikes for users running a large number of telegraf instances. + ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s + flush_jitter = "0s" + + ## Run telegraf in debug mode + debug = false + ## Run telegraf in quiet mode + quiet = false + ## Override default hostname, if empty use os.Hostname() + hostname = "" + + +# +# OUTPUTS: +# + + +# Configuration for influxdb server to send metrics to +[[outputs.influxdb]] + ## The full HTTP or UDP endpoint URL for your InfluxDB instance. + ## Multiple urls can be specified as part of the same cluster, + ## this means that only ONE of the urls will be written to each interval. + # urls = ["udp://localhost:8089"] # UDP endpoint example + urls = ["http://localhost:8086"] # required + ## The target database for metrics (telegraf will create it if not exists). + database = "telegraf" # required + ## Retention policy to write to. + retention_policy = "default" + ## Precision of writes, valid values are "ns", "us" (or "µs"), "ms", "s", "m", "h". + ## note: using "s" precision greatly improves InfluxDB compression. + precision = "s" + + ## Write timeout (for the InfluxDB client), formatted as a string. + ## If not provided, will default to 5s. 0s means no timeout (not recommended). + timeout = "5s" + # username = "telegraf" + # password = "metricsmetricsmetricsmetrics" + ## Set the user agent for HTTP POSTs (can be useful for log differentiation) + # user_agent = "telegraf" + ## Set UDP payload size, defaults to InfluxDB UDP Client default (512 bytes) + # udp_payload = 512 + + ## Optional SSL Config + # ssl_ca = "/etc/telegraf/ca.pem" + # ssl_cert = "/etc/telegraf/cert.pem" + # ssl_key = "/etc/telegraf/key.pem" + ## Use SSL but skip chain & host verification + # insecure_skip_verify = false + +# +# INPUTS: +# + +# Read metrics from one or many bare metal servers +[[inputs.ipmi]] + ## specify servers via a url matching: + ## [username[:password]@][protocol[(address)]] + ## e.g. + ## root:passwd@tcp(127.0.0.1:3306) + ## + ## If no servers are specified, then localhost is used as the host. + servers = ["USERID:PASSW0RD@lan(10.20.2.203)"] + +# +# SERVICE INPUTS: +# diff --git a/cmd/telegraf/telegraf b/cmd/telegraf/telegraf new file mode 100644 index 000000000..e0f43c306 Binary files /dev/null and b/cmd/telegraf/telegraf differ diff --git a/cmd/telegraf/telegraf.exe b/cmd/telegraf/telegraf.exe new file mode 100644 index 000000000..c85463c8d Binary files /dev/null and b/cmd/telegraf/telegraf.exe differ diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index a3300df66..0a3c4b9d3 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -15,6 +15,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/haproxy" _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb" + _ "github.com/influxdata/telegraf/plugins/inputs/ipmi" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" _ "github.com/influxdata/telegraf/plugins/inputs/leofs" diff --git a/plugins/inputs/ipmi/command.go b/plugins/inputs/ipmi/command.go new file mode 100644 index 000000000..949b20b0a --- /dev/null +++ b/plugins/inputs/ipmi/command.go @@ -0,0 +1,62 @@ +// command +package ipmi + +import ( + "bytes" + "fmt" + "os/exec" + "strconv" + "strings" +) + +type IpmiCommand struct { + *Connection +} + +func (t *IpmiCommand) options() []string { + intf := t.Interface + if intf == "" { + intf = "lanplus" + } + + options := []string{ + "-H", t.Hostname, + "-U", t.Username, + "-P", t.Password, + "-I", intf, + } + + if t.Port != 0 { + options = append(options, "-p", strconv.Itoa(t.Port)) + } + + return options +} + +func (t *IpmiCommand) cmd(args ...string) *exec.Cmd { + path := t.Path + opts := append(t.options(), args...) + + if path == "" { + path = "ipmitool" + } + + return exec.Command(path, opts...) + +} + +func (t *IpmiCommand) Run(args ...string) (string, error) { + cmd := t.cmd(args...) + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("run %s %s: %s (%s)", + cmd.Path, strings.Join(cmd.Args, " "), stderr.String(), err) + } + + return stdout.String(), err +} diff --git a/plugins/inputs/ipmi/connection.go b/plugins/inputs/ipmi/connection.go new file mode 100644 index 000000000..d1c090038 --- /dev/null +++ b/plugins/inputs/ipmi/connection.go @@ -0,0 +1,69 @@ +// connection +package ipmi + +import ( + "fmt" + "net" + "strings" +) + +// Connection properties for a Client +type Connection struct { + Hostname string + Username string + Password string + Path string + Port int + Interface string +} + +func NewConnection(server string) *Connection { + conn := &Connection{} + inx1 := strings.Index(server, "@") + inx2 := strings.Index(server, "(") + inx3 := strings.Index(server, ")") + + connstr := server + + if inx1 > 0 { + security := server[0:inx1] + connstr = server[inx1+1 : len(server)] + up := strings.Split(security, ":") + conn.Username = up[0] + conn.Password = up[1] + } + + if inx2 > 0 { + inx2 = strings.Index(connstr, "(") + inx3 = strings.Index(connstr, ")") + + conn.Interface = connstr[0:inx2] + conn.Hostname = connstr[inx2+1 : inx3] + } + + return conn +} + +// RemoteIP returns the remote (bmc) IP address of the Connection +func (c *Connection) RemoteIP() string { + if net.ParseIP(c.Hostname) == nil { + addrs, err := net.LookupHost(c.Hostname) + if err != nil && len(addrs) > 0 { + return addrs[0] + } + } + return c.Hostname +} + +// LocalIP returns the local (client) IP address of the Connection +func (c *Connection) LocalIP() string { + conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", c.Hostname, c.Port)) + if err != nil { + // don't bother returning an error, since this value will never + // make it to the bmc if we can't connect to it. + return c.Hostname + } + _ = conn.Close() + host, _, _ := net.SplitHostPort(conn.LocalAddr().String()) + return host +} diff --git a/plugins/inputs/ipmi/ipmi.go b/plugins/inputs/ipmi/ipmi.go new file mode 100644 index 000000000..87c969f8e --- /dev/null +++ b/plugins/inputs/ipmi/ipmi.go @@ -0,0 +1,119 @@ +// ipmi +package ipmi + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +type Ipmi struct { + Servers []string +} + +var sampleConfig = ` + ## specify servers via a url matching: + ## [username[:password]@][protocol[(address)]] + ## e.g. + ## root:passwd@lan(127.0.0.1) + ## + servers = ["USERID:PASSW0RD@lan(192.168.1.1)"] +` +var localhost = "" + +func (m *Ipmi) SampleConfig() string { + return sampleConfig +} + +func (m *Ipmi) Description() string { + return "Read metrics from one or many bare metal servers" +} + +func (m *Ipmi) Gather(acc telegraf.Accumulator) error { + if len(m.Servers) == 0 { + // if we can't get stats in this case, thats fine, don't report + // an error. + m.gatherServer(localhost, acc) + return nil + } + + for _, serv := range m.Servers { + err := m.gatherServer(serv, acc) + if err != nil { + return err + } + } + + return nil +} + +func (m *Ipmi) gatherServer(serv string, acc telegraf.Accumulator) error { + conn := NewConnection(serv) + + tool := &IpmiCommand{conn} + res, err := tool.Run("sdr") + if err != nil { + return err + } + + lines := strings.Split(res, "\n") + + for i := 0; i < len(lines); i++ { + vals := strings.Split(lines[i], "|") + if len(vals) == 3 { + tags := map[string]string{"host": conn.Hostname, "inst": trim(vals[0])} + fields := make(map[string]interface{}) + if strings.EqualFold("ok", trim(vals[2])) { + fields["status"] = 1 + } else { + fields["status"] = 0 + } + + val1 := trim(vals[1]) + + if strings.Index(val1, " ") > 0 { + val := strings.Split(val1, " ")[0] + fields["value"] = Atofloat(val) + } else { + fields["value"] = 0.0 + } + + acc.AddFields("ipmi_sensor", fields, tags, time.Now()) + } + } + + return nil +} + +func Atofloat4(val float64) float64 { + str := fmt.Sprintf("%.4f", val) + return Atofloat(str) +} + +func Atofloat2(val float64) float64 { + str := fmt.Sprintf("%.2f", val) + return Atofloat(str) +} + +func Atofloat(val string) float64 { + f, err := strconv.ParseFloat(val, 64) + if err != nil { + return float64(0) + } else { + return float64(f) + } +} + +func trim(s string) string { + return strings.TrimSpace(s) +} + +func init() { + inputs.Add("ipmi", func() telegraf.Input { + return &Ipmi{} + }) +}