pax_global_header00006660000000000000000000000064144414522200014510gustar00rootroot0000000000000052 comment=3f8561860c748de6aac0f79d4ef0305a1593028b prometheus-bird-exporter-1.4.2+ds/000077500000000000000000000000001444145222000170755ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/.github/000077500000000000000000000000001444145222000204355ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/.github/dependabot.yml000066400000000000000000000001461444145222000232660ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: / schedule: interval: daily prometheus-bird-exporter-1.4.2+ds/.github/workflows/000077500000000000000000000000001444145222000224725ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/.github/workflows/release.yml000066400000000000000000000007411444145222000246370ustar00rootroot00000000000000on: push: tags: - '*.*.*' name: Release jobs: goreleaser: name: Create Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: "1.20" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: args: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} prometheus-bird-exporter-1.4.2+ds/.github/workflows/test.yml000066400000000000000000000010371444145222000241750ustar00rootroot00000000000000on: push: branches: - main pull_request: branches: - main name: Test jobs: test: strategy: matrix: go-version: ["1.20.x"] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go if: success() uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Build run: go build - name: Run tests run: go test ./... -v -covermode=count prometheus-bird-exporter-1.4.2+ds/.gitignore000066400000000000000000000000321444145222000210600ustar00rootroot00000000000000.idea *.iml bird_exporter prometheus-bird-exporter-1.4.2+ds/.goreleaser.yml000066400000000000000000000004741444145222000220330ustar00rootroot00000000000000dist: artifacts before: hooks: - go mod download builds: - env: - CGO_ENABLED=0 goos: - linux - darwin - freebsd goarch: - amd64 - arm - arm64 ignore: - goos: freebsd goarch: arm64 ldflags: -s -w -X main.version={{.Version}} binary: bird_exporter prometheus-bird-exporter-1.4.2+ds/CODEOWNERS000066400000000000000000000000361444145222000204670ustar00rootroot00000000000000* @czerwonk prometheus-bird-exporter-1.4.2+ds/Dockerfile000066400000000000000000000005071444145222000210710ustar00rootroot00000000000000FROM golang as builder ADD . /go/bird_exporter/ WORKDIR /go/bird_exporter RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /go/bin/bird_exporter FROM alpine:latest RUN apk --no-cache add ca-certificates bash WORKDIR /app COPY --from=builder /go/bin/bird_exporter . EXPOSE 9324 ENTRYPOINT ["/app/bird_exporter"] prometheus-bird-exporter-1.4.2+ds/LICENSE000066400000000000000000000020601444145222000201000ustar00rootroot00000000000000MIT License Copyright (c) 2016 Daniel Czerwonk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-bird-exporter-1.4.2+ds/README.md000066400000000000000000000070621444145222000203610ustar00rootroot00000000000000# bird_exporter [![Go Report Card](https://goreportcard.com/badge/github.com/czerwonk/bird_exporter)](https://goreportcard.com/report/github.com/czerwonk/bird_exporter) Metric exporter for bird routing daemon to use with Prometheus. ## Remarks Since bird_exporter uses the bird unix sockets, bird has to be installed on the same machine as bird_exporter. Also the user executing bird_exporter must have permission to access the bird socket files. ### Bird configuration To get meaningful uptime information bird has to be configured this way: ``` timeformat protocol iso long; ``` ## Important information for users of bird 2.0+ Version 2.0 of bird routing daemon does support IPv4 and IPv6 in one single daemon now. For further information see [here](https://gitlab.labs.nic.cz/labs/bird/wikis/transition-notes-to-bird-2). Since version 1.1 bird_exporter can be used with bird 2.0+ using the `-bird.v2` parameter. When using this parameter bird_exporter queries the same bird socket for IPv4 and IPv6. In this mode the IP protocol is determined by the channel information and parameters `-bird.ipv4`, `-bird.ipv6` and `-bird.socket6` are ignored. ## Metric formats In version 1.0 a new metric format was introduced. To prevent a breaking change the new format is optional and can be enabled by using the ```-format.new``` flag. The new format handles protocols more generic and allows a better query structure. Also it adheres more to the metric naming best practices. In both formats protocol specific metrics are prefixed with the protocol name (e.g. OSPF running metric). Since verson 1.3 the new metric format is the default. This is a short example of the different formats: ### old format ``` bgp4_session_prefix_count_import{name="bgp1"} 600000 bgp6_session_prefix_count_import{name="bgp1"} 50000 ospfv3_running{name="ospf1"} 1 ``` ### new format ``` bird_protocol_prefix_import_count{name="bgp1",proto="BGP",ip_version="4"} 600000 bird_protocol_prefix_import_count{name="bgp1",proto="BGP",ip_version="6"} 50000 bird_ospfv3_running{name="ospf1"} 1 ``` ### Default Port In version 0.7.1 the default port changed to 9324 since port 9200 is the default port of Elasticsearch. The new port is now registered in the default port allocation list (https://github.com/prometheus/prometheus/wiki/Default-port-allocations) ### Sockets In version 0.8 communication to bird changed to sockets. The default socket path is ```/var/run/bird.ctl``` (for bird) and ```/var/run/bird6.ctl``` (for bird6). In case you are using different paths in your installation, the socket path can be specified by usind the ```-bird.socket``` (for bird) and ```-bird.socket6``` (for bird6) flag. ## Install ``` go get -u github.com/czerwonk/bird_exporter ``` ## Usage ``` bird_exporter -format.new=true ``` ## BIRD RS Dashboard this sample dashboard was created by [openbsod](https://github.com/openbsod). Thanks for contributing! https://grafana.com/dashboards/5259 ![alt text](https://github.com/czerwonk/bird_exporter/blob/master/grafana/img/bird_exporter.png) ## Features * BGP session state * OSPF neighbor/interface count * imported / exported / filtered prefix counts / route state changes (BGP, OSPF, Kernel, Static, Device, Direct, Babel) * protocol uptimes (BGP, OSPF, BFD) * BFD session status ## Third Party Components This software uses components of the following projects * Prometheus Go client library (https://github.com/prometheus/client_golang) ## License (c) Daniel Czerwonk, 2016. Licensed under [MIT](LICENSE) license. ## Prometheus see https://prometheus.io/ ## Bird routing daemon see http://bird.network.cz/ prometheus-bird-exporter-1.4.2+ds/bird_exporter.1.md000066400000000000000000000062161444145222000224330ustar00rootroot00000000000000--- date: 2018-06-20 footer: bird_exporter header: "bird_exporter's Manual" layout: page license: "Licensed under the MIT license" section: 1 title: BIRD_EXPORTER --- # NAME bird_exporter - A protocol state exporter for the BIRD routing daemon to use with Prometheus # SYNOPSIS **bird_exporter** [**OPTIONS**] # DESCRIPTION **bird_exporter** is a metric exporter for the BIRD routing daemon to use with Prometheus. Since **bird_exporter** uses the BIRD Unix socket(s), BIRD needs to be installed on the same machine as bird_exporter. The user executing bird_exporter must have read/write permission to access the BIRD Unix sockets. # OPTIONS **-bird.ipv4** Get protocols from bird (not compatible with **-bird.v2**) **-bird.ipv6** Get protocols from bird6 (not compatible with **-bird.v2**) **-bird.socket** */path/to/socket* Socket to communicate with bird routing daemon **-bird.socket6** */path/to/socket* Socket to communicate with bird6 routing daemon (not compatible with **-bird.v2**) **-bird.v2** BIRD major version >= 2.0 (multi channel protocols) **-format.new** New metric format (more convenient / generic) **-proto.bgp** Enables metrics for protocol BGP **-proto.direct** Enables metrics for protocol Direct **-proto.kernel** Enables metrics for protocol Kernel **-proto.ospf** Enables metrics for protocol OSPF **-proto.static** Enables metrics for protocol Static **-proto.babel** Enables metrics for protocol Babel **-version** Print version information **-web.listen-address** *[address]:port* Address on which to expose metrics and web interface **-web.telemetry-path** *path* Path under which to expose metrics (default "/metrics") Version 2.0 of BIRD supports both IPv4 and IPv6 in a single daemon. Since version 1.1 of **bird_exporter**, it can be used with BIRD 2.0+ using the **-bird.v2** option. When using this option, **bird_exporter** queries the same socket for both IPv4 and IPv6. In this mode the IP protocol is determined by the channel information, and options **-bird.ipv4**, **-bird.ipv6** and **-bird.socket6** are ignored. # BIRD CONFIGURATION To get meaningful uptime information, BIRD needs to be configured to use ISO-format timestamps: ``` timeformat protocol iso long; ``` # METRIC FORMATS In version 1.0, a new metric format was introduced. To avoid backwards incompatibility, the new format is optional and can be enabled by using the **-format.new** option. The new format handles protocols more generically and allows for a better query structure. It also adheres more to the Prometheus metric naming best practices. In both formats protocol specific metrics are prefixed with the protocol name (e.g. OSPF running metric). ## OLD METRIC FORMAT EXAMPLE ``` bgp4_session_prefix_count_import{name="bgp1"} 600000 bgp6_session_prefix_count_import{name="bgp1"} 50000 ospfv3_running{name="ospf1"} 1 ``` ## NEW METRIC FORMAT EXAMPLE ``` bird_protocol_prefix_import_count{name="bgp1",proto="BGP",ip_version="4"} 600000 bird_protocol_prefix_import_count{name="bgp1",proto="BGP",ip_version="6"} 50000 bird_ospfv3_running{name="ospf1"} 1 ``` # AUTHOR Daniel Czerwonk prometheus-bird-exporter-1.4.2+ds/charts/000077500000000000000000000000001444145222000203615ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/000077500000000000000000000000001444145222000231475ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/.helmignore000066400000000000000000000005351444145222000253040ustar00rootroot00000000000000# Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *.orig *~ # Various IDEs .project .idea/ *.tmproj .vscode/ prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/Chart.yaml000066400000000000000000000002671444145222000251010ustar00rootroot00000000000000apiVersion: v2 name: bird-exporter description: Exporter for bird-exporter type: application version: 0.1.0 appVersion: "1.4.1" sources: - https://github.com/czerwonk/bird-exporter prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/templates/000077500000000000000000000000001444145222000251455ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/templates/_helpers.tpl000066400000000000000000000034621444145222000274740ustar00rootroot00000000000000{{/* Expand the name of the chart. */}} {{- define "bird-exporter.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "bird-exporter.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define "bird-exporter.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} {{- define "bird-exporter.labels" -}} helm.sh/chart: {{ include "bird-exporter.chart" . }} {{ include "bird-exporter.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "bird-exporter.selectorLabels" -}} app.kubernetes.io/name: {{ include "bird-exporter.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "bird-exporter.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "bird-exporter.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/templates/daemonset.yaml000066400000000000000000000034371444145222000300170ustar00rootroot00000000000000apiVersion: apps/v1 kind: DaemonSet metadata: name: {{ include "bird-exporter.fullname" . }} labels: {{- include "bird-exporter.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "bird-exporter.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "bird-exporter.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: {{- toYaml .Values.exporter.args | nindent 12 }} ports: - name: http-metrics containerPort: 9324 protocol: TCP resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: - mountPath: /var/run/bird name: bird-socket readOnly: true {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} volumes: - name: bird-socket hostPath: path: {{ .Values.exporter.birdSocketPath }} prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/templates/service.yaml000066400000000000000000000005541444145222000274750ustar00rootroot00000000000000apiVersion: v1 kind: Service metadata: name: {{ include "bird-exporter.fullname" . }}-metrics labels: {{- include "bird-exporter.labels" . | nindent 4 }} spec: type: ClusterIP ports: - targetPort: http-metrics protocol: TCP port: 9324 name: http-metrics selector: {{- include "bird-exporter.selectorLabels" . | nindent 4 }} prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/templates/servicemonitor.yaml000066400000000000000000000013441444145222000311030ustar00rootroot00000000000000{{- if .Values.serviceMonitor.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: {{ include "bird-exporter.fullname" . }} spec: jobLabel: {{ .Values.serviceMonitor.jobLabel | quote }} namespaceSelector: matchNames: - {{ .Release.Namespace }} selector: matchLabels: {{- include "bird-exporter.labels" . | nindent 6 }} endpoints: - port: http-metrics {{- if .Values.serviceMonitor.metricRelabelings }} metricRelabelings: {{- toYaml .Values.serviceMonitor.metricRelabelings | nindent 6 }} {{- end }} {{- if .Values.serviceMonitor.relabelings }} relabelings: {{- toYaml .Values.serviceMonitor.relabelings | nindent 6 }} {{- end }} {{- end }} prometheus-bird-exporter-1.4.2+ds/charts/bird-exporter/values.yaml000066400000000000000000000021441444145222000253330ustar00rootroot00000000000000image: repository: czerwonk/bird_exporter pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" exporter: args: - "-format.new=true" - "-bird.socket=/var/run/bird/bird.ctl" - "-bird.socket6=/var/run/bird/bird6.ctl" # Host path of where the bird socket is. # This path will then be mounted into /var/run/bird in the container. birdSocketPath: "/var/run/bird" imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceMonitor: enabled: false jobLabel: "app.kubernetes.io/name" metricRelabelings: [] # - action: keep # regex: 'bird_.+' # sourceLabels: [__name__] relabelings: [] # - action: replace # sourceLabels: [__meta_kubernetes_pod_node_name] # targetLabel: nodename podAnnotations: {} podSecurityContext: fsGroup: 2000 securityContext: capabilities: drop: - ALL readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 resources: {} # limits: # cpu: 100m # memory: 32Mi # requests: # cpu: 100m # memory: 32Mi nodeSelector: {} tolerations: [] affinity: {} prometheus-bird-exporter-1.4.2+ds/client/000077500000000000000000000000001444145222000203535ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/client/bird_client.go000066400000000000000000000046441444145222000231700ustar00rootroot00000000000000package client import ( "fmt" "github.com/czerwonk/bird_exporter/parser" "github.com/czerwonk/bird_exporter/protocol" birdsocket "github.com/czerwonk/bird_socket" ) // BirdClient communicates with the bird socket to retrieve information type BirdClient struct { Options *BirdClientOptions } // BirdClientOptions defines options to connect to bird type BirdClientOptions struct { BirdV2 bool BirdEnabled bool Bird6Enabled bool BirdSocket string Bird6Socket string } // GetProtocols retrieves protocol information and statistics from bird func (c *BirdClient) GetProtocols() ([]*protocol.Protocol, error) { ipVersions := make([]string, 0) if c.Options.BirdV2 { ipVersions = append(ipVersions, "") } else { if c.Options.BirdEnabled { ipVersions = append(ipVersions, "4") } if c.Options.Bird6Enabled { ipVersions = append(ipVersions, "6") } } return c.protocolsFromBird(ipVersions) } // GetOSPFAreas retrieves OSPF specific information from bird func (c *BirdClient) GetOSPFAreas(protocol *protocol.Protocol) ([]*protocol.OSPFArea, error) { sock := c.socketFor(protocol.IPVersion) b, err := birdsocket.Query(sock, fmt.Sprintf("show ospf %s", protocol.Name)) if err != nil { return nil, err } return parser.ParseOSPF(b), nil } // GetBFDSessions retrieves BFD specific information from bird func (c *BirdClient) GetBFDSessions(protocol *protocol.Protocol) ([]*protocol.BFDSession, error) { sock := c.socketFor(protocol.IPVersion) b, err := birdsocket.Query(sock, fmt.Sprintf("show bfd sessions %s", protocol.Name)) if err != nil { return nil, err } return parser.ParseBFDSessions(protocol.Name, b), nil } func (c *BirdClient) protocolsFromBird(ipVersions []string) ([]*protocol.Protocol, error) { protocols := make([]*protocol.Protocol, 0) for _, ipVersion := range ipVersions { sock := c.socketFor(ipVersion) s, err := c.protocolsFromSocket(sock, ipVersion) if err != nil { return nil, err } protocols = append(protocols, s...) } return protocols, nil } func (c *BirdClient) protocolsFromSocket(socketPath string, ipVersion string) ([]*protocol.Protocol, error) { b, err := birdsocket.Query(socketPath, "show protocols all") if err != nil { return nil, err } return parser.ParseProtocols(b, ipVersion), nil } func (c *BirdClient) socketFor(ipVersion string) string { if !c.Options.BirdV2 && ipVersion == "6" { return c.Options.Bird6Socket } return c.Options.BirdSocket } prometheus-bird-exporter-1.4.2+ds/client/client.go000066400000000000000000000010471444145222000221620ustar00rootroot00000000000000package client import "github.com/czerwonk/bird_exporter/protocol" // Client retrieves information from Bird routing daemon type Client interface { // GetProtocols retrieves protocol information and statistics from bird GetProtocols() ([]*protocol.Protocol, error) // GetOSPFAreas retrieves OSPF specific information from bird GetOSPFAreas(protocol *protocol.Protocol) ([]*protocol.OSPFArea, error) // GetBFDSessions retrieves BFD specific information from bird GetBFDSessions(protocol *protocol.Protocol) ([]*protocol.BFDSession, error) } prometheus-bird-exporter-1.4.2+ds/examples/000077500000000000000000000000001444145222000207135ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/examples/kubernetes/000077500000000000000000000000001444145222000230625ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/examples/kubernetes/daemonset.yaml000066400000000000000000000016451444145222000257330ustar00rootroot00000000000000apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: bird-exporter namespace: kube-system labels: app: bird-exporter spec: updateStrategy: type: RollingUpdate template: metadata: labels: app: bird-exporter spec: tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: bird-exporter image: bird_exporter:latest args: ["-format.new=true", "-bird.socket=/var/run/bird/bird.ctl"] resources: limits: cpu: 100m memory: 32Mi requests: cpu: 100m memory: 32Mi volumeMounts: - mountPath: /var/run/bird/ name: bird-socket readOnly: true ports: - containerPort: 9324 name: metrics volumes: - name: bird-socket hostPath: path: /var/run/bird/ prometheus-bird-exporter-1.4.2+ds/go.mod000066400000000000000000000017471444145222000202140ustar00rootroot00000000000000module github.com/czerwonk/bird_exporter go 1.20 require ( github.com/czerwonk/bird_socket v0.0.0-20190111125930-6c39d61f8853 github.com/czerwonk/testutils v0.0.0-20170526233935-dd9dabe360d4 github.com/prometheus/client_golang v1.15.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect golang.org/x/sys v0.6.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) prometheus-bird-exporter-1.4.2+ds/go.sum000066400000000000000000000117741444145222000202420ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/czerwonk/bird_socket v0.0.0-20190111125930-6c39d61f8853 h1:XSnDYYRExBUBKn2K2fWE5Z4rYPkdsjBwN7SvU5CACzM= github.com/czerwonk/bird_socket v0.0.0-20190111125930-6c39d61f8853/go.mod h1:SQ2xpIC3W7ygMomUYEsxXBeun7RML/HCNXj24fbz+XI= github.com/czerwonk/testutils v0.0.0-20170526233935-dd9dabe360d4 h1:1QQjuJMb2LVM/sk4HS7svnGjM8um7EWk8lD5BwZ2X28= github.com/czerwonk/testutils v0.0.0-20170526233935-dd9dabe360d4/go.mod h1:Xibh2UDW2TbNjbi8QON4p0QxiYK/RM5USagAW7J3jUM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= prometheus-bird-exporter-1.4.2+ds/grafana/000077500000000000000000000000001444145222000204745ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/grafana/BIRD-RS-1522082867844.json000066400000000000000000000152161444145222000237640ustar00rootroot00000000000000{ "__inputs": [ { "name": "DS_PROMETHEUS", "label": "Prometheus", "description": "", "type": "datasource", "pluginId": "prometheus", "pluginName": "Prometheus" } ], "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "4.6.3" }, { "type": "panel", "id": "graph", "name": "Graph", "version": "" }, { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" } ], "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 1, "hideControls": true, "id": null, "links": [], "rows": [ { "collapse": false, "height": 509, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "${DS_PROMETHEUS}", "fill": 0, "id": 2, "legend": { "alignAsTable": true, "avg": false, "current": true, "hideEmpty": true, "hideZero": true, "max": false, "min": false, "rightSide": true, "show": true, "sort": "current", "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "span": 12, "stack": false, "steppedLine": true, "targets": [ { "dateTimeType": "DATETIME", "expr": "bird_protocol_prefix_export_count{ip_version=\"4\",proto=\"BGP\"}", "format": "time_series", "formattedQuery": "SELECT $timeSeries as t, count() FROM $table WHERE $timeFilter GROUP BY t ORDER BY t", "intervalFactor": 1, "legendFormat": "{{name}}", "query": "SELECT\n $timeSeries as t,\n count()\nFROM $table\nWHERE $timeFilter\nGROUP BY t\nORDER BY t", "refId": "A", "round": "0s" } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "PITER-IX prefixes exported", "tooltip": { "shared": true, "sort": 2, "value_type": "individual" }, "transparent": true, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": false }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "${DS_PROMETHEUS}", "fill": 0, "id": 3, "legend": { "alignAsTable": true, "avg": false, "current": true, "hideEmpty": true, "hideZero": true, "max": false, "min": false, "rightSide": true, "show": true, "sort": "current", "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "span": 12, "stack": false, "steppedLine": true, "targets": [ { "dateTimeType": "DATETIME", "expr": "bird_protocol_prefix_import_count{ip_version=\"4\",proto=\"BGP\"}", "format": "time_series", "formattedQuery": "SELECT $timeSeries as t, count() FROM $table WHERE $timeFilter GROUP BY t ORDER BY t", "intervalFactor": 1, "legendFormat": "{{name}}", "query": "SELECT\n $timeSeries as t,\n count()\nFROM $table\nWHERE $timeFilter\nGROUP BY t\nORDER BY t", "refId": "A", "round": "0s" } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "PITER-IX prefixes imported", "tooltip": { "shared": true, "sort": 2, "value_type": "individual" }, "transparent": true, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "none", "label": "prefixes", "logBase": 1, "max": null, "min": null, "show": false }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ] } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": false, "title": "Dashboard Row", "titleSize": "h6" } ], "schemaVersion": 14, "style": "dark", "tags": [], "templating": { "list": [] }, "time": { "from": "now-30m", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "", "title": "BIRD RS", "version": 15 }prometheus-bird-exporter-1.4.2+ds/grafana/img/000077500000000000000000000000001444145222000212505ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/grafana/img/bird_exporter.png000066400000000000000000002737161444145222000246460ustar00rootroot00000000000000PNG  IHDRdK'sBIT|d IDATxwxTU!Ф"** ş ZV *Rl *`` X@p ("ғ@(!=LGd0ށ}?{9̝{9Ʊkq)..4M|>@4 Btp8pDEEp8HLL$11@ؚ5k8ꨣ~9t֬YG1)\\\Lnn.`R@i|>q HNN&66@""""""""""""""r؉(5M۷ )..6I  ??M(vӢE G$M9N?~?x^k ciUn̖$Ò@Mii)XV&|yn;z7SӪ e-ˢʊȟBٽalII eeeu*Pfǻ=>Y)?:!}v>}.mTiDlz;tg)gO87ͼ|>n7;v ==鈈>n߾sInœM~~ѽq#4!XI'N ]zn7{!999ih>_i]0{zi5epҝsS(OKIn e#disNZjenVc   BVږQ\}@[qUX+lss\@X^?O"$ry^ èR.O>49餓X|9;w&11>b6oqG>}ݻ7~UVqꩧ?7H=qԴqϞ=8*GVC8U6S^7%=q8޽N:uFF>|8ՋN;72rH.B=z4;v`Ȑ!t֍,FaztҥOElr;wj ZsYDDDDDDDDDDDRc [RRr[FIE eUs?,f1U[0qf T ~#tbȐ!,[8:(ΝKٹs'4mڔŋӽ{w4i… K駟(--O. ,]cxY{ vWК4iBjjjC7CDDDDDDDDDDD ӴiS\ZW}ƣB׎}<~QycKM¾fkQ+dM &#Ӛ1rپ@జs0{̝;sN֭[ǀXhݻw'99 ز2N>dΝ[ گ_?~eռ;8p F%##ŋr?OAAUWΣO>4mKvv6~!|MuNZD&~FҺ{-,f͚EQQt:q: bxM<-[v]xޖ‹/0~8~CsKOoÎnqݴhђ>ř}rg@VV~ })`Cn 䬳Φy@y>/êUtr$%_GTGiz+@ ϾUN?3˺$h߾m]urtc';iE[KXS{xQ@rJOy뭷 صkW8}wIII!99ز2N:$ΝK߾}ϯ"~iӦ M6L4r'OpH޽[xXvm?sիSL!33Ve]F^x7$..Ν;t:k=Çň9~ n뻋sj<|8qqqT[.+++x͚54oޜ3q=/_M&^H\~X[!,^zA>_?6++SbĈ^yj#"""""""""R?P'v 3}Mrsszhqn~^Q#GpӍ72mr(۷S;߸qO<P w9묳a'gS9k6L4[IΝ93 {r2C;^ɤ7:u*{cqj<0yW]}5mڴ=vz-%g}6۷K(++kYH#Z~A?<{EӮ |&盢Hvpqqd>_!oe 7|0W_1h }Q[z<Ҹ m{xٵkWݾ};;v#}7|wylذ!?L :3KAپ/` q;M4 @L@nؼygf/w}`0ȳ<^m+++xE{}_&Mvk\@Ё6]nO<~#Gq '*-N:oK;DDDDDDDDDϫ0vҥ| /ujٓ 4-JJ1}zx Ep]wiF:t8ū!ya,kM4Ȯ]qv7‹.G!%%n #yqV=gR 0=3䣏|={rWʡtɸ\.^/<}:v1jԍy...lem#%%bRRR7o.e}gVh׌/0glve$$$qF^5J4x_xɓ'/0c ^uUzgټy3t(@v;n<#G5^W%&&2tО` ³VO)oŎ;*mu(Ï #@6 t:+}F1cp{7u'bŊz]@=E cկ>=sq9BgW~Ϟ=̘>oG7S?c|v s=˫`! <_WDDDDDDDDDD+mٲg~$zwLOii)ǟpSq@׮]1y^z^ȍ7mcpt֭̊ZuJnڴC>X̠CUӦM=S'rA^۶m#//4[޶m>0={кuky1t8w7H0,T stԑ_~.8SILJVѵk7eeU333ݻO†A>r-,{&Ndul޼N>nWYL?E]5^wYkTw~cn\ӦҸqc=[32]w|7 O"TczdYVud_kc Uih} JQA mq8.(I&ҲeKj>aڵkǓO>ɉ'Ȗ-[Oڵ+~D^^O>$v˖-N:1cƌ Ӻ֤.EVi۶->hk 7++I&iӦ:vj;Ӿ}{_yGP!/%5zgբ_KLrAku; $ V{LiVq*EDDDDDDDDD㢋u,_wԍx(zԩ3k׮|o / W6ؑo_>iv/_ιG׮p xPe|瓒]h)fٲm/yW_~+Nz/ ^Քy£\[qWׂ\k.B[MұcGq߽@y7+_§|r@ 0* 5 "@v{K?:vݣy[o_18|WO۫Zi~ rss;1b3f 77Өzbȑ,XT.\Κ5k۷//#`6l| ]v=:YP(DNNNmغu+={,}劊o߾Ջ۷:ZvSϦM>}:cر 6)Sx)Sкuc4{mM=8gzc?3W$0`_ҹKj_}0>X<-[rۮ]aކkFnݸ可ǵ֬kv]m?MIIOOkn:f ?}{ysN~lN`8k/m m۲h^)i׮=bBp8uv-_/33iӦ:ܹ3Zb۶m JjՊƍO笳0 Lˢq`ڵAW^ɒ%1޼:/Iy5hѲ%cǎ%66|< Ą?_^^~#OM U{ep@hqKnͳ=_eKp?Eb9$$$oTGC<,[٨ A00A:sǪsw ~_G&3mP"#OAplT7l*?rssy*R۶mY`ݺu#55 0d֬YCqq1^x!QQQ0v P;wsN>A\GhҤ k׮qH?sŽ~Գi2cƌCH 4 rJ8v-YB 9QthZeY,[C=e˖\2d0]8Ojjjg<cѶm;NO֭]~>0wywu'^x<6Vu W۷l2f͚ѡC͛_L\\\Uuָ\:|>z-GVj?66aÆuV~:N=uQ'O̶mxʜvtLas,dM ?e o;Uc dliiL4O>4wy}D&M~geӟ'!!g}'|q3g>E#p=&7ŋy袋#06??-Y 6p\Ϟ}=rVZqwӳ_~I륨<;묳ILL_aa!۶m) YPP@۶m/?k.袋1 -[qgsm۶q_N :&Tؾڵ sWf͚d w~:ڵk9G.]۹5sf#akIIIxsd1~f!,Ѽxӣ1\׿TTag?[*G{_<\?wǃi^'c82vo۷o_ alСseРAdffiȻYf@iii <;wV;]qUKǕW^CX`tҲeK ..;ү_?,˪u:ԓ\5kسg:ubn ˸ql`4/ Q9,^&7{,fϞUV仕Uƫn[ˍ7 ?^b+V?5*WVVWmߞo?~ѳo&ofkұSp練0gЃLNٓz |kܸ1ƔvͣGs7Ϟ5ŋӫW/^x%~?_}WS ~1118~[[u?o.7t3 a͚Ǎk!Y*>R,(?cx\tŔN/ #Frz?oXΦ IDATGaØ199yhRm?էU'*nݺqUWsUWW 3g6|c{Վ5lذCAAAyx~jtVo 2vN Y[y~==Xf GuTDeiՆ~'xiӦӢEJa֭[ټy3_~e׏CB!rss?~uׯdԨQU>ޫsL8'|o^/;v?~K=k׮eذatЁ~?||Wuf8∈ʞ۵bњD,*|hg{ڵcw-C5rbb"f`,"""""""""bGRRo? 4tsw{rqgsg/gKgg_sf߾/0MN?}ϢCDGGǏW{GYl(--0Jb y4B]!Vo zsNNMj|g*HNN{^ur QGmVmW_?ҧOp[o1x`222saʔ)OW(@Yau^6 mMq33;un 0@uԩ0|ح[9mSeYG.Ċ1U].-[$WϏY}ҡ S>,Y7ҫWJaϛoIYYYYY0v˖-dffү_?ΝaΝ }*"""""""""""""ROjx,~Xe -z24ǤY xHLLѱ2ewє""""""""""""#֭[x<B٧+GG'ׇ8ȏJx(~gXqMڽa8l*:mڴaӦMд$>O 4ML$ "v8n<Ox[ƍ5*VDDDDDDDDDDDDD"dJNN&99" 0 #Ƭa\. ea&N?dJHH !!`NDDDDDDDDDDDDD俞 """""""""""""g@VDDDDDDDDDDDDD(' dEDDDDDDDDDDDDDYzb4mjFY\.`C7CDDO"""zwȁYHH94ƊwȁS +"""""""""""""ROȊ""""""""""""""DH=Q +"""""""""""""ROȊ""""""""""""""DH=Q +"""""""""""""ROȊ""""""""""""""DH=Q +"""""""""""""ROȊ""""""""""""""DH=Q +"""""""""""""ROȊa-66SђY"ZlY+iu,]Mj‹t Bl̫3_o ot(^9;Uy.w}OX{My^{mNzn¬Y믪mzv饌u##F~ --Y/tP&/{A۷{>qc)""""""RG dEDDDDDІ yIJJb!L:/ ={eٲe޽6sυddl w[Fx |!Z7xN=q3>v,̚9 B%K]5t!ps|eY%""""""RW dEDDDDDPQQ1+W`Ϟ=oiu ~:Nҥ_­cp1zY}^}Fs=eywyiÏXh7of {;˗/m۶L| fL^qFIs!11;v+dGΧ!55G1 2<TO=z1 ˖l2Ə-ˢSN76mXnm| dEDDDDDɜw~,b˖-VObbg;P( T&3#^|߀eΜoxxqu2d JJJ1<: `i5kƽ<6M9ꨣq VkӇǎc;b-K5wǎl.rx`26ns.ddfT8vMyj뭭^/SM#::Sv{񦛸2ztv//! |6.\B GRb"7<9Ȋ4c=˖PRR?֭[k)UFҶݻwsDŽdffK,#ILLd׸exw|ܳ݇6*S5zk뇜:v{ IBbcǎ#--*  RV EMa7o}N nӶ-W\_?9Ȋ46̳POff&^7c]6|ÿ_O=c nK/_mأG1U (7/7;hܤI5?\a͛0e gdTZm}|\ze,K6mXq׭[DŽz0^yu&?;ws7ܮj*xvNQ,OUM˧޶m[zDDDDDDQ +"""""Ҁ oʱKXlټ&#Gbԩ,x]V|U=831 O=4w,^\RoѲ%{r3C Aw"~LS ==.Szgʕw/YK=8&Mč7=wUvUWom_@VX|M-Zk׾@x~*<ڵ Yp=M6EDDDDD(HLHO ?ZiDd^^/r+}!?CS'&&2YpV3z-[v]̉'Ń'??:~W֭]˕C ?VzKSVtrĉdff0f̭Iy 5v۳nZʼWnv-_zj쇲26lXϵ]r1tU͑GvOzj_{nZ^/q xa Gc=ym6nqp5[ng ?O 0*,YSzIqu5NlYǏc7~+0h^u;v$ #Řne1d%ĠCjlc ;&y)\׿/ީ""""""rPM6?qEDDDDDDj?bѢy) J """"""{iTFC7ᰠ~P +"""""""""""""Ro4eH=ԬY3bccbS&MhܸqC7Cl'%%!6EEEѲeˆnp8h۶mC7C"Ԯ]nD ==ZhAtttC7ClJNN&11!6%%%iѢEC7Clr\nݺ!h߾}C7A"nfHZjETTTC7CD""""""""""""""DH=Q +"""""""""""""ROȊ""""""""""""""DH=qczԩ˙cB+v;bgb+vN,)β< ^6nY! i}@da { }na ˏAeU@P\*apؿ=8C!L3a6CdwnT2h*rKgδB8"x.gZ0i:@65%cmKv8Q.m,@ʼnIc71rХR.}bYU IK1 䟀"e2(NM b^?$[Ŕ0IBfBjxlfia9쇿0 aRLLaX6o b..Gа8-?!73XX8,0#Q.AN~,^NO(^_Z{c60I(GV9W %yʘ&ֽ4Ua0mܜfp89e{P$3BA,ݛ8;tl}Z^Gt=kVv}""""""""Ri {oDG­Ot/ ,hFZvǨ(F1)'!eNv0w8 T 1mc"/gr\XX}N{_yF`3h } ,P!./p{6`(Ai0;s2dAӏf$tW.1qut[k L{Hm|u3MhIiB1imAcrZC?+#7M8v}""""""""Ri <*=v@ ۴TȇǴ9pDt 5bK( flg_ΐ`ɲ,6- odeO`0ˬ{} 3dF0m'E0{'4qN5 _o 28llt=%s ?r'aؼʂD۝mmnk8 3y-ލ9 0" 0lNmmZ!<.؜ `(c1vg0tpڿ.Uc$;d{T(a"Kp+s`Wc>#৑o3Ձf+[uXR Y XJJȚ>`(a(.`:q8ϰ`}6?- R0VeIM ]t>g ,,f3c8,ѿ+V,]HUMO"!8vҼ#nOd!Vih2m)jXWɞ2 Ӊa8NٗNHH沺C#5''m:qil`ڜ6uGs3p OHM,QLvGBxh~G~lCX26GGr]e/- 2睝;P<0n,h ho2q V!́`ww}n6odďY Y?L33mZx"z'.)FaaڼNP] G ],°y1u9~H;AICO 9\y9lM=_:BQQ1^oiUs9"<פI,"/@]#&&9.' DNWLϦh*w$E0%w:Đ[v-|~3KF>ۿ %P,e!<.AF˳z/ {ao{ݘ49$KsJjNic zÀ$ m7&ˀ7h3WG(dt\.o_wk fZ>SlgX{!,_!FT2 yb Pjsiq#Zl>,х7lڂPNv>2M֓.z'| x?jBv=s;I%קUznc7{ҹ$]xgF2,VIsJ#K? njvOү}to3/ZUG@x`͑ f m;:WLS\?1)Jrخ+ss˲pF4N]&+e |<(+°eF!jONn|!`irX`sLŊh62xOTM&mE;Fts WEdMHt8lݲ#|0ා/H7TX>ϱ'aަ [lFIߎIitLao?eGǔs ^Ğ#msBjIYQ{t-?,]NDDDDDD$i:A|VS˸[# wm:A>|բMRG4|MoxO:jz}u>_@I" W c/,ƲMwixa~19XZ%69*}>) 1.L^A¢$'~c/d Ho8w#>Tb흛0x*A3*-Nlb\<*c8dm+ `sFM3d9/_>uwNbcHڞFavf9 /w}>J K0m!(,hF@A${kwarUuwzIg%@d0``. 333WtAYTd/#2#0 *6Y$^?DbBҟ~=:u={E9 IDAToYtI4)M0wd{3\-<6pUXAGn wVM(MۑVI&A4&^ޫa&&*2!bl8?Q=/xj@[-꒟N;#ve) hU秴h΀1؇CO/k5ܛE e̥DcTWOr({joE4ChȟFͪЇl~ul MA3 -&P|ٿpK xl [Cx#X>p~@VB{m%@ӑX>@bdg)cd[*~y]tò{s\ض|j@ep:C6EՄ!tዺ@&+ޮR` f&IaJC8s=ԫ%}/a_z|f0V0 &7:Go, hm[|(.hc<׏*jEW,z-(de:l/a\_7YQ <@DJ6,TV0 h,Fc0(cXz ڨA~=*j1ZP6 Ɏ7 |_a$YɽSh@s<z4}|߲D,j*!B5 8?{>Gy6d"=kFh>>38ۗa8!;ҩtPw$ߏbaNDDDDDD4Z/?|3 EGGHF7"'˿#\dzW7j?N*E!~nu/ng&13vd#W霱Q^q"sDiB XYl fTfE#È. « x2Lrӆ a ;8-R:6-o!݄.'ke36"60O(ZÃ3YGnKit-!?0o'FH_z̨|Fe a ZѤpbغѮȗTp[%n6LѶŒ @=n|P@-V,Ö/-i"}B nS걩֥!(Nu#һ@NZAITǠ(mX156h%CGVܿѠ#ȏ6.#LJm˂mbnyoڨc`|y>VpbhWEnĞeς i܊X/ K0V] ۔ ^I'[=蝢t|iU7u7oY^DDDD3̜ ,ON7 NfQm!Bcz!Pb\wa VSi=ɿOQ]n : _wp  a]"'oD[a͕ 6g#s;:*PnFElkovA0NMB̈8])!m,:Etej"R|/ :F#("n iK^'ZYS~kM YP \qSA-/[D֒4?۲@mmpHYXXC&&OSڮ+z3Ma`GʒCMlxMM&rY(z+cmApd Lua4]eaW2u[ZW|<$Wk0\I `*(DPkEWLۯVa 5 5 ~F3C ܫYi:IDuShދ#(ڭxh.w+W`[ @TT0:T܀_R'pM_?[ZvRv/ ^yf=dv9sPVjV5'Yn݇? "\i=zdH߬>KxO |]avІp =/ R8 uy:+ ~vI93)tdm8B{:1#`]L)4hw"p>|Zd{a_@`.\5Hk& } <*uWXW;pJ}k+տ=Nzb!|3TyjT> -@ivG!6q栂!Dž"w/BY۩\' h箄E43TU1E%"zsh@6 CDnB/|&NmUHFVѐǶ%Mۭ_[T*P!h !\d2h4ұ)p*΀y"o[S ipK6M]$ #(hu<K=r0qsQH.,#b,w<ʱ)֥0?Ҳfo?n(!YHg~@4]Qh\.Q hK8kP t !6Phož]xlʿQ-@ChjvS<kC.Dy!EU A ׮$HKh 'Y[9̐}€s[p[0, xZ ШO^D1:LD9KC+\x1ׂn~І0Qޚ!bۨ T0B =hٮ7`\Ӆn*EQ "hq2qX Y`{Ye_a HjϰBDaC'a>6M reP:ii4h4&u0$+I4 `׬?|2Ҷov )Hgp|[iSkZwބJ^TX Xb NZMQYذM|Ѫ WCQfHv+l{ xyctNۅj]x]mdV3#H%Q!܆Pi04z ZQ&Qh7=Db|/!ֽ8>LBdr6zфlˢ>\vs`sC—yLYХ3/=/ X,Qզ<-TM! tX 礴Xx,BTVJn[!ۂ0=4 L#CJ> ʒP觮R86t*T޾> mܤTL|V7 aClrެ5eqz@,nӥnyȏ8my fJSDd"Q)^=(,IziuέD5ͮ, 8.itl[|~|y?s8hKJ>(6="vll6<3ћXAk;b1GcN"Q:VMB>|7gtvo(fx[0E&WyXyQDnߥ¼BD]hXtittiPOy^ӕ:ulX tFg6+ӕn&, w:mwmokqȯ?ɘׅ6G)ICs$׋h:#Nx.tS6k5pʲM oFpL:XJ,BAഡ۲L{?E%"zszzz:cHG~Tb qt{ӭS AK04h`^:-^}M>=="|Jsm(1T¼6lG5u\Ƽ|vAœ|B\RtJٚB_^ ]ױP@p:SבFw@Naρ5ccHX8Xa߱tcZSYp4l<6qB– uxu&4Y[.Ѩ8]/UyA}z{}_Z'T(t]<}myV Px1\VNm=N ]nL~ K._AҐh-ZRsuX1zB LvHZL=ЃCvQ-D `Z&<35n[66:dY4e݆!|r ϰB$ ])!4ivHJ{1:±֥h]nCWx^V='}@uXb}7}9Y W[+ w-?d]Ӭ^} C"cW4M~Z1Dgu]ЮVoQ#OQsnת$e=0PܨTanW)n+.Q( ݪT\"(VB7 З' 5$Ӳwؚ0Xa9VE4&_zTl -+ԐĴLu$V% y>.W@S{Lf'X!N:H5`ЩK}}Vh5Pi_:QIcjl60 Q,)B4MޕaH$bUK4Tm70 ~PXn9s%niJ햊FG"1A i"!yH DPl+BWD̶(fMje4MX."b1,։J:]Ph4ml ϯJ{tw\ UxidDj|`NRؿ42a(4q`@FNIk.Q]i?v./۲YuhkH^* ڭ8mһqv][<ZmM,"' MlPaKa_'ίR>J- % YbEbDks-kXB 1g[EW|d]lի+.離ǦZ*mr@YBF}@uXx鸼5d\f>J{Gǐ˯D\c >il`l 9wVIU $ⲗP!jcȩw /{0 ٫h4X^ z=Q9aC)RڭP+(^"&ZJK7ubȘ)W)։osR'{zН-* M,Bn} ӶyD=4 zm9~#*,-|NlBX.:NW.nx.~z͗DDo. uP'}|{?].8FYf ŏ<[ݝy,{pz#¥vp}'b`{(ZѤ|oJ^SYNPjUEՄn@4x|lRNA" u;. [v& "]f0 4Jq h¥U^fXBװSN( S\j٩a'&簾iL hQ>PGDf*SaVvl" r]č-˨Z'N;!߾gZ@zZ'mprRSV[Cwu3XAUZG^?ahl7&tٲs+مR$>6UۂmɞpOa8VX IDAT?~?u'OmoA|ܹʫp5W=DDDDDSXl۸ࡇWA/td2x)+âtR;p'%w g@2dYMĞ <σ2NuBP1S: /g@k#H< >V9T͝;BN3ب\師gqnߛ{/E\,/'%""""nmб7=N<0,fEꎃpdWAD`Rܔleܲ_,0i@ͫu$HzU^H~k=dy4}b2aƆ($`&x(DjaMqtDc8 uiCπT&v(]3l:#;`\r%u###W;v/_+W'> zuk֬_}ߍSN9aRK.A>G&׾5DQq~[&^~e~袋d}v/cݺu/~z7kb1|泟Ņ\n ?i?<5|%""""-hNVq8ʈ9s^S[ {q(]^GNa?  u"uwZW"Do{6@ },|lYFoQFێhk*zo?~~:>pꩧ⬳+Vo\s5XhFGGlgqN9j5y8sGy$ytxPв,U~x˖-ýދ l^xv>p 0 x;pWaw3ފR(>w4 ?û$_d64T;dXqPl4j:XVP]M4.xܹ8rɒdhj;_H }h|i\{[?iN9>h4Xd Rd<@k }_ZK/C9s |e-Gu$߹V|l6ߎUV OO[ dh4ɤ T/0?m8&^R۶>DM8nK%dqQ T v cJ/fuF /QT_URT_:wN,B,P?zr]D-KWۻyz~@{kt*\Gy*!&G@UөS>,6uo{j"&e2,@(xla:DB|{hP((I۾' rɤ8H_/H`TBR**6Rs C?]A c@tvف̟?6l0 j`Ɩ?R) oׇK;WZ|3BZQG{n\~Q#;ư|r\pJ\2|ߍG0MW_}5lߝw9ƗA>裏"Ps;\e˖<G陾&ߏjF1E!l60 Q,($L&p-"r($:.\kNwQHŋf͚. -\:(gr=8'NEةttiP(Ly^-N1!NcnwlӕN4/4?g6lذlK'kvcL^3=y0:2W!! 1m͛|>€1ڹf)^b6!!d&qu^p,@hth&e:a yʳhzamVbN-CuD?>R3DDDDDDDDDDDDDإ^ l1 b{tv@;5MDDDDDDDDDDDDDDSKьI&az.JKozJeDDDDDDDDDDDDDDSY""""""""""""")€,a@h0 KDDDDDDDDDDDD$d4K0 KDDDDDDDDDDDDl7ۿ8.䒝q 'lwry41߽mmsD'>cݺurrypYg_m};߉|#[{o;.O{L% DQzio|z7i<0~o'/gnCSH^{_ipq!N<gy&Q.0 gy&>wm~{Ҥip {}Rc0v;퓟ߎr ]{3~:i~K Y""""""""""""10; kvuu]~)4 { *0>w{4 HRZJ,J7ْo}+,\M45z{0:2gyfۖ.ž~y3 o !Bhqe 493 aӅ( q|q^-vsk3dk0͉D6a[&v0 O d0}țA%p!_VwĻޅ S9|1}4̙eIX7>>4 9|%K^{wq8 Ӄk| h6[}< x98sgQO*d9Njm*a8ݺ ,+uӹK D/TYCYl1,0 ̟-K`TG6i]@xl%헷ڍcCZ'61З-4-X,]4ї y,#J0'˱RL\u}q}lhoo0a:^yuX4 oF ]( 0LC|Kut ixy( (]*=ӠPo'y۶d yx!>*8)[JuEAZC)cST"*~~Zq^s]%0+N4"a;uC H";)ߞ\m&BTd@*J """"""o"X5 6  իNC4E¡ݴ]]]n\w߿ܹsʷn4-]+3[~o?2||泟e^:;m\\T=hD~#zyY`cj .ErPCf6X,3 a\_|؂6x%Il5bn"T@@XGO,MCP ~ɲѐN1'k#'h͔ ju }?㴏Ǖ7:LRk! I(ϓjOؐoTBtXi;%5ЛI_o<"Ώh)&:vT]z'q 7_1dzktN:; ̧͒rzYQI5s#H:E!]M4wv)hl[4 l?{cӪPou3MӀ+GDDDDDDD4t< KDDD;Pa'%ڙj U!TgDK8=+=~q~F I~_bLaV:0 KDDDD4˅!0RnwnhS$(@7ؓkdy),55>֮DDDDDDD3dh Jm^Å*N>/NE*&R]1QD"c)Y:X?XĂݢ4 O<=:Y"""""\/~/aou ˢ}h7 _ʼxزs5Q(Vd1Wj-`8_eY̏ГMҘ~,|f""""""y%"""""z)w&33bVّm,xְj3ٿHy>%""""M*Io[ôgd0Br:!䪬5!,EDDDDٰ,C!:=v)dn; 'OMv@ҙD;d$|?kRӛFK>,J@;1 lc~KۢZmz,|P8>l[(ԉzuOa&*)k3cSK `V9GsRlF"h,S_Єo]SK=% [L@wwED &>@uqa+Z:a ˩RXL 6s0 tuu!Jr/Z{[BRNT> ]l J|_Wqz]^Rfm/DrNjP,7$p0]X:ӏ<@LJipJv1 KDDDDDD˛V3Br}zn-U^&@ҩ,2P?NI*i6t9tN(R4Ua}ަikvV)J},\k׮5mI{ܹgz^Zݱ:3FϗnV]!ىY""""""KMTjˏ& LB>E!hԇctLӄmvuR-Y(hPju.0Kn.`3wh"""""""""""",㪫bǟ{oD"xL*?d4 |SW_}}{/E\,/瞛T\wᤓN%\|>xcʕX,| pÍ74M_C<&/gьai@4v_\ua_{qn>sN=T<#W>'|2Lm砃wߍO?_O~r˿XvN=TcѢE;LswRଳʕ+w41= z(Jwp|3i~wI!KDDDDDDDDDDDD3}9{'=2^hvI/pymaY~z<3h 6 mXz5W^Aoo/r g4 ,YT oϽwļ12nڴ ˖-}vy3 KDDDDDDDDDDDD3D`h#. \iVke9c1w\qkvi$A;wC}ނ#:ׇz+>uh6[}oǪUp_'N'Ì,lW<5<I5M!:(+5ssY2sf9GԺuk%&&֬Un$I]vUyyJ|l{s^I#ji4c/?sN:)''z޽չsg͛7f٧~s/C9d%꥽{,4c%bE84d4qD\.IY_kҤI>oMCqmvm8-YTgZZR;wԩ44*)))7@qc ,/U,VNԳgOIҞ={PIj+GZL~Ȑ\~ȐK:xPk_&N973ddhP>Zr$ԩS5k,IgΞ=[;wիo(##C˖- Jhk֬Vrr^mۡ! f0 c<233կ_e۶mӶm.]h-ZSA u=ĉ|! @h@А! BC ##}_VVScl6yZ3C, YeDGG\hfXlZׅ)uLSs:un׿=ZZ׭Xnp~U]{>! 0xj]^ZZ?^s=5-ͫiҥ{RQQevVXC,jaN,Fq.z}O>>c$=*W93vln pgƍRdd_+Xii):~:w)SEqׯ#>)>^999 v)0CSEEEKi##}_^^S&S U|>hi:AC, Y4a~:! b v>6?9/Pf@А! BC, Y\պuf*>,,Lk׮ P5׺uk]a㦚ZhnHL) Y̙@ݻwWMI*((ln׌ԙZڷwO9ix$I>t 8Pt뭷j…ZxRSSp8Y*|t:ȑz%=]|SN[^4FiҥϗRwt12 C7g%/[Rhz<Bw͑Tjݻ:wy,O;PnNv_fgg+99Yzi=>! XI2 \eeeŮZR;wԩ44*)))7,@anPBF~oZ*Iڳg_:\YfCC\ҌZ0qϹ! &ùFyzjвe4uT͚5Kg֡CF45kV+99Ys6lАU㊎$-ZH-Ym6m۶1 hX{}'C@А! BC͆ax<TGC ##}_YYsΙcZ} ֺ 4d|xqYqY Y 4dpU3,5`Fп=ZZ׭Xnp~U]{>! Zll-]X.]ػ.ggr :p4dl^)..}kA pgƍRdd_,j.aôjJbݺah-~KCMQU_̖Dp3ggzcsrrԩs']׳_rR+U+Uo#9:%眭ZRxDJ}ԭ{7䧷Hbbc5w|M?^eeeŮ}=޽[ONGħ̐@ѐf$Y /95n8l MӜW5rĈyy9b% 2Ѧ ȑ>! &#Kŕ{ `r<|~y]>mCɘ1(y4)(yCZ?2衋OnϹ! BCW7'! 0]p 4d @l. Чns^.̐! BC, Y@iڵ.>0~[u6nDѐLx<.^޽nJRAAevfԒ7Ծ{}ii4p?9[nEӧOWtt "ǣB͜9Syyy$ŢI&)!!AztIСCշo_Y,;vLiө'FgՂ^30 M6mܨ۷UWW+99YwiԨQ5jRRRn:=5 zw5l0M>]=\ͺ+VSJJڴiN:&#GҥK/61cd6oK^fɰۤgs +<*-ߡ yfIR^k.K>3=35̔$effnE*..֐!CtתT\s"##V#;PnNv_fgg+99Yzi=>! &㣗;]8ˣAӾվc~[RRRsf0:csarݺվ}{5J4o<Mm&IӒK*++(vʕڹsN) Y &!XIZ mհXvܩ>}Eo]{YߺukCԽ{wD:} fCC\ҌZ0qϹ! &SRo\IK'sRt͟?_*,,̙3ko߾] Rp8j!]gkbH}hk֬Vrr^mۡ! &O}9= pѲ>L}%zkN~~RRRR#kCk]~O/>qsqb 4d @h@А@a1'ق]pAtdO].Ξ=kjb9/_XXrfb!! fËvh"h@А! p! 4PXXRg vAhk]bջ55Wu,iiu+--ՈojO]tQb^***lnӓ+tr| \BCC…ztO>>cvء?ִLs5zXGt:6k>^+ͦT3z~4y<`Dczq,rzqah'IFyidqUS]ᗔ?nJ<<$t*))I߸Q _]N󢱽z+Z0>&<KIQeeϘoQvKcÆiʕ*((0ah-~KCMFSoRdzTy9v:wǏ׺P[nuݙ3gt;߷O,ls==wt]Ϟ:Ϲ9,4c%ɰXeuKN˥W:VT\! &2?AqrUKNۭyso߾$ͦlMÜW5rĈyy9bjx<4a֥Oy:>+rb 4d @h@؂]pA~aO=OPua, Y4aZ!h@А! BC-[hŊzjĈ 3hРW^r6LWֻᆱ+V(>>VX%K())Ikךq0~[u6nD| 43sѨQ.Z#Hl6VX 9rm֖-[U޽{+99Y?\.T\\|IMzuwK<O]mwﮤTPPp8ݮ37o^r2CW5ŢT9=ׯ5p P۶mr~[:uY?yd򗿔$UUU^ӤIjy뭷j…Zxq-<%I*((C{UXXl6[4i^u-]T_wR3t:ȑz%=]v804eʳڴqos^n:uJZjbcck-[Liii SO=U<77WLvh{yw(7'W;zcյ[W%xݮ'?+VԌhh抋U\\,ŢվC[Uka7I}v|snh2 !Ro呻gO Ull>#:uָ? .n◼4dd}ZFv{kg5BUh߯cbQuwڸa%q9991)I쩃!XIX ׺"Nj12n}_s._{K=f}GCMFəxt,"ՠao~?YG3>mڰAOsnY &㫅u70$9qhUQY)ah=ڼ9c>XN7&%;RF^! Y럟^o#C@А! BC,- czLAQ 97! BC, YmiZ:uBBB.۰qSp-\X7$&,0Lx<^3gWиJJdyzlh~o5#uSnOfpZO>ڴihZtnV [oh 4HÇwQJJƎz~Bqqq7z͛3, .{xxqJU-ouu\.\V޽չsg͛7&O?[n4{h؟bĉOiz/Vrr{={|KCMƽoUx\z.>~b>ׯf.ۭ H:ۯZ&MK.n_ۯVΝ;5u4 MJJJ|gi4G iJjUXl򆄄v+66V'OK٣dćJVke4]-[a7ӟׄ} Y4Ǐ*szJUr:oy{'NHx y<ӧ:uf͚%|CoڹsV^7xCZlݘ VyEvک7\v̚5{W6|unh26 J={(--M/v)IJNN?g}V۶mӶm.h"-Z˅I[l-[ꍻ]8qϹ9d1 )**JEEE5{ӧO"4̐$-X@ǏWiil6K/,\h߯aÆ 43,,@6vMF /,u93d @h@А! BC,%өe˗ #.4Y^_&p\bջ55Wu,Ë1ZZ |=x=J[?:vVXCh4y:%$hܹ:|>GgЎ;~:bљ< . T\\bY,wuتZc иO꓌ }KsӐ@aYZo\UU**k_i#;s ]ӣL?Z~yQMyIRTT~=9~n~?ݧM7΅ฮgO Ull>#:uָ? .n◼4dd]vUyyJ݀l $ۿ_7&%}5֡鋊CY~?YG3>mڰAOsnfɘ+AIԩ^!׭]i˖-Zr^5KSN$ϟo J0'11Q<8Pݣ͛3.;tcR.ed|un͜)~Ѳ-[˖\/_^2w>~=2衋OnϹ9d1 Y 4dЬ^ < `\_X6\n1EŲZ> ZX-V=25nfi܇bXdx11m0]m>W3xdٽ(riY0 CVKV9wsծ\52?Kط[^Ǚafj6-\o/\)hhXuCBO*t8M+(UxHqajz6`˖-_ӹJ+Jms݊pFW媖d#ReU\e^> 5/2*JG-e5EpZafK>)(-4IReu&.!3,LCa9 W\^"ɆMJv|iEB5b*}xLD3p(**JNӫ&ynzTtU*ӹ6 u0jU\\"r{7mޗv77~*Wka]v w0YaTM UJӹsM۶0e3?/yf_Av\SߧUNHl2ko_nl&~&]94Օ 1:xL('**J%%ne{6o?{~{\#?U#""dZUТ .h}>{x74hB-,i͛-\ͦV11rT;{ ا7Gvd+zu{>2OצZUmό>ͥOtڑ#'?}ǭ'"Ҩ Y#TMl:Ĵ7=x udzܩ3"|싼T~Q$Y~%X hiz\aY"JF6}۲VVLsHKMH;Wrk.sնe[s<I:[|N1&'~6"odtK CrXCL7K /8wʫD u໳)-Ë/ K 1UCD E{wYEڶjk5۫~@/W6߿}#<<\= Mqb"cLk^UUbd}OcY&*t>ox>[tN1->ߞ9Mk4TVLl:Ę~y%ؕOs0O#cMjá 3?wgSϓqZQYZ|yFRnA dw8dH4܂33Bj}o>{{*ݮ&w oiN~N޼RwAIL~wm#>bQHh>(\>ż٧ 74r\*bM9Njƫ>MmONNN1L74UjMZ69@W#66y ЦMԻ2VZܹs.&DDDt̙3.&(&&FYYY.&X,%$$.^ҥ;2`RBBT]|kNΝSyyyK 111Ri%ͦg-[*;;;إͦvɓ.&uUG v0)>>^9992.W GDÜ;wN.-[9pزeKK˗/עEԢE`j筰0]6eE۶4} e-ZWM:M!!sM5õpbݐSF=d1ixݮ37o^rӐU/--M/։'$I'OW_}>HCU߾}eXt1O>zG$I;vԮ]Z0-X@TNtرC5\ &h֭[oUC QaafΜ<饗^ґ#Gt-_԰aô~Sv]gVJJ'H?Piʕڲe,XG}r:z~IOQ6i$%$$(44T/8HE:uRǎ e)Sզ}6sӐ@b1[lQ`YV|z%I+Vŋ%IsUNW_髯o%K\޽{k̙:|:?^)))z_JӦMS^^ZjQFiȐ!*..mݦ~Zƍ$%''kݺuzWnIJOOף>{N?***>}hǎsmiz%jիW/M>]ٳMJ4n8eff[nJKKYwIKӇ|Pc0 mޜᗼCMaY-׼IR߾}k.0 2DsQZZEFF֌3f֬YKÇK=֭[ךW^ڵk%I}zU@7ohǎ$| K[ Ј#NECG3g':_lum! !%j$'&Jy^C1 qq zn*\Fo Y"4d DhM x>4d DV'Ծ}6L))PaAA$遇3O?7\ۍ IDATn4=7gMxxDS@ vk/k̝tm#urMػѼ+f$)&&Fx@[lQO+tI'7|zKO9E_~vJJKpi:3t񊊊Y4~8XK*UUUn+S'umktǨ;7_=7 Y-6@@ԻwopzIIYg{GK.Dw%߯kN"SO:K=zЛoA3:{w v+??qffӧ~"rbN9o=([dHw\ONPMͦ <ؘ%&%nh^h9~__\>}Nl7_]k׮՝5v3dj4+I]Τg#9NEEEI6|z;6ܿ1c4cto4CIJNlwrMZ`zkss,ZbeFfv\N{y4e$;n$&i:cmV]V6lЌ+iӦѣ. aiuZd!cy-[3P`zn[zzzt4C'55U@@VSQQQJKKSAAթݮm߾T`B.]m6ӀA999*((h2dff\VxrN$%%tjϞ=VBSNS˳:ԵkWmݺ4`PvvxNeeeTuuuV*%'&ÿoewƑd1`DА! !BCB,*\ Tݪzn8CB, Y"4d Dhh:tԃ>Ygq+**q .j:..NO=N#i!4c S8Uzto&N;z zgav =zPnn\.&LRw}ݻ+""BSNհaÔb=Z@@~ "ݮݻwE]~iڴiZ|^}F=쳺cIw߭?z->2mCee*++eչsguꔣ͛_htfӽާE j򯂞3djDJ9쿸/ֈ#4l0[;vl}}ћoaÆ?߯\ :TNS't5h |ͺ.L\.FW_}q.IP駟>zךēztcj2ܹq~l6,Y"r,ZOnE;gպwͪ$}{+..ֶm$IyyyZv$IJKKSll:(͜9!>;h={6:?O͙3GozjֶȾa馿(ݮѣ/oqиBW=~k_.yihN3Vv:$$4ڐl=T~5k讻:Z 38Cׯ7n:W EGGKw _jܻᆱgϞZbEP44m6 8M7nlr67PAh5>%={r4aCeee7n&M$i_oԩZj^}U͝;W/ /丯JZt~㑬WrakUZp!c^{UW^-XmJFF*++U]]mu*0 55U@@Vf CTTTPP`u*0n+''G۷o:ХKC/GNN zNdffIKK:$ө={X VJJ [3NS˳:ԵkWmݺ4`Pvvt*Z,t\˥zC1 QF7{l͚5go◅K@vviƶ1\ĉթS'×JFGCرcN!% D8CFrbbۨV}}X=7\.Ws, Y"4d Dh@А@b߿u=޽{Keo1wݭILգghThEFF4__i:6.NSc?>iSԮ}{@TW55Q :gMػѼKo4dpuȑVҎdەӹ:fekmlcԝhb盯_ukJ~wY,p\6W~~~z͒ͦO?E YNRc/6:mY) icWBBzm;yP\bu>k Y>5L'vw~,jw_oE~Ŷ+ԧωkڵ3wF6BnwPq,Z4c%+#d}7ԩS'{-mX')9Y6۾sm6_?C-Xܜ! VcR*'k_T=#zgZ|۰N=tW^M֯Ӓ%oW:省ZsӐ@qߟ:m߶M}i}/;A>\B, Y"4dжlc:` +mx<1y~3d Dh@А! !BCBiu@kd{|OSOi ?Ú?֭[wz\r:d۵lٗzv\]|%KDGkǎ5]?lz-׫MxaUUU3d)--a̘1%C3uMg;4Եtz7tC-p>3eff_5|-ymڼy:r! O<~z)=3z/\ӴitW2}q|^$wրL}*--ЀSNz5W Y%%_lTlH󈊊/I:s ݟAo 7ܠ|]vezr4rH!WG}_TJJc.B}ч3kر;Y}ھ}{P9h5{[R;vӐGwyCݾƉ'駟ֲe˚}Ϟ=uQGi̙ }g!ʕp͞3G*-)nn/[n$kN#n]e<|!U*'΁,Z4c%aw(#%E@@>ۊ4o<5fi͚5M^5}c __}5ڵkWڰ~$I?L'|B*,,@pKUVkWlN:I~m͟?_;wVݛuuԷo_egg7$9^:sՒ%n4x^y商 u14|Lnڵ+?y9C%_6l6Ν;kĉh޼yͺLƍӤI:uVZW_}Usŋ /jNuzTQQ!IݻڲesدZ}N>^111*)):4X a.]h۶mVrrrTPP ku*0 33S媭:&#eu*0 ))INS{:Z p:T^^թ]j֭VU\\,cu*0(++K:6[񜹊"4d Dh@А! !BCB, Y"4d Dh@А! !BCB, Y"pNt. yEli85ǝ(wU|sv/_קhpW~9 ŵz%)hoZ3s].ǛzGFG*22ZI1r4fm1`fUoӡuz x~S6S\ovRҕi76ז8ͦvJw ?>׸p;5=M#x ׸\o3$퍍N>籦9 O_uunCqzGEPUjזm1P\\:Ą4Rۮw4v]IIʰZ$4?׾}2f?43HNMў9|sr83NNIXaޞ__o#-==3Idn8..-8qUrVuU8/ؓ|tlj \)w_$㓫TUaDԩ&Pk|qxRTiމժv'؋lTLj;>JUl\tLj _d%Vm'Vc 6JՕƏ3#'PLdT;!J{h1Ww4&F5U?%0|`"UWodLhDUm1gٹS2\%׸:{cެi5MdT 長̚Jn9iZ.5XZ5&_Y5X연1Ś`iƚ`5u<m~ƛiƚ@v_#x!D^UUjkk +:sp8amvUSc qqq1~B p8|, Y"4d Dh@А! !BCB;$ښTM4IfjIPÅGkNW_}ο|ڥݻwv[;zxvޭpV~M_>,r$ Eݲ4zX)C֭[}:tl6FSw.EFF~nyԭXuItXO(w> PԍItXٺ굌ItXG(o. PԎItXTDDD2‘ium[Ghҥo֮] h麱> uk굌IxtXGKm?%ҵcm-]7&@%CzXlln$VNSYYYSO?#IٳV,_.Irݪ(/WFFFetjޑʪ0͑&Iճ>0ɑ֫W/[_(B떟:ct晿ӦMûCGuk*uItXOKNb}-]7'uc}fkh麱6 ĺ$\ZvM£h;8C6L/@7NzVtt|>4edIRBb/UJJJjǑ&fӭ#Fݻ52eUpD nYYYj׮ƎK ל9ڕ#JϓK?\{5vvӜ5u~MLl6S7' nnEeOLw;B'.V0cmb`h;hȆIuuƌ?_|I%%%zڲe$i޽\ !>A.zM;z>#mذ!۠db)- {G`V[[;vj '*''G;w N[SNzJMMդS4T;C9uk kS;S7' nOܺ5Z:P#kS;& nMK[֦zt:uJVZ$)];7y;hvƌK>2#Ѻeff /3ta/yo.mÆJOOoQ]]%<޶l"I*++SEEDVSuk hXFku0Z7'5U^XFڤu0Z7%ڱ6i\ᶿ 5Q}OT<! 7|#Iڳgf͞nݺԐ]f$)--Mc-oCv?WeVXxxo^M{ʕ+4r6yz .P6M_|L_~FuMwsNg}ڴi!hY4dbiii: }͓ߟ3P>O[6onVLΝ3Oƿޤ??49kt 7'.$vHmذAfofӤS$I#oM7~ŗ^TZzz衛oIOר;s55cv?ݢB]yUzᇴu{qڙYc[5kL˺37WS-ӽܭ3w駟hڵ<555}/In{lvqoKvM MWRRcF+//^dnoJLL 9vٗ_ꭷޔ$i9:tRIRQa}zc՘ѣi&mڴI_}RܹS]trr:7Gii9ކc;Uvv=^zU[[pL7oV4~} gUw>( ru`-7kY[Q<Ԙϣm- _zuEOe,IZ|cd1ⶆ>?UV^uQQ$)%560C IҤɓ}東ZVV{O_/.֭[M6iѺskzU\\|~Wii$Np:_xa.Rg44d~s}MKw]v4 !7u<*VHqt̔$Ϗ'=Ck.]$I32IOO?dECڀĄ|rwqЙz晧umkhՇtqbbF1JVZ%IzE16}*))9(uɥ*??_7WUTTo6mܨkE55[QlT7\ʷ)Cwvjuꮻk>vݫ.]jƍQee^!2Q[[͛԰nT]]NV}mٱM))-w)Y}]|JJJtTSSx]tџB٭N_}6Yg7xC[l֝86ovIҜٳn5s^ƌh̒%K4zt:t]cy)@ ѣs;ZϜkIci zzt#;Fr@@_~<=6];wԜٳ? M@ Qwܡ[{ȑwhU{^nw0Q^v!讱cT__'?Ys#$ h-==phX .:VXFnPLLLBd! h16٬NUxh@pb)\ gZ(##CVRSSbu0(>>^ڵ: ;Z :ꨣN&u`BNNNiLEGG[ JKKSbbi$Y Vffi өN:YLڵ)lEDDXLRTTi"4d Dh@А! !BCBb"ctVldḪ*GULd@ rUd 4?f?w[qq܊2y0; USW(vT).Dj񱱱 rWe{OC^QFSZq&83y~C2~3Zh9Nm竸X2P5TeM8'|?lVM}8pxam1zq ٛ߱< O%\>q+7g{ 7ۼ>~F=&\<6>ތ+` 9q::qxÅd1ja(&&2VU^Yn(.f7ހ/w+%.h%3<_YeRR ye(I*(5!p\1[WII㌰lӮݻLevK3|z y" $s9Ԯ w $$$Bo𨬮TBlI*[tq{*(Dͼ9W+˔b9toғ=k/ @[dMh6dkk9eJUWW[ HMMU PyXJsB 1֜$|>CMH%&&hw&~OvSq)>W3oz19v]۷W]}3Sv4Q+,+PfjGC1d~pǜfSFF \r|C}q~Myrӯ! ! h}6`u MRZZ NvhV2+_׿x4BK]m۶4`P\RN:D=$ñF?vC2l6H^CRqqv=>I߇e>/\^iAzX33QY]~R'Du=B1QhLʠݮY2 Y[̐:gn}J4pfՇ?2<h\*O7ɑ)_YMheMx]ΐk᳊~:~ZܫW+kʻ=k0LkALESҟn3%Cy?셇AΌeMyWec.wzuJK qbⴭ`V~8gJҦMPſgo%gVx_{W9p(AJ<{}/'Wrx_ߔww%{JZqXԂ U[/uջk/lBCqUJO6<_~i"}N6X)b<}7UjIReU9N^oo#o.%77,WQ!xi9eOHQG6Td;By7˳cӁ|^)A Y8޺FCڕKg&_M=x. wvTVV&gxn6`8-#++K:a@C\.-+4`@RRNcu*0 ::Z)))*,,:t:ph@А! !BCBٻfbDQȖijyZN'ӯR~撩IeeffYvL%@q MDPQ@֜L f.ZѮyzM֭SNTUU 6h…Zl}|ĉZljǥ*6&FfYwl $IO7RYRM8Qw~zoo>|XÇ?;we6yZpaѐe`08f9//O=zІ t4~!fҐ!C·?VYY)%9YA3gR\\rssծݭT_IO>{VX+V|l_T~Q``?"M ꔇ+Po=̺UTVe9Ƴ'OEᄏ85iDs̱?۸q˲<==ر2]}2Y=ާ,Ə"mIg~a=3h'l6) Y%|! kWeRϩ>նl:ٱcǴtR >  ܹ5fƏOVProӦ'IZ5wuޣ'OJ:{"M{^}眘 ,@q1XI0z(<8iu;Hoٲejܸ5kvQiiijӦs 17~>sN#;]g|,=z?NINͭZO@k֭u YsڈkkWRVyGV7$$D'OvfiܸqZtE|Ù^oo6mum,/33Spooo?̟/$TivIl6NUW]b̐hTtƺ&2Ju Yf|=kW^Q2z2M+ {-[TO`8o?@7p̟'IY3UXX\FYYYuOCƧ հA`󪪬zo&iJ1C۷mnȵ=٬GЂ^Syy$ǽ{5|س2zfP=عVY3А@q1XI0*<Կچlm8~\zz Y/[ڦKB4{<8|*++5bKZjڙzZժ۶Uu@CtmtXJJ+uW|Λ'___Ko֌s>M ~if8r֭Rob RӲl2 jwk{ݽNhx7ZfiԩZnKԌ IDATkkXޒݻӴkNuM0;M֭S-odfd{uw @'$\B+WvoujMSw`p|hА! .BC\Ny X*+R=NА! .BC\ ?`bcbd6}v-\%WQLl*+-J3Fyyy@CpYfiȐ!qE딒,f)..N]4fL6}I,xZhl6 5i$Wg޽{լY3fM6M /fcǎٳFN8cꡇRv픔^˗/vޛo^zI$Kf7*%9Ydt,.<<<\aaa|je DGGkȐ!ʕ+5rHXVb k;v^x[&IrԽ{w 0@}UVVt颏>H6l/_~yVRΝ%Ij߾kg@R@@5o_=o:*P+c5թ>=Iu YfQ~^^5Ϋҙ -**Rqq$iӦM=z}Ȑ$9rDvRyy$)++KU&M4g7W'..y_|y͟?_m۶UjjʜtzeXHJ:}}}GVYY~[3j], UYz7㔺6c6_eXΛc0sN5ѼtnZ<>Lf?A\ɓ:i:bӌ$Q V_wwjOKKS6me}6?駟P\\nZfƍ>zgx $kw딁7꺫qޙr9uiug۪Paa&M'O*!!AΞ6mRRR|r-^Xk׮Ւ%K.8o˖-z7j:]yQEFFS$I):uIӧk7~l64߫2]5\*<<\EEE*))qw8 $$D6MGxyy)44TFEGG+33QP 111#::Z٪tw8 ""BO&44TEd2)??Qooo+IW0LБ#GաCǫ:[ddλcXti֘f^\W7oΝ}; +z **J4c/3\Y ɓըQ#JFGCp#G;\+Ehpe1ꍠ:QZVӧO;ڸr*,9'dEhА! .BCC-؜8,Zޏ=(///y lJ=4 lVNvfϜZ,P';߀ڶm >Ј빡CdYuku Y\4i3fhڟzHrO;v蓕+ԵkWFi)0{ f~wuuϋU\\,ѨFtMd23թ>'dpԴi/J>ѣʕ #<׳C*';[CnN WY3:mƍ۶)++NqB!o_Y,Ȑlk޼EG+7b7o~ܻWÇ=+Ѩg Ճ;kgz?7Ƽ8JӇבu;{FQ6A{vքy?Vo۪VZi,@q1XIPPX`CͦX̙3$$///IҞ{u5ؗ:G`P c0:,ꍜѸaK˕ﴺэkذaz7W\Bц lR$IIO8-ܯEҵ+*a0(mw֭[[= aaa矹%"; "ͦwG\wGlwGF(edd;lUVV; ; *ŢBwGe2`; `2#G; C;Ǐb; <;JP=˕uCkYڸr\,x\) W! ˊߠ! .BC\NyJTUU96{! .BC\, YpȣSh46ŋհa~iSG8Y嵮 Yf͚p믿^w}'Pl쵺C9SzKj;S-$___͛7OC5tPkNIII֭$cǎZp^uM8Q5eI>EEE7x~رCU>QTTTCh4*&&FE맟~S-,@a|}\Α0fyϾްA_o:\Y .BC\, Yp  Za4:6\ } YpjY4dEhА1;pXjugOj/s,Zޏ=(.S-dRyYf&%鮻փ}Q˜k],puM'LѬ,wGC6\"<<\Zߢ$oϞ]wiϴzgyѱcuECWZQQQAaڵkZ^VZh4Ú-[K׮HiFR}?M6MPTF y󔚚.]*66V͛OW6UEEE'OGCݭjݧ߯oAfIU^+6R5bHyyyWo4u4>|X tIhgUTFٳcf'OѨ[om#YGԬys-zsx{{w虁T,xgΜ<7/ڴmIwyG491QQQQڹsv)ooo͞3GK?=5oII:x𠮻:=3:xtjJbN<`O ҙb~krof}z.X II?oza= 0A-[Tjjyynvޒ$fL&Y3M6Mz%IQQQPz'\{p)Y,h^̞zsϙsMeXT, I&oY-UU9nii.xZjii*//`P=TZRX0@+WԱc_k{<(ITXhh7k4=}}9xaaÇK={Bjl߶M&L>]J2L:q232$I驫dR^n},3#C&Y~~~򝷎 S4xӲX,1r H:{2Ν5\,[G%;8y֪*%NPC ׵Y:O֯['I԰aC1B8e}M۶m <\3g̨vϲ!PRRrXzzmNÞ{Nׯ׺kqns3f4/ʶۼY%ot7dopSNչ)q1XI2zx+f󷿩Yf$I:qℤmѢ$OS\wWZZ|$I:tо{l߶MsfV۶m%=-zJnOv,&6Veee:s˲]*'+%IM*'Ï>+V8'dPo9F8LeǝZ{@6lrfI O?ӧiTp@ L4ik],AY3}Tj{zzJTUU YVũI&3g}ƍV^ii|qgV׮ݔ0])))6mڨG3fl٢-[nѢEZhѥ::tW;AOtZ-,$ױcܘNϟÇD&Iyyy:ucO, i޽߿c2Õ"4dEFP@@Zwh`pJm\NV"4dEhА1;\J xl6+';[gPEE'ԲU+ TkqB䌖~F}o޽[2Ξ45j,X~[_s)--UJr c9pիV, 2{x]tG}B 6L˗/;zwoN>UVsΒ$o^_$Kk֬${WAAA޽ }*++کoN:ﯞ={*--Mh}߿Əc:rf͛+99YM^SJxe:Uӧ')44Nse1ͺӯyVURQij[, KSsUvv ;?ڶmTItɓh"}wԤI͙3lƍ}WԴiST||o%KTTT]viĈ5'N%IljrY+cjFR0]3gиdٔ)>Ֆ!,,p\xxTRR(p@HHl6 .o~󓏏E!^^^ UvvFQtwBLL222Vvv*++PAA $9bQ@L&;*;(pdRDD9(pPll:pPTT?D<;e:",,V͛sj߾}ZW ,jajԨ&M(h0rHwGWАe`08f t\Y z#( )תvm$*,9'dEhА! 8 (('brw ljIzzCޏ?&d6+55EKzK=d6mVu Y7&N|*m 믿^͚7W@@𧻓_AQF馛T ͱ3st-h ڱc~^7NT[oٳ5t0~1hrb߯oAfIU^+9WZZ]$I6M'NꪫMqqJINVqq$u{;VZ$}i>ѣʕoY?'\?9kn>qF0b^3FO(Zl鎯nЮ;|X~^®@a4hq"[eSK[,g,,,_]\>|XtQݳGl85#l6#T^^j YQ3^&٬U2QVΝ/{^RRr56-fFzIk>_];S%Iyj$>'4,7.+I<|Zug3Lrި_f^9R~6o?O۵K7t*IW?QZ@Qq*[A8j)SeqSk/CI+4{mذY\^:wZ*"=֫$i46o]M:MeJ۵mml Dž fSAA~~~Qns Rhh0VffbbbpPtt} ꯈQPY,; (Tkpiy{{+88X999L&EDDȑ#СCEEE\'YANPfVCkb48u?+o Yp"4dp1b)2&w~u)Xmw-,*PqB4Vmh5! .BC\, YpEՊ+NC{p;oj;~\,pC6`@===;tzzLM4QVTTǜeᩧ߯+((H}fSaa&M\wyRRRj], )==]oBBB>}X_5rH=sh=s:x𠮽ZM\g}& U ^~ZP(r@@~ $B(*"BED 7%L200 \f&pfk?{ג^xA`P---JR tQG)#T<jQ[bqg`pVy2 9y:>V.:^Գ |4ykQRiŋkժU[1<<#^YyP'3@ѢoDOWKK$oy晽IG\.t:x<]vIzzzb :1/,I:/{Y3~=#.+kNFJVuڵkumP((H:sdB!]}Ւ}{ZfnvR)=Vq٬*=*Od0g/V%YO<}{~LLLhʕ*U(}>U+)Fa@jYdFFWW% U*4fyg3HsސX^ YdFXLZ3i1Y[Fi8Hmݲ6¼p/w$mtu `ri~* ֠6/o;> kYɔXp2Ly.?~~_9|xCsrrwa\fp:S?b8|qmEu8L-%P+j\i+.lyU>@Ud0Vj\N7&&&f3v9kLX[t;ߩ{7nJ^LY 9a +o{}цaf3ڲesU Y@C?*`xg+Z~ @0 5€,1Oկy @x0e"J>r8M2V Jސa@jY,8}5YdFs,X,7, gqYÀ,d2inݺuU #8B+[Yŀ,SN9E'x֮]>[ַU/|A^{BP9&n^V*7XpŢqJ$Zz6lؠ6s9ujz׻Ub.ЪUW S* D5˱׽uZ~z衚SNzZ `i?v_Qu ׬.藿>y,Yh7mo$G_W/V6ʓY3$q[*E"r9=Ӻ+߯]__>o f'3.M_ɍU 6;<~ꪫt 7쩛h͚5o~Srp1 9#M -;wܱ}Q=Ӷ;3Y-49spP~0 5€,P# @x0-J>dRR(MsS"pUǡg"! 5€,Xx[&\yo>q:z]\w׮؋z>h60nqgM:#;Za&Iz+Vh{%I7ֶ69Ξ щo>I}Y3↕-IקoA*I{֣SOލu7VWVPPq3O?x`Vy2 祷M|xjEQIs?Ϳ|~uȏG~ɔP# @0 5€,Ԉ S&S*Ӥ2ŪL YdFa@jY57 8q}t-ŴqFxk> _UWd@8L&?[A)}_>]tJU*OdIs>+_7Jm׳:K'x֮]>[t;E]y ;#LjӦ亮.]~YUnC{_z]{]}ZrV^K_$Iz?p ׵ި|ѿ^|Yǀ, 7( trbTղwܩP(^xA`P---*q]p:ꨣdtG*3 ;=H\gqw7?jl:vrE:߿{B`]qTT^sN;M/֪U$Iׯ.sORONQy{~ ޣ\>'wYtuuU *L*4*0!5>)07577+2o<٩FW\lҥKyFW488R栶4>>\.@ggŢDm۶Ue˖iӦM khhHbU%KhddD|UYx\PF5¡)Fa@jYdFFWW%l6B`)USSSUǡg"! 5€,P# @0 DZHWA{36_Z kZinn?!ݴ3yݭ:u^|mrn{)9O]wtzg Y@˗'IR8_/^i=f3U^ `Θ`$9`S@F⭭r]WJ)hѢEKnWTg[uYWvcW哳Ώ5d0ggkXQ!Qz]~rs}m[ZZ{nIRww/_o]]]z_/IZlrt W~#Uɓ7d0g[fk-tIڼiz)=Zwc׿$K_m7lؠ|A]}53Q:C??|;h"B!}ob:ԱΗ$=Ӻ6ud)g⧏>g~ZT.Eܹ IDATji.Їeqh{ݨ{X<jYdOX,U, P#^+LH$OX4NfUTR>07dFa@jYdKXL7no_HD\r~׿V%?dXn݂.w%ꑟ<"ǩN~^u:uWxN'?ѧ?i|3uꩧcc=fQC! 9ɍɏtt;\SHT /P5k'І tw}ݧ5koW*҃>yiڵT(H$tuIַoo<Ӻu$i^k'.RTu`v.^ڍU\eފ˵Xp׶<_Ii]]]JRVsxW*>ܗ};}m{kmmL$ ,hO/M˪ԤH$a}bs<ۜ7le/׭mnPHڹkW}sgOޖ@ >m[]maw}bq߯m3Y%R΁,^XvR6ٲ,c[yyd^4ljEL#nooWW&1Lـ{yqϵEi||efz:}DҢCu'4)˰_юVy?k]:=O}9Bƙ[hFFU*jikNi.inn>mݦ֯~FWP7d[#mn /j3Ne^inh8/| RE7N7 ihBm-'c:|j/k`d2VzfE!&8c:|Ԣ Y$<*2;&2h4آwi8gXR0hޥ)[ue۰6$ma:|y72RWٴ~EM54To̦$u sG*Ws,l&Ϋ,$m9nc%s`ipGHD[VEYv_ڴ]*SPs7bH$T*cHxJ]S]PcH$w +J4PϘ&녭տ8=GL3×JQ{E{׀ElR&(Z1t6@$cj7?.m4bY~5j_x9ob_J&dYVF477+jxxUp8N 6*0ຮe˖FW.]͛770400A,~Ռ9hNE%՝ *4::@$3cΝ x>m۶Ue˖iӦM khhGGh%KhddDys0s_;0]wuz] kW\*5XL8 dFsF[<^|HDU*Db,P# @0 5€,`U<,p?/Ibgg7&uDua?^~ǀ,` {A;NuV UO?crU'`~;ԛft*SOՙgD"~vhTs;Ow\֭[uڵRtj}˧w:,t:tO}J?|yu-7+ QuFdyCtϷwCzϹj겏|DLo~S/TZK?SOIڪ3qcސB~ەƻ;F=#7Q|Ie2Iң?U{qtλ߭#sng~P=~}_7t>} UW~otƍuzwW1.z{{L&=Ck||UfEQ 7*0٩FW\lҥKyFW488ߛB]}}}W.ktU`SbQDUVyFW"۵sFWIm/:[#4J٩֪CyCjYdFa@jkt)DU i8U)RX( O:8]2SK$b.)n.ϫ)6NW}'iSS&iFHdGy-[XT$4JS*ey5[Ѻ 5B3>}Y ;lAYSZ,cSR"5>S((fOL; <:岂dzy#ٟMwSL@ X,V9P(2l|gޣ۞lommm\nҦli~4mmm7>ǭsbilڻUʾOL i{bB1v=ELc+FJ T*F$FL3i{al6[LgzxH$42f:k8jii)zGD 5JY_%ߗgl<†Kiy}_#m{(W* _le{J%,9 i$*+W*Y_lM#܏iQ)DWX]i")MU5f:q4*a71M{[C6T.+[(bwe bWWLF庮t˚BL3 +5N[=oG%ydf 1;lG)p*U4{;l'&MZذiJssc^%Ie2FW:::]hnnV4ppXltU`u] h˖- ,,]T7ont5`h``@*Yקq, q:;;U,jVyFW"۵sFW!YR{{{9YB,-j".R}J~ 2?nݺKOT'?:[,b[sMoz֬Y+WMCCCG?h4Kgqb>׿?pjڵz%I^xN8͛7k}4guNqF\R>j]ve:y?i=Xs=O~Rowu."\R:_3w߭D"իWkÆ ϩ Y!{Zt뼧~_erU+wᅦyfIҶmSO)Ko߮i釆K6mڤnI{.uQd2:#5<<|4v<01 9c&pU})birNŋj*I$mLY 9fJexf]vx<7XbF"Ȭ@,挳~|~衇j=YF~R|۰aN=@9]]]~+qU2T&itU`Ck||UfEQ 7*0٩FW\lҥKc``@*J i|||SSanTXT"htU`UittUH$vܹUקm۶5*0l2mڴՀ~ hU-K,ȴ)qQ]Lx &)ըF8T1e1H],#U*q\V 0NW*r,}h>vR)_/~7}b$W:V(P6=|W"0'\:ڦ)r-F1fu|YϾ/qT"\%75 )'fy~۝VIJ˕u:O%R4Ắz{{i Xv_ڴy3cqh"Eڃu]r4j''4Yz1u\o|r cV yeo6߳LKg477+hr˼OL*:CZZZԶ9}>!34JE~5phlPoo6NH*bnbtRm钓)63Ѩ&Ǔ E̛artRmb,ql6h4lX,* H)fm BL&+3f9Ś"Fi$ir,VvMgSRl?ߞ<5bu|L^јy#ٷHBm]Rɴ[ȧi5g^V P8ථS BaD,{1m㨩IVeҦli~4---zt2Qɾ+jLԤ]dw 'F5Vv=CLc{c/yfP)P \^|lBe.aO&h1qp(ȘbǤ4>r$Fr>1"IIE;e}RN$Uw>V.c8Y9Qa屜Q"^X1M.RndLy?R8)27[edAw(oq=bq4%˘Ψ`*%)3dRjxh}T^1e{;)'S*̼ XlX֔z4\n1M8#/)7:fH_YJ&mc65R( F8*U4/'$y%&+{9Mqt\NVט&Q⍷ظ*Ibl(b6gHv14PP ͵]ZjMLJ:djŮd6Igơmyouvv*N)3٥˱xR%/BoZ^<'(ML&_Rm:zZ2>ЫIDATYbD"+O_漑I=o6_CZ[[5{hN S*~Yc~e[ uh"رc^|7q-^Xw9`*xin+fo)P44ݚTXKyis֦bt:mrߣJ 7iiiQ D˚BLtf@8V<Vٜ}1^@@98X4{Ӽ0~9\noy,ۻa ^l񣣹~:e4===t6}%wlQCK]ߐU-u-r.J㽴l5dJsѨR!;arJ⺮ 4j3i$ʋHLtx<֡!,NL3MLԤȰ\밤B*pcĦ2h4|*:m91t1M8f5/k 1ty jmfjFbLcqܬf 1;lGkk;v9͔z4|NM{'1MKKI٬Khf:@ssFGXn&JzKQCK]dJYqrLT+2*ŒܠŮ1\ڪl*B|R o|l8\0 \((cqCXY\L/MMM}_Txr>)X6]&;( )12ž9˥tm ye-RIa{[Ǫ\,)`пzJ<䮝Vf'r)gŢ\0&Ǒ٩ɝ;ʲnٮ5b[CVl.۾r4ng1~ʲi6%[WX1m\oY%WlnV1_Pb_f:Ә&*(Jռ)4әPHXL8H"y%u崷+18X4Sie{z4}G]LgLc}~ ^8Pٯef 1t6}CQUlYi| WJ,rNU5d睎֐g yYhTì!;auvvj5dum۲UwRmݼՀ!`@!;4Ng'kG/!;J$Q{{vyJ}}ڱ5deھiSC~ TZD#! 2: R|3vob=e'dSa=:rZ , jKG,v[>in}UbM}GcO,ۻIΛ ;*2o=̏z-O#m. -/moWI˲2ຮb!ŋMu>/uX8t{,ڮﺦ1M75R'1MuʊuT.rZ<b"cs[D''n|ؗ:,~9g(:ILD"5Z4)LF<źB|GLD2nO|TfzC{M*,dsؽhLV>SXaFLtuu+ca3NWHjXgupyb`C鮼B$b|pbX++ (T̯zTIf:& ObZ^ ȗr,'`8N@|E߭()d6,gԫ]ו8N(/V 8o$)(*lɒ­d+,KLYWu庮#Yj~O}A;WQ bq~[^ eZ-TjގůTe z*r\J>ݗb^qRTYs"-/lB6P Yy % l4?ڻ:6k)=u#L _JIke yyir),ڻO*n1NW.FXT ͼ,qD%1⃚JM*xXOʥ\4j/Iۿ[1j>P\Ԓ@X²"]vg48;ιlgޯgN8;9sfD}fs9?'װNS CIܐ YWcSWJN|qU/.,@\aUnO"utt?0~AXr* Q|06?E[. Bܐ(oN$jmmmV;)oI*{rayr#[ԱySYӾ ܈c[Q>gʏRc~^cQVD,:NS#1X(<i$NŢNM׍떊[o0QwwF'RyfRN3c >ͤ4;Mū7< z=V֙3gey$yi&ijY]]:팚,,&6ot/rr9f ї|WA%K/Y~{ Joz9WA9q O펼a~[O,s=Gay#`Nz͍z<{*IsTwT)Y!oQhš_T1-wTKCvsvMsNSNs;DiX0\]I2^WiXX8aijij5wX$y9OAM: ԮNs2Lh֒7do^د|+T8 8j)^Ia}U7FT[JS-)ED5?!ǰO|YnwrmTqB,}UPNߖ U }YurrBK}b{\߳eH2 ?$:j޸\E*GzT5sOzj-]TCGhdd+_G|+cu׊422G=|5c ۷/$rzr뭚{\ݻ71ssG>KqR-[L|AH{XMܦZV%&IbM$''ɋ;7I:}չ~s#6^$$/&@ހfuڵk:;:/vmKtd6mܨ6m}V5\kV<:::uv꾎xŝs422nm۶]kܛКZܹMwĉ/RK+;7a&:|pCj qv\=rGӟbhM͚[e%;;Izn$YqF?IGܹOaͭ޹~s#&Kwvttĝh<8>t'5::*IU__~˒ ޓ$ɓ3gNs;?6v\NgϝKq4#$i_دz;ős޽{HXܹ j]wu[駟;aͭ޲ĝ$=qg'Owntĝ$';7I:M%&;7 hu/e2VHn###z`:m޼I߹ᆬВɭWgKC;JKi8wēOX,j`Vh9[=l5$I6qIFɍ~Frd$Jn_>OHn_:Hnl5$;F77dS266M*$ӗ<ʠKgΜ:Hn҅cz7t75U;owk$ }!x᷒_oؠ>:t(AFr[ꪫ̙3qf=Ѓ:'w9C/V#It,5$;F?N#OsMu.dܾu$%j$;Ivɍn4Y%KO>QRZ`$iE$I5{;Vu+jnjppvڥwy'MoiQs;w/_^Y+Kf95i֬Y/411ɶ2|;x$ĉ:u:;k3ɪ[=#jvt#jv!jn2'٫[sdzdzddz9 l~Yr99sZ۷o$͛7O>~~}:t>xl٪B߽CjH5VE4WVHi e9am֭/Vke5cȑ#ڽCmٲU Ys{jppn* ڳg3MꡗdϚWd̚$['ٲF?V\F?ɖ57L7ɐ57zI M2d͍n4g֬Y4TH,pC YH7d !r2IENDB`prometheus-bird-exporter-1.4.2+ds/main.go000066400000000000000000000112351444145222000203520ustar00rootroot00000000000000package main import ( "flag" "fmt" "net/http" "os" "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" ) const version string = "1.4.2" var ( showVersion = flag.Bool("version", false, "Print version information.") listenAddress = flag.String("web.listen-address", ":9324", "Address on which to expose metrics and web interface.") metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") birdSocket = flag.String("bird.socket", "/var/run/bird.ctl", "Socket to communicate with bird routing daemon") birdV2 = flag.Bool("bird.v2", false, "Bird major version >= 2.0 (multi channel protocols)") tlsEnabled = flag.Bool("tls.enabled", false, "Enables TLS") tlsCertChainPath = flag.String("tls.cert-file", "", "Path to TLS cert file") tlsKeyPath = flag.String("tls.key-file", "", "Path to TLS key file") newFormat = flag.Bool("format.new", true, "New metric format (more convenient / generic)") enableBGP = flag.Bool("proto.bgp", true, "Enables metrics for protocol BGP") enableOSPF = flag.Bool("proto.ospf", true, "Enables metrics for protocol OSPF") enableKernel = flag.Bool("proto.kernel", true, "Enables metrics for protocol Kernel") enableStatic = flag.Bool("proto.static", true, "Enables metrics for protocol Static") enableDirect = flag.Bool("proto.direct", true, "Enables metrics for protocol Direct") enableBabel = flag.Bool("proto.babel", true, "Enables metrics for protocol Babel") enableRPKI = flag.Bool("proto.rpki", true, "Enables metrics for protocol RPKI") enableBFD = flag.Bool("proto.bfd", true, "Enables metrics for protocol BFD") // pre bird 2.0 bird6Socket = flag.String("bird.socket6", "/var/run/bird6.ctl", "Socket to communicate with bird6 routing daemon (not compatible with -bird.v2)") birdEnabled = flag.Bool("bird.ipv4", true, "Get protocols from bird (not compatible with -bird.v2)") bird6Enabled = flag.Bool("bird.ipv6", true, "Get protocols from bird6 (not compatible with -bird.v2)") descriptionLabels = flag.Bool("format.description-labels", false, "Add labels from protocol descriptions.") descriptionLabelsRegex = flag.String("format.description-labels-regex", "(\\w+)=(\\w+)", "Regex to extract labels from protocol description") ) func init() { flag.Usage = func() { fmt.Println("Usage: bird_exporter [ ... ]\n\nParameters:") fmt.Println() flag.PrintDefaults() } } func main() { flag.Parse() if *showVersion { printVersion() os.Exit(0) } startServer() } func printVersion() { fmt.Println("bird_exporter") fmt.Printf("Version: %s\n", version) fmt.Println("Author(s): Daniel Czerwonk") fmt.Println("Metric exporter for bird routing daemon") } func startServer() { log.Infof("Starting bird exporter (Version: %s)", version) if !*newFormat { log.Info("INFO: You are using the old metric format. Please consider using the new (more convenient one) by setting -format.new=true.") } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` Bird Routing Daemon Exporter (Version ` + version + `)

Bird Routing Daemon Exporter

Metrics

More information:

github.com/czerwonk/bird_exporter

`)) }) http.HandleFunc(*metricsPath, handleMetricsRequest) log.Infof("Listening for %s on %s (TLS: %v)", *metricsPath, *listenAddress, *tlsEnabled) if *tlsEnabled { log.Fatal(http.ListenAndServeTLS(*listenAddress, *tlsCertChainPath, *tlsKeyPath, nil)) return } log.Fatal(http.ListenAndServe(*listenAddress, nil)) } func handleMetricsRequest(w http.ResponseWriter, r *http.Request) { reg := prometheus.NewRegistry() p := enabledProtocols() c := NewMetricCollector(*newFormat, p, *descriptionLabels) reg.MustRegister(c) l := log.New() l.Level = log.ErrorLevel promhttp.HandlerFor(reg, promhttp.HandlerOpts{ ErrorLog: l, ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(w, r) } func enabledProtocols() protocol.Proto { res := protocol.Proto(0) if *enableBGP { res |= protocol.BGP } if *enableOSPF { res |= protocol.OSPF } if *enableKernel { res |= protocol.Kernel } if *enableStatic { res |= protocol.Static } if *enableDirect { res |= protocol.Direct } if *enableBabel { res |= protocol.Babel } if *enableRPKI { res |= protocol.RPKI } if *enableBFD { res |= protocol.BFD } return res } prometheus-bird-exporter-1.4.2+ds/metric_collector.go000066400000000000000000000065071444145222000227650ustar00rootroot00000000000000package main import ( "github.com/czerwonk/bird_exporter/client" "github.com/czerwonk/bird_exporter/metrics" "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" ) type MetricCollector struct { exporters map[protocol.Proto][]metrics.MetricExporter client *client.BirdClient enabledProtocols protocol.Proto newFormat bool } func NewMetricCollector(newFormat bool, enabledProtocols protocol.Proto, descriptionLabels bool) *MetricCollector { c := getClient() var e map[protocol.Proto][]metrics.MetricExporter if newFormat { e = exportersForDefault(c, descriptionLabels) } else { e = exportersForLegacy(c) } return &MetricCollector{ exporters: e, client: c, enabledProtocols: enabledProtocols, newFormat: newFormat, } } func getClient() *client.BirdClient { o := &client.BirdClientOptions{ BirdSocket: *birdSocket, Bird6Socket: *bird6Socket, Bird6Enabled: *bird6Enabled, BirdEnabled: *birdEnabled, BirdV2: *birdV2, } return &client.BirdClient{Options: o} } func exportersForLegacy(c *client.BirdClient) map[protocol.Proto][]metrics.MetricExporter { l := metrics.NewLegacyLabelStrategy() return map[protocol.Proto][]metrics.MetricExporter{ protocol.BGP: {metrics.NewLegacyMetricExporter("bgp4_session", "bgp6_session", l)}, protocol.Direct: {metrics.NewLegacyMetricExporter("direct4", "direct6", l)}, protocol.Kernel: {metrics.NewLegacyMetricExporter("kernel4", "kernel6", l)}, protocol.OSPF: {metrics.NewLegacyMetricExporter("ospf", "ospfv3", l), metrics.NewOSPFExporter("", c)}, protocol.Static: {metrics.NewLegacyMetricExporter("static4", "static6", l)}, protocol.Babel: {metrics.NewLegacyMetricExporter("babel4", "babel6", l)}, protocol.RPKI: {metrics.NewLegacyMetricExporter("rpki4", "rpki6", l)}, protocol.BFD: {metrics.NewBFDExporter(c)}, } } func exportersForDefault(c *client.BirdClient, descriptionLabels bool) map[protocol.Proto][]metrics.MetricExporter { l := metrics.NewDefaultLabelStrategy(descriptionLabels, *descriptionLabelsRegex) e := metrics.NewGenericProtocolMetricExporter("bird_protocol", true, l) return map[protocol.Proto][]metrics.MetricExporter{ protocol.BGP: {e}, protocol.Direct: {e}, protocol.Kernel: {e}, protocol.OSPF: {e, metrics.NewOSPFExporter("bird_", c)}, protocol.Static: {e}, protocol.Babel: {e}, protocol.RPKI: {e}, protocol.BFD: {metrics.NewBFDExporter(c)}, } } var socketQueryDesc = prometheus.NewDesc( "bird_socket_query_success", "Result of querying bird socket: 0 = failed, 1 = suceeded", nil, nil, ) func (m *MetricCollector) Describe(ch chan<- *prometheus.Desc) { ch <- socketQueryDesc for _, v := range m.exporters { for _, e := range v { e.Describe(ch) } } } func (m *MetricCollector) Collect(ch chan<- prometheus.Metric) { protocols, err := m.client.GetProtocols() var queryResult float64 = 1 if err != nil { queryResult = 0 } ch <- prometheus.MustNewConstMetric(socketQueryDesc, prometheus.GaugeValue, queryResult) if err != nil { log.Errorln(err) return } for _, p := range protocols { if p.Proto == protocol.PROTO_UNKNOWN || (m.enabledProtocols&p.Proto != p.Proto) { continue } for _, e := range m.exporters[p.Proto] { e.Export(p, ch, m.newFormat) } } } prometheus-bird-exporter-1.4.2+ds/metrics/000077500000000000000000000000001444145222000205435ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/metrics/bfd_exporter.go000066400000000000000000000040761444145222000235640ustar00rootroot00000000000000package metrics import ( "github.com/czerwonk/bird_exporter/client" "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" ) var ( bfdUpDesc *prometheus.Desc bfdUptimeDesc *prometheus.Desc bfdIntervalDesc *prometheus.Desc bfdTimoutDesc *prometheus.Desc ) func init() { l := []string{"name", "ip", "interface"} prefix := "bird_bfd_session_" bfdUpDesc = prometheus.NewDesc(prefix+"up", "Session is up", l, nil) bfdUptimeDesc = prometheus.NewDesc(prefix+"uptime_seconds", "Session uptime in seconds", l, nil) bfdIntervalDesc = prometheus.NewDesc(prefix+"interval_seconds", "Session uptime in seconds", l, nil) bfdTimoutDesc = prometheus.NewDesc(prefix+"timeout_seconds", "Session timeout in seconds", l, nil) } type bfdMetricExporter struct { client client.Client } // NewBFDExporter creates a new MetricExporter for BFD metrics func NewBFDExporter(client client.Client) MetricExporter { return &bfdMetricExporter{client: client} } func (m *bfdMetricExporter) Describe(ch chan<- *prometheus.Desc) { ch <- bfdUpDesc ch <- bfdUptimeDesc ch <- bfdIntervalDesc ch <- bfdTimoutDesc } func (m *bfdMetricExporter) Export(p *protocol.Protocol, ch chan<- prometheus.Metric, newFormat bool) { if p.Proto != protocol.BFD { return } sessions, err := m.client.GetBFDSessions(p) if err != nil { log.Errorln(err) return } for _, s := range sessions { m.exportSession(s, p.Name, ch) } } func (m *bfdMetricExporter) exportSession(s *protocol.BFDSession, protocolName string, ch chan<- prometheus.Metric) { l := []string{protocolName, s.IP, s.Interface} var up float64 var uptime float64 if s.Up { up = 1 uptime = float64(s.Since) } ch <- prometheus.MustNewConstMetric(bfdUpDesc, prometheus.GaugeValue, up, l...) ch <- prometheus.MustNewConstMetric(bfdUptimeDesc, prometheus.GaugeValue, uptime, l...) ch <- prometheus.MustNewConstMetric(bfdIntervalDesc, prometheus.GaugeValue, s.Interval, l...) ch <- prometheus.MustNewConstMetric(bfdTimoutDesc, prometheus.GaugeValue, s.Timeout, l...) } prometheus-bird-exporter-1.4.2+ds/metrics/default_label_strategy.go000066400000000000000000000042701444145222000256020ustar00rootroot00000000000000package metrics import ( "regexp" "strings" "github.com/czerwonk/bird_exporter/protocol" ) // DefaultLabelStrategy defines the labels to add to an metric and its data retrieval method type DefaultLabelStrategy struct { descriptionLabels bool descriptionLabelsRegex *regexp.Regexp } func NewDefaultLabelStrategy(descriptionLabels bool, descriptionLabelsRegex string) *DefaultLabelStrategy { return &DefaultLabelStrategy{ descriptionLabels: descriptionLabels, descriptionLabelsRegex: regexp.MustCompile(descriptionLabelsRegex), } } // LabelNames returns the list of label names func (d *DefaultLabelStrategy) LabelNames(p *protocol.Protocol) []string { res := []string{"name", "proto", "ip_version", "import_filter", "export_filter"} if d.descriptionLabels && p.Description != "" { res = append(res, labelKeysFromDescription(p.Description, d)...) } return res } // LabelValues returns the values for a protocol func (d *DefaultLabelStrategy) LabelValues(p *protocol.Protocol) []string { res := []string{p.Name, protoString(p), p.IPVersion, p.ImportFilter, p.ExportFilter} if d.descriptionLabels && p.Description != "" { res = append(res, labelValuesFromDescription(p.Description, d)...) } return res } func labelKeysFromDescription(desc string, d *DefaultLabelStrategy) []string { res := []string{} matches := d.descriptionLabelsRegex.FindAllStringSubmatch(desc, -1) for _, submatch := range matches { res = append(res, strings.TrimSpace(submatch[1])) } return res } func labelValuesFromDescription(desc string, d *DefaultLabelStrategy) []string { res := []string{} matches := d.descriptionLabelsRegex.FindAllStringSubmatch(desc, -1) for _, submatch := range matches { res = append(res, strings.TrimSpace(submatch[2])) } return res } func protoString(p *protocol.Protocol) string { switch p.Proto { case protocol.BGP: return "BGP" case protocol.OSPF: if p.IPVersion == "4" { return "OSPF" } return "OSPFv3" case protocol.Static: return "Static" case protocol.Kernel: return "Kernel" case protocol.Direct: return "Direct" case protocol.Babel: return "Babel" case protocol.RPKI: return "RPKI" case protocol.BFD: return "BFD" } return "" } prometheus-bird-exporter-1.4.2+ds/metrics/default_label_strategy_test.go000066400000000000000000000016701444145222000266420ustar00rootroot00000000000000package metrics import ( "testing" "github.com/czerwonk/bird_exporter/protocol" "github.com/stretchr/testify/assert" ) func TestLabelNames(t *testing.T) { s := NewDefaultLabelStrategy(true, `(\w+\s*)=(\s*\w+)`) labels := s.LabelNames(&protocol.Protocol{ Name: "test", Description: " foo = bar x: y", ImportFilter: "in", ExportFilter: "out", IPVersion: "6", Proto: protocol.BGP, }) expected := []string{"name", "proto", "ip_version", "import_filter", "export_filter", "foo"} assert.Equal(t, expected, labels) } func TestLabelValues(t *testing.T) { s := NewDefaultLabelStrategy(true, `(\w+\s*)=(\s*\w+)`) values := s.LabelValues(&protocol.Protocol{ Name: "test", Description: " foo = bar x: y", ImportFilter: "in", ExportFilter: "out", IPVersion: "6", Proto: protocol.BGP, }) expected := []string{"test", "BGP", "6", "in", "out", "bar"} assert.Equal(t, expected, values) } prometheus-bird-exporter-1.4.2+ds/metrics/generic_exporter.go000066400000000000000000000207441444145222000244450ustar00rootroot00000000000000package metrics import ( "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" ) // GenericProtocolMetricExporter exports metrics retrieved from Bird routing daemon type GenericProtocolMetricExporter struct { labelStrategy LabelStrategy prefix string } // NewGenericProtocolMetricExporter creates a new instance of GenericProtocolMetricExporter func NewGenericProtocolMetricExporter(prefix string, newNaming bool, labelStrategy LabelStrategy) *GenericProtocolMetricExporter { m := &GenericProtocolMetricExporter{ prefix: prefix, labelStrategy: labelStrategy, } return m } func (m *GenericProtocolMetricExporter) Describe(ch chan<- *prometheus.Desc) { } func (m *GenericProtocolMetricExporter) Export(p *protocol.Protocol, ch chan<- prometheus.Metric, newNaming bool) { labels := m.labelStrategy.LabelNames(p) var importCountDesc *prometheus.Desc var exportCountDesc *prometheus.Desc var filterCountDesc *prometheus.Desc var preferredCountDesc *prometheus.Desc upDesc := prometheus.NewDesc(m.prefix+"_up", "Protocol is up", append(labels, "state"), nil) if newNaming { importCountDesc = prometheus.NewDesc(m.prefix+"_prefix_import_count", "Number of imported routes", labels, nil) exportCountDesc = prometheus.NewDesc(m.prefix+"_prefix_export_count", "Number of exported routes", labels, nil) filterCountDesc = prometheus.NewDesc(m.prefix+"_prefix_filter_count", "Number of filtered routes", labels, nil) preferredCountDesc = prometheus.NewDesc(m.prefix+"_prefix_preferred_count", "Number of preferred routes", labels, nil) } else { importCountDesc = prometheus.NewDesc(m.prefix+"_prefix_count_import", "Number of imported routes", labels, nil) exportCountDesc = prometheus.NewDesc(m.prefix+"_prefix_count_export", "Number of exported routes", labels, nil) filterCountDesc = prometheus.NewDesc(m.prefix+"_prefix_count_filter", "Number of filtered routes", labels, nil) preferredCountDesc = prometheus.NewDesc(m.prefix+"_prefix_count_preferred", "Number of preferred routes", labels, nil) } uptimeDesc := prometheus.NewDesc(m.prefix+"_uptime", "Uptime of the protocol in seconds", labels, nil) updatesImportIgnoreCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_import_ignore_count", "Number of incoming updates being ignored", labels, nil) updatesImportAcceptCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_import_accept_count", "Number of incoming updates being accepted", labels, nil) updatesImportFilterCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_import_filter_count", "Number of incoming updates being filtered", labels, nil) updatesImportRejectCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_import_reject_count", "Number of incoming updates being rejected", labels, nil) updatesImportReceiveCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_import_receive_count", "Number of received updates", labels, nil) withdrawsImportIgnoreCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_import_ignore_count", "Number of incoming withdraws being ignored", labels, nil) withdrawsImportAcceptCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_import_accept_count", "Number of incoming withdraws being accepted", labels, nil) withdrawsImportFilterCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_import_filter_count", "Number of incoming withdraws being filtered", labels, nil) withdrawsImportRejectCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_import_reject_count", "Number of incoming withdraws being rejected", labels, nil) withdrawsImportReceiveCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_import_receive_count", "Number of received withdraws", labels, nil) updatesExportIgnoreCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_export_ignore_count", "Number of outgoing updates being ignored", labels, nil) updatesExportAcceptCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_export_accept_count", "Number of outgoing updates being accepted", labels, nil) updatesExportFilterCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_export_filter_count", "Number of outgoing updates being filtered", labels, nil) updatesExportRejectCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_export_reject_count", "Number of outgoing updates being rejected", labels, nil) updatesExportReceiveCountDesc := prometheus.NewDesc(m.prefix+"_changes_update_export_receive_count", "Number of sent updates", labels, nil) withdrawsExportIgnoreCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_export_ignore_count", "Number of outgoing withdraws being ignored", labels, nil) withdrawsExportAcceptCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_export_accept_count", "Number of outgoing withdraws being accepted", labels, nil) withdrawsExportFilterCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_export_filter_count", "Number of outgoing withdraws being filtered", labels, nil) withdrawsExportRejectCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_export_reject_count", "Number of outgoing withdraws being rejected", labels, nil) withdrawsExportReceiveCountDesc := prometheus.NewDesc(m.prefix+"_changes_withdraw_export_receive_count", "Number of outgoing withdraws", labels, nil) l := m.labelStrategy.LabelValues(p) ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, float64(p.Up), append(l, p.State)...) ch <- prometheus.MustNewConstMetric(importCountDesc, prometheus.GaugeValue, float64(p.Imported), l...) ch <- prometheus.MustNewConstMetric(exportCountDesc, prometheus.GaugeValue, float64(p.Exported), l...) ch <- prometheus.MustNewConstMetric(filterCountDesc, prometheus.GaugeValue, float64(p.Filtered), l...) ch <- prometheus.MustNewConstMetric(preferredCountDesc, prometheus.GaugeValue, float64(p.Preferred), l...) ch <- prometheus.MustNewConstMetric(uptimeDesc, prometheus.GaugeValue, float64(p.Uptime), l...) ch <- prometheus.MustNewConstMetric(updatesImportReceiveCountDesc, prometheus.GaugeValue, float64(p.ImportUpdates.Received), l...) ch <- prometheus.MustNewConstMetric(updatesImportRejectCountDesc, prometheus.GaugeValue, float64(p.ImportUpdates.Rejected), l...) ch <- prometheus.MustNewConstMetric(updatesImportFilterCountDesc, prometheus.GaugeValue, float64(p.ImportUpdates.Filtered), l...) ch <- prometheus.MustNewConstMetric(updatesImportAcceptCountDesc, prometheus.GaugeValue, float64(p.ImportUpdates.Accepted), l...) ch <- prometheus.MustNewConstMetric(updatesImportIgnoreCountDesc, prometheus.GaugeValue, float64(p.ImportUpdates.Ignored), l...) ch <- prometheus.MustNewConstMetric(updatesExportReceiveCountDesc, prometheus.GaugeValue, float64(p.ExportUpdates.Received), l...) ch <- prometheus.MustNewConstMetric(updatesExportRejectCountDesc, prometheus.GaugeValue, float64(p.ExportUpdates.Rejected), l...) ch <- prometheus.MustNewConstMetric(updatesExportFilterCountDesc, prometheus.GaugeValue, float64(p.ExportUpdates.Filtered), l...) ch <- prometheus.MustNewConstMetric(updatesExportAcceptCountDesc, prometheus.GaugeValue, float64(p.ExportUpdates.Accepted), l...) ch <- prometheus.MustNewConstMetric(updatesExportIgnoreCountDesc, prometheus.GaugeValue, float64(p.ExportUpdates.Ignored), l...) ch <- prometheus.MustNewConstMetric(withdrawsImportReceiveCountDesc, prometheus.GaugeValue, float64(p.ImportWithdraws.Received), l...) ch <- prometheus.MustNewConstMetric(withdrawsImportRejectCountDesc, prometheus.GaugeValue, float64(p.ImportWithdraws.Rejected), l...) ch <- prometheus.MustNewConstMetric(withdrawsImportFilterCountDesc, prometheus.GaugeValue, float64(p.ImportWithdraws.Filtered), l...) ch <- prometheus.MustNewConstMetric(withdrawsImportAcceptCountDesc, prometheus.GaugeValue, float64(p.ImportWithdraws.Accepted), l...) ch <- prometheus.MustNewConstMetric(withdrawsImportIgnoreCountDesc, prometheus.GaugeValue, float64(p.ImportWithdraws.Ignored), l...) ch <- prometheus.MustNewConstMetric(withdrawsExportReceiveCountDesc, prometheus.GaugeValue, float64(p.ExportWithdraws.Received), l...) ch <- prometheus.MustNewConstMetric(withdrawsExportRejectCountDesc, prometheus.GaugeValue, float64(p.ExportWithdraws.Rejected), l...) ch <- prometheus.MustNewConstMetric(withdrawsExportFilterCountDesc, prometheus.GaugeValue, float64(p.ExportWithdraws.Filtered), l...) ch <- prometheus.MustNewConstMetric(withdrawsExportAcceptCountDesc, prometheus.GaugeValue, float64(p.ExportWithdraws.Accepted), l...) ch <- prometheus.MustNewConstMetric(withdrawsExportIgnoreCountDesc, prometheus.GaugeValue, float64(p.ExportWithdraws.Ignored), l...) } prometheus-bird-exporter-1.4.2+ds/metrics/label_strategy.go000066400000000000000000000006001444145222000240670ustar00rootroot00000000000000package metrics import "github.com/czerwonk/bird_exporter/protocol" // LabelStrategy abstracts the label generation for protocol metrics type LabelStrategy interface { // LabelNames is the list of label names LabelNames(p *protocol.Protocol) []string // Label values is the list of values for the labels specified in `LabelNames()` LabelValues(p *protocol.Protocol) []string } prometheus-bird-exporter-1.4.2+ds/metrics/legacy_exporter.go000066400000000000000000000016261444145222000242730ustar00rootroot00000000000000package metrics import ( "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" ) type LegacyMetricExporter struct { ipv4Exporter *GenericProtocolMetricExporter ipv6Exporter *GenericProtocolMetricExporter } func NewLegacyMetricExporter(prefixIpv4, prefixIpv6 string, labelStrategy LabelStrategy) MetricExporter { return &LegacyMetricExporter{ ipv4Exporter: NewGenericProtocolMetricExporter(prefixIpv4, false, labelStrategy), ipv6Exporter: NewGenericProtocolMetricExporter(prefixIpv6, false, labelStrategy), } } func (e *LegacyMetricExporter) Describe(ch chan<- *prometheus.Desc) { e.ipv4Exporter.Describe(ch) e.ipv6Exporter.Describe(ch) } func (e *LegacyMetricExporter) Export(p *protocol.Protocol, ch chan<- prometheus.Metric, newFormat bool) { if p.IPVersion == "4" { e.ipv4Exporter.Export(p, ch, false) } else { e.ipv6Exporter.Export(p, ch, false) } } prometheus-bird-exporter-1.4.2+ds/metrics/legacy_label_strategy.go000066400000000000000000000006121444145222000254160ustar00rootroot00000000000000package metrics import "github.com/czerwonk/bird_exporter/protocol" type LegacyLabelStrategy struct { } func NewLegacyLabelStrategy() *LegacyLabelStrategy { return &LegacyLabelStrategy{} } func (*LegacyLabelStrategy) LabelNames(p *protocol.Protocol) []string { return []string{"name"} } func (*LegacyLabelStrategy) LabelValues(p *protocol.Protocol) []string { return []string{p.Name} } prometheus-bird-exporter-1.4.2+ds/metrics/metrics_exporter.go000066400000000000000000000004201444145222000244640ustar00rootroot00000000000000package metrics import ( "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" ) type MetricExporter interface { Describe(ch chan<- *prometheus.Desc) Export(p *protocol.Protocol, ch chan<- prometheus.Metric, newFormat bool) } prometheus-bird-exporter-1.4.2+ds/metrics/ospf_exporter.go000066400000000000000000000051221444145222000237710ustar00rootroot00000000000000package metrics import ( "github.com/czerwonk/bird_exporter/client" "github.com/czerwonk/bird_exporter/protocol" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" ) type ospfDesc struct { runningDesc *prometheus.Desc interfaceCountDesc *prometheus.Desc neighborCountDesc *prometheus.Desc neighborAdjacentCountDesc *prometheus.Desc } type ospfMetricExporter struct { descriptions map[string]*ospfDesc client client.Client } // NewOSPFExporter creates a new MetricExporter for OSPF metrics func NewOSPFExporter(prefix string, client client.Client) MetricExporter { d := make(map[string]*ospfDesc) d["4"] = getDesc(prefix + "ospf") d["6"] = getDesc(prefix + "ospfv3") return &ospfMetricExporter{descriptions: d, client: client} } func getDesc(prefix string) *ospfDesc { labels := []string{"name"} d := &ospfDesc{} d.runningDesc = prometheus.NewDesc(prefix+"_running", "State of OSPF: 0 = Alone, 1 = Running (Neighbor-Adjacencies established)", labels, nil) labels = append(labels, "area") d.interfaceCountDesc = prometheus.NewDesc(prefix+"_interface_count", "Number of interfaces in the area", labels, nil) d.neighborCountDesc = prometheus.NewDesc(prefix+"_neighbor_count", "Number of neighbors in the area", labels, nil) d.neighborAdjacentCountDesc = prometheus.NewDesc(prefix+"_neighbor_adjacent_count", "Number of adjacent neighbors in the area", labels, nil) return d } func (m *ospfMetricExporter) Describe(ch chan<- *prometheus.Desc) { m.describe("4", ch) m.describe("6", ch) } func (m *ospfMetricExporter) describe(ipVersion string, ch chan<- *prometheus.Desc) { d := m.descriptions[ipVersion] ch <- d.runningDesc ch <- d.interfaceCountDesc ch <- d.neighborCountDesc ch <- d.neighborAdjacentCountDesc } func (m *ospfMetricExporter) Export(p *protocol.Protocol, ch chan<- prometheus.Metric, newFormat bool) { d := m.descriptions[p.IPVersion] var running float64 if p.State == "Running" { running = 1 } ch <- prometheus.MustNewConstMetric(d.runningDesc, prometheus.GaugeValue, running, p.Name) areas, err := m.client.GetOSPFAreas(p) if err != nil { log.Errorln(err) return } for _, area := range areas { l := []string{p.Name, area.Name} ch <- prometheus.MustNewConstMetric(d.interfaceCountDesc, prometheus.GaugeValue, float64(area.InterfaceCount), l...) ch <- prometheus.MustNewConstMetric(d.neighborCountDesc, prometheus.GaugeValue, float64(area.NeighborCount), l...) ch <- prometheus.MustNewConstMetric(d.neighborAdjacentCountDesc, prometheus.GaugeValue, float64(area.NeighborAdjacentCount), l...) } } prometheus-bird-exporter-1.4.2+ds/parser/000077500000000000000000000000001444145222000203715ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/parser/bfd.go000066400000000000000000000022521444145222000214540ustar00rootroot00000000000000package parser import ( "bufio" "bytes" "regexp" "strings" "github.com/czerwonk/bird_exporter/protocol" ) var ( bfdSessionRegex *regexp.Regexp ) func init() { bfdSessionRegex = regexp.MustCompile(`^([^\s]+)\s+([^\s]+)\s+(Up|Down|Init)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|[^\s]+)\s+([0-9\.]+)\s+([0-9\.]+)$`) } type bfdContext struct { line string sessions []*protocol.BFDSession protocol string } func ParseBFDSessions(protocolName string, data []byte) []*protocol.BFDSession { reader := bytes.NewReader(data) scanner := bufio.NewScanner(reader) c := &bfdContext{ sessions: make([]*protocol.BFDSession, 0), protocol: protocolName, } for scanner.Scan() { c.line = strings.TrimSpace(scanner.Text()) parseBFDSessionLine(c) } return c.sessions } func parseBFDSessionLine(c *bfdContext) { m := bfdSessionRegex.FindStringSubmatch(c.line) if m == nil { return } sess := protocol.BFDSession{ ProtocolName: c.protocol, IP: m[1], Interface: m[2], Since: parseUptime(m[4]), Interval: parseFloat(m[5]), Timeout: parseFloat(m[6]), } if m[3] == "Up" { sess.Up = true } c.sessions = append(c.sessions, &sess) } prometheus-bird-exporter-1.4.2+ds/parser/bfd_test.go000066400000000000000000000025761444145222000225240ustar00rootroot00000000000000package parser import ( "testing" "time" "github.com/czerwonk/bird_exporter/protocol" "github.com/stretchr/testify/assert" ) func TestParseBFDSessions(t *testing.T) { overrideNowFunc(func() time.Time { return time.Date(2022, 1, 27, 10, 0, 0, 0, time.Local) }) data := `BIRD 2.0.7 ready. bfd1: IP address Interface State Since Interval Timeout 192.168.64.9 enp0s2 Up 2022-01-27 09:00:00 0.100 1.000 192.168.64.10 enp0s2 Down 2022-01-27 08:00:00 0.300 0.000 192.168.64.12 enp0s2 Init 2022-01-27 08:00:00 0.300 5.000` s := ParseBFDSessions("bfd1", []byte(data)) assert.Equal(t, 3, len(s), "session count") s1 := protocol.BFDSession{ ProtocolName: "bfd1", IP: "192.168.64.9", Interface: "enp0s2", Up: true, Since: 3600, Interval: 0.1, Timeout: 1, } s2 := protocol.BFDSession{ ProtocolName: "bfd1", IP: "192.168.64.10", Interface: "enp0s2", Up: false, Since: 7200, Interval: 0.3, Timeout: 0, } s3 := protocol.BFDSession{ ProtocolName: "bfd1", IP: "192.168.64.12", Interface: "enp0s2", Up: false, Since: 7200, Interval: 0.3, Timeout: 5, } assert.Equal(t, []*protocol.BFDSession{&s1, &s2, &s3}, s, "sessions") } prometheus-bird-exporter-1.4.2+ds/parser/helper.go000066400000000000000000000023641444145222000222040ustar00rootroot00000000000000package parser import ( "fmt" "strconv" "time" log "github.com/sirupsen/logrus" ) var ( nowFunc func() time.Time ) func init() { nowFunc = func() time.Time { return time.Now() } } func overrideNowFunc(f func() time.Time) { nowFunc = f } func currentTime() time.Time { return nowFunc() } func parseInt(value string) int64 { i, err := strconv.ParseInt(value, 10, 64) if err != nil { log.Errorln(err) return 0 } return i } func parseFloat(value string) float64 { i, err := strconv.ParseFloat(value, 64) if err != nil { log.Errorln(err) return 0 } return i } func parseUptimeForIso(s string) int { start, err := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local) if err != nil { log.Errorln(err) return 0 } return int(currentTime().Sub(start).Seconds()) } func parseUptimeForDuration(duration []string) int { h := parseInt(duration[2]) m := parseInt(duration[3]) s := parseInt(duration[4]) str := fmt.Sprintf("%dh%dm%ds", h, m, s) d, err := time.ParseDuration(str) if err != nil { log.Errorln(err) return 0 } return int(d.Seconds()) } func parseUptimeForTimestamp(timestamp string) int { since := parseInt(timestamp) s := time.Unix(since, 0) d := currentTime().Sub(s) return int(d.Seconds()) } prometheus-bird-exporter-1.4.2+ds/parser/ospf.go000066400000000000000000000026241444145222000216730ustar00rootroot00000000000000package parser import ( "regexp" "bufio" "bytes" "strings" "github.com/czerwonk/bird_exporter/protocol" ) type ospfRegex struct { area *regexp.Regexp counters *regexp.Regexp } type ospfContext struct { line string areas []*protocol.OSPFArea current *protocol.OSPFArea } func init() { ospf = &ospfRegex{ area: regexp.MustCompile("Area: [^\\s]+ \\(([^\\s]+)\\)"), counters: regexp.MustCompile("Number of ([^:]+):\\s*(\\d+)"), } } var ospf *ospfRegex func ParseOSPF(data []byte) []*protocol.OSPFArea { reader := bytes.NewReader(data) scanner := bufio.NewScanner(reader) c := &ospfContext{ areas: make([]*protocol.OSPFArea, 0), } for scanner.Scan() { c.line = strings.Trim(scanner.Text(), " ") parseLineForOspfArea(c) parseLineForOspfCounters(c) } return c.areas } func parseLineForOspfArea(c *ospfContext) { m := ospf.area.FindStringSubmatch(c.line) if m == nil { return } a := &protocol.OSPFArea{Name: m[1]} c.current = a c.areas = append(c.areas, a) } func parseLineForOspfCounters(c *ospfContext) { if c.current == nil { return } m := ospf.counters.FindStringSubmatch(c.line) if m == nil { return } name := m[1] value := parseInt(m[2]) if name == "interfaces" { c.current.InterfaceCount = value } if name == "neighbors" { c.current.NeighborCount = value } if name == "adjacent neighbors" { c.current.NeighborAdjacentCount = value } } prometheus-bird-exporter-1.4.2+ds/parser/ospf_test.go000066400000000000000000000026261444145222000227340ustar00rootroot00000000000000package parser import ( "testing" "github.com/czerwonk/testutils/assert" ) func TestOSPFArea(t *testing.T) { data := "ospf1:\n" + "RFC1583 compatibility: disabled\n" + "Stub router: No\n" + "RT scheduler tick: 1\n" + "Number of areas: 2\n" + "Number of LSAs in DB: 33\n" + " Area: 0.0.0.0 (0) [BACKBONE]\n" + " Stub: No\n" + " NSSA: No\n" + " Transit: No\n" + " Number of interfaces: 3\n" + " Number of neighbors: 2\n" + " Number of adjacent neighbors: 1\n" + " Area: 0.0.0.1 (1)\n" + " Stub: No\n" + " NSSA: No\n" + " Transit: No\n" + " Number of interfaces: 4\n" + " Number of neighbors: 6\n" + " Number of adjacent neighbors: 5\n" a := ParseOSPF([]byte(data)) assert.IntEqual("areas", 2, len(a), t) a1 := a[0] assert.StringEqual("Area1 Name", "0", a1.Name, t) assert.Int64Equal("Area1 InterfaceCount", 3, a1.InterfaceCount, t) assert.Int64Equal("Area1 NeighborCount", 2, a1.NeighborCount, t) assert.Int64Equal("Area1 NeighborAdjacentCount", 1, a1.NeighborAdjacentCount, t) a2 := a[1] assert.StringEqual("Area2 Name", "1", a2.Name, t) assert.Int64Equal("Area2 InterfaceCount", 4, a2.InterfaceCount, t) assert.Int64Equal("Area2 NeighborCount", 6, a2.NeighborCount, t) assert.Int64Equal("Area2 NeighborAdjacentCount", 5, a2.NeighborAdjacentCount, t) } prometheus-bird-exporter-1.4.2+ds/parser/parser.go000066400000000000000000000130251444145222000222150ustar00rootroot00000000000000package parser import ( "bufio" "bytes" "regexp" "strconv" "strings" "github.com/czerwonk/bird_exporter/protocol" ) var ( protocolRegex *regexp.Regexp descriptionRegex *regexp.Regexp routeRegex *regexp.Regexp uptimeRegex *regexp.Regexp routeChangeRegex *regexp.Regexp filterRegex *regexp.Regexp channelRegex *regexp.Regexp ) type context struct { current *protocol.Protocol line string handled bool protocols []*protocol.Protocol ipVersion string } func init() { protocolRegex = regexp.MustCompile(`^(?:1002\-)?([^\s]+)\s+(MRT|BGP|BFD|OSPF|RPKI|RIP|RAdv|Pipe|Perf|Direct|Babel|Device|Kernel|Static)\s+([^\s]+)\s+([^\s]+)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|[^\s]+)(?:\s+(.*?))?$`) descriptionRegex = regexp.MustCompile(`Description:\s+(.*)`) routeRegex = regexp.MustCompile(`^\s+Routes:\s+(\d+) imported, (?:(\d+) filtered, )?(\d+) exported(?:, (\d+) preferred)?`) uptimeRegex = regexp.MustCompile(`^(?:((\d+):(\d{2}):(\d{2}))|(\d+)|(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}))$`) routeChangeRegex = regexp.MustCompile(`(Import|Export) (updates|withdraws):\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s*`) filterRegex = regexp.MustCompile(`(Input|Output) filter:\s+(.*)`) channelRegex = regexp.MustCompile(`Channel ipv(4|6)`) } // ParseProtocols parses bird output and returns protocol.Protocol structs func ParseProtocols(data []byte, ipVersion string) []*protocol.Protocol { reader := bytes.NewReader(data) scanner := bufio.NewScanner(reader) c := &context{protocols: make([]*protocol.Protocol, 0), ipVersion: ipVersion} var handlers = []func(*context){ handleEmptyLine, parseLineForProtocol, parseLineForDescription, parseLineForChannel, parseLineForRoutes, parseLineForRouteChanges, parseLineForFilterName, } for scanner.Scan() { c.line = strings.TrimRight(scanner.Text(), " ") c.handled = false for _, h := range handlers { if !c.handled { h(c) } } } return c.protocols } func handleEmptyLine(c *context) { if c.line != "" { return } c.current = nil c.handled = true } func parseLineForProtocol(c *context) { match := protocolRegex.FindStringSubmatch(c.line) if match == nil { return } proto := parseProto(match[2]) ut := parseUptime(match[5]) c.current = protocol.NewProtocol(match[1], proto, c.ipVersion, ut) c.current.Up = parseState(match[4]) c.current.State = match[6] c.protocols = append(c.protocols, c.current) c.handled = true } func parseLineForDescription(c *context) { match := descriptionRegex.FindStringSubmatch(c.line) if match == nil { return } if len(match) <= 1 { return } c.current.Description = strings.Join(match[1:], " ") } func parseProto(val string) protocol.Proto { switch val { case "BGP": return protocol.BGP case "OSPF": return protocol.OSPF case "Direct": return protocol.Direct case "Kernel": return protocol.Kernel case "Static": return protocol.Static case "Babel": return protocol.Babel case "RPKI": return protocol.RPKI case "BFD": return protocol.BFD } return protocol.PROTO_UNKNOWN } func parseState(state string) int { if state == "up" { return 1 } return 0 } func parseUptime(value string) int { match := uptimeRegex.FindStringSubmatch(value) if match == nil { return 0 } if len(match[1]) > 0 { return parseUptimeForDuration(match) } if len(match[5]) > 0 { return parseUptimeForTimestamp(value) } return parseUptimeForIso(value) } func parseLineForChannel(c *context) { if c.ipVersion != "" || c.current == nil { return } channel := channelRegex.FindStringSubmatch(c.line) if channel == nil { return } if len(c.current.IPVersion) == 0 { c.current.IPVersion = channel[1] } else { c.current = &protocol.Protocol{ Name: c.current.Name, Proto: c.current.Proto, Up: c.current.Up, Uptime: c.current.Uptime, IPVersion: channel[1], } c.protocols = append(c.protocols, c.current) } c.handled = true } func parseLineForRoutes(c *context) { if c.current == nil { return } match := routeRegex.FindStringSubmatch(c.line) if match == nil { return } c.current.Imported, _ = strconv.ParseInt(match[1], 10, 64) c.current.Exported, _ = strconv.ParseInt(match[3], 10, 64) if len(match[2]) > 0 { c.current.Filtered, _ = strconv.ParseInt(match[2], 10, 64) } if len(match[4]) > 0 { c.current.Preferred, _ = strconv.ParseInt(match[4], 10, 64) } c.handled = true } func parseLineForRouteChanges(c *context) { if c.current == nil { return } match := routeChangeRegex.FindStringSubmatch(c.line) if match == nil { return } x := getRouteChangeCount(match, c.current) x.Received = parseRouteChangeValue(match[3]) x.Rejected = parseRouteChangeValue(match[4]) x.Filtered = parseRouteChangeValue(match[5]) x.Ignored = parseRouteChangeValue(match[6]) x.Accepted = parseRouteChangeValue(match[7]) c.handled = true } func getRouteChangeCount(values []string, p *protocol.Protocol) *protocol.RouteChangeCount { if values[1] == "Import" { if values[2] == "updates" { return &p.ImportUpdates } return &p.ImportWithdraws } if values[2] == "updates" { return &p.ExportUpdates } return &p.ExportWithdraws } func parseRouteChangeValue(value string) int64 { if value == "---" { return 0 } return parseInt(value) } func parseLineForFilterName(c *context) { if c.current == nil { return } match := filterRegex.FindStringSubmatch(c.line) if match == nil { return } if match[1] == "Input" { c.current.ImportFilter = match[2] } else { c.current.ExportFilter = match[2] } c.handled = true } prometheus-bird-exporter-1.4.2+ds/parser/parser_test.go000066400000000000000000000363361444145222000232660ustar00rootroot00000000000000package parser import ( "testing" "time" "github.com/czerwonk/bird_exporter/protocol" "github.com/czerwonk/testutils/assert" ) func TestEstablishedBgpOldTimeFormat(t *testing.T) { overrideNowFunc(func() time.Time { return time.Date(2018, 1, 1, 2, 0, 0, 0, time.UTC) }) data := "foo BGP master up 1514768400 Established\ntest\nbar\n Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "4") x := p[0] assert.StringEqual("name", "foo", x.Name, t) assert.IntEqual("proto", int(protocol.BGP), int(x.Proto), t) assert.IntEqual("established", 1, x.Up, t) assert.Int64Equal("imported", 12, x.Imported, t) assert.Int64Equal("exported", 34, x.Exported, t) assert.Int64Equal("filtered", 1, x.Filtered, t) assert.Int64Equal("preferred", 100, x.Preferred, t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) assert.Int64Equal("uptime", 3600, int64(x.Uptime), t) } func TestEstablishedBgpCurrentTimeFormat(t *testing.T) { data := "foo BGP master up 00:01:00 Established\ntest\nbar\n Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "foo", x.Name, t) assert.IntEqual("proto", int(protocol.BGP), int(x.Proto), t) assert.IntEqual("established", 1, x.Up, t) assert.Int64Equal("imported", 12, x.Imported, t) assert.Int64Equal("exported", 34, x.Exported, t) assert.Int64Equal("filtered", 1, x.Filtered, t) assert.Int64Equal("preferred", 100, x.Preferred, t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) assert.IntEqual("uptime", 60, x.Uptime, t) } func TestEstablishedBgpIsoLongTimeFormat(t *testing.T) { overrideNowFunc(func() time.Time { return time.Date(2018, 1, 1, 2, 0, 0, 0, time.Local) }) data := "foo BGP master up 2018-01-01 01:00:00 Established\ntest\nbar\n Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "foo", x.Name, t) assert.IntEqual("proto", int(protocol.BGP), int(x.Proto), t) assert.IntEqual("established", 1, x.Up, t) assert.Int64Equal("imported", 12, x.Imported, t) assert.Int64Equal("exported", 34, x.Exported, t) assert.Int64Equal("filtered", 1, x.Filtered, t) assert.Int64Equal("preferred", 100, x.Preferred, t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) assert.Int64Equal("uptime", 3600, int64(x.Uptime), t) } func TestIpv6BGP(t *testing.T) { data := "foo BGP master up 00:01:00 Established\ntest\nbar\n Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "6") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("ipVersion", "6", x.IPVersion, t) } func TestActiveBGP(t *testing.T) { data := "bar BGP master start 2016-01-01 Active\ntest\nbar" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "bar", x.Name, t) assert.IntEqual("proto", int(protocol.BGP), int(x.Proto), t) assert.IntEqual("established", 0, x.Up, t) assert.IntEqual("imported", 0, int(x.Imported), t) assert.IntEqual("exported", 0, int(x.Exported), t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) assert.IntEqual("uptime", 0, int(x.Uptime), t) } func Test2BGPSessions(t *testing.T) { data := "foo BGP master up 00:01:00 Established\ntest\n Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\nbar BGP master start 2016-01-01 Active\nxxx" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 2, len(p), t) } func TestUpdateAndWithdrawCounts(t *testing.T) { data := "foo BGP master up 00:01:00 Established\ntest\n" + " Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\n" + " Route change stats: received rejected filtered ignored accepted\n" + " Import updates: 1 2 3 4 5\n" + " Import withdraws: 6 7 8 9 10\n" + " Export updates: 11 12 13 14 15\n" + " Export withdraws: 16 17 18 19 ---" p := ParseProtocols([]byte(data), "4") x := p[0] assert.Int64Equal("import updates received", 1, x.ImportUpdates.Received, t) assert.Int64Equal("import updates rejected", 2, x.ImportUpdates.Rejected, t) assert.Int64Equal("import updates filtered", 3, x.ImportUpdates.Filtered, t) assert.Int64Equal("import updates ignored", 4, x.ImportUpdates.Ignored, t) assert.Int64Equal("import updates accepted", 5, x.ImportUpdates.Accepted, t) assert.Int64Equal("import withdraws received", 6, x.ImportWithdraws.Received, t) assert.Int64Equal("import withdraws rejected", 7, x.ImportWithdraws.Rejected, t) assert.Int64Equal("import withdraws filtered", 8, x.ImportWithdraws.Filtered, t) assert.Int64Equal("import withdraws ignored", 9, x.ImportWithdraws.Ignored, t) assert.Int64Equal("import withdraws accepted", 10, x.ImportWithdraws.Accepted, t) assert.Int64Equal("export updates received", 11, x.ExportUpdates.Received, t) assert.Int64Equal("export updates rejected", 12, x.ExportUpdates.Rejected, t) assert.Int64Equal("export updates filtered", 13, x.ExportUpdates.Filtered, t) assert.Int64Equal("export updates ignored", 14, x.ExportUpdates.Ignored, t) assert.Int64Equal("export updates accepted", 15, x.ExportUpdates.Accepted, t) assert.Int64Equal("export withdraws received", 16, x.ExportWithdraws.Received, t) assert.Int64Equal("export withdraws rejected", 17, x.ExportWithdraws.Rejected, t) assert.Int64Equal("export withdraws filtered", 18, x.ExportWithdraws.Filtered, t) assert.Int64Equal("export withdraws ignored", 19, x.ExportWithdraws.Ignored, t) assert.Int64Equal("export withdraws accepted", 0, x.ExportWithdraws.Accepted, t) } func TestWithBird2(t *testing.T) { data := "Name Proto Table State Since Info\n" + "bgp1 BGP master up 1494926415\n" + " Channel ipv6\n" + " Routes: 1 imported, 2 filtered, 3 exported, 4 preferred\n" + " Input filter: none\n" + " Output filter: all\n" + "\n" + "direct1 Direct --- up 1513027903\n" + " Channel ipv4\n" + " State: UP\n" + " Table: master4\n" + " Preference: 240\n" + " Input filter: ACCEPT\n" + " Output filter: REJECT\n" + " Routes: 12 imported, 1 filtered, 34 exported, 100 preferred\n" + " Route change stats: received rejected filtered ignored accepted\n" + " Import updates: 1 2 3 4 5\n" + " Import withdraws: 6 7 8 9 10\n" + " Export updates: 11 12 13 14 15\n" + " Export withdraws: 16 17 18 19 ---\n" + " Channel ipv6\n" + " State: UP\n" + " Table: master6\n" + " Preference: 240\n" + " Input filter: ACCEPT\n" + " Output filter: REJECT\n" + " Routes: 3 imported, 7 filtered, 5 exported, 13 preferred\n" + " Route change stats: received rejected filtered ignored accepted\n" + " Import updates: 20 21 22 23 24\n" + " Import withdraws: 25 26 27 28 29\n" + " Export updates: 30 31 32 33 34\n" + " Export withdraws: 35 36 37 38 ---\n" + "\n" + "ospf1 OSPF master up 1494926415\n" + " Channel ipv4\n" + " Routes: 4 imported, 3 filtered, 2 exported, 1 preferred\n" + "\n" p := ParseProtocols([]byte(data), "") assert.IntEqual("protocols", 4, len(p), t) x := p[0] assert.StringEqual("BGP ipv6 name", "bgp1", x.Name, t) assert.IntEqual("BGP ipv6 proto", int(protocol.BGP), int(x.Proto), t) assert.StringEqual("BGP ipv6 ip version", "6", x.IPVersion, t) assert.Int64Equal("BGP ipv6 imported", 1, x.Imported, t) assert.Int64Equal("BGP ipv6 exported", 3, x.Exported, t) assert.Int64Equal("BGP ipv6 filtered", 2, x.Filtered, t) assert.Int64Equal("BGP ipv6 preferred", 4, x.Preferred, t) assert.StringEqual("BGP import filter", "none", x.ImportFilter, t) assert.StringEqual("BGP export filter", "all", x.ExportFilter, t) x = p[1] assert.StringEqual("Direct ipv4 name", "direct1", x.Name, t) assert.IntEqual("Direct ipv4 proto", int(protocol.Direct), int(x.Proto), t) assert.StringEqual("Direct ipv4 ip version", "4", x.IPVersion, t) assert.Int64Equal("Direct ipv4 imported", 12, x.Imported, t) assert.Int64Equal("Direct ipv4 exported", 34, x.Exported, t) assert.Int64Equal("Direct ipv4 filtered", 1, x.Filtered, t) assert.Int64Equal("Direct ipv4 preferred", 100, x.Preferred, t) assert.Int64Equal("Direct ipv4 import updates received", 1, x.ImportUpdates.Received, t) assert.Int64Equal("Direct ipv4 import updates rejected", 2, x.ImportUpdates.Rejected, t) assert.Int64Equal("Direct ipv4 import updates filtered", 3, x.ImportUpdates.Filtered, t) assert.Int64Equal("Direct ipv4 import updates ignored", 4, x.ImportUpdates.Ignored, t) assert.Int64Equal("Direct ipv4 import updates accepted", 5, x.ImportUpdates.Accepted, t) assert.Int64Equal("Direct ipv4 import withdraws received", 6, x.ImportWithdraws.Received, t) assert.Int64Equal("Direct ipv4 import withdraws rejected", 7, x.ImportWithdraws.Rejected, t) assert.Int64Equal("Direct ipv4 import withdraws filtered", 8, x.ImportWithdraws.Filtered, t) assert.Int64Equal("Direct ipv4 import withdraws ignored", 9, x.ImportWithdraws.Ignored, t) assert.Int64Equal("Direct ipv4 import withdraws accepted", 10, x.ImportWithdraws.Accepted, t) assert.Int64Equal("Direct ipv4 export updates received", 11, x.ExportUpdates.Received, t) assert.Int64Equal("Direct ipv4 export updates rejected", 12, x.ExportUpdates.Rejected, t) assert.Int64Equal("Direct ipv4 export updates filtered", 13, x.ExportUpdates.Filtered, t) assert.Int64Equal("Direct ipv4 export updates ignored", 14, x.ExportUpdates.Ignored, t) assert.Int64Equal("Direct ipv4 export updates accepted", 15, x.ExportUpdates.Accepted, t) assert.Int64Equal("Direct ipv4 export withdraws received", 16, x.ExportWithdraws.Received, t) assert.Int64Equal("Direct ipv4 export withdraws rejected", 17, x.ExportWithdraws.Rejected, t) assert.Int64Equal("Direct ipv4 export withdraws filtered", 18, x.ExportWithdraws.Filtered, t) assert.Int64Equal("Direct ipv4 export withdraws ignored", 19, x.ExportWithdraws.Ignored, t) assert.Int64Equal("Direct ipv4 export withdraws accepted", 0, x.ExportWithdraws.Accepted, t) x = p[2] assert.StringEqual("Direct ipv6 name", "direct1", x.Name, t) assert.IntEqual("Direct ipv6 proto", int(protocol.Direct), int(x.Proto), t) assert.StringEqual("Direct ipv6 ip version", "6", x.IPVersion, t) assert.Int64Equal("Direct ipv6 imported", 3, x.Imported, t) assert.Int64Equal("Direct ipv6 exported", 5, x.Exported, t) assert.Int64Equal("Direct ipv6 filtered", 7, x.Filtered, t) assert.Int64Equal("Direct ipv6 preferred", 13, x.Preferred, t) assert.Int64Equal("Direct ipv6 import updates received", 20, x.ImportUpdates.Received, t) assert.Int64Equal("Direct ipv6 import updates rejected", 21, x.ImportUpdates.Rejected, t) assert.Int64Equal("Direct ipv6 import updates filtered", 22, x.ImportUpdates.Filtered, t) assert.Int64Equal("Direct ipv6 import updates ignored", 23, x.ImportUpdates.Ignored, t) assert.Int64Equal("Direct ipv6 import updates accepted", 24, x.ImportUpdates.Accepted, t) assert.Int64Equal("Direct ipv6 import withdraws received", 25, x.ImportWithdraws.Received, t) assert.Int64Equal("Direct ipv6 import withdraws rejected", 26, x.ImportWithdraws.Rejected, t) assert.Int64Equal("Direct ipv6 import withdraws filtered", 27, x.ImportWithdraws.Filtered, t) assert.Int64Equal("Direct ipv6 import withdraws ignored", 28, x.ImportWithdraws.Ignored, t) assert.Int64Equal("Direct ipv6 import withdraws accepted", 29, x.ImportWithdraws.Accepted, t) assert.Int64Equal("Direct ipv6 export updates received", 30, x.ExportUpdates.Received, t) assert.Int64Equal("Direct ipv6 export updates rejected", 31, x.ExportUpdates.Rejected, t) assert.Int64Equal("Direct ipv6 export updates filtered", 32, x.ExportUpdates.Filtered, t) assert.Int64Equal("Direct ipv6 export updates ignored", 33, x.ExportUpdates.Ignored, t) assert.Int64Equal("Direct ipv6 export updates accepted", 34, x.ExportUpdates.Accepted, t) assert.Int64Equal("Direct ipv6 export withdraws received", 35, x.ExportWithdraws.Received, t) assert.Int64Equal("Direct ipv6 export withdraws rejected", 36, x.ExportWithdraws.Rejected, t) assert.Int64Equal("Direct ipv6 export withdraws filtered", 37, x.ExportWithdraws.Filtered, t) assert.Int64Equal("Direct ipv6 export withdraws ignored", 38, x.ExportWithdraws.Ignored, t) assert.Int64Equal("Direct ipv6 export withdraws accepted", 0, x.ExportWithdraws.Accepted, t) x = p[3] assert.StringEqual("OSPF ipv4 name", "ospf1", x.Name, t) assert.IntEqual("OSPF ipv4 proto", int(protocol.OSPF), int(x.Proto), t) assert.StringEqual("OSPF ipv4 ip version", "4", x.IPVersion, t) assert.Int64Equal("OSPF ipv4 imported", 4, x.Imported, t) assert.Int64Equal("OSPF ipv4 exported", 2, x.Exported, t) assert.Int64Equal("OSPF ipv4 filtered", 3, x.Filtered, t) assert.Int64Equal("OSPF ipv4 preferred", 1, x.Preferred, t) } func TestOSPFOldTimeFormat(t *testing.T) { data := "ospf1 OSPF master up 1481973060 Running\ntest\nbar\n Routes: 12 imported, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "ospf1", x.Name, t) assert.IntEqual("proto", int(protocol.OSPF), int(x.Proto), t) assert.IntEqual("up", 1, x.Up, t) assert.Int64Equal("imported", 12, x.Imported, t) assert.Int64Equal("exported", 34, x.Exported, t) assert.Int64Equal("preferred", 100, x.Preferred, t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) } func TestOSPFCurrentTimeFormat(t *testing.T) { data := "ospf1 OSPF master up 00:01:00 Running\ntest\nbar\n Routes: 12 imported, 34 exported, 100 preferred\nxxx" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "ospf1", x.Name, t) assert.IntEqual("proto", int(protocol.OSPF), int(x.Proto), t) assert.IntEqual("up", 1, x.Up, t) assert.Int64Equal("imported", 12, x.Imported, t) assert.Int64Equal("exported", 34, x.Exported, t) assert.Int64Equal("preferred", 100, x.Preferred, t) assert.StringEqual("ipVersion", "4", x.IPVersion, t) assert.IntEqual("uptime", 60, x.Uptime, t) } func TestRPKIUp(t *testing.T) { data := "rpki1 RPKI --- up 2021-12-31 13:04:29 Established" p := ParseProtocols([]byte(data), "4") assert.IntEqual("protocols", 1, len(p), t) x := p[0] assert.StringEqual("name", "rpki1", x.Name, t) assert.IntEqual("proto", int(protocol.RPKI), int(x.Proto), t) assert.StringEqual("state", "Established", x.State, t) assert.IntEqual("up", 1, x.Up, t) } prometheus-bird-exporter-1.4.2+ds/protocol/000077500000000000000000000000001444145222000207365ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/protocol/bfd_session.go000066400000000000000000000002751444145222000235670ustar00rootroot00000000000000package protocol type BFDSession struct { ProtocolName string IP string Interface string Up bool Since int Interval float64 Timeout float64 } prometheus-bird-exporter-1.4.2+ds/protocol/ospf_area.go000066400000000000000000000002401444145222000232200ustar00rootroot00000000000000package protocol type OSPFArea struct { Name string InterfaceCount int64 NeighborCount int64 NeighborAdjacentCount int64 } prometheus-bird-exporter-1.4.2+ds/protocol/protocol.go000066400000000000000000000020061444145222000231240ustar00rootroot00000000000000package protocol const ( PROTO_UNKNOWN = Proto(0) BGP = Proto(1) OSPF = Proto(2) Kernel = Proto(4) Static = Proto(8) Direct = Proto(16) Babel = Proto(32) RPKI = Proto(64) BFD = Proto(128) ) type Proto int type Protocol struct { Name string Description string IPVersion string ImportFilter string ExportFilter string Proto Proto Up int State string Imported int64 Exported int64 Filtered int64 Preferred int64 Uptime int ImportUpdates RouteChangeCount ImportWithdraws RouteChangeCount ExportUpdates RouteChangeCount ExportWithdraws RouteChangeCount } type RouteChangeCount struct { Received int64 Rejected int64 Filtered int64 Ignored int64 Accepted int64 } func NewProtocol(name string, proto Proto, ipVersion string, uptime int) *Protocol { return &Protocol{Name: name, Proto: proto, IPVersion: ipVersion, Uptime: uptime} } prometheus-bird-exporter-1.4.2+ds/vendor/000077500000000000000000000000001444145222000203725ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/000077500000000000000000000000001444145222000224315ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/000077500000000000000000000000001444145222000242735ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/000077500000000000000000000000001444145222000265635ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/.gitignore000066400000000000000000000004401444145222000305510ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ .idea *.iml prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/.travis.yml000066400000000000000000000000151444145222000306700ustar00rootroot00000000000000language: go prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/LICENSE000066400000000000000000000020601444145222000275660ustar00rootroot00000000000000MIT License Copyright (c) 2017 Daniel Czerwonk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/README.md000066400000000000000000000013341444145222000300430ustar00rootroot00000000000000# bird_socket [![GoDoc](https://godoc.org/github.com/czerwonk/bird_socket?status.svg)](https://godoc.org/github.com/czerwonk/bird_socket) [![Build Status](https://travis-ci.org/czerwonk/bird_socket.svg)](https://travis-ci.org/czerwonk/bird_socket) [![Go Report Card](https://goreportcard.com/badge/github.com/czerwonk/bird_socket)](https://goreportcard.com/report/github.com/czerwonk/bird_socket) [![Sourcegraph](https://sourcegraph.com/github.com/czerwonk/bird_socket/-/badge.svg)](https://sourcegraph.com/github.com/czerwonk/bird_socket?badge) Golang library to communicate with Bird routing daemon ## License (c) Daniel Czerwonk, 2017. Licensed under [MIT](LICENSE) license. ## Bird routing daemon see http://bird.network.cz/ prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/bird_socket/bird_socket.go000066400000000000000000000036351444145222000314110ustar00rootroot00000000000000package birdsocket import ( "net" "regexp" "strings" ) var birdReturnCodeRegex *regexp.Regexp func init() { birdReturnCodeRegex = regexp.MustCompile("\\d{4} \n$") } // BirdSocket encapsulates communication with Bird routing daemon type BirdSocket struct { SocketPath string BufferSize int Timeout int conn net.Conn } // NewSocket creates a new socket func NewSocket(socketPath string) *BirdSocket { return &BirdSocket{SocketPath: socketPath, BufferSize: 4096, Timeout: 30} } // Query sends an ad hoc query to Bird and waits for the reply func Query(socketPath, qry string) ([]byte, error) { s := NewSocket(socketPath) _, err := s.Connect() if err != nil { return nil, err } defer s.Close() return s.Query(qry) } // Connect connects to the Bird unix socket func (s *BirdSocket) Connect() ([]byte, error) { var err error s.conn, err = net.Dial("unix", s.SocketPath) if err != nil { return nil, err } buf := make([]byte, s.BufferSize) n, err := s.conn.Read(buf[:]) if err != nil { return nil, err } return buf[:n], err } // Close closes the connection to the socket func (s *BirdSocket) Close() { if s.conn != nil { s.conn.Close() } } // Query sends an query to Bird and waits for the reply func (s *BirdSocket) Query(qry string) ([]byte, error) { _, err := s.conn.Write([]byte(strings.Trim(qry, "\n") + "\n")) if err != nil { return nil, err } output, err := s.readFromSocket(s.conn) if err != nil { return nil, err } return output, nil } func (s *BirdSocket) readFromSocket(conn net.Conn) ([]byte, error) { b := make([]byte, 0) buf := make([]byte, s.BufferSize) done := false for !done { n, err := conn.Read(buf[:]) if err != nil { return nil, err } b = append(b, buf[:n]...) done = endsWithBirdReturnCode(b) } return b, nil } func endsWithBirdReturnCode(b []byte) bool { if len(b) < 6 { return false } return birdReturnCodeRegex.Match(b[len(b)-6:]) } prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/000077500000000000000000000000001444145222000263335ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/.travis.yml000066400000000000000000000000151444145222000304400ustar00rootroot00000000000000language: go prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/LICENSE000066400000000000000000000020601444145222000273360ustar00rootroot00000000000000MIT License Copyright (c) 2016 Daniel Czerwonk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/README.md000066400000000000000000000006121444145222000276110ustar00rootroot00000000000000# testutils [![Build Status](https://travis-ci.org/czerwonk/testutils.svg)][travis] Simple collection of assertion functions to prevent boilerplate code ## Remark this collection will grow in the future ## Install ``` go get -u github.com/czerwonk/testutils ``` ## License (c) Daniel Czerwonk, 2016. Licensed under [MIT](LICENSE) license. [travis]: https://travis-ci.org/czerwonk/testutils prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/assert/000077500000000000000000000000001444145222000276345ustar00rootroot00000000000000prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/assert/assert.go000066400000000000000000000034651444145222000314740ustar00rootroot00000000000000package assert import "testing" const equalError string = "%s: expected %v but got %v" type Assertion func() bool func StringEqual(name string, expected, current string, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func IntEqual(name string, expected, current int, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func ByteEqual(name string, expected, current byte, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Int32Equal(name string, expected, current int32, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Int64Equal(name string, expected, current int64, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Float32Equal(name string, expected, current float32, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Float64Equal(name string, expected, current float64, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Complex64Equal(name string, expected, current complex64, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func Complex128Equal(name string, expected, current complex128, t *testing.T) { if current != expected { t.Fatalf(equalError, name, expected, current) } } func True(name string, current bool, t *testing.T) { if !current { t.Fatalf(equalError, name, true, false) } } func False(name string, current bool, t *testing.T) { if current { t.Fatalf(equalError, name, false, true) } } func That(name, text string, assertion Assertion, t *testing.T) { if !assertion() { t.Fatalf("%s: %s", name, text) } } prometheus-bird-exporter-1.4.2+ds/vendor/github.com/czerwonk/testutils/assert/assert_test.go000066400000000000000000000023361444145222000325270ustar00rootroot00000000000000package assert import "testing" func TestStringEqual(t *testing.T) { var x string = "x" var y string = "x" StringEqual("test", x, y, t) } func TestIntEqual(t *testing.T) { var x int = 123 var y int = 123 IntEqual("test", x, y, t) } func TestByteEqual(t *testing.T) { var x int = 123 var y int = 123 IntEqual("test", x, y, t) } func TestInt32Equal(t *testing.T) { var x int32 = 123 var y int32 = 123 Int32Equal("test", x, y, t) } func TestInt64Equal(t *testing.T) { var x int64 = 123 var y int64 = 123 Int64Equal("test", x, y, t) } func TestFloat32Equal(t *testing.T) { var x float32 = 1.23 var y float32 = 1.23 Float32Equal("test", x, y, t) } func TestFloat64Equal(t *testing.T) { var x float64 = 1.23 var y float64 = 1.23 Float64Equal("test", x, y, t) } func TestComplex64Equal(t *testing.T) { var x complex64 = 1.23 var y complex64 = 1.23 Complex64Equal("test", x, y, t) } func TestComplex128Equal(t *testing.T) { var x complex128 = 1.23 var y complex128 = 1.23 Complex128Equal("test", x, y, t) } func TestTrue(t *testing.T) { True("test", true, t) } func TestFalse(t *testing.T) { False("test", false, t) } func TestThat(t *testing.T) { That("test", "foo", func() bool { return true }, t) }