diff --git a/Dockerfile b/Dockerfile index 9f26d82..1e4c5d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.18.4 # Installing required packages FROM alpine/doctl ENV PYTHONUNBUFFERED=1 -RUN apk add --update --no-cache python3 py-pip && ln -sf python3 /usr/bin/python +RUN apk add --update --no-cache python3 py-pip gcc python3-dev musl-dev linux-headers && ln -sf python3 /usr/bin/python #RUN python3 -m ensurepip RUN pip3 install --break-system-packages --no-cache --upgrade pip setuptools diff --git a/README.md b/README.md index 1639c24..cafa20d 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,37 @@ A prometheus exporter for Immich. Get metrics from a server and offers them in a ## How to use it -Here is an example docker run command +Here is an example docker run command ``` -docker run -e IMMICH_PORT=8080 -e IMMICH_HOST=192.168.178.1 -e IMMICH_API_TOKEN=TOKEN -p 8000:8000 friendlyfriend/prometheus-immich-exporter +docker run -e IMMICH_PORT=8080 -e IMMICH_HOST=192.168.178.1 -e IMMICH_API_TOKEN= -p 8000:8000 alexf007/prometheus-immich-exporter ``` +Or you can add the following to your immich docker-compose.yaml +``` + immich-exporter: + image: alexf007/prometheus-immich-exporter + container_name: immich_exporter + environment: + - IMMICH_PORT=3001 + - IMMICH_HOST=immich-server + - IMMICH_API_TOKEN= + ports: + - 8000:8000 + restart: unless-stopped +``` + Add this to your prometheus.yml ``` - - job_name: "Immich_exporter" + - job_name: "immich_exporter" static_configs: - targets: ['yourimmichexporter:port'] ``` +In case if you run prometheus from the same docker-compose you can use this job +``` + - job_name: immich_exporter + static_configs: + - targets: ['immich-exporter:8000'] +``` The application reads configuration using environment variables: | Environment variable | Default | Description | @@ -35,12 +55,12 @@ These are the metrics this program exports, assuming the `METRICS_PREFIX` is `im | `metric name` | `description` | |------------------------------------------|---------------------------------------------------------------------------| -| `immich_server_info_version_number` | `pings server and passes version number with the use of labels={version}` | -| `immich_server_info_diskAvailable` | `available space on disk` | -| `immich_server_info_totalDiskSize` | `total disk size` | -| `immich_server_info_diskUse` | `disk space used by your system` | -| `immich_server_info_diskUsagePercentage` | `same as above but in percentage` | - +| `immich_server_info_version_number` | `pings server and passes version number with the use of labels={version}` | +| `immich_server_info_diskAvailable` | `available space on disk` | +| `immich_server_info_totalDiskSize` | `total disk size` | +| `immich_server_info_diskUse` | `disk space used by your system` | +| `immich_server_info_diskUsagePercentage` | `same as above but in percentage` | + | `metric name` | `description` | |---------------------------------------|---------------------------------------------| | `immich_server_stats_user_count` | `number of users signed up ` | @@ -50,7 +70,13 @@ These are the metrics this program exports, assuming the `METRICS_PREFIX` is `im | `immich_server_stats_videos_growth` | `sum of all videos of all users` | | `immich_server_stats_usage_by_users` | `the disk space each user uses` | | `immich_server_stats_usage_growth` | `sum of disk space taken up by all users` | - + +| `metric name` | `description` | +|---------------------------------------|------------------------------------------------------------------------| +| `immich_system_info_loadAverage` | `array of load average (1m, 5m 15m)` | +| `immich_system_info_memory` | `array of memory states (Total, Available, Percent, Used, Free)` | +| `immich_system_cpu_usage` | `Representing the current system-wide CPU utilization as a percentage` | + ## Screenshot diff --git a/grafana/README.md b/grafana/README.md index c31b4c1..d4d1e4d 100644 --- a/grafana/README.md +++ b/grafana/README.md @@ -2,7 +2,7 @@ ## Import -To import the dashboard into your grafana, download the [dashboard.json](https://github.com/friendlyFriend4000/prometheus-immich-exporter/raw/master/grafana/dashboard-immich.json) file and import it into your server. Select your prometheus instance and that should be all. +To import the dashboard into your grafana, download the [dashboard.json](./dashboard-immich.json) file and import it into your server. Select your prometheus instance and that should be all. The graphs can be customized in their relative time. Mind that it takes time to populate them if you set relative time to monthly or yearly diff --git a/grafana/dashboard-immich.json b/grafana/dashboard-immich.json index f09639c..1a67736 100644 --- a/grafana/dashboard-immich.json +++ b/grafana/dashboard-immich.json @@ -113,12 +113,14 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": { "titleSize": 6 }, - "textMode": "name" + "textMode": "name", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -183,9 +185,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -245,9 +249,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -307,9 +313,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -369,9 +377,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -431,9 +441,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -454,6 +466,309 @@ "title": "Remaining disk size", "type": "stat" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "amount of CPU usage over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 3 + }, + "id": 51, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "immich_system_info_loadAverage", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{period}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Load Average", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 3 + }, + "id": 53, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "immich_system_info_cpu_usage", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "CPU usage", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Free memory over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 3 + }, + "id": 52, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "immich_system_info_memory{type=\"Free\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{type}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Free memory", + "type": "timeseries" + }, { "datasource": { "type": "prometheus", @@ -482,7 +797,7 @@ "h": 4, "w": 6, "x": 0, - "y": 3 + "y": 11 }, "hideTimeOverride": false, "id": 24, @@ -498,9 +813,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -549,7 +866,7 @@ "h": 4, "w": 6, "x": 6, - "y": 3 + "y": 11 }, "hideTimeOverride": false, "id": 26, @@ -565,9 +882,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -611,7 +930,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3 + "y": 11 }, "id": 8, "options": { @@ -619,7 +938,7 @@ "value" ], "legend": { - "displayMode": "list", + "displayMode": "table", "placement": "right", "showLegend": true, "values": [ @@ -636,6 +955,7 @@ "values": false }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -684,7 +1004,7 @@ "h": 4, "w": 6, "x": 0, - "y": 7 + "y": 15 }, "hideTimeOverride": false, "id": 28, @@ -700,9 +1020,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -748,7 +1070,7 @@ "h": 4, "w": 6, "x": 6, - "y": 7 + "y": 15 }, "hideTimeOverride": false, "id": 30, @@ -764,9 +1086,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -810,7 +1134,7 @@ "h": 4, "w": 6, "x": 0, - "y": 11 + "y": 19 }, "hideTimeOverride": false, "id": 32, @@ -826,9 +1150,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -873,7 +1199,7 @@ "h": 4, "w": 6, "x": 6, - "y": 11 + "y": 19 }, "hideTimeOverride": false, "id": 34, @@ -889,9 +1215,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -936,7 +1264,7 @@ "h": 8, "w": 12, "x": 12, - "y": 11 + "y": 19 }, "id": 4, "options": { @@ -961,6 +1289,7 @@ "values": false }, "tooltip": { + "maxHeight": 600, "mode": "multi", "sort": "none" } @@ -1012,7 +1341,7 @@ "h": 4, "w": 6, "x": 0, - "y": 15 + "y": 23 }, "hideTimeOverride": false, "id": 36, @@ -1028,9 +1357,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1076,7 +1407,7 @@ "h": 4, "w": 6, "x": 6, - "y": 15 + "y": 23 }, "hideTimeOverride": false, "id": 38, @@ -1092,9 +1423,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1124,6 +1457,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1175,7 +1509,7 @@ "h": 7, "w": 24, "x": 0, - "y": 19 + "y": 27 }, "hideTimeOverride": false, "id": 22, @@ -1187,6 +1521,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1220,6 +1555,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1271,7 +1607,7 @@ "h": 7, "w": 24, "x": 0, - "y": 26 + "y": 34 }, "hideTimeOverride": false, "id": 47, @@ -1283,6 +1619,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1332,7 +1669,7 @@ "h": 4, "w": 6, "x": 0, - "y": 33 + "y": 41 }, "hideTimeOverride": false, "id": 42, @@ -1348,9 +1685,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1396,7 +1735,7 @@ "h": 4, "w": 6, "x": 6, - "y": 33 + "y": 41 }, "hideTimeOverride": false, "id": 40, @@ -1412,9 +1751,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1457,7 +1798,7 @@ "h": 8, "w": 12, "x": 12, - "y": 33 + "y": 41 }, "id": 6, "options": { @@ -1465,7 +1806,7 @@ "percent" ], "legend": { - "displayMode": "list", + "displayMode": "table", "placement": "right", "showLegend": true, "values": [ @@ -1482,6 +1823,7 @@ "values": false }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1530,7 +1872,7 @@ "h": 4, "w": 6, "x": 0, - "y": 37 + "y": 45 }, "hideTimeOverride": false, "id": 44, @@ -1546,9 +1888,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1594,7 +1938,7 @@ "h": 4, "w": 6, "x": 6, - "y": 37 + "y": 45 }, "hideTimeOverride": false, "id": 46, @@ -1610,9 +1954,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.5", + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -1642,6 +1988,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1693,7 +2040,7 @@ "h": 7, "w": 24, "x": 0, - "y": 41 + "y": 49 }, "hideTimeOverride": false, "id": 49, @@ -1705,6 +2052,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1738,6 +2086,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1789,7 +2138,7 @@ "h": 7, "w": 24, "x": 0, - "y": 48 + "y": 56 }, "hideTimeOverride": false, "id": 50, @@ -1801,6 +2150,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -1825,20 +2175,20 @@ ], "refresh": "30s", "revision": 1, - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [], "templating": { "list": [] }, "time": { - "from": "now-5m", + "from": "now-15m", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "", "title": "immich", "uid": "ZWWp3aa4k", - "version": 31, + "version": 2, "weekStart": "" -} \ No newline at end of file +} diff --git a/grafana/screenshot.png b/grafana/screenshot.png index 18c5bfd..b9743ac 100644 Binary files a/grafana/screenshot.png and b/grafana/screenshot.png differ diff --git a/immich_exporter/exporter.py b/immich_exporter/exporter.py index 5bf44d2..5dcf2ce 100644 --- a/immich_exporter/exporter.py +++ b/immich_exporter/exporter.py @@ -5,6 +5,8 @@ import signal import faulthandler import requests +import psutil + from prometheus_client import start_http_server from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY import logging @@ -16,12 +18,22 @@ logger = logging.getLogger() class ImmichMetricsCollector: - def __init__(self, config): self.config = config - def collect(self): + def request(self, endpoint): + response = requests.request( + "GET", + self.combine_url(endpoint), + headers={ + "Accept": "application/json", + "x-api-key": self.config["token"] + } + ) + return response + + def collect(self): metrics = self.get_immich_metrics() for metric in metrics: @@ -40,43 +52,61 @@ class ImmichMetricsCollector: logger.info(prom_metric) def get_immich_metrics(self): - metrics = [] metrics.extend(self.get_immich_server_version_number()) metrics.extend(self.get_immich_storage()) - metrics.extend(self.get_immich_users_stat) - metrics.extend(self.get_immich_users_stat_growth()) + metrics.extend(self.get_immich_users_stat()) + metrics.extend(self.get_system_stats()) return metrics - def get_immich_users_stat_growth(self): - + def get_immich_users_stat(self): try: endpoint_user_stats = "/api/server-info/statistics" - response_user_stats = requests.request( - "GET", - self.combine_url(endpoint_user_stats), - headers={'Accept': 'application/json', - "x-api-key": self.config["token"]} - ) + response_user_stats = self.request(endpoint_user_stats).json() except requests.exceptions.RequestException as e: - logger.error(f"Couldn't get server version: {e}") + logger.error(f"API ERROR: can't get server statistic: {e}") - user_data = response_user_stats.json()["usageByUser"] - # photos growth gauge - user_count = len(response_user_stats.json()["usageByUser"]) + user_data = response_user_stats["usageByUser"] + user_count = len(response_user_stats["usageByUser"]) photos_growth_total = 0 videos_growth_total = 0 usage_growth_total = 0 + metrics = [] + for x in range(0, user_count): photos_growth_total += user_data[x]["photos"] - # total video growth videos_growth_total += user_data[x]["videos"] - # total disk growth usage_growth_total += user_data[x]["usage"] + metrics.append( + { + "name": f"{self.config['metrics_prefix']}_server_stats_photos_by_users", + "value": user_data[x]['photos'], + "labels": {"firstName": user_data[x]["userName"].split()[0]}, + "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " + } + ) + metrics.append( + { + "name": f"{self.config['metrics_prefix']}_server_stats_videos_by_users", + "value": user_data[x]['videos'], + "labels": {"firstName": user_data[x]["userName"].split()[0]}, + "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " + } + ) + metrics.append( + { + "name": f"{self.config['metrics_prefix']}_server_stats_usage_by_users", + "value": (user_data[x]['usage']), + "labels": { + "firstName": user_data[x]["userName"].split()[0], + }, + "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " + } + ) - return [ + metrics += [ { "name": f"{self.config['metrics_prefix']}_server_stats_user_count", "value": user_count, @@ -96,113 +126,39 @@ class ImmichMetricsCollector: "name": f"{self.config['metrics_prefix']}_server_stats_usage_growth", "value": usage_growth_total, "help": "videos counter that is added or removed" - } - + }, ] - @property - def get_immich_users_stat(self): - - global response_user_stats - try: - endpoint_user_stats = "/api/server-info/statistics" - response_user_stats = requests.request( - "GET", - self.combine_url(endpoint_user_stats), - headers={'Accept': 'application/json', - "x-api-key": self.config["token"]} - ) - except requests.exceptions.RequestException as e: - logger.error(f"API ERROR: can't get server statistic: {e}") - logger.info(f"API TOKEN CORRECT?") - logger.info(f"API ENDPOINT CHANGED?") - - metrics = [] - # To get the user count an api-endpoint exists but this works too. As a result one less api call is being made - - try: - user_count = len(response_user_stats.json()["usageByUser"]) - except Exception: - logger.error("Is the Immich api token valid? Traceback:KeyError: 'usageByUser': ") - # json array of all users with stats - # this line throws an error if api token is wrong. if the token is wrong - # or invalid this will return a KeyError : 'usage by user' - user_data = response_user_stats.json()["usageByUser"] - - for x in range(0, user_count): - metrics.append( - { - "name": f"{self.config['metrics_prefix']}_server_stats_photos_by_users", - "value": user_data[x]['photos'], - "labels": { - "firstName": user_data[x]["userName"].split()[0], - }, - "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " - } - ) - - # videos - for x in range(0, user_count): - metrics.append( - { - "name": f"{self.config['metrics_prefix']}_server_stats_videos_by_users", - "value": user_data[x]['videos'], - "labels": { - "firstName": user_data[x]["userName"].split()[0], - }, - "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " - } - ) - # usage - for x in range(0, user_count): - metrics.append( - { - "name": f"{self.config['metrics_prefix']}_server_stats_usage_by_users", - "value": (user_data[x]['usage']), - "labels": { - "firstName": user_data[x]["userName"].split()[0], - }, - "help": f"Number of photos by user {user_data[x]['userName'].split()[0]} " - } - ) - return metrics def get_immich_storage(self): try: endpoint_storage = "/api/server-info/storage" - response_storage = requests.request( - "GET", - self.combine_url(endpoint_storage), - headers={'Accept': 'application/json', - "x-api-key": self.config["token"]} - ) + response_storage = self.request(endpoint_storage).json() except requests.exceptions.RequestException as e: logger.error(f"Couldn't get storage info: {e}") - - response_json = response_storage.json() return [ { "name": f"{self.config['metrics_prefix']}_server_info_diskAvailable", - "value": (response_json["diskAvailableRaw"]), + "value": (response_storage["diskAvailableRaw"]), "help": "Available space on disk", }, { "name": f"{self.config['metrics_prefix']}_server_info_totalDiskSize", - "value": (response_json["diskSizeRaw"]), + "value": (response_storage["diskSizeRaw"]), "help": "total disk size", # "type": "counter" }, { "name": f"{self.config['metrics_prefix']}_server_info_diskUse", - "value": (response_json["diskUseRaw"]), + "value": (response_storage["diskUseRaw"]), "help": "disk space in use", # "type": "counter" }, { "name": f"{self.config['metrics_prefix']}_server_info_diskUsagePercentage", - "value": (response_json["diskUsagePercentage"]), + "value": (response_storage["diskUsagePercentage"]), "help": "disk usage in percent", # "type": "counter" } @@ -219,22 +175,15 @@ class ImmichMetricsCollector: while True: try: - - response_server_version = requests.request( - "GET", - self.combine_url(server_version_endpoint), - headers={'Accept': 'application/json', - "x-api-key": self.config["token"]} - ) + response = self.request(server_version_endpoint).json() except requests.exceptions.RequestException as e: logger.error(f"Couldn't get server version") continue break - server_version_number = (str(response_server_version.json()["major"]) + "." + - str(response_server_version.json()["minor"]) + "." + - str(response_server_version.json()["patch"]) - ) + server_version_number = ( + str(response["major"]) + "." + str(response["minor"]) + "." + str(response["patch"]) + ) return [ { @@ -246,11 +195,71 @@ class ImmichMetricsCollector: } ] + def get_system_stats(self): + loadAvg = os.getloadavg() + virtualMem = psutil.virtual_memory() + cpu = psutil.cpu_percent(interval=1, percpu=False) + return [ + { + "name": f"{self.config['metrics_prefix']}_system_info_loadAverage", + "value": loadAvg[0], + "help": "CPU Load average 1m", + "labels": {"period": "1m"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_loadAverage", + "value": loadAvg[1], + "help": "CPU Load average 5m", + "labels": {"period": "5m"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_loadAverage", + "value": loadAvg[2], + "help": "CPU Load average 15m", + "labels": {"period": "15m"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_memory", + "value": virtualMem[0], + "help": "Virtual Memory - Total", + "labels": {"type": "Total"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_memory", + "value": virtualMem[1], + "help": "Virtual Memory - Available", + "labels": {"type": "Available"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_memory", + "value": virtualMem[2], + "help": "Virtual Memory - Percent", + "labels": {"type": "Percent"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_memory", + "value": virtualMem[3], + "help": "Virtual Memory - Used", + "labels": {"type": "Used"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_memory", + "value": virtualMem[4], + "help": "Virtual Memory - Free", + "labels": {"type": "Free"}, + }, + { + "name": f"{self.config['metrics_prefix']}_system_info_cpu_usage", + "value": cpu, + "help": "Representing the current system-wide CPU utilization as a percentage", + }, + ] + def combine_url(self, api_endpoint): prefix_url = "http://" base_url = self.config["immich_host"] base_url_port = self.config["immich_port"] - combined_url = prefix_url + base_url + ":" + base_url_port + api_endpoint + combined_url = f"{prefix_url}{base_url}:{base_url_port}{api_endpoint}" return combined_url @@ -288,21 +297,19 @@ def get_config_value(key, default=""): def check_server_up(immichHost, immichPort): - # counter = 0 while True: counter = counter + 1 try: - requests.request( "GET", - "http://" + immichHost + ":" + immichPort + "/api/server-info/ping", + f"http://{immichHost}:{immichPort}/api/server-info/ping", headers={'Accept': 'application/json'} ) except requests.exceptions.RequestException as e: - logger.error(f"CONNECTION ERROR. Cannot reach immich at " + immichHost + ":" + immichPort + "." - f"Is immich up and running?") + logger.error( + f"CONNECTION ERROR. Cannot reach immich at {immichHost}:{immichPort}. Is immich up and running?") if 0 <= counter <= 60: time.sleep(1) elif 11 <= counter <= 300: @@ -311,21 +318,22 @@ def check_server_up(immichHost, immichPort): time.sleep(60) continue break - logger.info(f"Found immich up and running at " + immichHost + ":" + immichPort + ".") - logger.info(f"Attempting to connect to immich") + logger.info(f"Found immich up and running at {immichHost}:{immichPort}.") + logger.info("Attempting to connect to immich") time.sleep(1) - logger.info("Exporter v1.0.9") + logger.info("Exporter 1.2.0") def check_immich_api_key(immichHost, immichPort, immichApiKey): while True: try: - requests.request( "GET", - "http://" + immichHost + ":" + immichPort + "/api/server-info/", - headers={'Accept': 'application/json', - "x-api-key": immichApiKey} + f"http://{immichHost}:{immichPort}/api/server-info/", + headers={ + "Accept": "application/json", + "x-api-key": immichApiKey + } ) except requests.exceptions.RequestException as e: logger.error(f"CONNECTION ERROR. Possible API key error") diff --git a/setup.py b/setup.py index 280b451..af956f5 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,12 @@ from setuptools import setup with open("README.md", "r") as fh: long_description = fh.read() +version = '1.2.0' + setup( name='prometheus-immich-exporter', packages=['immich_exporter'], - version='1.1.0', + version=version, long_description=long_description, long_description_content_type="text/markdown", description='Prometheus exporter for immich', @@ -18,7 +20,7 @@ setup( keywords=['prometheus', 'immich'], classifiers=[], python_requires='>=3', - install_requires=['attrdict==2.0.1', 'prometheus_client==0.19.0 ', 'requests==2.31.0', 'python-json-logger==2.0.7'], + install_requires=['attrdict==2.0.1', 'prometheus_client==0.19.0 ', 'requests==2.31.0', 'python-json-logger==2.0.7', 'psutil==5.9.8'], entry_points={ 'console_scripts': [ 'immich_exporter=immich_exporter.exporter:main',