commit b406214901287246e049aa6b86a2b08b5eef9344
Author: Odd Stråbø <oddstr13@openshell.no>
Date:   Thu Jul 27 17:00:33 2023 +0000

    Initial commit

diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml
new file mode 100644
index 0000000..d8f3aa2
--- /dev/null
+++ b/.devcontainer/compose.yaml
@@ -0,0 +1,11 @@
+version: '3.8'
+services:
+  devcontainer:
+    image: mcr.microsoft.com/devcontainers/python:1-3.11-bookworm
+    volumes:
+      - ../..:/workspaces:cached
+      - /var/run/docker.sock:/var/run/docker.sock:ro
+    #network_mode: service:db
+    command: sleep infinity
+    ports:
+      - 9101:9101
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..759b2ca
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,35 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/python
+{
+	"name": "Python 3",
+	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+	"dockerComposeFile":"compose.yaml",
+	"service": "devcontainer",
+	"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
+	//"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm",
+	"features": {
+		"ghcr.io/devcontainers-contrib/features/act:1": {}
+	},
+
+	// Features to add to the dev container. More info: https://containers.dev/features.
+	// "features": {},
+
+	// Use 'forwardPorts' to make a list of ports inside the container available locally.
+	// "forwardPorts": [],
+
+	// Use 'postCreateCommand' to run commands after the container is created.
+	"postCreateCommand": "pip install --user -r requirements.txt",
+	"customizations": {
+		"vscode": {
+			"extensions": [
+				"ninoseki.vscode-pylens"
+			]
+		}
+	}
+
+	// Configure tool-specific properties.
+	// "customizations": {},
+
+	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+	// "remoteUser": "root"
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..3f8d13e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM python:3.11-bookworm
+RUN pip install -r requirements.txt
+COPY app.py /app.py
+ENTRYPOINT python /app.py
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..9300573
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,4 @@
+openshell-export-docker-status
+==============================
+
+A Prometheus exporter for docker network interface stats
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..56d6b1d
--- /dev/null
+++ b/app.py
@@ -0,0 +1,65 @@
+import time
+from typing import Dict
+import docker
+import prometheus_client
+from prometheus_client.core import REGISTRY, CounterMetricFamily, InfoMetricFamily, StateSetMetricFamily
+
+class ContainerCollector(object):
+    docker_client: docker.Client
+
+    def __init__(self, docker_client: docker.Client):
+        self.docker_client = docker_client
+
+    def collect(self):
+        nw_counters: Dict[str, CounterMetricFamily] = {}
+        for stat in ["bytes", "packets", "errors", "dropped"]:
+            for rt_short in ("rx", "tx"):
+                rt_long = "receive" if rt_short == "rx" else "transmit"
+                key = f"{rt_short}_{stat}"
+                nw_counters[key] = CounterMetricFamily(
+                    f"node_network_{rt_long}_{stat}_total",
+                    f"Docker container stats network.{key}",
+                    labels=["container", "device"],
+                )
+        # "created", "running", "paused", "restarting", "removing", "exited", "dead"
+        # node_network_receive_packets_total
+        # node_network_speed_bytes
+        # node_network_transmit_bytes_total
+        # node_network_transmit_dropped_total
+        # node_network_transmit_errors_total
+        m_container_status = InfoMetricFamily("docker_container_info", "Container info.", labels=["container"])
+        
+        for container in self.docker_client.containers(all=True):
+            container_name = container.get("Names", [""])[0].strip("/")
+            container_id = container.get("Id")
+            container_status = container.get('State','').lower()
+            
+            m_container_status.add_metric([container_name], dict(status=container_status))
+            
+            #print(container_name, container_id)
+            stats = self.docker_client.stats(container_id, stream=False)
+            # print(stats)
+            for interface, ifstats in stats.get("networks", {}).items():
+                #print(interface, ifstats)
+
+                for stat, value in ifstats.items():
+                    if stat in nw_counters:
+                        nw_counters[stat].add_metric(
+                            [container_name, interface],
+                            value,
+                        )
+        yield m_container_status
+        for metric in nw_counters.values():
+            yield metric
+
+
+REGISTRY.register(ContainerCollector(docker.Client()))
+prometheus_client.start_http_server(9101)
+
+
+print("Started")
+try:
+    while True:
+        time.sleep(10)
+except KeyboardInterrupt:
+    pass
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..75af0da
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,12 @@
+version: '2.1'
+services:
+  exporter-docker-stats:
+    image: local/openshell-export-docker-status
+    build: .
+    pull_policy: never
+    restart: always
+    container_name: exporter-docker-stats
+    ports:
+      - 9101:9101
+    volumes:
+       - /var/run/docker.sock:/var/run/docker.sock:ro
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4ad64d3
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+docker-py == 1.10.6
+prometheus-client == 0.17.1
+urllib3 >= 1.26.0, < 2.0.0
\ No newline at end of file