Skip to content

Commit 37bc250

Browse files
Merge pull request #9 from crane-cloud/fx-prome-filters
Add prometheus filter validator
2 parents 2de9bbc + e9cf7d0 commit 37bc250

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

app/controllers/app.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from app.helpers.authenticate import (
66
jwt_required
77
)
8-
from app.helpers.utils import get_app_data
8+
from app.helpers.utils import get_app_data, is_valid_prometheus_query
99

1010

1111
class AppUsageView(Resource):
@@ -26,6 +26,11 @@ def post(self, resource):
2626
namespace = app.namespace
2727
prometheus = Prometheus()
2828

29+
if step:
30+
is_valid, message = is_valid_prometheus_query(step, start, end)
31+
if not is_valid:
32+
return dict(status='fail', message=message), 400
33+
2934
try:
3035
if resource == 'cpu':
3136
prom_data = prometheus.query_rang(

app/controllers/project.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from prometheus_http_client import Prometheus
33
from flask_restful import Resource, request
4-
from app.helpers.utils import get_project_data
4+
from app.helpers.utils import get_project_data,is_valid_prometheus_query
55

66
from app.helpers.authenticate import (
77
jwt_required
@@ -25,6 +25,11 @@ def post(self, resource):
2525
namespace = project.namespace
2626
prometheus = Prometheus()
2727

28+
if step:
29+
is_valid, message = is_valid_prometheus_query(step, start, end)
30+
if not is_valid:
31+
return dict(status='fail', message=message), 400
32+
2833
try:
2934
if resource == 'cpu':
3035
prom_data = prometheus.query_rang(

app/helpers/utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
from app.schemas import MetricsSchema
55
from types import SimpleNamespace
6+
import re
67

78
PRODUCT_BASE_URL = os.getenv('PRODUCT_BASE_URL')
89
PROJECT_ENDPOINT = f"{PRODUCT_BASE_URL}/projects"
@@ -106,3 +107,46 @@ def get_app_data(request):
106107
namespace=project_data.namespace,
107108
status_code=200
108109
)
110+
111+
112+
113+
# default mx data points for prometheus
114+
MAX_DATA_POINTS = 11000
115+
STEP_UNITS_IN_SECONDS = {
116+
"s": 1,
117+
"m": 60,
118+
"h": 3600,
119+
"d": 86400,
120+
"w": 604800,
121+
}
122+
123+
def parse_step_to_seconds(step: str) -> int:
124+
"""
125+
Parses a Prometheus step string like '1m', '2h', '4d' into seconds.
126+
"""
127+
match = re.match(r"^(\d+)([smhdw])$", step)
128+
if not match:
129+
raise ValueError("Invalid step format. Use formats like 30s, 5m, 2h, 1d.")
130+
value, unit = match.groups()
131+
return int(value) * STEP_UNITS_IN_SECONDS[unit]
132+
133+
def is_valid_prometheus_query(step: str, start_ts: int, end_ts: int) -> (bool, str):
134+
"""
135+
Validates if the number of points in a Prometheus query is within the allowed range.
136+
"""
137+
try:
138+
step_seconds = parse_step_to_seconds(step)
139+
except ValueError as e:
140+
return False, str(e)
141+
142+
if start_ts >= end_ts:
143+
return False, "Start timestamp must be less than end timestamp."
144+
145+
total_duration = end_ts - start_ts
146+
num_points = total_duration // step_seconds
147+
148+
if num_points > MAX_DATA_POINTS:
149+
return False, f"Query returns {num_points} points, which exceeds the limit of {MAX_DATA_POINTS}. Increase the step or reduce the time range."
150+
151+
return True, f"Query valid: {num_points} data points."
152+

0 commit comments

Comments
 (0)