mirror of
https://github.com/MaxenceG2M/prometheus-immich-exporter.git
synced 2025-12-08 14:53:23 +00:00
ruffing + pylinting code
This commit is contained in:
@@ -1,15 +1,14 @@
|
|||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import signal
|
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
|
||||||
import requests
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
from prometheus_client import start_http_server
|
|
||||||
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
import requests
|
||||||
|
from prometheus_client import start_http_server
|
||||||
|
from prometheus_client.core import REGISTRY, CounterMetricFamily, GaugeMetricFamily
|
||||||
from pythonjsonlogger import jsonlogger
|
from pythonjsonlogger import jsonlogger
|
||||||
|
|
||||||
# Enable dumps on stderr in case of segfault
|
# Enable dumps on stderr in case of segfault
|
||||||
@@ -25,14 +24,10 @@ class ImmichMetricsCollector:
|
|||||||
response = requests.request(
|
response = requests.request(
|
||||||
"GET",
|
"GET",
|
||||||
self.combine_url(endpoint),
|
self.combine_url(endpoint),
|
||||||
headers={
|
headers={"Accept": "application/json", "x-api-key": self.config["token"]},
|
||||||
"Accept": "application/json",
|
|
||||||
"x-api-key": self.config["token"]
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
logger.info("Requested the metrics")
|
logger.info("Requested the metrics")
|
||||||
metrics = self.get_immich_metrics()
|
metrics = self.get_immich_metrics()
|
||||||
@@ -67,7 +62,7 @@ class ImmichMetricsCollector:
|
|||||||
|
|
||||||
response_user_stats = self.request(endpoint_user_stats).json()
|
response_user_stats = self.request(endpoint_user_stats).json()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.error(f"API ERROR: can't get server statistic: {e}")
|
logger.error("API ERROR: can't get server statistic: %s", e)
|
||||||
|
|
||||||
user_data = response_user_stats["usageByUser"]
|
user_data = response_user_stats["usageByUser"]
|
||||||
user_count = len(response_user_stats["usageByUser"])
|
user_count = len(response_user_stats["usageByUser"])
|
||||||
@@ -84,27 +79,27 @@ class ImmichMetricsCollector:
|
|||||||
metrics.append(
|
metrics.append(
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_server_stats_photos_by_users",
|
"name": f"{self.config['metrics_prefix']}_server_stats_photos_by_users",
|
||||||
"value": user_data[x]['photos'],
|
"value": user_data[x]["photos"],
|
||||||
"labels": {"firstName": user_data[x]["userName"].split()[0]},
|
"labels": {"firstName": user_data[x]["userName"].split()[0]},
|
||||||
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} "
|
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} ",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
metrics.append(
|
metrics.append(
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_server_stats_videos_by_users",
|
"name": f"{self.config['metrics_prefix']}_server_stats_videos_by_users",
|
||||||
"value": user_data[x]['videos'],
|
"value": user_data[x]["videos"],
|
||||||
"labels": {"firstName": user_data[x]["userName"].split()[0]},
|
"labels": {"firstName": user_data[x]["userName"].split()[0]},
|
||||||
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} "
|
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} ",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
metrics.append(
|
metrics.append(
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_server_stats_usage_by_users",
|
"name": f"{self.config['metrics_prefix']}_server_stats_usage_by_users",
|
||||||
"value": (user_data[x]['usage']),
|
"value": (user_data[x]["usage"]),
|
||||||
"labels": {
|
"labels": {
|
||||||
"firstName": user_data[x]["userName"].split()[0],
|
"firstName": user_data[x]["userName"].split()[0],
|
||||||
},
|
},
|
||||||
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} "
|
"help": f"Number of photos by user {user_data[x]['userName'].split()[0]} ",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -112,22 +107,22 @@ class ImmichMetricsCollector:
|
|||||||
{
|
{
|
||||||
"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,
|
||||||
"help": "number of users on the immich server"
|
"help": "number of users on the immich server",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_server_stats_photos_growth",
|
"name": f"{self.config['metrics_prefix']}_server_stats_photos_growth",
|
||||||
"value": photos_growth_total,
|
"value": photos_growth_total,
|
||||||
"help": "photos counter that is added or removed"
|
"help": "photos counter that is added or removed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_server_stats_videos_growth",
|
"name": f"{self.config['metrics_prefix']}_server_stats_videos_growth",
|
||||||
"value": videos_growth_total,
|
"value": videos_growth_total,
|
||||||
"help": "videos counter that is added or removed"
|
"help": "videos counter that is added or removed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -138,7 +133,7 @@ class ImmichMetricsCollector:
|
|||||||
endpoint_storage = "/storage"
|
endpoint_storage = "/storage"
|
||||||
response_storage = self.request(endpoint_storage).json()
|
response_storage = self.request(endpoint_storage).json()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.error(f"Couldn't get storage info: {e}")
|
logger.error("Couldn't get storage info: %s", e)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -163,29 +158,32 @@ class ImmichMetricsCollector:
|
|||||||
"value": (response_storage["diskUsagePercentage"]),
|
"value": (response_storage["diskUsagePercentage"]),
|
||||||
"help": "disk usage in percent",
|
"help": "disk usage in percent",
|
||||||
# "type": "counter"
|
# "type": "counter"
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_immich_server_version_number(self):
|
def get_immich_server_version_number(self):
|
||||||
# Requesting immich_server_number serves two purposes. As the name says it returns the version number
|
# Requesting immich_server_number serves two purposes.
|
||||||
|
# As the name says it returns the version number
|
||||||
# 1. get version the full server version number
|
# 1. get version the full server version number
|
||||||
# 2. check if immich api key is correct
|
# 2. check if immich api key is correct
|
||||||
# throwing connectionRefused exception usually means that immich isn't running
|
# throwing connectionRefused exception usually means that immich isn't running
|
||||||
|
|
||||||
server_version_endpoint = "/version"
|
server_version_endpoint = "/version"
|
||||||
|
|
||||||
response_server_version = ""
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
response = self.request(server_version_endpoint).json()
|
response = self.request(server_version_endpoint).json()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException:
|
||||||
logger.error(f"Couldn't get server version")
|
logger.error("Couldn't get server version")
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
server_version_number = (
|
server_version_number = (
|
||||||
str(response["major"]) + "." + str(response["minor"]) + "." + str(response["patch"])
|
str(response["major"])
|
||||||
|
+ "."
|
||||||
|
+ str(response["minor"])
|
||||||
|
+ "."
|
||||||
|
+ str(response["patch"])
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -193,61 +191,60 @@ class ImmichMetricsCollector:
|
|||||||
"name": f"{self.config['metrics_prefix']}_server_info_version_number",
|
"name": f"{self.config['metrics_prefix']}_server_info_version_number",
|
||||||
"value": bool(server_version_number),
|
"value": bool(server_version_number),
|
||||||
"help": "server version number",
|
"help": "server version number",
|
||||||
"labels": {"version": server_version_number}
|
"labels": {"version": server_version_number},
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_system_stats(self):
|
def get_system_stats(self):
|
||||||
loadAvg = os.getloadavg()
|
load_avg = os.getloadavg()
|
||||||
virtualMem = psutil.virtual_memory()
|
virtual_mem = psutil.virtual_memory()
|
||||||
cpu = psutil.cpu_percent(interval=1, percpu=False)
|
cpu = psutil.cpu_percent(interval=1, percpu=False)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
||||||
"value": loadAvg[0],
|
"value": load_avg[0],
|
||||||
"help": "CPU Load average 1m",
|
"help": "CPU Load average 1m",
|
||||||
"labels": {"period": "1m"},
|
"labels": {"period": "1m"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
||||||
"value": loadAvg[1],
|
"value": load_avg[1],
|
||||||
"help": "CPU Load average 5m",
|
"help": "CPU Load average 5m",
|
||||||
"labels": {"period": "5m"},
|
"labels": {"period": "5m"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
"name": f"{self.config['metrics_prefix']}_system_info_loadAverage",
|
||||||
"value": loadAvg[2],
|
"value": load_avg[2],
|
||||||
"help": "CPU Load average 15m",
|
"help": "CPU Load average 15m",
|
||||||
"labels": {"period": "15m"},
|
"labels": {"period": "15m"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
||||||
"value": virtualMem[0],
|
"value": virtual_mem[0],
|
||||||
"help": "Virtual Memory - Total",
|
"help": "Virtual Memory - Total",
|
||||||
"labels": {"type": "Total"},
|
"labels": {"type": "Total"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
||||||
"value": virtualMem[1],
|
"value": virtual_mem[1],
|
||||||
"help": "Virtual Memory - Available",
|
"help": "Virtual Memory - Available",
|
||||||
"labels": {"type": "Available"},
|
"labels": {"type": "Available"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
||||||
"value": virtualMem[2],
|
"value": virtual_mem[2],
|
||||||
"help": "Virtual Memory - Percent",
|
"help": "Virtual Memory - Percent",
|
||||||
"labels": {"type": "Percent"},
|
"labels": {"type": "Percent"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
||||||
"value": virtualMem[3],
|
"value": virtual_mem[3],
|
||||||
"help": "Virtual Memory - Used",
|
"help": "Virtual Memory - Used",
|
||||||
"labels": {"type": "Used"},
|
"labels": {"type": "Used"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
"name": f"{self.config['metrics_prefix']}_system_info_memory",
|
||||||
"value": virtualMem[4],
|
"value": virtual_mem[4],
|
||||||
"help": "Virtual Memory - Free",
|
"help": "Virtual Memory - Free",
|
||||||
"labels": {"type": "Free"},
|
"labels": {"type": "Free"},
|
||||||
},
|
},
|
||||||
@@ -263,29 +260,31 @@ class ImmichMetricsCollector:
|
|||||||
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"]
|
||||||
base_api_endpoint = "/api/server"
|
base_api_endpoint = "/api/server"
|
||||||
combined_url = f"{prefix_url}{base_url}:{base_url_port}{base_api_endpoint}{api_endpoint}"
|
combined_url = (
|
||||||
|
f"{prefix_url}{base_url}:{base_url_port}{base_api_endpoint}{api_endpoint}"
|
||||||
|
)
|
||||||
|
|
||||||
return combined_url
|
return combined_url
|
||||||
|
|
||||||
|
|
||||||
# test
|
# test
|
||||||
class SignalHandler():
|
class SignalHandler:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.shutdownCount = 0
|
self.shutdown_count = 0
|
||||||
|
|
||||||
# Register signal handler
|
# Register signal handler
|
||||||
signal.signal(signal.SIGINT, self._on_signal_received)
|
signal.signal(signal.SIGINT, self._on_signal_received)
|
||||||
signal.signal(signal.SIGTERM, self._on_signal_received)
|
signal.signal(signal.SIGTERM, self._on_signal_received)
|
||||||
|
|
||||||
def is_shutting_down(self):
|
def is_shutting_down(self):
|
||||||
return self.shutdownCount > 0
|
return self.shutdown_count > 0
|
||||||
|
|
||||||
def _on_signal_received(self, signal, frame):
|
def _on_signal_received(self):
|
||||||
if self.shutdownCount > 1:
|
if self.shutdown_count > 1:
|
||||||
logger.warning("Forcibly killing exporter")
|
logger.warning("Forcibly killing exporter")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logger.info("Exporter is shutting down")
|
logger.info("Exporter is shutting down")
|
||||||
self.shutdownCount += 1
|
self.shutdown_count += 1
|
||||||
|
|
||||||
|
|
||||||
def get_config_value(key, default=""):
|
def get_config_value(key, default=""):
|
||||||
@@ -295,12 +294,12 @@ def get_config_value(key, default=""):
|
|||||||
with open(input_path, "r") as input_file:
|
with open(input_path, "r") as input_file:
|
||||||
return input_file.read().strip()
|
return input_file.read().strip()
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(f"Unable to read value for {key} from {input_path}: {str(e)}")
|
logger.error("Unable to read value for %s from %s: %s", key, input_path, e)
|
||||||
|
|
||||||
return os.environ.get(key, default)
|
return os.environ.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
def check_server_up(immichHost, immichPort):
|
def check_server_up(immich_host, immich_port):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -308,12 +307,15 @@ def check_server_up(immichHost, immichPort):
|
|||||||
try:
|
try:
|
||||||
requests.request(
|
requests.request(
|
||||||
"GET",
|
"GET",
|
||||||
f"http://{immichHost}:{immichPort}/api/server/ping",
|
f"http://{immich_host}:{immich_port}/api/server/ping",
|
||||||
headers={'Accept': 'application/json'}
|
headers={"Accept": "application/json"},
|
||||||
)
|
)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"CONNECTION ERROR. Cannot reach immich at {immichHost}:{immichPort}. Is immich up and running?")
|
"CONNECTION ERROR. Cannot reach immich at %s:%s. Is immich up and running?",
|
||||||
|
immich_host,
|
||||||
|
immich_port,
|
||||||
|
)
|
||||||
if 0 <= counter <= 60:
|
if 0 <= counter <= 60:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
elif 11 <= counter <= 300:
|
elif 11 <= counter <= 300:
|
||||||
@@ -322,41 +324,37 @@ 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("Found immich up and running at %s:%s.", immich_host, immich_port)
|
||||||
logger.info("Attempting to connect to immich")
|
logger.info("Attempting to connect to immich")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
logger.info("Exporter 1.2.1")
|
logger.info("Exporter 1.2.1")
|
||||||
|
|
||||||
|
|
||||||
def check_immich_api_key(immichHost, immichPort, immichApiKey):
|
def check_immich_api_key(immich_host, immich_port, immich_api_key):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
requests.request(
|
requests.request(
|
||||||
"GET",
|
"GET",
|
||||||
f"http://{immichHost}:{immichPort}/api/server/",
|
f"http://{immich_host}:{immich_port}/api/server/",
|
||||||
headers={
|
headers={"Accept": "application/json", "x-api-key": immich_api_key},
|
||||||
"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("CONNECTION ERROR. Possible API key error")
|
||||||
logger.error({e})
|
logger.error({e})
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
continue
|
continue
|
||||||
logger.info(f"Success.")
|
logger.info("Success.")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Init logger so it can be used
|
# Init logger so it can be used
|
||||||
logHandler = logging.StreamHandler()
|
log_handler = logging.StreamHandler()
|
||||||
formatter = jsonlogger.JsonFormatter(
|
formatter = jsonlogger.JsonFormatter(
|
||||||
"%(asctime) %(levelname) %(message)",
|
"%(asctime) %(levelname) %(message)", datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
datefmt="%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
)
|
||||||
logHandler.setFormatter(formatter)
|
log_handler.setFormatter(formatter)
|
||||||
logger.addHandler(logHandler)
|
logger.addHandler(log_handler)
|
||||||
logger.setLevel("INFO") # default until config is loaded
|
logger.setLevel("INFO") # default until config is loaded
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@@ -380,7 +378,9 @@ def main():
|
|||||||
logger.error("No host specified, please set IMMICH_PORT environment variable")
|
logger.error("No host specified, please set IMMICH_PORT environment variable")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not config["token"]:
|
if not config["token"]:
|
||||||
logger.error("No token specified, please set IMMICH_API_TOKEN environment variable")
|
logger.error(
|
||||||
|
"No token specified, please set IMMICH_API_TOKEN environment variable"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Register our custom collector
|
# Register our custom collector
|
||||||
@@ -393,9 +393,7 @@ def main():
|
|||||||
# Start server
|
# Start server
|
||||||
start_http_server(config["exporter_port"])
|
start_http_server(config["exporter_port"])
|
||||||
|
|
||||||
logger.info(
|
logger.info("Exporter listening on port %s", config["exporter_port"])
|
||||||
f"Exporter listening on port {config['exporter_port']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
while not signal_handler.is_shutting_down():
|
while not signal_handler.is_shutting_down():
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|||||||
@@ -22,3 +22,10 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
immich_exporter = "immich_exporter.exporter:main"
|
immich_exporter = "immich_exporter.exporter:main"
|
||||||
|
|
||||||
|
[tool.pylint.'MESSAGES CONTROL']
|
||||||
|
disable= [
|
||||||
|
"C0114", # Missing module docstring
|
||||||
|
"C0115", # Missing class docstring
|
||||||
|
"C0116", # Missing function or method docstring
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user