From 6729bdeebe561162cb69a452b18f77dac8f9a159 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 15 Jan 2019 08:37:44 +0100 Subject: [PATCH 1/9] Fixes timeout for optics check on interfaces that are down --- optics/optics_collector.go | 2 +- optics/parser.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/optics/optics_collector.go b/optics/optics_collector.go index 1b5e355..596584a 100644 --- a/optics/optics_collector.go +++ b/optics/optics_collector.go @@ -39,7 +39,7 @@ func (*opticsCollector) Describe(ch chan<- *prometheus.Desc) { // Collect collects metrics from Cisco func (c *opticsCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { - out, err := client.RunCommand("show interface") + out, err := client.RunCommand("show interfaces stats | exclude disabled") if err != nil { return err } diff --git a/optics/parser.go b/optics/parser.go index f98d2f6..34a223d 100644 --- a/optics/parser.go +++ b/optics/parser.go @@ -12,10 +12,10 @@ import ( // ParseInterfaces parses cli output and returns list of interface names func (c *opticsCollector) ParseInterfaces(ostype string, output string) ([]string, error) { if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { - return nil, errors.New("'show interfaces' is not implemented for " + ostype) + return nil, errors.New("'show interfaces stats' is not implemented for " + ostype) } var items []string - deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/\.-]+) is.*$`) + deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/\.-]+)\s*$`) lines := strings.Split(output, "\n") for _, line := range lines { matches := deviceNameRegexp.FindStringSubmatch(line) From 7795bca9d826d3c87498b15d44270eaeb5ac90c8 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 7 Jun 2019 11:01:20 +0200 Subject: [PATCH 2/9] Fixes interface state detection for NX-OS --- interfaces/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interfaces/parser.go b/interfaces/parser.go index e2862f9..dfef766 100644 --- a/interfaces/parser.go +++ b/interfaces/parser.go @@ -19,7 +19,7 @@ func (c *interfaceCollector) Parse(ostype string, output string) ([]Interface, e macRegexp, _ := regexp.Compile(`^\s+Hardware(?: is|:) .+, address(?: is|:) (.*) \(.*\)$`) deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/\.-]+) is.*$`) adminStatusRegexp, _ := regexp.Compile(`^.+ is (administratively)?\s*(up|down).*, line protocol is.*$`) - adminStatusNXOSRegexp, _ := regexp.Compile(`^\S+ is (up|down)(?:\s|,)(\(Administratively down\))?.*$`) + adminStatusNXOSRegexp, _ := regexp.Compile(`^\S+ is (up|down)(?:\s|,)?(\(Administratively down\))?.*$`) descRegexp, _ := regexp.Compile(`^\s+Description: (.*)$`) dropsRegexp, _ := regexp.Compile(`^\s+Input queue: \d+\/\d+\/(\d+)\/\d+ .+ Total output drops: (\d+)$`) inputBytesRegexp, _ := regexp.Compile(`^\s+\d+ (?:packets input,|input packets)\s+(\d+) bytes.*$`) From 1fd2fcac94cf0a32fdee592de96849d102d6142a Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 7 Jun 2019 11:24:46 +0200 Subject: [PATCH 3/9] Adds speed as label to interfaces --- interfaces/interface.go | 2 ++ interfaces/interface_collector.go | 4 ++-- interfaces/parser.go | 25 ++++++++++++++----------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/interfaces/interface.go b/interfaces/interface.go index fe16aa6..e3a1c7d 100644 --- a/interfaces/interface.go +++ b/interfaces/interface.go @@ -16,4 +16,6 @@ type Interface struct { InputBytes float64 OutputBytes float64 + + Speed string } diff --git a/interfaces/interface_collector.go b/interfaces/interface_collector.go index 7ed73b1..b82b5dd 100644 --- a/interfaces/interface_collector.go +++ b/interfaces/interface_collector.go @@ -24,7 +24,7 @@ var ( ) func init() { - l := []string{"target", "name", "description", "mac"} + l := []string{"target", "name", "description", "mac", "speed"} receiveBytesDesc = prometheus.NewDesc(prefix+"receive_bytes", "Received data in bytes", l, nil) receiveErrorsDesc = prometheus.NewDesc(prefix+"receive_errors", "Number of errors caused by incoming packets", l, nil) receiveDropsDesc = prometheus.NewDesc(prefix+"receive_drops", "Number of dropped incoming packets", l, nil) @@ -94,7 +94,7 @@ func (c *interfaceCollector) Collect(client *rpc.Client, ch chan<- prometheus.Me } for _, item := range items { - l := append(labelValues, item.Name, item.Description, item.MacAddress) + l := append(labelValues, item.Name, item.Description, item.MacAddress, item.Speed) errorStatus := 0 if item.AdminStatus != item.OperStatus { diff --git a/interfaces/parser.go b/interfaces/parser.go index dfef766..13b020a 100644 --- a/interfaces/parser.go +++ b/interfaces/parser.go @@ -15,17 +15,18 @@ func (c *interfaceCollector) Parse(ostype string, output string) ([]Interface, e return nil, errors.New("'show interface' is not implemented for " + ostype) } items := []Interface{} - newIfRegexp, _ := regexp.Compile(`(?:^!?(?: |admin|show|.+#).*$|^$)`) - macRegexp, _ := regexp.Compile(`^\s+Hardware(?: is|:) .+, address(?: is|:) (.*) \(.*\)$`) - deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/\.-]+) is.*$`) - adminStatusRegexp, _ := regexp.Compile(`^.+ is (administratively)?\s*(up|down).*, line protocol is.*$`) - adminStatusNXOSRegexp, _ := regexp.Compile(`^\S+ is (up|down)(?:\s|,)?(\(Administratively down\))?.*$`) - descRegexp, _ := regexp.Compile(`^\s+Description: (.*)$`) - dropsRegexp, _ := regexp.Compile(`^\s+Input queue: \d+\/\d+\/(\d+)\/\d+ .+ Total output drops: (\d+)$`) - inputBytesRegexp, _ := regexp.Compile(`^\s+\d+ (?:packets input,|input packets)\s+(\d+) bytes.*$`) - outputBytesRegexp, _ := regexp.Compile(`^\s+\d+ (?:packets output,|output packets)\s+(\d+) bytes.*$`) - inputErrorsRegexp, _ := regexp.Compile(`^\s+(\d+) input error(?:s,)? .*$`) - outputErrorsRegexp, _ := regexp.Compile(`^\s+(\d+) output error(?:s,)? .*$`) + newIfRegexp := regexp.MustCompile(`(?:^!?(?: |admin|show|.+#).*$|^$)`) + macRegexp := regexp.MustCompile(`^\s+Hardware(?: is|:) .+, address(?: is|:) (.*) \(.*\)$`) + deviceNameRegexp := regexp.MustCompile(`^([a-zA-Z0-9\/\.-]+) is.*$`) + adminStatusRegexp := regexp.MustCompile(`^.+ is (administratively)?\s*(up|down).*, line protocol is.*$`) + adminStatusNXOSRegexp := regexp.MustCompile(`^\S+ is (up|down)(?:\s|,)?(\(Administratively down\))?.*$`) + descRegexp := regexp.MustCompile(`^\s+Description: (.*)$`) + dropsRegexp := regexp.MustCompile(`^\s+Input queue: \d+\/\d+\/(\d+)\/\d+ .+ Total output drops: (\d+)$`) + inputBytesRegexp := regexp.MustCompile(`^\s+\d+ (?:packets input,|input packets)\s+(\d+) bytes.*$`) + outputBytesRegexp := regexp.MustCompile(`^\s+\d+ (?:packets output,|output packets)\s+(\d+) bytes.*$`) + inputErrorsRegexp := regexp.MustCompile(`^\s+(\d+) input error(?:s,)? .*$`) + outputErrorsRegexp := regexp.MustCompile(`^\s+(\d+) output error(?:s,)? .*$`) + speedRegexp := regexp.MustCompile(`^\s+(.*)-duplex,\s(\d+) ((\wb)/s).*$`) current := Interface{} lines := strings.Split(output, "\n") @@ -75,6 +76,8 @@ func (c *interfaceCollector) Parse(ostype string, output string) ([]Interface, e current.InputErrors = util.Str2float64(matches[1]) } else if matches := outputErrorsRegexp.FindStringSubmatch(line); matches != nil { current.OutputErrors = util.Str2float64(matches[1]) + } else if matches := speedRegexp.FindStringSubmatch(line); matches != nil { + current.Speed = matches[2] + " " + matches[3] } } return append(items, current), nil From f9c0e30b7c4f15ddf176c17f1f48fb7b31b24c6a Mon Sep 17 00:00:00 2001 From: hellerve Date: Fri, 11 Oct 2019 11:21:00 +0200 Subject: [PATCH 4/9] core: make timeout configurable --- cisco_collector.go | 7 ++++--- connector/connection.go | 16 +++++++--------- main.go | 3 ++- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cisco_collector.go b/cisco_collector.go index 15c0f77..a4d0613 100644 --- a/cisco_collector.go +++ b/cisco_collector.go @@ -33,11 +33,12 @@ func init() { type ciscoCollector struct { targets []string collectors map[string]collector.RPCCollector + timeout int } -func newCiscoCollector(targets []string) *ciscoCollector { +func newCiscoCollector(targets []string, timeout int) *ciscoCollector { collectors := collectors() - return &ciscoCollector{targets, collectors} + return &ciscoCollector{targets, collectors, timeout} } func collectors() map[string]collector.RPCCollector { @@ -99,7 +100,7 @@ func (c *ciscoCollector) collectForHost(host string, ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(t).Seconds(), l...) }() - conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers) + conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers, c.timeout) if err != nil { log.Errorln(err) ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, 0, l...) diff --git a/connector/connection.go b/connector/connection.go index 1616413..15c19c1 100644 --- a/connector/connection.go +++ b/connector/connection.go @@ -15,14 +15,12 @@ import ( "golang.org/x/crypto/ssh" ) -const timeoutInSeconds = 5 - var ( cachedConfig *ssh.ClientConfig lock = &sync.Mutex{} ) -func config(user, keyFile string, legacyCiphers bool) (*ssh.ClientConfig, error) { +func config(user, keyFile string, legacyCiphers bool, timeout int) (*ssh.ClientConfig, error) { lock.Lock() defer lock.Unlock() @@ -39,7 +37,7 @@ func config(user, keyFile string, legacyCiphers bool) (*ssh.ClientConfig, error) User: user, Auth: []ssh.AuthMethod{pk}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), - Timeout: timeoutInSeconds * time.Second, + Timeout: time.Duration(timeout) * time.Second, } if legacyCiphers { cachedConfig.SetDefaults() @@ -50,7 +48,7 @@ func config(user, keyFile string, legacyCiphers bool) (*ssh.ClientConfig, error) } // NewSSSHConnection connects to device -func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool) (*SSHConnection, error) { +func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool, timeout int) (*SSHConnection, error) { if !strings.Contains(host, ":") { host = host + ":22" } @@ -59,7 +57,7 @@ func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool) (*SSHConn Host: host, legacyCiphers: legacyCiphers, } - err := c.Connect(user, keyFile) + err := c.Connect(user, keyFile, timeout) if err != nil { return nil, err } @@ -78,8 +76,8 @@ type SSHConnection struct { } // Connect connects to the device -func (c *SSHConnection) Connect(user, keyFile string) error { - config, err := config(user, keyFile, c.legacyCiphers) +func (c *SSHConnection) Connect(user, keyFile string, timeout int) error { + config, err := config(user, keyFile, c.legacyCiphers, timeout) if err != nil { return err } @@ -127,7 +125,7 @@ func (c *SSHConnection) RunCommand(cmd string) (string, error) { select { case res := <-outputChan: return res.output, res.err - case <-time.After(timeoutInSeconds * time.Second): + case <-time.After(cachedConfig.Timeout): return "", errors.New("Timeout reached") } diff --git a/main.go b/main.go index 20e2985..80de4db 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ var ( sshHosts = flag.String("ssh.targets", "", "SSH Hosts to scrape") sshUsername = flag.String("ssh.user", "cisco_exporter", "Username to use for SSH connection") sshKeyFile = flag.String("ssh.keyfile", "", "Key file to use for SSH connection") + sshTimeout = flag.Int("ssh.timeout", 5, "Timeout to use for SSH connection") debug = flag.Bool("debug", false, "Show verbose debug output in log") legacyCiphers = flag.Bool("legacy.ciphers", false, "Allow legacy CBC ciphers") bgpEnabled = flag.Bool("bgp.enabled", true, "Scrape bgp metrics") @@ -79,7 +80,7 @@ func handleMetricsRequest(w http.ResponseWriter, r *http.Request) { reg := prometheus.NewRegistry() targets := strings.Split(*sshHosts, ",") - c := newCiscoCollector(targets) + c := newCiscoCollector(targets, *sshTimeout) reg.MustRegister(c) promhttp.HandlerFor(reg, promhttp.HandlerOpts{ From 1f9e0aa4c9d9b4270e2c42eb61acba368538c952 Mon Sep 17 00:00:00 2001 From: hellerve Date: Fri, 11 Oct 2019 12:14:17 +0200 Subject: [PATCH 5/9] readme: add ssh timeout flag --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 03d9fa1..eb76f36 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ web.telemetry-path | Path under which to expose metrics. | /metrics ssh.targets | Comma seperated list of hosts to scrape | ssh.user | Username to use for SSH connection | cisco_exporter ssh.keyfile | Key file to use for SSH connection | cisco_exporter +ssh.timeout | Timeout in seconds to use for SSH connection | 5 debug | Show verbose debug output | false legacy.ciphers | Allow insecure legacy ciphers: aes128-cbc 3des-cbc aes192-cbc aes256-cbc | false From d86abf794bf9a2b33abd2b837dc63451421ad611 Mon Sep 17 00:00:00 2001 From: hellerve Date: Fri, 11 Oct 2019 13:20:35 +0200 Subject: [PATCH 6/9] collector: simplify change --- cisco_collector.go | 7 +++---- main.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cisco_collector.go b/cisco_collector.go index a4d0613..b2d3211 100644 --- a/cisco_collector.go +++ b/cisco_collector.go @@ -33,12 +33,11 @@ func init() { type ciscoCollector struct { targets []string collectors map[string]collector.RPCCollector - timeout int } -func newCiscoCollector(targets []string, timeout int) *ciscoCollector { +func newCiscoCollector(targets []string) *ciscoCollector { collectors := collectors() - return &ciscoCollector{targets, collectors, timeout} + return &ciscoCollector{targets, collectors} } func collectors() map[string]collector.RPCCollector { @@ -100,7 +99,7 @@ func (c *ciscoCollector) collectForHost(host string, ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(t).Seconds(), l...) }() - conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers, c.timeout) + conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers, *sshTimeout) if err != nil { log.Errorln(err) ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, 0, l...) diff --git a/main.go b/main.go index 80de4db..ff17c63 100644 --- a/main.go +++ b/main.go @@ -80,7 +80,7 @@ func handleMetricsRequest(w http.ResponseWriter, r *http.Request) { reg := prometheus.NewRegistry() targets := strings.Split(*sshHosts, ",") - c := newCiscoCollector(targets, *sshTimeout) + c := newCiscoCollector(targets) reg.MustRegister(c) promhttp.HandlerFor(reg, promhttp.HandlerOpts{ From c7f892e9be7c440cf1f22d9c0409c0d54f0b3553 Mon Sep 17 00:00:00 2001 From: hellerve Date: Fri, 11 Oct 2019 13:31:02 +0200 Subject: [PATCH 7/9] core: make batch size configurable (references #5) --- README.md | 1 + cisco_collector.go | 2 +- connector/connection.go | 6 ++++-- main.go | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb76f36..098c17a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ ssh.targets | Comma seperated list of hosts to scrape | ssh.user | Username to use for SSH connection | cisco_exporter ssh.keyfile | Key file to use for SSH connection | cisco_exporter ssh.timeout | Timeout in seconds to use for SSH connection | 5 +ssh.batch-size | The SSH response batch size | 10000 debug | Show verbose debug output | false legacy.ciphers | Allow insecure legacy ciphers: aes128-cbc 3des-cbc aes192-cbc aes256-cbc | false diff --git a/cisco_collector.go b/cisco_collector.go index b2d3211..d910011 100644 --- a/cisco_collector.go +++ b/cisco_collector.go @@ -99,7 +99,7 @@ func (c *ciscoCollector) collectForHost(host string, ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(t).Seconds(), l...) }() - conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers, *sshTimeout) + conn, err := connector.NewSSSHConnection(host, *sshUsername, *sshKeyFile, *legacyCiphers, *sshTimeout, *sshBatchSize) if err != nil { log.Errorln(err) ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, 0, l...) diff --git a/connector/connection.go b/connector/connection.go index 15c19c1..fde4b0b 100644 --- a/connector/connection.go +++ b/connector/connection.go @@ -48,7 +48,7 @@ func config(user, keyFile string, legacyCiphers bool, timeout int) (*ssh.ClientC } // NewSSSHConnection connects to device -func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool, timeout int) (*SSHConnection, error) { +func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool, timeout int, batchSize int) (*SSHConnection, error) { if !strings.Contains(host, ":") { host = host + ":22" } @@ -56,6 +56,7 @@ func NewSSSHConnection(host, user, keyFile string, legacyCiphers bool, timeout i c := &SSHConnection{ Host: host, legacyCiphers: legacyCiphers, + batchSize: batchSize, } err := c.Connect(user, keyFile, timeout) if err != nil { @@ -73,6 +74,7 @@ type SSHConnection struct { stdout io.Reader session *ssh.Session legacyCiphers bool + batchSize int } // Connect connects to the device @@ -158,7 +160,7 @@ func loadPublicKeyFile(file string) (ssh.AuthMethod, error) { func (c *SSHConnection) readln(ch chan result, cmd string, r io.Reader) { re := regexp.MustCompile(`.+#\s?$`) - buf := make([]byte, 10000) + buf := make([]byte, c.batchSize) loadStr := "" for { n, err := r.Read(buf) diff --git a/main.go b/main.go index ff17c63..0a5d4ab 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ var ( sshUsername = flag.String("ssh.user", "cisco_exporter", "Username to use for SSH connection") sshKeyFile = flag.String("ssh.keyfile", "", "Key file to use for SSH connection") sshTimeout = flag.Int("ssh.timeout", 5, "Timeout to use for SSH connection") + sshBatchSize = flag.Int("ssh.batch-size", 10000, "The SSH response batch size") debug = flag.Bool("debug", false, "Show verbose debug output in log") legacyCiphers = flag.Bool("legacy.ciphers", false, "Allow legacy CBC ciphers") bgpEnabled = flag.Bool("bgp.enabled", true, "Scrape bgp metrics") From 44b1ed5201f1bbf41045897ddbb3362d2606fc33 Mon Sep 17 00:00:00 2001 From: hellerve Date: Thu, 17 Oct 2019 13:57:35 +0200 Subject: [PATCH 8/9] connector: remove unreachable code --- connector/connection.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/connector/connection.go b/connector/connection.go index fde4b0b..f69033d 100644 --- a/connector/connection.go +++ b/connector/connection.go @@ -130,8 +130,6 @@ func (c *SSHConnection) RunCommand(cmd string) (string, error) { case <-time.After(cachedConfig.Timeout): return "", errors.New("Timeout reached") } - - return "", errors.New("Something went wrong") } // Close closes connection From a19ecbeed020d72628b57a4efeb77aa545345eef Mon Sep 17 00:00:00 2001 From: vidister Date: Sat, 7 Dec 2019 23:30:25 +0100 Subject: [PATCH 9/9] Fix naming of bgp_collector --- bgp/{interface_collector.go => bgp_collector.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bgp/{interface_collector.go => bgp_collector.go} (100%) diff --git a/bgp/interface_collector.go b/bgp/bgp_collector.go similarity index 100% rename from bgp/interface_collector.go rename to bgp/bgp_collector.go