From 3e5980d0170bacb9bc436f53dffb8d5f60de6a9a Mon Sep 17 00:00:00 2001 From: calerogers Date: Thu, 13 Apr 2017 15:53:02 -0700 Subject: [PATCH] Irqstat input plugin (#2494) closes #2469 --- CHANGELOG.md | 1 + README.md | 1 + plugins/inputs/all/all.go | 1 + plugins/inputs/interrupts/README.md | 35 +++++ plugins/inputs/interrupts/interrupts.go | 140 +++++++++++++++++++ plugins/inputs/interrupts/interrupts_test.go | 59 ++++++++ 6 files changed, 237 insertions(+) create mode 100644 plugins/inputs/interrupts/README.md create mode 100644 plugins/inputs/interrupts/interrupts.go create mode 100644 plugins/inputs/interrupts/interrupts_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3ee39df..d4792790f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ be deprecated eventually. ### Features +- [#2494](https://github.com/influxdata/telegraf/pull/2494): Add interrupts input plugin. - [#2094](https://github.com/influxdata/telegraf/pull/2094): Add generic socket listener & writer. - [#2204](https://github.com/influxdata/telegraf/pull/2204): Extend http_response to support searching for a substring in response. Return 1 if found, else 0. - [#2137](https://github.com/influxdata/telegraf/pull/2137): Added userstats to mysql input plugin. diff --git a/README.md b/README.md index f46c2e298..2dc6997d6 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ configuration options. * [httpjson](./plugins/inputs/httpjson) (generic JSON-emitting http service plugin) * [internal](./plugins/inputs/internal) * [influxdb](./plugins/inputs/influxdb) +* [interrupts](./plugins/inputs/interrupts) * [ipmi_sensor](./plugins/inputs/ipmi_sensor) * [iptables](./plugins/inputs/iptables) * [jolokia](./plugins/inputs/jolokia) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 983179e90..f7207da84 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -30,6 +30,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb" _ "github.com/influxdata/telegraf/plugins/inputs/internal" + _ "github.com/influxdata/telegraf/plugins/inputs/interrupts" _ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor" _ "github.com/influxdata/telegraf/plugins/inputs/iptables" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" diff --git a/plugins/inputs/interrupts/README.md b/plugins/inputs/interrupts/README.md new file mode 100644 index 000000000..aec30094e --- /dev/null +++ b/plugins/inputs/interrupts/README.md @@ -0,0 +1,35 @@ +# Interrupts Input Plugin + +The interrupts plugin gathers metrics about IRQs from `/proc/interrupts` and `/proc/softirqs`. + +### Configuration +``` +[[inputs.interrupts]] + ## A list of IRQs to include for metric ingestion, if not specified + ## will default to collecting all IRQs. + include = ["0", "1", "30", "NET_RX"] +``` + +### Measurements +There are two measurements reported by this plugin. +- `interrupts` gathers metrics from the `/proc/interrupts` file +- `soft_interrupts` gathers metrics from the `/proc/softirqs` file + +### Fields +- CPUx: the amount of interrupts for the IRQ handled by that CPU +- total: total amount of interrupts for all CPUs + +### Tags +- irq: the IRQ +- type: the type of interrupt +- device: the name of the device that is located at that IRQ + +### Example Output +``` +./telegraf -config ~/interrupts_config.conf -test +* Plugin: inputs.interrupts, Collection 1 +> interrupts,irq=0,type=IO-APIC,device=2-edge\ timer,host=hostname CPU0=23i,total=23i 1489346531000000000 +> interrupts,irq=1,host=hostname,type=IO-APIC,device=1-edge\ i8042 CPU0=9i,total=9i 1489346531000000000 +> interrupts,irq=30,type=PCI-MSI,device=65537-edge\ virtio1-input.0,host=hostname CPU0=1i,total=1i 1489346531000000000 +> soft_interrupts,irq=NET_RX,host=hostname CPU0=280879i,total=280879i 1489346531000000000 +``` diff --git a/plugins/inputs/interrupts/interrupts.go b/plugins/inputs/interrupts/interrupts.go new file mode 100644 index 000000000..1feb6441c --- /dev/null +++ b/plugins/inputs/interrupts/interrupts.go @@ -0,0 +1,140 @@ +package interrupts + +import ( + "bufio" + "fmt" + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "io/ioutil" + "strconv" + "strings" +) + +type Interrupts struct{} + +type IRQ struct { + ID string + Type string + Device string + Total int64 + Cpus []int64 +} + +func NewIRQ(id string) *IRQ { + return &IRQ{ID: id, Cpus: []int64{}} +} + +const sampleConfig = ` + ## To filter which IRQs to collect, make use of tagpass / tagdrop, i.e. + # [inputs.interrupts.tagdrop] + # irq = [ "NET_RX", "TASKLET" ] +` + +func (s *Interrupts) Description() string { + return "This plugin gathers interrupts data from /proc/interrupts and /proc/softirqs." +} + +func (s *Interrupts) SampleConfig() string { + return sampleConfig +} + +func parseInterrupts(irqdata string) ([]IRQ, error) { + var irqs []IRQ + var cpucount int + scanner := bufio.NewScanner(strings.NewReader(irqdata)) + ok := scanner.Scan() + if ok { + cpus := strings.Fields(scanner.Text()) + if cpus[0] == "CPU0" { + cpucount = len(cpus) + } + } else if scanner.Err() != nil { + return irqs, fmt.Errorf("Reading %s: %s", scanner.Text(), scanner.Err()) + } + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if !strings.HasSuffix(fields[0], ":") { + continue + } + irqid := strings.TrimRight(fields[0], ":") + irq := NewIRQ(irqid) + irqvals := fields[1:len(fields)] + for i := 0; i < cpucount; i++ { + if i < len(irqvals) { + irqval, err := strconv.ParseInt(irqvals[i], 10, 64) + if err != nil { + return irqs, fmt.Errorf("Unable to parse %q from %q: %s", irqvals[i], scanner.Text(), err) + } + irq.Cpus = append(irq.Cpus, irqval) + } + } + for _, irqval := range irq.Cpus { + irq.Total += irqval + } + _, err := strconv.ParseInt(irqid, 10, 64) + if err == nil && len(fields) >= cpucount+2 { + irq.Type = fields[cpucount+1] + irq.Device = strings.Join(fields[cpucount+2:], " ") + } else if len(fields) > cpucount { + irq.Type = strings.Join(fields[cpucount+1:], " ") + } + irqs = append(irqs, *irq) + } + return irqs, nil +} + +func fileToString(path string) (string, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + content := string(data) + return content, nil +} + +func gatherTagsFields(irq IRQ) (map[string]string, map[string]interface{}) { + tags := map[string]string{"irq": irq.ID, "type": irq.Type, "device": irq.Device} + fields := map[string]interface{}{"total": irq.Total} + for i := 0; i < len(irq.Cpus); i++ { + cpu := fmt.Sprintf("CPU%d", i) + fields[cpu] = irq.Cpus[i] + } + return tags, fields +} + +func (s *Interrupts) Gather(acc telegraf.Accumulator) error { + irqdata, err := fileToString("/proc/interrupts") + if err != nil { + acc.AddError(fmt.Errorf("Reading %s: %s", "/proc/interrupts", err)) + } + irqs, err := parseInterrupts(irqdata) + if err != nil { + acc.AddError(fmt.Errorf("Parsing %s: %s", "/proc/interrupts", err)) + } else { + for _, irq := range irqs { + tags, fields := gatherTagsFields(irq) + acc.AddFields("interrupts", fields, tags) + } + } + + irqdata, err = fileToString("/proc/softirqs") + if err != nil { + acc.AddError(fmt.Errorf("Reading %s: %s", "/proc/softirqs", err)) + } + irqs, err = parseInterrupts(irqdata) + if err != nil { + acc.AddError(fmt.Errorf("Parsing %s: %s", "/proc/softirqs", err)) + } else { + for _, irq := range irqs { + tags, fields := gatherTagsFields(irq) + acc.AddFields("softirqs", fields, tags) + } + } + return nil +} + +func init() { + inputs.Add("interrupts", func() telegraf.Input { + return &Interrupts{} + }) +} diff --git a/plugins/inputs/interrupts/interrupts_test.go b/plugins/inputs/interrupts/interrupts_test.go new file mode 100644 index 000000000..d968eb094 --- /dev/null +++ b/plugins/inputs/interrupts/interrupts_test.go @@ -0,0 +1,59 @@ +package interrupts + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParseInterrupts(t *testing.T) { + interruptStr := ` CPU0 CPU1 + 0: 134 0 IO-APIC-edge timer + 1: 7 3 IO-APIC-edge i8042 +NMI: 0 0 Non-maskable interrupts +LOC: 2338608687 2334309625 Local timer interrupts +MIS: 0 +NET_RX: 867028 225 +TASKLET: 205 0` + + parsed := []IRQ{ + IRQ{ + ID: "0", Type: "IO-APIC-edge", Device: "timer", + Cpus: []int64{int64(134), int64(0)}, Total: int64(134), + }, + IRQ{ + ID: "1", Type: "IO-APIC-edge", Device: "i8042", + Cpus: []int64{int64(7), int64(3)}, Total: int64(10), + }, + IRQ{ + ID: "NMI", Type: "Non-maskable interrupts", + Cpus: []int64{int64(0), int64(0)}, Total: int64(0), + }, + IRQ{ + ID: "LOC", Type: "Local timer interrupts", + Cpus: []int64{int64(2338608687), int64(2334309625)}, + Total: int64(4672918312), + }, + IRQ{ + ID: "MIS", Cpus: []int64{int64(0)}, Total: int64(0), + }, + IRQ{ + ID: "NET_RX", Cpus: []int64{int64(867028), int64(225)}, + Total: int64(867253), + }, + IRQ{ + ID: "TASKLET", Cpus: []int64{int64(205), int64(0)}, + Total: int64(205), + }, + } + got, err := parseInterrupts(interruptStr) + require.Equal(t, nil, err) + require.NotEqual(t, 0, len(got)) + require.Equal(t, len(got), len(parsed)) + for i := 0; i < len(parsed); i++ { + assert.Equal(t, parsed[i], got[i]) + for k := 0; k < len(parsed[i].Cpus); k++ { + assert.Equal(t, parsed[i].Cpus[k], got[i].Cpus[k]) + } + } +}