Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional bitrate HTTP parameter #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ This can be also be limited by the `iperf3.timeout` command-line flag. If neithe
## Prometheus Configuration

The iPerf3 exporter needs to be passed the target as a parameter, this can be done with relabelling.
Optional: pass the port that the target iperf3 server is lisenting on as the "port" parameter.
Optional:
- pass the port that the target iperf3 server is lisenting on as the "port" parameter.
- pass the target bitrate ( #[KMG][/#]) as the "bitrate" parameter, default is unlimited.

Example config:
```yml
Expand Down
63 changes: 42 additions & 21 deletions iperf3_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"net/http"
"os/exec"
"regexp"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -64,7 +65,8 @@ type iperfResult struct {
// the prometheus metrics package.
type Exporter struct {
target string
port int
port int
bitrate string
period time.Duration
timeout time.Duration
mutex sync.RWMutex
Expand All @@ -76,13 +78,16 @@ type Exporter struct {
receivedBytes *prometheus.Desc
}

var bitrateMask = regexp.MustCompile(`^[0-9]+([KMG])?(\/[0-9]+)?$`)

// NewExporter returns an initialized Exporter.
func NewExporter(target string, port int, period time.Duration, timeout time.Duration) *Exporter {
func NewExporter(target string, port int, period time.Duration, timeout time.Duration, bitrate string) *Exporter {
return &Exporter{
target: target,
port: port,
port: port,
period: period,
timeout: timeout,
bitrate: bitrate,
success: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "success"), "Was the last iperf3 probe successful.", nil, nil),
sentSeconds: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "sent_seconds"), "Total seconds spent sending packets.", nil, nil),
sentBytes: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "sent_bytes"), "Total sent bytes.", nil, nil),
Expand Down Expand Up @@ -110,7 +115,14 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
defer cancel()

out, err := exec.CommandContext(ctx, iperfCmd, "-J", "-t", strconv.FormatFloat(e.period.Seconds(), 'f', 0, 64), "-c", e.target, "-p", strconv.Itoa(e.port)).Output()
var iperfArgs []string
iperfArgs = append(iperfArgs, "-J", "-t", strconv.FormatFloat(e.period.Seconds(), 'f', 0, 64), "-c", e.target, "-p", strconv.Itoa(e.port))

if e.bitrate != "" {
iperfArgs = append(iperfArgs, "-b", e.bitrate)
}

out, err := exec.CommandContext(ctx, iperfCmd, iperfArgs...).Output()
if err != nil {
ch <- prometheus.MustNewConstMetric(e.success, prometheus.GaugeValue, 0)
iperfErrors.Inc()
Expand Down Expand Up @@ -140,22 +152,22 @@ func handler(w http.ResponseWriter, r *http.Request) {
iperfErrors.Inc()
return
}
var targetPort int
port := r.URL.Query().Get("port")
if port != "" {
var err error
targetPort, err = strconv.Atoi(port)
if err != nil {
http.Error(w, fmt.Sprintf("'port' parameter must be an integer: %s", err), http.StatusBadRequest)
iperfErrors.Inc()
return
}
}
if targetPort == 0 {
targetPort = 5201
}

var targetPort int
port := r.URL.Query().Get("port")
if port != "" {
var err error
targetPort, err = strconv.Atoi(port)
if err != nil {
http.Error(w, fmt.Sprintf("'port' parameter must be an integer: %s", err), http.StatusBadRequest)
iperfErrors.Inc()
return
}
}
if targetPort == 0 {
targetPort = 5201
}

var runPeriod time.Duration
period := r.URL.Query().Get("period")
if period != "" {
Expand All @@ -171,6 +183,15 @@ func handler(w http.ResponseWriter, r *http.Request) {
runPeriod = time.Second * 5
}

bitrate := r.URL.Query().Get("bitrate")
if bitrate != "" {
if !bitrateMask.MatchString(bitrate) {
http.Error(w, "bitrate must provided as #[KMG][/#], target bitrate in bits/sec (0 for unlimited), (default 1 Mbit/sec for UDP, unlimited for TCP) (optional slash and packet count for burst mode)", http.StatusBadRequest)
iperfErrors.Inc()
return
}
}

// If a timeout is configured via the Prometheus header, add it to the request.
var timeoutSeconds float64
if v := r.Header.Get("X-Prometheus-Scrape-Timeout-Seconds"); v != "" {
Expand Down Expand Up @@ -198,7 +219,7 @@ func handler(w http.ResponseWriter, r *http.Request) {

start := time.Now()
registry := prometheus.NewRegistry()
exporter := NewExporter(target, targetPort, runPeriod, runTimeout)
exporter := NewExporter(target, targetPort, runPeriod, runTimeout, bitrate)
registry.MustRegister(exporter)

// Delegate http serving to Prometheus client library, which will call collector.Collect.
Expand Down