Compare commits

..

12 Commits

Author SHA1 Message Date
malarinv
638edf65de Publish iperf3-monitor-0.2.6.tgz 2026-09-26 05:03:21 +00:00
malarinv
ddcfe257d9 Publish iperf3-monitor-0.2.5.tgz 2025-07-02 11:03:53 +00:00
malarinv
109d8a0fab Publish iperf3-monitor-0.2.4.tgz 2025-07-02 08:39:13 +00:00
malarinv
812b8ce2a7 Publish iperf3-monitor-0.2.3.tgz 2025-07-02 08:02:44 +00:00
malarinv
ad652029aa Publish iperf3-monitor-0.2.2.tgz 2025-07-02 07:30:10 +00:00
malarinv
d8cebb7495 Publish iperf3-monitor-0.2.1.tgz 2025-07-02 06:29:50 +00:00
malarinv
a3cf2ca10b Publish iperf3-monitor-0.1.9.tgz 2025-07-01 20:10:45 +00:00
malarinv
2509f4a599 Publish iperf3-monitor-0.1.8.tgz 2025-06-20 20:43:42 +00:00
malarinv
58cb25a845 Publish iperf3-monitor-0.1.6.tgz 2025-06-20 19:36:24 +00:00
malarinv
a38cb9e075 Publish iperf3-monitor-0.1.5.tgz 2025-06-20 13:41:15 +00:00
malarinv
1351171424 Publish iperf3-monitor-0.1.4.tgz 2025-06-20 12:13:54 +00:00
malarinv
6fb6aa66d2 Publish iperf3-monitor-0.1.1.tgz 2025-06-19 21:43:47 +00:00
37 changed files with 2266 additions and 1265 deletions

View File

@@ -9,26 +9,6 @@ env:
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
test-exporter:
name: Run Exporter Unit Tests
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r exporter/requirements.txt
- name: Run unit tests
run: |
python -m unittest exporter/test_exporter.py
validate-chart: validate-chart:
name: Validate Helm Chart name: Validate Helm Chart
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -39,16 +19,7 @@ jobs:
- name: Set up Helm - name: Set up Helm
uses: azure/setup-helm@v3 uses: azure/setup-helm@v3
with: with:
version: v3.10.0 # Using a specific version, can be updated version: v3.10.0
- name: Add Helm repositories
run: |
helm repo add bjw-s https://bjw-s-labs.github.io/helm-charts/ --force-update
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts --force-update
helm repo update
- name: Build Helm chart dependencies
run: helm dependency build ./charts/iperf3-monitor
- name: Helm Lint - name: Helm Lint
run: helm lint ./charts/iperf3-monitor run: helm lint ./charts/iperf3-monitor
@@ -56,49 +27,23 @@ jobs:
build: build:
name: Build Docker Image name: Build Docker Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read # Needed to checkout the repository
packages: write # Needed to push Docker images to GHCR
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# Tag with the PR number if it's a pull request event
type=match,pattern=pull_request,value=pr-{{number}}
# Tag with the git SHA
type=sha,prefix=
# Tag with 'latest' if on the main branch (though this workflow only runs on PRs to main)
type=ref,event=branch,pattern=main,value=latest
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image - name: Build Docker image
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
context: ./exporter context: ./exporter
# Push the image if the event is a pull request. push: false # Do not push on PRs
# The workflow currently only triggers on pull_request events.
push: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
test: test:
name: Run Tests name: Run Tests

View File

@@ -17,33 +17,11 @@ jobs:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r exporter/requirements.txt
- name: Run unit tests
run: |
python -m unittest exporter/test_exporter.py
- name: Set up Helm - name: Set up Helm
uses: azure/setup-helm@v3 uses: azure/setup-helm@v3
with: with:
version: v3.10.0 version: v3.10.0
- name: Add Helm repositories
run: |
helm repo add bjw-s https://bjw-s-labs.github.io/helm-charts/ --force-update
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts --force-update
helm repo update
- name: Build Helm chart dependencies
run: helm dependency build ./charts/iperf3-monitor
- name: Helm Lint - name: Helm Lint
run: helm lint ./charts/iperf3-monitor run: helm lint ./charts/iperf3-monitor
@@ -58,12 +36,6 @@ jobs:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
@@ -76,11 +48,6 @@ jobs:
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
# This ensures that for a git tag like "v0.1.0",
# an image tag "0.1.0" is generated.
# It will also generate "latest" for the most recent semver tag.
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
@@ -89,7 +56,6 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
package-and-publish-chart: package-and-publish-chart:
name: Package and Publish Helm Chart name: Package and Publish Helm Chart
@@ -113,15 +79,6 @@ jobs:
sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\ sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\
sudo chmod +x /usr/bin/yq sudo chmod +x /usr/bin/yq
- name: Add Helm repositories
run: |
helm repo add bjw-s https://bjw-s-labs.github.io/helm-charts/ --force-update
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts --force-update
helm repo update
- name: Build Helm chart dependencies
run: helm dependency build ./charts/iperf3-monitor
- name: Set Chart Version from Tag - name: Set Chart Version from Tag
run: | run: |
VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//') VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')

6
.gitignore vendored
View File

@@ -36,8 +36,4 @@ Thumbs.db
# Helm # Helm
!charts/iperf3-monitor/.helmignore !charts/iperf3-monitor/.helmignore
charts/iperf3-monitor/charts/ charts/*.tgz # Ignore packaged chart files
# Rendered Kubernetes manifests (for local testing)
rendered-manifests.yaml
rendered-manifests-updated.yaml

File diff suppressed because it is too large Load Diff

142
README.md
View File

@@ -74,123 +74,37 @@ nameOverride: ""
# -- Override the fully qualified app name. # -- Override the fully qualified app name.
fullnameOverride: "" fullnameOverride: ""
# Exporter Configuration (`controllers.exporter`) exporter:
# The iperf3 exporter is managed under the `controllers.exporter` section, # -- Configuration for the exporter container image.
# leveraging the `bjw-s/common-library` for robust workload management. image:
controllers: # -- The container image repository for the exporter.
exporter: repository: ghcr.io/malarinv/iperf3-monitor
# -- Enable the exporter controller. # -- The container image tag for the exporter. If not set, the chart's appVersion is used.
enabled: true tag: ""
# -- Set the controller type for the exporter. # -- The image pull policy for the exporter container.
# Valid options are "deployment" or "daemonset". pullPolicy: IfNotPresent
# Use "daemonset" for N-to-N node monitoring where an exporter runs on each node (or selected nodes).
# Use "deployment" for a centralized exporter (typically with replicaCount: 1).
# @default -- "deployment"
type: deployment
# -- Number of desired exporter pods. Only used if type is "deployment".
# @default -- 1
replicas: 1
# -- Application-specific configuration for the iperf3 exporter. # -- Number of exporter pod replicas. Typically 1 is sufficient.
# These values are used to populate environment variables for the exporter container. replicaCount: 1
appConfig:
# -- Interval in seconds between complete test cycles (i.e., testing all server nodes).
testInterval: 300
# -- Log level for the iperf3 exporter (e.g., DEBUG, INFO, WARNING, ERROR, CRITICAL).
logLevel: INFO
# -- Timeout in seconds for a single iperf3 test run.
testTimeout: 10
# -- Protocol to use for testing (tcp or udp).
testProtocol: tcp
# -- iperf3 server port to connect to. Should match the server's listening port.
serverPort: "5201"
# -- Label selector to find iperf3 server pods.
# This is templated. Default: 'app.kubernetes.io/name=<chart-name>,app.kubernetes.io/instance=<release-name>,app.kubernetes.io/component=server'
serverLabelSelector: 'app.kubernetes.io/name={{ include "iperf3-monitor.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=server'
# -- Pod-level configurations for the exporter. # -- Interval in seconds between complete test cycles (i.e., testing all server nodes).
pod: testInterval: 300
# -- Annotations for the exporter pod.
annotations: {}
# -- Labels for the exporter pod (the common library adds its own defaults too).
labels: {}
# -- Node selector for scheduling exporter pods. Useful for DaemonSet or specific scheduling with Deployments.
# Example:
# nodeSelector:
# kubernetes.io/os: linux
nodeSelector: {}
# -- Tolerations for scheduling exporter pods.
# Example:
# tolerations:
# - key: "node-role.kubernetes.io/control-plane"
# operator: "Exists"
# effect: "NoSchedule"
tolerations: []
# -- Affinity rules for scheduling exporter pods.
# Example:
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# - matchExpressions:
# - key: "kubernetes.io/arch"
# operator: In
# values:
# - amd64
affinity: {}
# -- Security context for the exporter pod.
# securityContext:
# fsGroup: 65534
# runAsUser: 65534
# runAsGroup: 65534
# runAsNonRoot: true
securityContext: {}
# -- Automount service account token for the pod.
automountServiceAccountToken: true
# -- Container-level configurations for the main exporter container. # -- Timeout in seconds for a single iperf3 test run.
containers: testTimeout: 10
exporter: # Name of the primary container
image: # -- Protocol to use for testing (tcp or udp).
repository: ghcr.io/malarinv/iperf3-monitor testProtocol: tcp
tag: "" # Defaults to .Chart.AppVersion
pullPolicy: IfNotPresent # -- CPU and memory resource requests and limits for the exporter pod.
# -- Custom environment variables for the exporter container. # @default -- A small default is provided if commented out.
# These are merged with the ones generated from appConfig. resources: {}
# env: # requests:
# MY_CUSTOM_VAR: "my_value" # cpu: "100m"
env: {} # memory: "128Mi"
# -- Ports for the exporter container. # limits:
ports: # cpu: "500m"
metrics: # Name of the port # memory: "256Mi"
port: 9876 # Container port for metrics
protocol: TCP
enabled: true
# -- CPU and memory resource requests and limits.
# resources:
# requests:
# cpu: "100m"
# memory: "128Mi"
# limits:
# cpu: "500m"
# memory: "256Mi"
resources: {}
# -- Probes configuration for the exporter container.
# probes:
# liveness:
# enabled: true # Example: enable liveness probe
# spec: # Customize probe spec if needed
# initialDelaySeconds: 30
# periodSeconds: 15
# timeoutSeconds: 5
# failureThreshold: 3
probes:
liveness:
enabled: false
readiness:
enabled: false
startup:
enabled: false
server: server:
# -- Configuration for the iperf3 server container image (DaemonSet). # -- Configuration for the iperf3 server container image (DaemonSet).

View File

@@ -1,9 +1,6 @@
dependencies: dependencies:
- name: kube-prometheus-stack - name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts repository: https://prometheus-community.github.io/helm-charts
version: 91.5.3 version: 75.3.6
- name: common digest: sha256:d15acd48bfc0b842654ae025e1bd1969e636a66508020312d555db84f381c379
repository: https://bjw-s-labs.github.io/helm-charts/ generated: "2025-06-19T20:40:53.415529365Z"
version: 4.1.2
digest: sha256:a3a063101484521564d3e8922c1c1fe7ae8040fe28c2ae246a40d0c00b860e65
generated: "2026-09-26T09:52:53.951178644+05:30"

View File

@@ -21,20 +21,10 @@ maintainers:
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png # Optional icon URL icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png # Optional icon URL
annotations: annotations:
artifacthub.io/changes: | artifacthub.io/changes: |
- kind: changed - Add initial Helm chart structure.
description: Refactor exporter to subprocess with active stall watchdog and connect-timeout
- kind: added
description: Expose TCP retransmits, mean RTT (ms), and cycle timestamp metrics
- kind: changed
description: Run exporter container as non-root on Python 3.12
- kind: fixed
description: Support testDuration, connectTimeoutMs, and stallThresholdSeconds in Helm values
artifacthub.io/category: networking artifacthub.io/category: networking
dependencies: dependencies:
- name: kube-prometheus-stack # Example dependency if you package the whole stack - name: kube-prometheus-stack # Example dependency if you package the whole stack
version: ">=30.0.0" # Specify a compatible version range version: ">=30.0.0" # Specify a compatible version range
repository: https://prometheus-community.github.io/helm-charts repository: https://prometheus-community.github.io/helm-charts
condition: "dependencies.install, serviceMonitor.enabled" condition: serviceMonitor.enabled # Only include if ServiceMonitor is enabled (assuming Prometheus Operator)
- name: common
version: "4.1.2"
repository: "https://bjw-s-labs.github.io/helm-charts/"

View File

@@ -1,194 +0,0 @@
{
"__inputs": [],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "8.0.0"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"targets": [
{
"expr": "avg(iperf_network_bandwidth_mbps) by (source_node, destination_node)",
"format": "heatmap",
"legendFormat": "{{source_node}} -> {{destination_node}}",
"refId": "A"
}
],
"cards": { "cardPadding": null, "cardRound": null },
"color": {
"mode": "spectrum",
"scheme": "red-yellow-green",
"exponent": 0.5,
"reverse": false
},
"dataFormat": "tsbuckets",
"yAxis": { "show": true, "format": "short" },
"xAxis": { "show": true }
},
{
"title": "Bandwidth Over Time (Source: $source_node, Dest: $destination_node)",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"targets": [
{
"expr": "iperf_network_bandwidth_mbps{source_node=~\"^$source_node$\", destination_node=~\"^$destination_node$\", protocol=~\"^$protocol$\"}",
"legendFormat": "Bandwidth",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "mbps"
}
}
},
{
"title": "Jitter Over Time (Source: $source_node, Dest: $destination_node)",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"targets": [
{
"expr": "iperf_network_jitter_ms{source_node=~\"^$source_node$\", destination_node=~\"^$destination_node$\", protocol=\"udp\"}",
"legendFormat": "Jitter",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms"
}
}
}
],
"refresh": "30s",
"schemaVersion": 36,
"style": "dark",
"tags": ["iperf3", "network", "kubernetes"],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"definition": "label_values(iperf_network_bandwidth_mbps, source_node)",
"hide": 0,
"includeAll": false,
"multi": false,
"name": "source_node",
"options": [],
"query": "label_values(iperf_network_bandwidth_mbps, source_node)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"definition": "label_values(iperf_network_bandwidth_mbps{source_node=~\"^$source_node$\"}, destination_node)",
"hide": 0,
"includeAll": false,
"multi": false,
"name": "destination_node",
"options": [],
"query": "label_values(iperf_network_bandwidth_mbps{source_node=~\"^$source_node$\"}, destination_node)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": { "selected": true, "text": "tcp", "value": "tcp" },
"hide": 0,
"includeAll": false,
"multi": false,
"name": "protocol",
"options": [
{ "selected": true, "text": "tcp", "value": "tcp" },
{ "selected": false, "text": "udp", "value": "udp" }
],
"query": "tcp,udp",
"skipUrlSync": false,
"type": "custom"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Kubernetes iperf3 Network Performance",
"uid": "k8s-iperf3-dashboard",
"version": 1,
"weekStart": ""
}

View File

@@ -47,9 +47,9 @@ app.kubernetes.io/instance: {{ .Release.Name }}
Create the name of the service account to use Create the name of the service account to use
*/}} */}}
{{- define "iperf3-monitor.serviceAccountName" -}} {{- define "iperf3-monitor.serviceAccountName" -}}
{{- if .Values.rbac.create -}} {{- if .Values.serviceAccount.create -}}
{{- default (include "iperf3-monitor.fullname" .) .Values.serviceAccount.name -}} {{- default (include "iperf3-monitor.fullname" .) .Values.serviceAccount.name -}}
{{- else -}} {{- else -}}
{{- default "default" .Values.serviceAccount.name -}} {{- default "default" .Values.serviceAccount.name -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}

View File

@@ -1,144 +0,0 @@
{{- /*
This template is responsible for rendering the 'exporter' controller (Deployment or DaemonSet)
by calling the bjw-s common library.
The primary values for the exporter are expected under .Values.controllers.exporter.
Modifications to environment variables and service account are handled here before
passing the configuration to the common library.
*/}}
{{- /*
Prepare a local, modifiable copy of the .Values. This allows us to adjust the
exporter controller's configuration (like env vars and SA) specifically for this chart's needs
before the common library processes it.
Convert to map[string]interface{} via toYaml/fromYaml to ensure compatibility with 'dig'.
*/}}
{{- $localValues := .Values | toYaml | fromYaml | deepCopy -}}
{{- $chart := .Chart -}}
{{- $release := .Release -}}
{{- $appName := include "iperf3-monitor.name" . -}}
{{- $fullName := include "iperf3-monitor.fullname" . -}}
{{- /*
Define the key for the exporter controller, typically "exporter" as per our values.yaml.
*/}}
{{- $exporterControllerKey := "exporter" -}}
{{- /*
Attempt to get the exporter controller's configuration block.
Proceed with modifications only if the exporter controller is defined.
*/}}
{{- $exporterControllerConfig := get $localValues.controllers $exporterControllerKey -}}
{{- if $exporterControllerConfig -}}
{{- /*
Construct the base set of environment variables required by the iperf3-exporter application.
These are derived from the 'appConfig' section of the exporter's controller configuration.
*/}}
{{- $baseExporterEnv := dict -}}
{{- if $exporterControllerConfig.appConfig -}}
{{- $_ := set $baseExporterEnv "SOURCE_NODE_NAME" (dict "valueFrom" (dict "fieldRef" (dict "fieldPath" "spec.nodeName"))) -}}
{{- $_ := set $baseExporterEnv "IPERF_TEST_INTERVAL" ($exporterControllerConfig.appConfig.testInterval | default "300" | toString) -}}
{{- $_ := set $baseExporterEnv "IPERF_TEST_PROTOCOL" ($exporterControllerConfig.appConfig.testProtocol | default "tcp") -}}
{{- $_ := set $baseExporterEnv "LOG_LEVEL" ($exporterControllerConfig.appConfig.logLevel | default "INFO") -}}
{{- $_ := set $baseExporterEnv "IPERF_SERVER_PORT" ($exporterControllerConfig.appConfig.serverPort | default "5201" | toString) -}}
{{- $_ := set $baseExporterEnv "IPERF_SERVER_NAMESPACE" (dict "valueFrom" (dict "fieldRef" (dict "fieldPath" "metadata.namespace"))) -}}
{{- $testDuration := $exporterControllerConfig.appConfig.testDuration | default $exporterControllerConfig.appConfig.testTimeout | default "5" | toString -}}
{{- $_ := set $baseExporterEnv "IPERF_TEST_DURATION" $testDuration -}}
{{- $_ := set $baseExporterEnv "IPERF_TEST_TIMEOUT" $testDuration -}}
{{- $_ := set $baseExporterEnv "IPERF_CONNECT_TIMEOUT_MS" ($exporterControllerConfig.appConfig.connectTimeoutMs | default "3000" | toString) -}}
{{- $_ := set $baseExporterEnv "IPERF_STALL_THRESHOLD_SECONDS" ($exporterControllerConfig.appConfig.stallThresholdSeconds | default "5" | toString) -}}
{{- $serverLabelSelectorDefault := printf "app.kubernetes.io/name=%s,app.kubernetes.io/instance=%s,app.kubernetes.io/component=server" $appName $release.Name -}}
{{- $serverLabelSelector := tpl ($exporterControllerConfig.appConfig.serverLabelSelector | default $serverLabelSelectorDefault) . -}}
{{- $_ := set $baseExporterEnv "IPERF_SERVER_LABEL_SELECTOR" $serverLabelSelector -}}
{{- end -}}
{{- /*
Merge the base environment variables with any user-defined environment variables.
User-defined variables (from .Values.controllers.exporter.containers.exporter.env)
will take precedence in case of conflicting keys.
*/}}
{{- $userExporterEnv := $exporterControllerConfig.containers.exporter.env | default dict -}}
{{- $finalExporterEnv := mergeOverwrite $baseExporterEnv $userExporterEnv -}}
{{- /*
Ensure the container structure exists and update its 'env' field with the final set.
The common library expects this under controllers.<key>.containers.<containerName>.env
*/}}
{{- if not $exporterControllerConfig.containers -}}
{{- $_ := set $exporterControllerConfig "containers" dict -}}
{{- end -}}
{{- if not $exporterControllerConfig.containers.exporter -}}
{{- $_ := set $exporterControllerConfig.containers "exporter" dict -}}
{{- end -}}
{{- $_ := set $exporterControllerConfig.containers.exporter "env" $finalExporterEnv -}}
{{- /*
Ensure the container image tag is set, defaulting to Chart.AppVersion if empty,
as the common library validation requires it during 'helm template'.
NOTE: BJW-S common library typically handles defaulting image.tag to Chart.appVersion
if image.tag is empty or null in values. The custom logic below prepending "v"
is specific to this chart and might be redundant if the common library's default
is preferred. For now, we keep it as it was the reason for previous errors if tag was not set.
However, if common library handles it, this block could be removed and image.tag in values.yaml set to "" or null.
Forcing the tag to be set (even if to chart.appVersion) ensures the common library doesn't complain.
The issue encountered during `helm template` earlier (empty output) was resolved by
explicitly setting the tag (e.g. via --set or by ensuring values.yaml has it).
The common library's internal validation likely needs *a* tag to be present in the values passed to it,
even if that tag is derived from AppVersion. This block ensures that.
*/}}
{{- $exporterContainerCfg := get $exporterControllerConfig.containers "exporter" -}}
{{- if $exporterContainerCfg -}}
{{- if not $exporterContainerCfg.image.tag -}}
{{- if $chart.AppVersion -}}
{{- $_ := set $exporterContainerCfg.image "tag" (printf "%s" $chart.AppVersion) -}} # Removed "v" prefix
{{- else -}}
{{- fail (printf "Error: Container image tag is not specified for controller '%s', container '%s', and Chart.AppVersion is also empty." $exporterControllerKey "exporter") -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /*
Configure the Service Account for the exporter controller.
This ensures the controller pod uses the ServiceAccount that is intended by this chart's
RBAC configuration (.Values.rbac.create and .Values.serviceAccount.name).
*/}}
{{- $serviceAccountNameFromValues := $localValues.serviceAccount.name | default (printf "%s-exporter" $fullName) -}}
{{- if not $exporterControllerConfig.serviceAccount -}}
{{- $_ := set $exporterControllerConfig "serviceAccount" dict -}}
{{- end -}}
{{- $_ := set $exporterControllerConfig.serviceAccount "name" $serviceAccountNameFromValues -}}
{{- $_ := set $exporterControllerConfig.serviceAccount "create" $localValues.rbac.create -}}
{{- $_ := set $exporterControllerConfig.serviceAccount "automountServiceAccountToken" ($exporterControllerConfig.pod.automountServiceAccountToken | default true) -}}
{{- /*
Replace the original exporter controller config in our $localValues copy
with the modified version (that now includes the correct env and SA settings).
*/}}
{{- $_ := set $localValues.controllers $exporterControllerKey $exporterControllerConfig -}}
{{- end -}}
{{- /*
Ensure .Values.global exists and is a map, as the common library expects it.
*/}}
{{- if not (get $localValues "global") -}}
{{- $_ := set $localValues "global" dict -}}
{{- else if not (kindIs "map" (get $localValues "global")) -}}
{{- $_ := set $localValues "global" dict -}}
{{- end -}}
{{- /*
Ensure defaultPodOptionsStrategy exists, as common lib expects it at the root of Values.
*/}}
{{- if not (get $localValues "defaultPodOptionsStrategy") -}}
{{- $_ := set $localValues "defaultPodOptionsStrategy" "overwrite" -}}
{{- end -}}
{{- /*
Call the common library's main render function for controllers.
This function iterates through all controllers defined under $localValues.controllers
(in our case, just "exporter") and renders them using their specified type and configuration.
The context passed must mirror the global Helm context, including 'Values', 'Chart', 'Release', 'Capabilities', and 'Template'.
*/}}
{{- include "bjw-s.common.render.controllers" (dict "Values" $localValues "Chart" $chart "Release" $release "Capabilities" .Capabilities "Template" .Template) | nindent 0 -}}

View File

@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "iperf3-monitor.fullname" . }}-exporter
labels:
{{- include "iperf3-monitor.labels" . | nindent 4 }}
app.kubernetes.io/component: exporter
spec:
replicas: {{ .Values.exporter.replicaCount }}
selector:
matchLabels:
{{- include "iperf3-monitor.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: exporter
template:
metadata:
labels:
{{- include "iperf3-monitor.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: exporter
spec:
serviceAccountName: {{ include "iperf3-monitor.serviceAccountName" . }}
containers:
- name: iperf3-exporter
image: "{{ .Values.exporter.image.repository }}:{{ .Values.exporter.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.exporter.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
name: metrics
env:
- name: SOURCE_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: IPERF_TEST_INTERVAL
value: "{{ .Values.exporter.testInterval }}"
- name: IPERF_TEST_PROTOCOL
value: "{{ .Values.exporter.testProtocol }}"
- name: IPERF_SERVER_PORT
value: "5201" # Hardcoded as per server DaemonSet
- name: IPERF_SERVER_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: IPERF_SERVER_LABEL_SELECTOR
value: 'app.kubernetes.io/name={{ include "iperf3-monitor.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=server'
{{- with .Values.exporter.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}

View File

@@ -1,13 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-grafana-dashboard
labels:
grafana_dashboard: "1"
app.kubernetes.io/name: {{ include "iperf3-monitor.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
data:
iperf3-dashboard.json: |
{{ .Files.Get "grafana/iperf3-dashboard.json" | nindent 4 }}

View File

@@ -7,10 +7,9 @@ metadata:
{{- include "iperf3-monitor.labels" . | nindent 4 }} {{- include "iperf3-monitor.labels" . | nindent 4 }}
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: ClusterRole
metadata: metadata:
name: {{ include "iperf3-monitor.fullname" . }}-role name: {{ include "iperf3-monitor.fullname" . }}-role
namespace: {{ .Release.Namespace }}
labels: labels:
{{- include "iperf3-monitor.labels" . | nindent 4 }} {{- include "iperf3-monitor.labels" . | nindent 4 }}
rules: rules:
@@ -19,10 +18,9 @@ rules:
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: {{ include "iperf3-monitor.fullname" . }}-rb name: {{ include "iperf3-monitor.fullname" . }}-rb
namespace: {{ .Release.Namespace }}
labels: labels:
{{- include "iperf3-monitor.labels" . | nindent 4 }} {{- include "iperf3-monitor.labels" . | nindent 4 }}
subjects: subjects:
@@ -30,7 +28,7 @@ subjects:
name: {{ include "iperf3-monitor.serviceAccountName" . }} name: {{ include "iperf3-monitor.serviceAccountName" . }}
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
roleRef: roleRef:
kind: Role # Changed from ClusterRole kind: ClusterRole
name: {{ include "iperf3-monitor.fullname" . }}-role name: {{ include "iperf3-monitor.fullname" . }}-role
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
{{- end -}} {{- end -}}

View File

@@ -11,7 +11,7 @@ spec:
{{- include "iperf3-monitor.selectorLabels" . | nindent 4 }} {{- include "iperf3-monitor.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: exporter app.kubernetes.io/component: exporter
ports: ports:
- name: metrics # Assuming 'metrics' is the intended name, aligns with values structure - name: metrics
port: {{ .Values.service.main.ports.metrics.port }} port: {{ .Values.service.port }}
targetPort: {{ .Values.service.main.ports.metrics.targetPort }} targetPort: {{ .Values.service.targetPort }}
protocol: {{ .Values.service.main.ports.metrics.protocol | default "TCP" }} protocol: TCP

View File

@@ -8,121 +8,39 @@ nameOverride: ""
# -- Override the fully qualified app name. # -- Override the fully qualified app name.
fullnameOverride: "" fullnameOverride: ""
controllers: exporter:
exporter: # -- Configuration for the exporter container image.
# -- Enable the exporter controller. image:
enabled: true # -- The container image repository for the exporter.
# -- Set the controller type for the exporter. repository: ghcr.io/malarinv/iperf3-monitor
# Valid options are "deployment" or "daemonset". # -- The container image tag for the exporter. If not set, the chart's appVersion is used.
# @default -- "deployment" tag: ""
type: deployment # -- The image pull policy for the exporter container.
# -- Number of desired exporter pods. Only used if type is "deployment". pullPolicy: IfNotPresent
# @default -- 1
replicas: 1
# -- Application-specific configuration for the iperf3 exporter. # -- Number of exporter pod replicas. Typically 1 is sufficient.
# These values are used to populate environment variables for the exporter container. replicaCount: 1
appConfig:
# -- Interval in seconds between complete test cycles (i.e., testing all server nodes).
testInterval: 300
# -- Log level for the iperf3 exporter (e.g., DEBUG, INFO, WARNING, ERROR, CRITICAL).
logLevel: INFO
# -- Timeout in seconds for a single iperf3 test run (kept for backward compatibility).
testTimeout: 5
# -- Duration in seconds for a single iperf3 test run.
testDuration: 5
# -- Connect timeout in milliseconds for control connection setup.
connectTimeoutMs: 3000
# -- Stall threshold in seconds (abort test if data transfer halts during test).
stallThresholdSeconds: 5
# -- Protocol to use for testing (tcp or udp).
testProtocol: tcp
# -- iperf3 server port to connect to. Should match the server's listening port.
# @default -- "5201" (hardcoded in the original chart for server daemonset)
serverPort: "5201"
# -- Label selector to find iperf3 server pods.
# This will be templated in the actual deployment.
# Example default (if not overridden by template logic): 'app.kubernetes.io/component=server'
serverLabelSelector: 'app.kubernetes.io/name={{ include "iperf3-monitor.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=server'
# -- Interval in seconds between complete test cycles (i.e., testing all server nodes).
testInterval: 300
# -- Pod-level configurations for the exporter, leveraging bjw-s common library structure. # -- Timeout in seconds for a single iperf3 test run.
pod: testTimeout: 10
# -- Annotations for the exporter pod.
annotations: {}
# -- Labels for the exporter pod.
labels:
app.kubernetes.io/component: exporter # Ensure pods get the component label for service selection
# -- Node selector for scheduling exporter pods.
nodeSelector: {}
# -- Tolerations for scheduling exporter pods.
tolerations: []
# -- Affinity rules for scheduling exporter pods.
affinity: {}
# -- Security context for the exporter pod.
securityContext: {}
# fsGroup: 65534
# runAsUser: 65534
# runAsGroup: 65534
# runAsNonRoot: true
# -- Automount service account token for the pod.
automountServiceAccountToken: true # Default from common lib
# -- Container-level configurations for the main exporter container. # -- Protocol to use for testing (tcp or udp).
containers: testProtocol: tcp
exporter: # This is the primary container, name it 'exporter'
image:
# -- The container image repository for the exporter.
repository: ghcr.io/malarinv/iperf3-monitor
# -- The container image tag for the exporter. If not set, the chart's appVersion is used.
tag: "" # Defaults to .Chart.AppVersion via common library
# -- The image pull policy for the exporter container.
pullPolicy: IfNotPresent
# -- Environment variables for the exporter container. # -- CPU and memory resource requests and limits for the exporter pod.
# The actual env map will be constructed in the main chart template # @default -- A small default is provided if commented out.
# and passed to the common library. This section is for user overrides resources:
# if they want to directly set other env vars using common lib's env schema. {}
env: {} # requests:
# Example: # cpu: "100m"
# MY_CUSTOM_VAR: "my_value" # memory: "128Mi"
# ANOTHER_VAR: # limits:
# valueFrom: # cpu: "500m"
# secretKeyRef: # memory: "256Mi"
# name: mysecret
# key: mykey
# -- Ports for the exporter container.
# Expected by Kubernetes and bjw-s common library as a list of objects.
ports:
- name: metrics # Name of the port, referenced by Service's targetPort
# -- Port number for the metrics endpoint on the container.
containerPort: 9876
# -- Protocol for the metrics port.
protocol: TCP
# -- Whether this port definition is enabled. Specific to bjw-s common library.
enabled: true
# -- CPU and memory resource requests and limits for the exporter container.
resources:
{}
# requests:
# cpu: "100m"
# memory: "128Mi"
# limits:
# cpu: "500m"
# memory: "256Mi"
# -- Probes configuration for the exporter container.
probes:
liveness:
enabled: false
readiness:
enabled: false
startup:
enabled: false
# Server configuration (iperf3 server daemonset)
server: server:
# -- Configuration for the iperf3 server container image (DaemonSet). # -- Configuration for the iperf3 server container image (DaemonSet).
image: image:
@@ -132,6 +50,8 @@ server:
tag: latest tag: latest
# -- CPU and memory resource requests and limits for the iperf3 server pods (DaemonSet). # -- CPU and memory resource requests and limits for the iperf3 server pods (DaemonSet).
# These should be very low as the server is mostly idle.
# @default -- A small default is provided if commented out.
resources: resources:
{} {}
# requests: # requests:
@@ -142,9 +62,13 @@ server:
# memory: "128Mi" # memory: "128Mi"
# -- Node selector for scheduling iperf3 server pods. # -- Node selector for scheduling iperf3 server pods.
# Use this to restrict the DaemonSet to a subset of nodes.
# @default -- {} (schedule on all nodes)
nodeSelector: {} nodeSelector: {}
# -- Tolerations for scheduling iperf3 server pods on tainted nodes. # -- Tolerations for scheduling iperf3 server pods on tainted nodes (e.g., control-plane nodes).
# This is often necessary to include master nodes in the test mesh.
# @default -- Tolerates control-plane and master taints.
tolerations: tolerations:
- key: "node-role.kubernetes.io/control-plane" - key: "node-role.kubernetes.io/control-plane"
operator: "Exists" operator: "Exists"
@@ -153,60 +77,44 @@ server:
operator: "Exists" operator: "Exists"
effect: "NoSchedule" effect: "NoSchedule"
# RBAC and ServiceAccount settings
# These are for the exporter. The exporter deployment (managed by common library)
# will need to use the ServiceAccount specified here or one created by the library.
rbac: rbac:
# -- If true, create ServiceAccount, ClusterRole, and ClusterRoleBinding for the exporter. # -- If true, create ServiceAccount, ClusterRole, and ClusterRoleBinding for the exporter.
# Set to false if you manage RBAC externally.
create: true create: true
serviceAccount: serviceAccount:
# -- The name of the ServiceAccount to use/create for the exporter pod. # -- The name of the ServiceAccount to use for the exporter pod.
# If rbac.create is true, this SA is created. The exporter pod must use this SA. # Only used if rbac.create is false. If not set, it defaults to the chart's fullname.
name: "iperf3-monitor" name: ""
# Service Monitor configuration for Prometheus
serviceMonitor: serviceMonitor:
# -- If true, create a ServiceMonitor resource. # -- If true, create a ServiceMonitor resource for integration with Prometheus Operator.
# Requires a running Prometheus Operator in the cluster.
enabled: true enabled: true
# -- Scrape interval for the ServiceMonitor.
# -- Scrape interval for the ServiceMonitor. How often Prometheus scrapes the exporter metrics.
interval: 60s interval: 60s
# -- Scrape timeout for the ServiceMonitor.
# -- Scrape timeout for the ServiceMonitor. How long Prometheus waits for metrics response.
scrapeTimeout: 30s scrapeTimeout: 30s
# Service configuration for the exporter # -- Configuration for the exporter Service.
# This defines how the exporter is exposed.
# The common library can also manage services, or we can use our own template.
# This structure is compatible with bjw-s common library's service management if we choose to use it.
service: service:
main: # A key for the service, 'main' is a common convention. # -- Service type. ClusterIP is typically sufficient.
# -- Enable the exporter service. type: ClusterIP
enabled: true # -- Port on which the exporter service is exposed.
# -- Service type. port: 9876
type: ClusterIP # ClusterIP is typical for internal services scraped by Prometheus. # -- Target port on the exporter pod.
# -- Ports configuration for the service. targetPort: 9876
ports:
metrics: # Name of the service port, should align with a container port name.
# -- Port number on which the service is exposed.
port: 9876
# -- Target port on the exporter pod. Can be a number or name.
# Refers to the 'metrics' port defined in controllers.exporter.containers.exporter.ports.
targetPort: metrics
protocol: TCP
# Network Policy (optional) # -- Optional configuration for a network policy to allow traffic to the iperf3 server DaemonSet.
# This is often necessary if you are using a network policy controller.
networkPolicy: networkPolicy:
# -- If true, create a NetworkPolicy resource. # -- If true, create a NetworkPolicy resource.
enabled: false enabled: false
# -- Source selectors for ingress rules. # -- Specify source selectors if needed (e.g., pods in a specific namespace).
from: [] from: []
# -- Namespace selectors for ingress rules. # -- Specify namespace selectors if needed.
namespaceSelector: {} namespaceSelector: {}
# -- Pod selectors for ingress rules. # -- Specify pod selectors if needed.
podSelector: {} podSelector: {}
# Dependency Configuration (for Prometheus Operator)
dependencies:
# -- Set to false by default. Set to true to install a Prometheus operator dependency (used if serviceMonitor.enabled=true).
# -- If false (default), and serviceMonitor.enabled is true, you must have a compatible Prometheus Operator already running in your cluster.
install: false

14
devbox.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.7/.schema/devbox.schema.json",
"packages": [],
"shell": {
"init_hook": [
"echo 'Welcome to devbox!' > /dev/null"
],
"scripts": {
"test": [
"echo \"Error: no test specified\" && exit 1"
]
}
}
}

4
devbox.lock Normal file
View File

@@ -0,0 +1,4 @@
{
"lockfile_version": "1",
"packages": {}
}

View File

@@ -1,24 +1,28 @@
# Stage 1: Build stage with dependencies # Stage 1: Build stage with dependencies
FROM python:3.12-slim AS builder FROM python:3.9-slim as builder
WORKDIR /app WORKDIR /app
# Install iperf3 and build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc iperf3 libiperf-dev && \
rm -rf /var/lib/apt/lists/*
# Install Python dependencies # Install Python dependencies
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
# Stage 2: Final runtime stage # Stage 2: Final runtime stage
FROM python:3.12-slim FROM python:3.9-slim
WORKDIR /app WORKDIR /app
# Install iperf3 # Copy iperf3 binary and library from the builder stage
RUN apt-get update && \ COPY --from=builder /usr/bin/iperf3 /usr/bin/iperf3
apt-get install -y --no-install-recommends iperf3 && \ COPY --from=builder /usr/lib/x86_64-linux-gnu/libiperf.so.0 /usr/lib/x86_64-linux-gnu/libiperf.so.0
rm -rf /var/lib/apt/lists/*
# Copy installed Python packages from the builder stage # Copy installed Python packages from the builder stage
COPY --from=builder /install /usr/local COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
# Copy the exporter application code # Copy the exporter application code
COPY exporter.py . COPY exporter.py .
@@ -26,9 +30,5 @@ COPY exporter.py .
# Expose the metrics port # Expose the metrics port
EXPOSE 9876 EXPOSE 9876
# Run as non-root user (nobody)
USER 65534:65534
# Set the entrypoint # Set the entrypoint
CMD ["python", "exporter.py"] CMD ["python", "exporter.py"]

View File

@@ -1,53 +1,28 @@
"""
Prometheus exporter for iperf3 network performance monitoring.
This script runs iperf3 tests between the node it's running on (source) and
other iperf3 server pods discovered in a Kubernetes cluster. It then exposes
these metrics for Prometheus consumption.
"""
import os import os
import sys
import time import time
import json
import signal
import logging import logging
import argparse
import subprocess
from kubernetes import client, config from kubernetes import client, config
from prometheus_client import start_http_server, Gauge from prometheus_client import start_http_server, Gauge
import iperf3
# --- Global Configuration & Setup --- # --- Configuration ---
# Configure logging
parser = argparse.ArgumentParser(description="iperf3 Prometheus exporter.") logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
parser.add_argument(
'--log-level',
default=os.environ.get('LOG_LEVEL', 'INFO').upper(),
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
help='Set the logging level. Overrides LOG_LEVEL environment variable. (Default: INFO)'
)
args, _ = parser.parse_known_args()
log_level_str = args.log_level
numeric_level = getattr(logging, log_level_str.upper(), None)
if not isinstance(numeric_level, int):
logging.error(f"Invalid log level: {log_level_str}. Defaulting to INFO.")
numeric_level = logging.INFO
logging.basicConfig(level=numeric_level, format='%(asctime)s - %(levelname)s - %(message)s')
# --- Prometheus Metrics Definition --- # --- Prometheus Metrics Definition ---
IPERF_BANDWIDTH_MBPS = Gauge( IPERF_BANDWIDTH_MBPS = Gauge(
'iperf_network_bandwidth_mbps', 'iperf_network_bandwidth_mbps',
'Network bandwidth measured by iperf3 in Megabits per second (Mbps)', 'Network bandwidth measured by iperf3 in Megabits per second',
['source_node', 'destination_node', 'protocol'] ['source_node', 'destination_node', 'protocol']
) )
IPERF_JITTER_MS = Gauge( IPERF_JITTER_MS = Gauge(
'iperf_network_jitter_ms', 'iperf_network_jitter_ms',
'Network jitter measured by iperf3 in milliseconds (ms) for UDP tests', 'Network jitter measured by iperf3 in milliseconds',
['source_node', 'destination_node', 'protocol'] ['source_node', 'destination_node', 'protocol']
) )
IPERF_PACKETS_TOTAL = Gauge( IPERF_PACKETS_TOTAL = Gauge(
'iperf_network_packets_total', 'iperf_network_packets_total',
'Total packets transmitted/received during the iperf3 UDP test', 'Total packets transmitted or received during the iperf3 test',
['source_node', 'destination_node', 'protocol'] ['source_node', 'destination_node', 'protocol']
) )
IPERF_LOST_PACKETS = Gauge( IPERF_LOST_PACKETS = Gauge(
@@ -60,60 +35,14 @@ IPERF_TEST_SUCCESS = Gauge(
'Indicates if the iperf3 test was successful (1) or failed (0)', 'Indicates if the iperf3 test was successful (1) or failed (0)',
['source_node', 'destination_node', 'protocol'] ['source_node', 'destination_node', 'protocol']
) )
IPERF_TCP_RETRANSMITS = Gauge(
'iperf_network_tcp_retransmits_total',
'Total TCP retransmits measured by iperf3 during the test',
['source_node', 'destination_node', 'protocol']
)
IPERF_TCP_RTT_MS = Gauge(
'iperf_network_tcp_rtt_ms',
'Mean TCP round-trip time in milliseconds (ms) measured by iperf3',
['source_node', 'destination_node', 'protocol']
)
IPERF_TEST_DURATION_SECONDS = Gauge(
'iperf_network_test_duration_seconds',
'Duration of the iperf3 test in seconds',
['source_node', 'destination_node', 'protocol']
)
IPERF_LAST_TEST_TIMESTAMP_SECONDS = Gauge(
'iperf_exporter_last_success_timestamp_seconds',
'Unix timestamp of the last completed test cycle',
['source_node']
)
IPERF_SERVERS_DISCOVERED = Gauge(
'iperf_exporter_servers_discovered',
'Number of target iperf3 servers discovered in the cluster',
['source_node']
)
# --- Graceful Shutdown & Exceptions --- def discover_iperf_servers():
shutdown_requested = False
active_subprocess = None
def signal_handler(signum, frame):
global shutdown_requested, active_subprocess
logging.info(f"Received signal {signum}. Initiating graceful shutdown...")
shutdown_requested = True
if active_subprocess and active_subprocess.poll() is None:
try:
active_subprocess.kill()
except Exception:
pass
class IperfStallException(Exception):
"""Raised when an iperf3 test experiences a silent or busy-wait stall."""
pass
class IperfTimeoutException(Exception):
"""Raised when an iperf3 test exceeds its maximum expected deadline."""
pass
# --- Kubernetes Discovery ---
def discover_iperf_servers(source_node_name):
""" """
Discovers iperf3 server pods within a Kubernetes cluster. Discover iperf3 server pods in the cluster using the Kubernetes API.
""" """
try: try:
# Load in-cluster configuration
# Assumes the exporter runs in a pod with a service account having permissions
config.load_incluster_config() config.load_incluster_config()
v1 = client.CoreV1Api() v1 = client.CoreV1Api()
@@ -122,317 +51,110 @@ def discover_iperf_servers(source_node_name):
logging.info(f"Discovering iperf3 servers with label '{label_selector}' in namespace '{namespace}'") logging.info(f"Discovering iperf3 servers with label '{label_selector}' in namespace '{namespace}'")
ret = v1.list_namespaced_pod(namespace=namespace, label_selector=label_selector, watch=False) # List pods across all namespaces with the specified label selector
# Note: list_pod_for_all_namespaces requires cluster-wide permissions
ret = v1.list_pod_for_all_namespaces(label_selector=label_selector, watch=False)
servers = [] servers = []
for item in ret.items: for i in ret.items:
if item.status.pod_ip and item.status.phase == 'Running': # Ensure pod has an IP and is running
if i.status.pod_ip and i.status.phase == 'Running':
servers.append({ servers.append({
'ip': item.status.pod_ip, 'ip': i.status.pod_ip,
'node_name': item.spec.node_name 'node_name': i.spec.node_name
}) })
logging.info(f"Discovered {len(servers)} iperf3 server pods in namespace '{namespace}'.") logging.info(f"Discovered {len(servers)} iperf3 server pods.")
IPERF_SERVERS_DISCOVERED.labels(source_node=source_node_name).set(len(servers))
return servers return servers
except config.ConfigException as e:
logging.error(f"Kubernetes config error: {e}. Is the exporter running in a cluster with RBAC permissions?")
return []
except Exception as e: except Exception as e:
logging.error(f"Error discovering iperf servers: {e}") logging.error(f"Error discovering iperf servers: {e}")
return [] return [] # Return empty list on error to avoid crashing the loop
# --- iperf3 Subprocess Execution with Stall Detection --- def run_iperf_test(server_ip, server_port, protocol, source_node, dest_node):
def execute_iperf_test(server_ip, server_port, protocol, duration, connect_timeout_ms, stall_threshold_sec):
""" """
Executes the iperf3 CLI in a subprocess with JSON output and active stall detection. Runs a single iperf3 test and updates Prometheus metrics.
""" """
global active_subprocess logging.info(f"Running iperf3 test from {source_node} to {dest_node} ({server_ip}:{server_port}) using {protocol.upper()}")
cmd = [ client = iperf3.Client()
"iperf3", client.server_hostname = server_ip
"-c", server_ip, client.port = server_port
"-p", str(server_port), client.protocol = protocol
"-t", str(duration), # Duration of the test (seconds)
"--connect-timeout", str(connect_timeout_ms), client.duration = int(os.getenv('IPERF_TEST_DURATION', 5))
"-J", # Output results as JSON for easy parsing
] client.json_output = True
if protocol == 'udp':
cmd.append("-u")
logging.debug(f"Executing: {' '.join(cmd)}") result = client.run()
proc = subprocess.Popen( # Parse results and update metrics
cmd, parse_and_publish_metrics(result, source_node, dest_node, protocol)
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
active_subprocess = proc
start_time = time.monotonic() def parse_and_publish_metrics(result, source_node, dest_node, protocol):
# Maximum deadline: connect timeout + duration + stall threshold buffer
connect_timeout_sec = connect_timeout_ms / 1000.0
max_deadline = start_time + connect_timeout_sec + duration + stall_threshold_sec
last_io_change = start_time
last_wchar = 0
test_transfer_detected = False
try:
while proc.poll() is None:
if shutdown_requested:
proc.kill()
proc.wait()
raise KeyboardInterrupt("Shutdown requested")
now = time.monotonic()
if now > max_deadline:
proc.kill()
proc.wait()
raise IperfTimeoutException(f"iperf3 test exceeded absolute deadline of {max_deadline - start_time:.1f}s")
time.sleep(0.5)
# Check I/O progress via /proc/<pid>/io to detect busy-spin without I/O
try:
with open(f"/proc/{proc.pid}/io", "r") as f:
for line in f:
if line.startswith("wchar:"):
wchar = int(line.split()[1])
if wchar > last_wchar:
last_wchar = wchar
last_io_change = now
test_transfer_detected = True
break
except (FileNotFoundError, ProcessLookupError, PermissionError):
pass
# If traffic was active and has now completely ceased progressing for >= stall_threshold_sec
if test_transfer_detected and (now - last_io_change) >= stall_threshold_sec:
proc.kill()
proc.wait()
raise IperfStallException(f"iperf3 stalled: data transfer ceased for {now - last_io_change:.1f}s")
stdout, stderr = proc.communicate()
return proc.returncode, stdout, stderr
finally:
active_subprocess = None
# --- Metric Reset Helper ---
def reset_failure_metrics(labels):
IPERF_TEST_SUCCESS.labels(**labels).set(0)
for g in (
IPERF_BANDWIDTH_MBPS,
IPERF_JITTER_MS,
IPERF_PACKETS_TOTAL,
IPERF_LOST_PACKETS,
IPERF_TCP_RETRANSMITS,
IPERF_TCP_RTT_MS,
IPERF_TEST_DURATION_SECONDS,
):
try:
g.labels(**labels).set(0)
except KeyError:
pass
# --- Metric Parsing ---
def parse_and_publish_metrics(data, source_node, dest_node, protocol):
""" """
Parses iperf3 JSON result and updates Prometheus gauges. Parses the iperf3 result and updates Prometheus gauges.
Handles both successful and failed tests.
""" """
labels = {'source_node': source_node, 'destination_node': dest_node, 'protocol': protocol} labels = {'source_node': source_node, 'destination_node': dest_node, 'protocol': protocol}
if not data or not isinstance(data, dict): if result and result.error:
logging.warning(f"Test from {source_node} to {dest_node} ({protocol.upper()}) failed: empty or invalid result") logging.error(f"Test from {source_node} to {dest_node} failed: {result.error}")
reset_failure_metrics(labels) IPERF_TEST_SUCCESS.labels(**labels).set(0)
# Set metrics to 0 on failure
try:
IPERF_BANDWIDTH_MBPS.labels(**labels).set(0)
IPERF_JITTER_MS.labels(**labels).set(0)
IPERF_PACKETS_TOTAL.labels(**labels).set(0)
IPERF_LOST_PACKETS.labels(**labels).set(0)
except KeyError:
# Labels might not be registered yet if this is the first failure
pass
return return
if data.get("error"): if not result:
error_msg = data.get("error") logging.error(f"Test from {source_node} to {dest_node} failed to return a result object.")
logging.warning(f"Test from {source_node} to {dest_node} ({protocol.upper()}) failed: {error_msg}") IPERF_TEST_SUCCESS.labels(**labels).set(0)
reset_failure_metrics(labels) try:
return IPERF_BANDWIDTH_MBPS.labels(**labels).set(0)
IPERF_JITTER_MS.labels(**labels).set(0)
IPERF_PACKETS_TOTAL.labels(**labels).set(0)
IPERF_LOST_PACKETS.labels(**labels).set(0)
except KeyError:
pass
return
end = data.get("end")
if not end or not isinstance(end, dict):
logging.warning(f"Test from {source_node} to {dest_node} ({protocol.upper()}) failed: missing 'end' summary in JSON")
reset_failure_metrics(labels)
return
# Successful test execution
IPERF_TEST_SUCCESS.labels(**labels).set(1) IPERF_TEST_SUCCESS.labels(**labels).set(1)
sum_received = end.get("sum_received") or {} # The summary data is typically in result.json['end']['sum_sent'] or result.json['end']['sum_received']
sum_sent = end.get("sum_sent") or {} # The iperf3-python client often exposes this directly as attributes like sent_Mbps or received_Mbps
sum_udp = end.get("sum") or {} # For TCP, we usually care about the received bandwidth on the client side (which is the exporter)
# For UDP, the client report contains jitter, lost packets, etc.
bandwidth_mbps = 0
if hasattr(result, 'received_Mbps') and result.received_Mbps is not None:
bandwidth_mbps = result.received_Mbps
elif hasattr(result, 'sent_Mbps') and result.sent_Mbps is not None:
# Fallback, though received_Mbps is usually more relevant for TCP client
bandwidth_mbps = result.sent_Mbps
# Add a check for the raw JSON output structure as a fallback
elif result.json and 'end' in result.json and 'sum_received' in result.json['end'] and result.json['end']['sum_received']['bits_per_second'] is not None:
bandwidth_mbps = result.json['end']['sum_received']['bits_per_second'] / 1000000
elif result.json and 'end' in result.json and 'sum_sent' in result.json['end'] and result.json['end']['sum_sent']['bits_per_second'] is not None:
bandwidth_mbps = result.json['end']['sum_sent']['bits_per_second'] / 1000000
bandwidth_mbps = 0.0
if protocol == 'tcp':
if "bits_per_second" in sum_received and sum_received["bits_per_second"] is not None:
bandwidth_mbps = sum_received["bits_per_second"] / 1_000_000.0
elif "bits_per_second" in sum_sent and sum_sent["bits_per_second"] is not None:
bandwidth_mbps = sum_sent["bits_per_second"] / 1_000_000.0
# TCP Retransmits
retransmits = sum_sent.get("retransmits", 0)
IPERF_TCP_RETRANSMITS.labels(**labels).set(retransmits)
# TCP RTT
mean_rtt_us = 0.0
streams = end.get("streams", [])
if streams and isinstance(streams, list):
sender_info = streams[0].get("sender", {})
mean_rtt_us = sender_info.get("mean_rtt", sender_info.get("rtt", 0.0))
IPERF_TCP_RTT_MS.labels(**labels).set(mean_rtt_us / 1000.0)
# Zero out UDP-specific gauges
IPERF_JITTER_MS.labels(**labels).set(0)
IPERF_PACKETS_TOTAL.labels(**labels).set(0)
IPERF_LOST_PACKETS.labels(**labels).set(0)
# Test duration
test_seconds = sum_received.get("seconds", sum_sent.get("seconds", 0.0))
IPERF_TEST_DURATION_SECONDS.labels(**labels).set(test_seconds)
else:
if "bits_per_second" in sum_udp and sum_udp["bits_per_second"] is not None:
bandwidth_mbps = sum_udp["bits_per_second"] / 1_000_000.0
elif "bits_per_second" in sum_sent and sum_sent["bits_per_second"] is not None:
bandwidth_mbps = sum_sent["bits_per_second"] / 1_000_000.0
IPERF_JITTER_MS.labels(**labels).set(sum_udp.get("jitter_ms", 0.0) or 0.0)
IPERF_PACKETS_TOTAL.labels(**labels).set(sum_udp.get("packets", 0) or 0)
IPERF_LOST_PACKETS.labels(**labels).set(sum_udp.get("lost_packets", 0) or 0)
IPERF_TCP_RETRANSMITS.labels(**labels).set(0)
IPERF_TCP_RTT_MS.labels(**labels).set(0)
test_seconds = sum_udp.get("seconds", 0.0)
IPERF_TEST_DURATION_SECONDS.labels(**labels).set(test_seconds)
IPERF_BANDWIDTH_MBPS.labels(**labels).set(bandwidth_mbps) IPERF_BANDWIDTH_MBPS.labels(**labels).set(bandwidth_mbps)
# --- Test Execution Wrapper --- # UDP specific metrics
def run_iperf_test(server_ip, server_port, protocol, source_node_name, dest_node_name, duration, connect_timeout_ms, stall_threshold_sec): if protocol == 'udp':
""" # iperf3-python exposes UDP results directly
Runs a single iperf3 test with stall monitoring and publishes metrics. IPERF_JITTER_MS.labels(**labels).set(result.jitter_ms if hasattr(result, 'jitter_ms') and result.jitter_ms is not None else 0)
""" IPERF_PACKETS_TOTAL.labels(**labels).set(result.packets if hasattr(result, 'packets') and result.packets is not None else 0)
logging.info(f"Running iperf3 {protocol.upper()} test from {source_node_name} to {dest_node_name} ({server_ip}:{server_port})") IPERF_LOST_PACKETS.labels(**labels).set(result.lost_packets if hasattr(result, 'lost_packets') and result.lost_packets is not None else 0)
labels = {'source_node': source_node_name, 'destination_node': dest_node_name, 'protocol': protocol} else:
# Ensure UDP metrics are zeroed or absent for TCP tests
try:
returncode, stdout, stderr = execute_iperf_test(
server_ip=server_ip,
server_port=server_port,
protocol=protocol,
duration=duration,
connect_timeout_ms=connect_timeout_ms,
stall_threshold_sec=stall_threshold_sec,
)
if not stdout.strip():
logging.warning(f"iperf3 produced no stdout for {dest_node_name}. Stderr: {stderr.strip()}")
reset_failure_metrics(labels)
return
try: try:
result_json = json.loads(stdout) IPERF_JITTER_MS.labels(**labels).set(0)
except json.JSONDecodeError as e: IPERF_PACKETS_TOTAL.labels(**labels).set(0)
logging.warning(f"Failed to parse iperf3 JSON output for {dest_node_name}: {e}. Output: {stdout[:200]}") IPERF_LOST_PACKETS.labels(**labels).set(0)
reset_failure_metrics(labels) except KeyError:
return pass
parse_and_publish_metrics(result_json, source_node_name, dest_node_name, protocol)
except IperfStallException as e:
logging.warning(f"Test from {source_node_name} to {dest_node_name} ({protocol.upper()}) aborted: {e}")
reset_failure_metrics(labels)
except IperfTimeoutException as e:
logging.warning(f"Test from {source_node_name} to {dest_node_name} ({protocol.upper()}) timed out: {e}")
reset_failure_metrics(labels)
except KeyboardInterrupt:
raise
except Exception as e:
logging.error(f"Unexpected exception during iperf3 test for {dest_node_name}: {e}")
reset_failure_metrics(labels)
# --- Main Exporter Loop ---
def main_loop():
"""
Main operational loop of the iperf3 exporter.
"""
test_interval = int(os.getenv('IPERF_TEST_INTERVAL', 300))
server_port = int(os.getenv('IPERF_SERVER_PORT', 5201))
protocol = os.getenv('IPERF_TEST_PROTOCOL', 'tcp').lower()
source_node_name = os.getenv('SOURCE_NODE_NAME')
# Support either IPERF_TEST_DURATION or IPERF_TEST_TIMEOUT from Helm values
duration = int(os.getenv('IPERF_TEST_DURATION', os.getenv('IPERF_TEST_TIMEOUT', 5)))
connect_timeout_ms = int(os.getenv('IPERF_CONNECT_TIMEOUT_MS', 3000))
stall_threshold_sec = int(os.getenv('IPERF_STALL_THRESHOLD_SECONDS', 5))
if not source_node_name:
logging.error("CRITICAL: SOURCE_NODE_NAME environment variable not set. This is required. Exiting.")
sys.exit(1)
logging.info(
f"Exporter configured. Source Node: {source_node_name}, "
f"Test Interval: {test_interval}s, Duration: {duration}s, Server Port: {server_port}, Protocol: {protocol.upper()}, "
f"Connect Timeout: {connect_timeout_ms}ms, Stall Threshold: {stall_threshold_sec}s"
)
while not shutdown_requested:
logging.info("Starting new iperf test cycle...")
servers = discover_iperf_servers(source_node_name)
if not servers:
logging.warning("No iperf servers discovered in this cycle. Check K8s setup and RBAC permissions.")
else:
for server in servers:
if shutdown_requested:
break
dest_node_name = server.get('node_name', 'unknown_destination_node')
server_ip = server.get('ip')
if not server_ip:
logging.warning(f"Discovered server entry missing an IP: {server}. Skipping.")
continue
if dest_node_name == source_node_name:
logging.info(f"Skipping test to self: {source_node_name} to {server_ip} (on same node: {dest_node_name}).")
continue
run_iperf_test(
server_ip=server_ip,
server_port=server_port,
protocol=protocol,
source_node_name=source_node_name,
dest_node_name=dest_node_name,
duration=duration,
connect_timeout_ms=connect_timeout_ms,
stall_threshold_sec=stall_threshold_sec,
)
if not shutdown_requested:
IPERF_LAST_TEST_TIMESTAMP_SECONDS.labels(source_node=source_node_name).set(time.time())
logging.info(f"Test cycle completed. Sleeping for {test_interval} seconds.")
slept = 0
while slept < test_interval and not shutdown_requested:
time.sleep(1)
slept += 1
logging.info("Exporter main loop stopped gracefully.")
if __name__ == '__main__':
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
listen_port = int(os.getenv('LISTEN_PORT', 9876))
try:
start_http_server(listen_port)
logging.info(f"Prometheus exporter listening on port {listen_port}")
except Exception as e:
logging.error(f"Failed to start Prometheus HTTP server on port {listen_port}: {e}")
sys.exit(1)
main_loop()

View File

@@ -1,2 +1,3 @@
prometheus-client>=0.20.0 prometheus-client
kubernetes>=29.0.0 iperf3
kubernetes

View File

@@ -1,111 +0,0 @@
import os
import sys
import unittest
import json
from unittest.mock import patch, MagicMock
import importlib.util
spec = importlib.util.spec_from_file_location("exporter_mod", os.path.join(os.path.dirname(__file__), "exporter.py"))
exporter = importlib.util.module_from_spec(spec)
spec.loader.exec_module(exporter)
class TestExporter(unittest.TestCase):
def setUp(self):
self.source = "node-a"
self.dest = "node-b"
self.labels = {'source_node': self.source, 'destination_node': self.dest, 'protocol': 'tcp'}
self.udp_labels = {'source_node': self.source, 'destination_node': self.dest, 'protocol': 'udp'}
def test_parse_tcp_success(self):
tcp_json = {
"end": {
"streams": [
{
"sender": {
"mean_rtt": 250, # 250 microseconds = 0.25 ms
"retransmits": 3
}
}
],
"sum_sent": {
"seconds": 5.0,
"bytes": 50000000,
"bits_per_second": 80000000.0,
"retransmits": 3
},
"sum_received": {
"seconds": 5.0,
"bytes": 49000000,
"bits_per_second": 78400000.0
}
}
}
exporter.parse_and_publish_metrics(tcp_json, self.source, self.dest, 'tcp')
self.assertEqual(exporter.IPERF_TEST_SUCCESS.labels(**self.labels)._value.get(), 1)
self.assertAlmostEqual(exporter.IPERF_BANDWIDTH_MBPS.labels(**self.labels)._value.get(), 78.4)
self.assertEqual(exporter.IPERF_TCP_RETRANSMITS.labels(**self.labels)._value.get(), 3)
self.assertAlmostEqual(exporter.IPERF_TCP_RTT_MS.labels(**self.labels)._value.get(), 0.25)
self.assertEqual(exporter.IPERF_TEST_DURATION_SECONDS.labels(**self.labels)._value.get(), 5.0)
self.assertEqual(exporter.IPERF_JITTER_MS.labels(**self.labels)._value.get(), 0)
def test_parse_udp_success(self):
udp_json = {
"end": {
"sum": {
"seconds": 5.0,
"bytes": 1000000,
"bits_per_second": 1600000.0,
"jitter_ms": 1.25,
"packets": 1000,
"lost_packets": 2
}
}
}
exporter.parse_and_publish_metrics(udp_json, self.source, self.dest, 'udp')
self.assertEqual(exporter.IPERF_TEST_SUCCESS.labels(**self.udp_labels)._value.get(), 1)
self.assertAlmostEqual(exporter.IPERF_BANDWIDTH_MBPS.labels(**self.udp_labels)._value.get(), 1.6)
self.assertEqual(exporter.IPERF_JITTER_MS.labels(**self.udp_labels)._value.get(), 1.25)
self.assertEqual(exporter.IPERF_PACKETS_TOTAL.labels(**self.udp_labels)._value.get(), 1000)
self.assertEqual(exporter.IPERF_LOST_PACKETS.labels(**self.udp_labels)._value.get(), 2)
self.assertEqual(exporter.IPERF_TCP_RETRANSMITS.labels(**self.udp_labels)._value.get(), 0)
def test_parse_error_result(self):
err_json = {
"error": "error - unable to connect to server: Connection timed out"
}
exporter.parse_and_publish_metrics(err_json, self.source, self.dest, 'tcp')
self.assertEqual(exporter.IPERF_TEST_SUCCESS.labels(**self.labels)._value.get(), 0)
self.assertEqual(exporter.IPERF_BANDWIDTH_MBPS.labels(**self.labels)._value.get(), 0)
self.assertEqual(exporter.IPERF_TCP_RETRANSMITS.labels(**self.labels)._value.get(), 0)
self.assertEqual(exporter.IPERF_TCP_RTT_MS.labels(**self.labels)._value.get(), 0)
@patch("subprocess.Popen")
def test_stall_exception_raised_on_stall(self, mock_popen):
# Simulate a process that never terminates and produces no I/O progress
fake_proc = MagicMock()
fake_proc.poll.return_value = None # Process is running
fake_proc.pid = 999999 # Non-existent PID to simulate no /proc IO advancement
mock_popen.return_value = fake_proc
with self.assertRaises(exporter.IperfTimeoutException):
exporter.execute_iperf_test(
server_ip="127.0.0.1",
server_port=5201,
protocol="tcp",
duration=1,
connect_timeout_ms=500,
stall_threshold_sec=1,
)
fake_proc.kill.assert_called()
if __name__ == '__main__':
unittest.main()

489
index.yaml Normal file
View File

@@ -0,0 +1,489 @@
apiVersion: v1
entries:
iperf3-monitor:
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- kind: changed
description: Refactor exporter to subprocess with active stall watchdog and connect-timeout
- kind: added
description: Expose TCP retransmits, mean RTT (ms), and cycle timestamp metrics
- kind: changed
description: Run exporter container as non-root on Python 3.12
- kind: fixed
description: Support testDuration, connectTimeoutMs, and stallThresholdSeconds in Helm values
apiVersion: v2
appVersion: 0.2.6
created: "2026-09-26T05:03:21.169108238Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 9401f12a41a6cb27f0ce136a1c694582275f9ee9673710e6d0e83da901e9f61e
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.6.tgz
version: 0.2.6
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.2.5
created: "2025-07-02T11:03:53.021696134Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: da889e8441c7aa057db40f1b33cfa73ce4102b0a6a8c5f502680b4cada101a76
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.5.tgz
version: 0.2.5
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.2.4
created: "2025-07-02T08:39:13.621609745Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 3929933b0e4b46d4a3e4274e495b27a467baa1cc59e0457f488b1fef834e815d
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.4.tgz
version: 0.2.4
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.2.3
created: "2025-07-02T08:02:44.490472865Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: dbd898b7c57c41e19e1abe46285ed0f8353b11bf7ab3d618c09101e5f50624a2
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.3.tgz
version: 0.2.3
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.2.2
created: "2025-07-02T07:30:10.740811061Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 9404cc9b70c01370727cd6aabbe634698d6c2b86188853c5e7ea020547e839c3
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.2.tgz
version: 0.2.2
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.2.1
created: "2025-07-02T06:29:50.048523169Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
- name: common
repository: https://bjw-s-labs.github.io/helm-charts/
version: 4.1.2
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 78018c1dcdafe6768f874a524005e535e1744a9a76f026d8027f2ac69bf49c4c
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.2.1.tgz
version: 0.2.1
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.9
created: "2025-07-01T20:10:45.681665311Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: ba71668f2e79f143971758eddf2c1ccb81e8fa8dd681748f4a34718af83c523c
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.9.tgz
version: 0.1.9
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.8
created: "2025-06-20T20:43:41.906216787Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: dd1707ef16c06a1a3c4979983a0141c0f19bbd51b7b95a3bda22033780a517ba
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.8.tgz
version: 0.1.8
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.6
created: "2025-06-20T19:36:24.8256252Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: e23e2801a298edf8c345fcce210f7186a2304cf8321f315a244e44f0ef83e4bd
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.6.tgz
version: 0.1.6
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.5
created: "2025-06-20T13:41:15.223267266Z"
dependencies:
- condition: dependencies.install, serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: dependencies.install, serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 565b4615cbfa166cb5b158d43bc0f79fb2482c6213e8d9c67b32cd2e2dac6103
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.5.tgz
version: 0.1.5
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.4
created: "2025-06-20T12:13:54.388432454Z"
dependencies:
- condition: serviceMonitor.enabled, !dependencies.useTrueChartsPrometheusOperator
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
- condition: serviceMonitor.enabled, dependencies.useTrueChartsPrometheusOperator
name: prometheus-operator
repository: oci://tccr.io/truecharts
version: '>=8.11.1'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: 6c0dbe54f696cdc2d98ecb54cff5c5b3c9eaea913e3caa798ad81c587ef5d199
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.4.tgz
version: 0.1.4
- annotations:
artifacthub.io/category: networking
artifacthub.io/changes: |
- Add initial Helm chart structure.
apiVersion: v2
appVersion: 0.1.1
created: "2025-06-19T21:43:47.560731092Z"
dependencies:
- condition: serviceMonitor.enabled
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: '>=30.0.0'
description: A Helm chart for deploying a Kubernetes-native iperf3 network performance
monitoring service with Prometheus and Grafana.
digest: e334d11e50f6303486830cc1384bfe21eecfe803f665868eaeb294d5ef168559
home: https://github.com/malarinv/iperf3-monitor
icon: https://raw.githubusercontent.com/malarinv/iperf3-monitor/main/icon.png
keywords:
- iperf3
- network
- performance
- monitoring
- kubernetes
- prometheus
- grafana
maintainers:
- email: malarkannan.invention@gmail.com
name: Malar Invention
name: iperf3-monitor
sources:
- https://github.com/malarinv/iperf3-monitor
type: application
urls:
- https://malarinv.github.io/iperf3-monitor/iperf3-monitor-0.1.1.tgz
version: 0.1.1
generated: "2026-09-26T05:03:21.131001085Z"

BIN
iperf3-monitor-0.1.1.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.1.4.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.1.5.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.1.6.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.1.8.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.1.9.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.1.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.2.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.3.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.4.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.5.tgz Normal file

Binary file not shown.

BIN
iperf3-monitor-0.2.6.tgz Normal file

Binary file not shown.

View File

@@ -1,25 +0,0 @@
# Backends named and versions exact: `uv` and `yq` were `latest`, and `helm`
# was a floating major-less spec that CI did not share — ci.yaml installs
# v3.10.0 while this file declared 3.17.3, so local `mise run chart:lint` and
# the pipeline that runs it were validating two different helm versions.
#
# `aqua:` for helm and yq, matching the other repos in this workspace. The
# `github:` backend cannot name the yq binary `yq` at all on the mise version
# this repo pins, and it mis-extracts helm 3.17.x.
[tools]
"core:python" = "3.12"
"aqua:astral-sh/uv" = "0.13.1"
"aqua:helm/helm" = "3.17.3"
"aqua:mikefarah/yq" = "4.54.1"
[tasks.test]
description = "Run exporter unit tests"
run = "uv run --with-requirements exporter/requirements.txt python -m unittest discover -s exporter -p 'test_*.py'"
[tasks."chart:build"]
description = "Build Helm chart dependencies"
run = "helm dependency build ./charts/iperf3-monitor"
[tasks."chart:lint"]
description = "Lint Helm chart"
run = "helm lint ./charts/iperf3-monitor"