diff --git a/apps/metrics/backend/services/starrocks_metrics_service.py b/apps/metrics/backend/services/starrocks_metrics_service.py index 7c5f45b..fbf3080 100644 --- a/apps/metrics/backend/services/starrocks_metrics_service.py +++ b/apps/metrics/backend/services/starrocks_metrics_service.py @@ -92,6 +92,7 @@ class StarRocksMetricsService: self, product_id: str, metric_name: str, + step: str, start_date: Union[str, date], end_date: Union[str, date] ) -> List[Dict[str, Any]]: @@ -162,6 +163,14 @@ class StarRocksMetricsService: else: end_dt = end_date + # Normalize and validate step (default '1d') + step = step or '1d' + if step not in {"1d", "1m"}: + raise HTTPException( + status_code=400, + detail="Invalid step. Supported values are '1d' and '1m'" + ) + # Validate date range if start_dt >= end_dt: raise HTTPException( @@ -191,7 +200,14 @@ class StarRocksMetricsService: ) # Parse the result and format it - formatted_data = self._format_query_result(result, metric_name, product_id, start_dt, end_dt) + formatted_data = self._format_query_result( + starrocks_result=result, + metric_name=metric_name, + product_id=product_id, + step=step, + start_date=start_dt, + end_date=end_dt + ) await self.module_logger.log_info( f"Successfully queried metric '{metric_name}' with {len(formatted_data)} data points") @@ -201,7 +217,7 @@ class StarRocksMetricsService: await self.module_logger.log_error(f"Failed to query metric '{metric_name}': {e}") raise - def _format_query_result(self, starrocks_result: List[Dict[str, Any]], metric_name: str, product_id: str, start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]: + def _format_query_result(self, starrocks_result: List[Dict[str, Any]], metric_name: str, product_id: str, step: str, start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]: """ Format StarRocks query result into the required format and fill missing dates with 0 values. @@ -219,25 +235,42 @@ class StarRocksMetricsService: result_dict = {} for row in starrocks_result: - # Format the date + # Normalize the date according to step granularity date_value = row.get("date") - if date_value: - if isinstance(date_value, str): - date_str = date_value - else: - # If it's a datetime object, format it as a string - if hasattr(date_value, 'strftime'): - # Convert to date first, then format consistently - if hasattr(date_value, 'date'): - date_obj = date_value.date() if hasattr(date_value, 'date') else date_value - else: - date_obj = date_value - date_str = date_obj.strftime('%Y-%m-%d') + ' 00:00:00' - else: - date_str = str(date_value) - else: + if not date_value: continue + def month_start(d: datetime) -> datetime: + return datetime(d.year, d.month, 1) + + # Parse and normalize + if isinstance(date_value, str): + parsed_dt = None + for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d'): + try: + parsed_dt = datetime.strptime(date_value, fmt) + break + except ValueError: + continue + if parsed_dt is None: + date_str = str(date_value) + else: + if step == '1m': + date_str = month_start(parsed_dt).strftime('%Y-%m-01 00:00:00') + else: + date_str = parsed_dt.strftime('%Y-%m-%d') + ' 00:00:00' + else: + if hasattr(date_value, 'strftime'): + dt_obj = date_value + if step == '1m': + date_str = month_start(dt_obj).strftime('%Y-%m-01 00:00:00') + else: + if hasattr(dt_obj, 'date'): + dt_obj = dt_obj.date() + date_str = dt_obj.strftime('%Y-%m-%d') + ' 00:00:00' + else: + date_str = str(date_value) + # Get the value value = row.get("value", 0) if value is None: @@ -256,32 +289,54 @@ class StarRocksMetricsService: "labels": labels } - # Generate complete date range and fill missing dates with 0 + # Generate complete range and fill missing points with 0 formatted_data = [] - current_date = start_date.date() - end_date_only = end_date.date() - - while current_date < end_date_only: - date_str = current_date.strftime('%Y-%m-%d') + ' 00:00:00' - - if date_str in result_dict: - # Use existing data - formatted_data.append(result_dict[date_str]) - else: - # Fill missing date with 0 value - labels = { - "product_id": product_id, - "metric_type": metric_name - } - - formatted_data.append({ - "date": date_str, - "value": 0, - "metric": metric_name, - "labels": labels - }) - - current_date += timedelta(days=1) + if step == '1d': + current_dt = datetime(start_date.year, start_date.month, start_date.day) + end_dt_exclusive = datetime(end_date.year, end_date.month, end_date.day) + while current_dt < end_dt_exclusive: + date_str = current_dt.strftime('%Y-%m-%d') + ' 00:00:00' + if date_str in result_dict: + formatted_data.append(result_dict[date_str]) + else: + labels = { + "product_id": product_id, + "metric_type": metric_name + } + formatted_data.append({ + "date": date_str, + "value": 0, + "metric": metric_name, + "labels": labels + }) + current_dt += timedelta(days=1) + elif step == '1m': + def month_start(d: datetime) -> datetime: + return datetime(d.year, d.month, 1) + + def add_one_month(d: datetime) -> datetime: + year = d.year + (1 if d.month == 12 else 0) + month = 1 if d.month == 12 else d.month + 1 + return datetime(year, month, 1) + + current_dt = month_start(start_date) + end_month_exclusive = month_start(end_date) + while current_dt < end_month_exclusive: + date_str = current_dt.strftime('%Y-%m-01 00:00:00') + if date_str in result_dict: + formatted_data.append(result_dict[date_str]) + else: + labels = { + "product_id": product_id, + "metric_type": metric_name + } + formatted_data.append({ + "date": date_str, + "value": 0, + "metric": metric_name, + "labels": labels + }) + current_dt = add_one_month(current_dt) return formatted_data diff --git a/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py b/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py index 37ac3ea..a035a05 100644 --- a/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py +++ b/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py @@ -26,6 +26,7 @@ class MetricQueryRequest(BaseModel): """Request model for metric query.""" product_id: str = Field(..., description="Product ID to identify which product's data to query") metric_name: str = Field(..., description="Name of the metric to query") + step: str = Field(..., description="Aggregation step, e.g., 1d or 1m") start_date: str = Field(..., description="Start date in YYYY-MM-DD HH:MM:SS format") end_date: str = Field(..., description="End date in YYYY-MM-DD HH:MM:SS format") @@ -53,6 +54,7 @@ async def metrics_query( data_points = await starrocks_service.query_metric_by_time_range( product_id=request.product_id, metric_name=request.metric_name, + step=request.step, start_date=request.start_date, end_date=request.end_date )