Skip to content

Commit

Permalink
Add the ipmi plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ebookbug authored and sparrc committed Mar 23, 2016
1 parent a95710e commit 65fccb5
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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"
Expand Down
50 changes: 50 additions & 0 deletions plugins/inputs/ipmi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Telegraf ipmi plugin

Get bare metal metrics using the command line utility `ipmitool`

see ipmitool(https://sourceforge.net/projects/ipmitool/files/ipmitool/)

The plugin will use the following command to collect remote host sensor stats:

ipmitool -I lan -H 192.168.1.1 -U USERID -P PASSW0RD sdr

## Measurements

- ipmi_sensor:

* Tags: `server`,`host`
* Fields:
- status
- value

## Configuration

```toml
[[inputs.ipmi]]
## specify servers via a url matching:
## [username[:password]@][protocol[(address)]]
## e.g.
## root:passwd@lan(127.0.0.1)
##
servers = ["USERID:PASSW0RD@lan(10.20.2.203)"]
```

## Output

> ipmi_sensor,host=10.20.2.203,inst=Ambient\ Temp status=1i,value=20 1458488465012559455
> ipmi_sensor,host=10.20.2.203,inst=Altitude status=1i,value=80 1458488465012688613
> ipmi_sensor,host=10.20.2.203,inst=Avg\ Power status=1i,value=220 1458488465012776511
> ipmi_sensor,host=10.20.2.203,inst=Planar\ 3.3V status=1i,value=3.28 1458488465012861875
> ipmi_sensor,host=10.20.2.203,inst=Planar\ 5V status=1i,value=4.9 1458488465012944188
> ipmi_sensor,host=10.20.2.203,inst=Planar\ 12V status=1i,value=12.04 1458488465013008485
> ipmi_sensor,host=10.20.2.203,inst=Planar\ VBAT status=1i,value=3.04 1458488465013072508
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1A\ Tach status=1i,value=2610 1458488465013137932
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1B\ Tach status=1i,value=1775 1458488465013279896
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2A\ Tach status=1i,value=1972 1458488465013358177
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2B\ Tach status=1i,value=1275 1458488465013434023
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3A\ Tach status=1i,value=2929 1458488465013514567
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3B\ Tach status=1i,value=2125 1458488465013582616
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 1 status=1i,value=0 1458488465013643746
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 2 status=1i,value=0 1458488465013714887
> ipmi_sensor,host=10.20.2.203,inst=Fan\ 3 status=1i,value=0 1458488465013861854
39 changes: 39 additions & 0 deletions plugins/inputs/ipmi/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// command
package ipmi

import (
"bytes"
"fmt"
"os/exec"
"strings"
)

type CommandRunner struct{}

func (t CommandRunner) cmd(conn *Connection, args ...string) *exec.Cmd {
path := conn.Path
opts := append(conn.options(), args...)

if path == "" {
path = "ipmitool"
}

return exec.Command(path, opts...)

}

func (t CommandRunner) Run(conn *Connection, args ...string) (string, error) {
cmd := t.cmd(conn, 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
}
90 changes: 90 additions & 0 deletions plugins/inputs/ipmi/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// connection
package ipmi

import (
"fmt"
"net"
"strconv"
"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
}

func (t *Connection) options() []string {
intf := t.Interface
if intf == "" {
intf = "lan"
}

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
}

// 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
}
113 changes: 113 additions & 0 deletions plugins/inputs/ipmi/ipmi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// ipmi
package ipmi

import (
"strconv"
"strings"
"time"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

type Ipmi struct {
Servers []string
runner Runner
}

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)"]
`

func NewIpmi() *Ipmi {
return &Ipmi{
runner: CommandRunner{},
}
}

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 m.runner == nil {
m.runner = CommandRunner{}
}
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)

res, err := m.runner.Run(conn, "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{"server": conn.Hostname, "name": 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
}

type Runner interface {
Run(conn *Connection, args ...string) (string, error)
}

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{}
})
}
Loading

0 comments on commit 65fccb5

Please sign in to comment.