Skip to content

Commit dbaa192

Browse files
Merge pull request #11 from crane-cloud/ft-metrics-query-params
Fix: critical metrics filter fetching issue
2 parents 15da119 + 189d0c6 commit dbaa192

File tree

2 files changed

+80
-16
lines changed

2 files changed

+80
-16
lines changed

app/helpers/utils.py

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from app.schemas import MetricsSchema
55
from types import SimpleNamespace
66
import re
7+
from marshmallow import ValidationError
78

89
PRODUCT_BASE_URL = os.getenv('PRODUCT_BASE_URL')
910
PROJECT_ENDPOINT = f"{PRODUCT_BASE_URL}/projects"
@@ -14,17 +15,28 @@ def get_project_data(request):
1415
project_query_data = request.get_json()
1516

1617
metric_schema = MetricsSchema()
17-
validated_query_data = metric_schema.load(
18-
project_query_data)
19-
20-
# if errors:
21-
# return dict(status='fail', message=errors), 400
18+
try:
19+
validated_query_data = metric_schema.load(project_query_data)
20+
except ValidationError as e:
21+
# Format error messages properly
22+
error_messages = []
23+
for field, messages in e.messages.items():
24+
for message in messages:
25+
error_messages.append(f"{field}: {message}")
26+
return SimpleNamespace(status='fail', message='; '.join(error_messages), status_code=400)
2227

2328
current_time = datetime.datetime.now()
2429
yesterday_time = current_time + datetime.timedelta(days=-1)
2530

26-
start = validated_query_data.get('start', yesterday_time.timestamp())
27-
end = validated_query_data.get('end', current_time.timestamp())
31+
start_raw = validated_query_data.get('start') or yesterday_time.timestamp()
32+
end_raw = validated_query_data.get('end') or current_time.timestamp()
33+
34+
# Convert millisecond timestamps to seconds if needed
35+
start_val = float(start_raw)
36+
end_val = float(end_raw)
37+
38+
start = start_val / 1000 if start_val > 1e10 else start_val
39+
end = end_val / 1000 if end_val > 1e10 else end_val
2840
step = validated_query_data.get('step', '1h')
2941
project_id = validated_query_data.get('project_id', '')
3042
project_name = validated_query_data.get('project_name', '')
@@ -67,8 +79,15 @@ def get_app_data(request):
6779
app_query_data = request.get_json()
6880

6981
metric_schema = MetricsSchema()
70-
validated_query_data = metric_schema.load(
71-
app_query_data)
82+
try:
83+
validated_query_data = metric_schema.load(app_query_data)
84+
except ValidationError as e:
85+
# Format error messages properly
86+
error_messages = []
87+
for field, messages in e.messages.items():
88+
for message in messages:
89+
error_messages.append(f"{field}: {message}")
90+
return SimpleNamespace(status='fail', message='; '.join(error_messages), status_code=400)
7291

7392
app_name = validated_query_data.get('app_name', '')
7493
app_id = validated_query_data.get('app_id', '')
@@ -122,6 +141,26 @@ def get_app_data(request):
122141
}
123142

124143

144+
def suggest_optimal_step(duration_seconds: float) -> str:
145+
min_step_seconds = duration_seconds / MAX_DATA_POINTS
146+
147+
# Define step options in ascending order
148+
step_options = [
149+
(60, "1m"), (300, "5m"), (600, "10m"), (900, "15m"), (1800, "30m"),
150+
(3600, "1h"), (7200, "2h"), (10800, "3h"), (14400, "4h"), (18000, "5h"),
151+
(21600, "6h"), (43200, "12h"), (86400, "1d"), (172800, "2d"), (604800, "1w")
152+
]
153+
154+
# Find the smallest step that satisfies the constraint
155+
for step_seconds, step_str in step_options:
156+
if step_seconds >= min_step_seconds:
157+
return step_str
158+
159+
# If even weekly steps aren't enough, calculate a custom step
160+
weeks_needed = int(min_step_seconds / 604800) + 1
161+
return f"{weeks_needed}w"
162+
163+
125164
def parse_step_to_seconds(step: str) -> int:
126165
"""
127166
Parses a Prometheus step string like '1m', '2h', '4d' into seconds.
@@ -134,7 +173,7 @@ def parse_step_to_seconds(step: str) -> int:
134173
return int(value) * STEP_UNITS_IN_SECONDS[unit]
135174

136175

137-
def is_valid_prometheus_query(step: str, start_ts: int, end_ts: int) -> (bool, str):
176+
def is_valid_prometheus_query(step: str, start_ts: int, end_ts: int) -> tuple[bool, str]:
138177
"""
139178
Validates if the number of points in a Prometheus query is within the allowed range.
140179
"""
@@ -147,9 +186,10 @@ def is_valid_prometheus_query(step: str, start_ts: int, end_ts: int) -> (bool, s
147186
return False, "Start timestamp must be less than end timestamp."
148187

149188
total_duration = end_ts - start_ts
150-
num_points = total_duration // step_seconds
189+
num_points = total_duration / step_seconds
151190

152191
if num_points > MAX_DATA_POINTS:
153-
return False, f"Query returns {num_points} points, which exceeds the limit of {MAX_DATA_POINTS}. Increase the step or reduce the time range."
192+
suggested_step = suggest_optimal_step(total_duration)
193+
return False, f"Query returns {num_points} points, which exceeds the limit of {MAX_DATA_POINTS}. Try using step '{suggested_step}' or reduce the time range."
154194

155-
return True, f"Query valid: {num_points} data points."
195+
return True, f"Query valid: {int(num_points)} data points."

app/schemas/monitoring_metrics.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
1-
from marshmallow import Schema, fields, validate
1+
from marshmallow import Schema, fields, validate, ValidationError
2+
3+
4+
def validate_timestamp(value):
5+
if value is None:
6+
return None
7+
8+
if isinstance(value, bool):
9+
raise ValidationError('Please send a timestamp value (numeric format)')
10+
11+
# Check for date-like strings
12+
if isinstance(value, str) and any(char in value for char in ['-', '/', ':', 'T', 'Z']):
13+
raise ValidationError('Please send a timestamp value (numeric format), not a date string')
14+
15+
# Convert to float
16+
try:
17+
timestamp = float(value)
18+
except (ValueError, TypeError):
19+
raise ValidationError('Please send a timestamp value (numeric format)')
20+
21+
# Range check
22+
if timestamp < 0 or (timestamp < 1e10 and timestamp < 946684800) or timestamp > 4e12:
23+
raise ValidationError('Invalid timestamp range')
24+
25+
return timestamp
226

327

428
class MetricsSchema(Schema):
5-
start = fields.Float()
6-
end = fields.Float()
29+
start = fields.Raw(validate=validate_timestamp, allow_none=True)
30+
end = fields.Raw(validate=validate_timestamp, allow_none=True)
731
step = fields.String(validate=[
832
validate.Regexp(
933
regex=r'^(?!\s*$)', error='step value should be a valid string'

0 commit comments

Comments
 (0)