mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2024-11-22 12:11:26 +00:00
feat: make query result typed (WIP, tests are broken)
This commit is contained in:
parent
57c5b9781d
commit
2d07505b4d
|
@ -6,23 +6,24 @@ from selfprivacy_api.services.prometheus import Prometheus
|
||||||
from selfprivacy_api.utils.monitoring import (
|
from selfprivacy_api.utils.monitoring import (
|
||||||
MonitoringQueries,
|
MonitoringQueries,
|
||||||
MonitoringQueryError,
|
MonitoringQueryError,
|
||||||
MonitoringResponse,
|
MonitoringValuesResult,
|
||||||
|
MonitoringMetricsResult,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class Monitoring:
|
class Monitoring:
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def disk_usage(
|
def cpu_usage(
|
||||||
self,
|
self,
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60,
|
step: int = 60,
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringValuesResult:
|
||||||
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
||||||
return MonitoringQueryError(error="Prometheus is not running")
|
return MonitoringQueryError(error="Prometheus is not running")
|
||||||
|
|
||||||
return MonitoringQueries.disk_usage(start, end, step)
|
return MonitoringQueries.cpu_usage(start, end, step)
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def memory_usage(
|
def memory_usage(
|
||||||
|
@ -30,23 +31,23 @@ class Monitoring:
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60,
|
step: int = 60,
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringValuesResult:
|
||||||
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
||||||
return MonitoringQueryError(error="Prometheus is not running")
|
return MonitoringQueryError(error="Prometheus is not running")
|
||||||
|
|
||||||
return MonitoringQueries.memory_usage(start, end, step)
|
return MonitoringQueries.memory_usage(start, end, step)
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def cpu_usage(
|
def disk_usage(
|
||||||
self,
|
self,
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60,
|
step: int = 60,
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringMetricsResult:
|
||||||
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
||||||
return MonitoringQueryError(error="Prometheus is not running")
|
return MonitoringQueryError(error="Prometheus is not running")
|
||||||
|
|
||||||
return MonitoringQueries.cpu_usage(start, end, step)
|
return MonitoringQueries.disk_usage(start, end, step)
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def network_usage(
|
def network_usage(
|
||||||
|
@ -54,8 +55,8 @@ class Monitoring:
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60,
|
step: int = 60,
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringMetricsResult:
|
||||||
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
if Prometheus().get_status() != ServiceStatus.ACTIVE:
|
||||||
return MonitoringQueryError(error="Prometheus is not running")
|
return MonitoringQueryError(error="Prometheus is not running")
|
||||||
|
|
||||||
return MonitoringQueries.cpu_usage(start, end, step)
|
return MonitoringQueries.network_usage(start, end, step)
|
||||||
|
|
|
@ -26,9 +26,9 @@ class AddMonitoring(Migration):
|
||||||
def migrate(self) -> None:
|
def migrate(self) -> None:
|
||||||
with FlakeServiceManager() as manager:
|
with FlakeServiceManager() as manager:
|
||||||
if "monitoring" not in manager.services:
|
if "monitoring" not in manager.services:
|
||||||
manager.services[
|
manager.services["monitoring"] = (
|
||||||
"monitoring"
|
"git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/monitoring"
|
||||||
] = "git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes&dir=sp-modules/monitoring"
|
)
|
||||||
with WriteUserData() as data:
|
with WriteUserData() as data:
|
||||||
if "monitoring" not in data["modules"]:
|
if "monitoring" not in data["modules"]:
|
||||||
data["modules"]["monitoring"] = {
|
data["modules"]["monitoring"] = {
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
from strawberry.scalars import JSON
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Annotated, Union
|
from typing import Optional, Annotated, Union, List, Tuple
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
PROMETHEUS_URL = "http://localhost:9001"
|
PROMETHEUS_URL = "http://localhost:9001"
|
||||||
|
@ -15,9 +14,16 @@ PROMETHEUS_URL = "http://localhost:9001"
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
@dataclass
|
@dataclass
|
||||||
class MonitoringQueryResult:
|
class MonitoringValue:
|
||||||
result_type: str
|
timestamp: datetime
|
||||||
result: JSON
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry.type
|
||||||
|
@dataclass
|
||||||
|
class MonitoringMetric:
|
||||||
|
id: str
|
||||||
|
values: List[MonitoringValue]
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
|
@ -25,15 +31,23 @@ class MonitoringQueryError:
|
||||||
error: str
|
error: str
|
||||||
|
|
||||||
|
|
||||||
MonitoringResponse = Annotated[
|
MonitoringValuesResult = Annotated[
|
||||||
Union[MonitoringQueryResult, MonitoringQueryError],
|
Union[List[MonitoringValue], MonitoringQueryError],
|
||||||
strawberry.union("MonitoringQueryResponse"),
|
strawberry.union("MonitoringValuesResult"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
MonitoringMetricsResult = Annotated[
|
||||||
|
Union[List[MonitoringMetric], MonitoringQueryError],
|
||||||
|
strawberry.union("MonitoringMetricsResult"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MonitoringQueries:
|
class MonitoringQueries:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _send_query(query: str, start: int, end: int, step: int) -> MonitoringResponse:
|
def _send_query(
|
||||||
|
query: str, start: int, end: int, step: int, result_type: Optional[str] = None
|
||||||
|
) -> Union[dict, MonitoringQueryError]:
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{PROMETHEUS_URL}/api/v1/query_range",
|
f"{PROMETHEUS_URL}/api/v1/query_range",
|
||||||
|
@ -49,20 +63,45 @@ class MonitoringQueries:
|
||||||
error="Prometheus returned unexpected HTTP status code"
|
error="Prometheus returned unexpected HTTP status code"
|
||||||
)
|
)
|
||||||
json = response.json()
|
json = response.json()
|
||||||
return MonitoringQueryResult(
|
if result_type and json["data"]["resultType"] != result_type:
|
||||||
result_type=json["data"]["resultType"], result=json["data"]["result"]
|
return MonitoringQueryError(
|
||||||
|
error="Unexpected resultType returned from Prometheus, request failed"
|
||||||
)
|
)
|
||||||
|
return json["data"]
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
return MonitoringQueryError(
|
return MonitoringQueryError(
|
||||||
error=f"Prometheus request failed! Error: {str(error)}"
|
error=f"Prometheus request failed! Error: {str(error)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prometheus_value_to_monitoring_value(x: Tuple[int, str]):
|
||||||
|
return MonitoringValue(timestamp=datetime.fromtimestamp(x[0]), value=x[1])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prometheus_respone_to_monitoring_metrics(
|
||||||
|
responese: dict, id_key: str
|
||||||
|
) -> List[MonitoringMetric]:
|
||||||
|
return list(
|
||||||
|
map(
|
||||||
|
lambda x: MonitoringMetric(
|
||||||
|
id=x["metric"][id_key],
|
||||||
|
values=list(
|
||||||
|
map(
|
||||||
|
MonitoringQueries._prometheus_value_to_monitoring_value,
|
||||||
|
x["values"],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responese["result"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cpu_usage(
|
def cpu_usage(
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60, # seconds
|
step: int = 60, # seconds
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringValuesResult:
|
||||||
"""
|
"""
|
||||||
Get CPU information.
|
Get CPU information.
|
||||||
|
|
||||||
|
@ -85,11 +124,18 @@ class MonitoringQueries:
|
||||||
|
|
||||||
query = '100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)'
|
query = '100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)'
|
||||||
|
|
||||||
return MonitoringQueries._send_query(
|
data = MonitoringQueries._send_query(
|
||||||
query,
|
query, start_timestamp, end_timestamp, step, result_type="matrix"
|
||||||
start_timestamp,
|
)
|
||||||
end_timestamp,
|
|
||||||
step,
|
if isinstance(data, MonitoringQueryError):
|
||||||
|
return data
|
||||||
|
|
||||||
|
return list(
|
||||||
|
map(
|
||||||
|
MonitoringQueries._prometheus_value_to_monitoring_value,
|
||||||
|
data["result"][0]["values"],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -97,7 +143,7 @@ class MonitoringQueries:
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60, # seconds
|
step: int = 60, # seconds
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringValuesResult:
|
||||||
"""
|
"""
|
||||||
Get memory usage.
|
Get memory usage.
|
||||||
|
|
||||||
|
@ -120,11 +166,18 @@ class MonitoringQueries:
|
||||||
|
|
||||||
query = "100 - (100 * (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))"
|
query = "100 - (100 * (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))"
|
||||||
|
|
||||||
return MonitoringQueries._send_query(
|
data = MonitoringQueries._send_query(
|
||||||
query,
|
query, start_timestamp, end_timestamp, step, result_type="matrix"
|
||||||
start_timestamp,
|
)
|
||||||
end_timestamp,
|
|
||||||
step,
|
if isinstance(data, MonitoringQueryError):
|
||||||
|
return data
|
||||||
|
|
||||||
|
return list(
|
||||||
|
map(
|
||||||
|
MonitoringQueries._prometheus_value_to_monitoring_value,
|
||||||
|
data["result"][0]["values"],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -132,7 +185,7 @@ class MonitoringQueries:
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60, # seconds
|
step: int = 60, # seconds
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringMetricsResult:
|
||||||
"""
|
"""
|
||||||
Get disk usage information.
|
Get disk usage information.
|
||||||
|
|
||||||
|
@ -155,11 +208,15 @@ class MonitoringQueries:
|
||||||
|
|
||||||
query = """100 - (100 * sum by (device) (node_filesystem_avail_bytes{fstype!="rootfs"}) / sum by (device) (node_filesystem_size_bytes{fstype!="rootfs"}))"""
|
query = """100 - (100 * sum by (device) (node_filesystem_avail_bytes{fstype!="rootfs"}) / sum by (device) (node_filesystem_size_bytes{fstype!="rootfs"}))"""
|
||||||
|
|
||||||
return MonitoringQueries._send_query(
|
data = MonitoringQueries._send_query(
|
||||||
query,
|
query, start_timestamp, end_timestamp, step, result_type="matrix"
|
||||||
start_timestamp,
|
)
|
||||||
end_timestamp,
|
|
||||||
step,
|
if isinstance(data, MonitoringQueryError):
|
||||||
|
return data
|
||||||
|
|
||||||
|
return MonitoringQueries._prometheus_respone_to_monitoring_metrics(
|
||||||
|
data, "device"
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -167,7 +224,7 @@ class MonitoringQueries:
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
end: Optional[datetime] = None,
|
end: Optional[datetime] = None,
|
||||||
step: int = 60, # seconds
|
step: int = 60, # seconds
|
||||||
) -> MonitoringResponse:
|
) -> MonitoringMetricsResult:
|
||||||
"""
|
"""
|
||||||
Get network usage information for both download and upload.
|
Get network usage information for both download and upload.
|
||||||
|
|
||||||
|
@ -195,9 +252,13 @@ class MonitoringQueries:
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return MonitoringQueries._send_query(
|
data = MonitoringQueries._send_query(
|
||||||
query,
|
query, start_timestamp, end_timestamp, step, result_type="matrix"
|
||||||
start_timestamp,
|
)
|
||||||
end_timestamp,
|
|
||||||
step,
|
if isinstance(data, MonitoringQueryError):
|
||||||
|
return data
|
||||||
|
|
||||||
|
return MonitoringQueries._prometheus_respone_to_monitoring_metrics(
|
||||||
|
data, "device"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
# pylint: disable=missing-function-docstring
|
# pylint: disable=missing-function-docstring
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import List, Dict
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from selfprivacy_api.utils.monitoring import MonitoringQueryResult
|
from selfprivacy_api.utils.monitoring import MonitoringQueryResult
|
||||||
|
@ -11,17 +13,7 @@ from tests.test_graphql.common import (
|
||||||
get_data,
|
get_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MOCK_VALUES = [
|
||||||
def generate_mock_metrics(name: str):
|
|
||||||
return {
|
|
||||||
"data": {
|
|
||||||
"monitoring": {
|
|
||||||
f"{name}": {
|
|
||||||
"resultType": "matrix",
|
|
||||||
"result": [
|
|
||||||
{
|
|
||||||
"metric": {"instance": "127.0.0.1:9002"},
|
|
||||||
"values": [
|
|
||||||
[1720135748, "3.75"],
|
[1720135748, "3.75"],
|
||||||
[1720135808, "4.525000000139698"],
|
[1720135808, "4.525000000139698"],
|
||||||
[1720135868, "4.541666666433841"],
|
[1720135868, "4.541666666433841"],
|
||||||
|
@ -43,18 +35,81 @@ def generate_mock_metrics(name: str):
|
||||||
[1720136828, "4.75416666672875"],
|
[1720136828, "4.75416666672875"],
|
||||||
[1720136888, "4.624999999844775"],
|
[1720136888, "4.624999999844775"],
|
||||||
[1720136948, "3.9041666667132375"],
|
[1720136948, "3.9041666667132375"],
|
||||||
],
|
]
|
||||||
}
|
|
||||||
],
|
@dataclass
|
||||||
}
|
class DumbResponse:
|
||||||
}
|
status_code: int
|
||||||
}
|
json_data: dict
|
||||||
}
|
|
||||||
|
def json(self):
|
||||||
|
return self.json_data
|
||||||
|
|
||||||
|
|
||||||
MOCK_CPU_USAGE_RESPONSE = generate_mock_metrics("cpuUsage")
|
def generate_prometheus_response(result_type: str, result: List[Dict]):
|
||||||
MOCK_DISK_USAGE_RESPONSE = generate_mock_metrics("diskUsage")
|
return DumbResponse(
|
||||||
MOCK_MEMORY_USAGE_RESPONSE = generate_mock_metrics("memoryUsage")
|
status_code=200,
|
||||||
|
json_data={
|
||||||
|
'data': {
|
||||||
|
'resultType': result_type,
|
||||||
|
'result': result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
MOCK_SINGLE_METRIC_PROMETHEUS_RESPONSE = generate_prometheus_response(
|
||||||
|
'matrix',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'values': MOCK_VALUES
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
MOCK_MULTIPLE_METRIC_DEVICE_PROMETHEUS_RESPONSE = generate_prometheus_response(
|
||||||
|
'matrix',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'metric': {
|
||||||
|
'device': 'a'
|
||||||
|
},
|
||||||
|
'values': MOCK_VALUES
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': {
|
||||||
|
'device': 'b'
|
||||||
|
},
|
||||||
|
'values': MOCK_VALUES
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'metric': {
|
||||||
|
'device': 'c'
|
||||||
|
},
|
||||||
|
'values': MOCK_VALUES
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# def generate_mock_metrics(name: str):
|
||||||
|
# return {
|
||||||
|
# "data": {
|
||||||
|
# "monitoring": {
|
||||||
|
# f"{name}": {
|
||||||
|
# "resultType": "matrix",
|
||||||
|
# "result": [
|
||||||
|
# {
|
||||||
|
# "metric": {"instance": "127.0.0.1:9002"},
|
||||||
|
# "values": ,
|
||||||
|
# }
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
# MOCK_CPU_USAGE_RESPONSE = generate_mock_metrics("cpuUsage")
|
||||||
|
# MOCK_DISK_USAGE_RESPONSE = generate_mock_metrics("diskUsage")
|
||||||
|
# MOCK_MEMORY_USAGE_RESPONSE = generate_mock_metrics("memoryUsage")
|
||||||
|
|
||||||
|
|
||||||
def generate_mock_query(name):
|
def generate_mock_query(name):
|
||||||
|
@ -85,9 +140,7 @@ def prometheus_result_from_dict(dict):
|
||||||
def mock_cpu_usage(mocker):
|
def mock_cpu_usage(mocker):
|
||||||
mock = mocker.patch(
|
mock = mocker.patch(
|
||||||
"selfprivacy_api.utils.prometheus.PrometheusQueries._send_query",
|
"selfprivacy_api.utils.prometheus.PrometheusQueries._send_query",
|
||||||
return_value=prometheus_result_from_dict(
|
return_value=MOCK_CPU_USAGE_RESPONSE["data"]["monitoring"]["cpuUsage"],
|
||||||
MOCK_CPU_USAGE_RESPONSE["data"]["monitoring"]["cpuUsage"]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue