feat: add load average and virtual memory metrics

Added load average and virtual memory metrics
Some optimizations
This commit is contained in:
Oleksiy Fomenko
2024-06-15 15:18:00 -04:00
parent 311493f537
commit 28326160d4
7 changed files with 424 additions and 196 deletions

View File

@@ -3,7 +3,7 @@ FROM alpine:3.18.4
# Installing required packages # Installing required packages
FROM alpine/doctl FROM alpine/doctl
ENV PYTHONUNBUFFERED=1 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 python3 -m ensurepip
RUN pip3 install --break-system-packages --no-cache --upgrade pip setuptools RUN pip3 install --break-system-packages --no-cache --upgrade pip setuptools

View File

@@ -50,7 +50,12 @@ 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_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_by_users` | `the disk space each user uses` |
| `immich_server_stats_usage_growth` | `sum of disk space taken up by all users` | | `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)` |
## Screenshot ## Screenshot

View File

@@ -2,7 +2,7 @@
## Import ## 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 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

View File

@@ -113,12 +113,14 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"showPercentChange": false,
"text": { "text": {
"titleSize": 6 "titleSize": 6
}, },
"textMode": "name" "textMode": "name",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -183,9 +185,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -245,9 +249,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -307,9 +313,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -369,9 +377,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -431,9 +441,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -454,6 +466,209 @@
"title": "Remaining disk size", "title": "Remaining disk size",
"type": "stat" "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": 12,
"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}"
},
"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": 12,
"x": 12,
"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": { "datasource": {
"type": "prometheus", "type": "prometheus",
@@ -482,7 +697,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 3 "y": 11
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 24, "id": 24,
@@ -498,9 +713,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -549,7 +766,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 3 "y": 11
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 26, "id": 26,
@@ -565,9 +782,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -611,7 +830,7 @@
"h": 8, "h": 8,
"w": 12, "w": 12,
"x": 12, "x": 12,
"y": 3 "y": 11
}, },
"id": 8, "id": 8,
"options": { "options": {
@@ -684,7 +903,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 7 "y": 15
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 28, "id": 28,
@@ -700,9 +919,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -748,7 +969,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 7 "y": 15
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 30, "id": 30,
@@ -764,9 +985,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -810,7 +1033,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 11 "y": 19
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 32, "id": 32,
@@ -826,9 +1049,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -873,7 +1098,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 11 "y": 19
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 34, "id": 34,
@@ -889,9 +1114,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -936,7 +1163,7 @@
"h": 8, "h": 8,
"w": 12, "w": 12,
"x": 12, "x": 12,
"y": 11 "y": 19
}, },
"id": 4, "id": 4,
"options": { "options": {
@@ -1012,7 +1239,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 15 "y": 23
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 36, "id": 36,
@@ -1028,9 +1255,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1076,7 +1305,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 15 "y": 23
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 38, "id": 38,
@@ -1092,9 +1321,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1124,6 +1355,7 @@
"mode": "palette-classic" "mode": "palette-classic"
}, },
"custom": { "custom": {
"axisBorderShow": false,
"axisCenteredZero": false, "axisCenteredZero": false,
"axisColorMode": "text", "axisColorMode": "text",
"axisLabel": "", "axisLabel": "",
@@ -1175,7 +1407,7 @@
"h": 7, "h": 7,
"w": 24, "w": 24,
"x": 0, "x": 0,
"y": 19 "y": 27
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 22, "id": 22,
@@ -1220,6 +1452,7 @@
"mode": "palette-classic" "mode": "palette-classic"
}, },
"custom": { "custom": {
"axisBorderShow": false,
"axisCenteredZero": false, "axisCenteredZero": false,
"axisColorMode": "text", "axisColorMode": "text",
"axisLabel": "", "axisLabel": "",
@@ -1271,7 +1504,7 @@
"h": 7, "h": 7,
"w": 24, "w": 24,
"x": 0, "x": 0,
"y": 26 "y": 34
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 47, "id": 47,
@@ -1332,7 +1565,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 33 "y": 41
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 42, "id": 42,
@@ -1348,9 +1581,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1396,7 +1631,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 33 "y": 41
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 40, "id": 40,
@@ -1412,9 +1647,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1457,7 +1694,7 @@
"h": 8, "h": 8,
"w": 12, "w": 12,
"x": 12, "x": 12,
"y": 33 "y": 41
}, },
"id": 6, "id": 6,
"options": { "options": {
@@ -1530,7 +1767,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 0, "x": 0,
"y": 37 "y": 45
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 44, "id": 44,
@@ -1546,9 +1783,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1594,7 +1833,7 @@
"h": 4, "h": 4,
"w": 6, "w": 6,
"x": 6, "x": 6,
"y": 37 "y": 45
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 46, "id": 46,
@@ -1610,9 +1849,11 @@
"fields": "", "fields": "",
"values": false "values": false
}, },
"textMode": "auto" "showPercentChange": false,
"textMode": "auto",
"wideLayout": true
}, },
"pluginVersion": "10.1.5", "pluginVersion": "11.0.0",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -1642,6 +1883,7 @@
"mode": "palette-classic" "mode": "palette-classic"
}, },
"custom": { "custom": {
"axisBorderShow": false,
"axisCenteredZero": false, "axisCenteredZero": false,
"axisColorMode": "text", "axisColorMode": "text",
"axisLabel": "", "axisLabel": "",
@@ -1693,7 +1935,7 @@
"h": 7, "h": 7,
"w": 24, "w": 24,
"x": 0, "x": 0,
"y": 41 "y": 49
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 49, "id": 49,
@@ -1738,6 +1980,7 @@
"mode": "palette-classic" "mode": "palette-classic"
}, },
"custom": { "custom": {
"axisBorderShow": false,
"axisCenteredZero": false, "axisCenteredZero": false,
"axisColorMode": "text", "axisColorMode": "text",
"axisLabel": "", "axisLabel": "",
@@ -1789,7 +2032,7 @@
"h": 7, "h": 7,
"w": 24, "w": 24,
"x": 0, "x": 0,
"y": 48 "y": 56
}, },
"hideTimeOverride": false, "hideTimeOverride": false,
"id": 50, "id": 50,
@@ -1832,13 +2075,13 @@
"list": [] "list": []
}, },
"time": { "time": {
"from": "now-5m", "from": "now-15m",
"to": "now" "to": "now"
}, },
"timepicker": {}, "timepicker": {},
"timezone": "", "timezone": "",
"title": "immich", "title": "immich",
"uid": "ZWWp3aa4k", "uid": "ZWWp3aa4k",
"version": 31, "version": 5,
"weekStart": "" "weekStart": ""
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 228 KiB

View File

@@ -5,6 +5,8 @@ import signal
import faulthandler import faulthandler
import requests import requests
import psutil
from prometheus_client import start_http_server from prometheus_client import start_http_server
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
import logging import logging
@@ -16,12 +18,22 @@ logger = logging.getLogger()
class ImmichMetricsCollector: class ImmichMetricsCollector:
def __init__(self, config): def __init__(self, config):
self.config = 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() metrics = self.get_immich_metrics()
for metric in metrics: for metric in metrics:
@@ -40,44 +52,61 @@ class ImmichMetricsCollector:
logger.info(prom_metric) logger.info(prom_metric)
def get_immich_metrics(self): def get_immich_metrics(self):
metrics = [] metrics = []
metrics.extend(self.get_immich_server_version_number()) metrics.extend(self.get_immich_server_version_number())
metrics.extend(self.get_immich_storage()) metrics.extend(self.get_immich_storage())
metrics.extend(self.get_immich_users_stat) metrics.extend(self.get_immich_users_stat())
metrics.extend(self.get_immich_users_stat_growth())
metrics.extend(self.get_system_stats()) metrics.extend(self.get_system_stats())
return metrics return metrics
def get_immich_users_stat_growth(self): def get_immich_users_stat(self):
try: try:
endpoint_user_stats = "/api/server-info/statistics" endpoint_user_stats = "/api/server-info/statistics"
response_user_stats = requests.request( response_user_stats = self.request(endpoint_user_stats).json()
"GET",
self.combine_url(endpoint_user_stats),
headers={'Accept': 'application/json',
"x-api-key": self.config["token"]}
)
except requests.exceptions.RequestException as e: 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"] user_data = response_user_stats["usageByUser"]
# photos growth gauge user_count = len(response_user_stats["usageByUser"])
user_count = len(response_user_stats.json()["usageByUser"])
photos_growth_total = 0 photos_growth_total = 0
videos_growth_total = 0 videos_growth_total = 0
usage_growth_total = 0 usage_growth_total = 0
metrics = []
for x in range(0, user_count): for x in range(0, user_count):
photos_growth_total += user_data[x]["photos"] photos_growth_total += user_data[x]["photos"]
# total video growth
videos_growth_total += user_data[x]["videos"] videos_growth_total += user_data[x]["videos"]
# total disk growth
usage_growth_total += user_data[x]["usage"] 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", "name": f"{self.config['metrics_prefix']}_server_stats_user_count",
"value": user_count, "value": user_count,
@@ -97,113 +126,39 @@ class ImmichMetricsCollector:
"name": f"{self.config['metrics_prefix']}_server_stats_usage_growth", "name": f"{self.config['metrics_prefix']}_server_stats_usage_growth",
"value": usage_growth_total, "value": usage_growth_total,
"help": "videos counter that is added or removed" "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 return metrics
def get_immich_storage(self): def get_immich_storage(self):
try: try:
endpoint_storage = "/api/server-info/storage" endpoint_storage = "/api/server-info/storage"
response_storage = requests.request( response_storage = self.request(endpoint_storage).json()
"GET",
self.combine_url(endpoint_storage),
headers={'Accept': 'application/json',
"x-api-key": self.config["token"]}
)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"Couldn't get storage info: {e}") logger.error(f"Couldn't get storage info: {e}")
response_json = response_storage.json()
return [ return [
{ {
"name": f"{self.config['metrics_prefix']}_server_info_diskAvailable", "name": f"{self.config['metrics_prefix']}_server_info_diskAvailable",
"value": (response_json["diskAvailableRaw"]), "value": (response_storage["diskAvailableRaw"]),
"help": "Available space on disk", "help": "Available space on disk",
}, },
{ {
"name": f"{self.config['metrics_prefix']}_server_info_totalDiskSize", "name": f"{self.config['metrics_prefix']}_server_info_totalDiskSize",
"value": (response_json["diskSizeRaw"]), "value": (response_storage["diskSizeRaw"]),
"help": "total disk size", "help": "total disk size",
# "type": "counter" # "type": "counter"
}, },
{ {
"name": f"{self.config['metrics_prefix']}_server_info_diskUse", "name": f"{self.config['metrics_prefix']}_server_info_diskUse",
"value": (response_json["diskUseRaw"]), "value": (response_storage["diskUseRaw"]),
"help": "disk space in use", "help": "disk space in use",
# "type": "counter" # "type": "counter"
}, },
{ {
"name": f"{self.config['metrics_prefix']}_server_info_diskUsagePercentage", "name": f"{self.config['metrics_prefix']}_server_info_diskUsagePercentage",
"value": (response_json["diskUsagePercentage"]), "value": (response_storage["diskUsagePercentage"]),
"help": "disk usage in percent", "help": "disk usage in percent",
# "type": "counter" # "type": "counter"
} }
@@ -220,22 +175,15 @@ class ImmichMetricsCollector:
while True: while True:
try: try:
response = self.request(server_version_endpoint).json()
response_server_version = requests.request(
"GET",
self.combine_url(server_version_endpoint),
headers={'Accept': 'application/json',
"x-api-key": self.config["token"]}
)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"Couldn't get server version") logger.error(f"Couldn't get server version")
continue continue
break break
server_version_number = (str(response_server_version.json()["major"]) + "." + server_version_number = (
str(response_server_version.json()["minor"]) + "." + str(response["major"]) + "." + str(response["minor"]) + "." + str(response["patch"])
str(response_server_version.json()["patch"]) )
)
return [ return [
{ {
@@ -249,24 +197,55 @@ class ImmichMetricsCollector:
def get_system_stats(self): def get_system_stats(self):
loadAvg = os.getloadavg() loadAvg = os.getloadavg()
virtualMem = psutil.virtual_memory()
return [ return [
{ {
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage_1", "name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
"value": (loadAvg[0]), "value": loadAvg[0],
"help": "CPU Load average 1m", "help": "CPU Load average 1m",
"labels": {"Load Average": "1m"}, "labels": {"period": "1m"},
}, },
{ {
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage_5", "name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
"value": (loadAvg[1]), "value": loadAvg[1],
"help": "CPU Load average 5m", "help": "CPU Load average 5m",
"labels": {"Load Average": "5m"}, "labels": {"period": "5m"},
}, },
{ {
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage_15", "name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
"value": (loadAvg[2]), "value": loadAvg[2],
"help": "CPU Load average 15m", "help": "CPU Load average 15m",
"labels": {"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"},
}, },
] ]
@@ -274,7 +253,7 @@ class ImmichMetricsCollector:
prefix_url = "http://" prefix_url = "http://"
base_url = self.config["immich_host"] base_url = self.config["immich_host"]
base_url_port = self.config["immich_port"] 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 return combined_url
@@ -312,21 +291,19 @@ def get_config_value(key, default=""):
def check_server_up(immichHost, immichPort): def check_server_up(immichHost, immichPort):
#
counter = 0 counter = 0
while True: while True:
counter = counter + 1 counter = counter + 1
try: try:
requests.request( requests.request(
"GET", "GET",
"http://" + immichHost + ":" + immichPort + "/api/server-info/ping", f"http://{immichHost}:{immichPort}/api/server-info/ping",
headers={'Accept': 'application/json'} headers={'Accept': 'application/json'}
) )
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"CONNECTION ERROR. Cannot reach immich at " + immichHost + ":" + immichPort + "." logger.error(
f"Is immich up and running?") f"CONNECTION ERROR. Cannot reach immich at {immichHost}:{immichPort}. Is immich up and running?")
if 0 <= counter <= 60: if 0 <= counter <= 60:
time.sleep(1) time.sleep(1)
elif 11 <= counter <= 300: elif 11 <= counter <= 300:
@@ -335,21 +312,22 @@ def check_server_up(immichHost, immichPort):
time.sleep(60) time.sleep(60)
continue continue
break break
logger.info(f"Found immich up and running at " + immichHost + ":" + immichPort + ".") logger.info(f"Found immich up and running at {immichHost}:{immichPort}.")
logger.info(f"Attempting to connect to immich") logger.info("Attempting to connect to immich")
time.sleep(1) time.sleep(1)
logger.info("Exporter v1.0.9") logger.info("Exporter 1.2.0")
def check_immich_api_key(immichHost, immichPort, immichApiKey): def check_immich_api_key(immichHost, immichPort, immichApiKey):
while True: while True:
try: try:
requests.request( requests.request(
"GET", "GET",
"http://" + immichHost + ":" + immichPort + "/api/server-info/", f"http://{immichHost}:{immichPort}/api/server-info/",
headers={'Accept': 'application/json', headers={
"x-api-key": immichApiKey} "Accept": "application/json",
"x-api-key": immichApiKey
}
) )
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"CONNECTION ERROR. Possible API key error") logger.error(f"CONNECTION ERROR. Possible API key error")

View File

@@ -3,10 +3,12 @@ from setuptools import setup
with open("README.md", "r") as fh: with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
version = '1.2.0'
setup( setup(
name='prometheus-immich-exporter', name='prometheus-immich-exporter',
packages=['immich_exporter'], packages=['immich_exporter'],
version='1.1.0', version=version,
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
description='Prometheus exporter for immich', description='Prometheus exporter for immich',
@@ -18,7 +20,7 @@ setup(
keywords=['prometheus', 'immich'], keywords=['prometheus', 'immich'],
classifiers=[], classifiers=[],
python_requires='>=3', 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={ entry_points={
'console_scripts': [ 'console_scripts': [
'immich_exporter=immich_exporter.exporter:main', 'immich_exporter=immich_exporter.exporter:main',