diff --git a/apps/metrics/Dockerfile b/apps/metrics/Dockerfile index 17e82e7..4ca7eee 100644 --- a/apps/metrics/Dockerfile +++ b/apps/metrics/Dockerfile @@ -10,9 +10,28 @@ COPY requirements.txt . # Install dependencies RUN pip install --no-cache-dir -r requirements.txt +# Copy environment file +COPY local.env . + # Copy application code COPY . . +ENV MONGODB_NAME = "freeleaps2" +ENV MONGODB_URI = "mongodb://freeleaps2-mongodb:27017" + +#app_settings +ENV GITEA_TOKEN = "" +ENV GITEA_URL = "" +ENV GITEA_DEPOT_ORGANIZATION = "" +ENV CODE_DEPOT_HTTP_PORT = "" +ENV CODE_DEPOT_SSH_PORT = "" +ENV CODE_DEPOT_DOMAIN_NAME = "" + +#log_settings +ENV LOG_BASE_PATH = "./logs" +ENV BACKEND_LOG_FILE_NAME = "freeleaps-metrics" +ENV APPLICATION_ACTIVITY_LOG = "freeleaps-metrics-activity" + # Expose port EXPOSE 8009 diff --git a/apps/metrics/README.md b/apps/metrics/README.md index 54d367d..d56822e 100644 --- a/apps/metrics/README.md +++ b/apps/metrics/README.md @@ -1,35 +1,28 @@ # ๐Ÿ“Š Metrics Service -> A lightweight FastAPI microservice for user registration analytics and statistics +> A lightweight FastAPI microservice for user registration analytics and metrics [![Python](https://img.shields.io/badge/Python-3.12+-blue.svg)](https://python.org) [![FastAPI](https://img.shields.io/badge/FastAPI-0.114+-green.svg)](https://fastapi.tiangolo.com) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://docker.com) -The Metrics service provides real-time APIs for querying user registration data from StarRocks database, offering flexible analytics and insights into user growth patterns. +The Metrics service provides real-time APIs for querying user registration data (via StarRocks) and querying monitoring metrics (via Prometheus). ## โœจ Features -### ๐Ÿ“Š User Registration Statistics APIs -- **Date Range Query** - Query registration data for specific date ranges -- **Recent N Days Query** - Get registration data for the last N days -- **Start Date + Days Query** - Query N days starting from a specified date -- **Statistics Summary** - Get comprehensive statistics and analytics -- **POST Method Support** - JSON request body support for complex queries +### ๐Ÿ“Š Registration Analytics (StarRocks) +- Date Range Query (start_date ~ end_date) +- Typed responses with Pydantic models -### ๐Ÿ—„๏ธ Database Integration -- **StarRocks Database Connection** - - Host: `freeleaps-starrocks-cluster-fe-service.freeleaps-data-platform.svc` - - Port: `9030` - - Database: `freeleaps` - - Table: `dws_daily_registered_users` +### ๐Ÿ“ˆ Prometheus Metrics +- Predefined PromQL metrics per product +- Time-range queries and metric info discovery ### ๐Ÿ”ง Technical Features -- **Data Models**: Pydantic validation for data integrity -- **Connection Management**: Automatic database connection and disconnection -- **Error Handling**: Comprehensive exception handling with user-friendly error messages -- **Logging**: Structured logging using Loguru -- **API Documentation**: Auto-generated Swagger/OpenAPI documentation +- Data Models: Pydantic v2 +- Async HTTP and robust error handling +- Structured logging +- Auto-generated Swagger/OpenAPI docs ## ๐Ÿ“ Project Structure @@ -37,31 +30,43 @@ The Metrics service provides real-time APIs for querying user registration data metrics/ โ”œโ”€โ”€ backend/ # Business logic layer โ”‚ โ”œโ”€โ”€ infra/ # Infrastructure components -โ”‚ โ”‚ โ””โ”€โ”€ database_client.py +โ”‚ โ”‚ โ””โ”€โ”€ external_service/ +โ”‚ โ”‚ โ”œโ”€โ”€ prometheus_client.py +โ”‚ โ”‚ โ””โ”€โ”€ starrocks_client.py โ”‚ โ”œโ”€โ”€ models/ # Data models โ”‚ โ”‚ โ””โ”€โ”€ user_registration_models.py โ”‚ โ””โ”€โ”€ services/ # Business services +โ”‚ โ”œโ”€โ”€ prometheus_metrics_service.py โ”‚ โ””โ”€โ”€ registration_analytics_service.py โ”œโ”€โ”€ webapi/ # API layer โ”‚ โ”œโ”€โ”€ routes/ # API endpoints -โ”‚ โ”‚ โ””โ”€โ”€ registration_metrics.py -โ”‚ โ”œโ”€โ”€ config/ # Configuration -โ”‚ โ”‚ โ””โ”€โ”€ app_settings.py -โ”‚ โ”œโ”€โ”€ bootstrap/ # App initialization -โ”‚ โ”‚ โ””โ”€โ”€ app_factory.py -โ”‚ โ””โ”€โ”€ main.py # FastAPI app entry point -โ”œโ”€โ”€ common/ # Shared utilities -โ”œโ”€โ”€ requirements.txt # Dependencies -โ”œโ”€โ”€ Dockerfile # Container config -โ”œโ”€โ”€ local.env # Environment variables -โ””โ”€โ”€ README.md # Documentation +โ”‚ โ”‚ โ”œโ”€โ”€ metrics/ # Aggregated routers (prefix: /api/metrics) +โ”‚ โ”‚ โ”œโ”€โ”€ prometheus_metrics/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ available_metrics.py +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ metric_info.py +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ metrics_query.py +โ”‚ โ”‚ โ””โ”€โ”€ starrocks_metrics/ +โ”‚ โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ”‚ โ”œโ”€โ”€ available_metrics.py +โ”‚ โ”‚ โ”œโ”€โ”€ metric_info.py +โ”‚ โ”‚ โ””โ”€โ”€ metrics_query.py +โ”‚ โ”œโ”€โ”€ bootstrap/ +โ”‚ โ”‚ โ””โ”€โ”€ application.py +โ”‚ โ””โ”€โ”€ main.py +โ”œโ”€โ”€ common/ +โ”œโ”€โ”€ requirements.txt +โ”œโ”€โ”€ Dockerfile +โ”œโ”€โ”€ local.env +โ””โ”€โ”€ README.md ``` ## ๐Ÿš€ Quick Start ### Prerequisites - Python 3.12+ or Docker -- Access to StarRocks database +- Access to StarRocks database (for registration analytics) +- Access to Prometheus (for monitoring metrics) ### ๐Ÿ Python Setup @@ -79,8 +84,12 @@ python3 -m uvicorn webapi.main:app --host 0.0.0.0 --port 8009 --reload # 1. Build image docker build -t metrics:latest . -# 2. Run container +# 2. Run container (env from baked local.env) docker run --rm -p 8009:8009 metrics:latest + +# Alternatively: run with a custom env file +# (this overrides envs copied into the image) +docker run --rm -p 8009:8009 --env-file local.env metrics:latest ``` ### ๐Ÿ“– Access Documentation @@ -88,69 +97,36 @@ Visit `http://localhost:8009/docs` for interactive API documentation. ## ๐Ÿ“Š API Endpoints -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/metrics/daily-registered-users` | GET/POST | Query registration data by date range | -| `/api/metrics/recent-registered-users` | GET | Get recent N days data | -| `/api/metrics/registered-users-by-days` | GET | Query N days from start date | -| `/api/metrics/registration-summary` | GET | Get statistical summary | +All endpoints are exposed under the API base prefix `/api/metrics`. -### Example Requests +### StarRocks (Registration Analytics) +- POST `/api/metrics/starrocks/dru_query` โ€” Query daily registered users time series for a date range +- GET `/api/metrics/starrocks/product/{product_id}/available-metrics` โ€” List available StarRocks-backed metrics for the product +- GET `/api/metrics/starrocks/product/{product_id}/metric/{metric_name}/info` โ€” Get metric info for the product +Example request: ```bash -# Get last 7 days -curl "http://localhost:8009/api/metrics/recent-registered-users?days=7" - -# Get date range -curl "http://localhost:8009/api/metrics/daily-registered-users?start_date=2024-09-10&end_date=2024-09-20" - -# Get summary statistics -curl "http://localhost:8009/api/metrics/registration-summary?start_date=2024-09-10&end_date=2024-09-20" +curl -X POST "http://localhost:8009/api/metrics/starrocks/dru_query" \ + -H "Content-Type: application/json" \ + -d '{ + "product_id": "freeleaps", + "start_date": "2024-09-10", + "end_date": "2024-09-20" + }' ``` -### Parameters -- `start_date` / `end_date`: Date in `YYYY-MM-DD` format -- `days`: Number of days (max: 365) -- `product_id`: Product identifier (default: "freeleaps") - -## ๐Ÿ“ˆ Response Format - -### Standard Response -```json -{ - "dates": ["2024-09-10", "2024-09-11", "2024-09-12"], - "counts": [39, 38, 31], - "total_registrations": 108, - "query_period": "2024-09-10 to 2024-09-12" -} -``` - -### Summary Response -```json -{ - "total_registrations": 282, - "average_daily": 25.64, - "max_daily": 39, - "min_daily": 8, - "days_with_registrations": 10, - "total_days": 11 -} -``` +### Prometheus +- POST `/api/metrics/prometheus/metrics_query` โ€” Query metric time series by product/metric +- GET `/api/metrics/prometheus/product/{product_id}/available-metrics` โ€” List available metrics for product +- GET `/api/metrics/prometheus/product/{product_id}/metric/{metric_name}/info` โ€” Get metric info ## ๐Ÿงช Testing -### Quick Test ```bash # Health check curl http://localhost:8009/ - -# Test recent registrations -curl "http://localhost:8009/api/metrics/recent-registered-users?days=7" ``` -### Interactive Testing -Visit `http://localhost:8009/docs` for the Swagger UI interface where you can test all endpoints directly. - ## โš™๏ธ Configuration ### Environment Variables @@ -170,9 +146,12 @@ STARROCKS_DATABASE=freeleaps LOG_BASE_PATH=./logs BACKEND_LOG_FILE_NAME=metrics APPLICATION_ACTIVITY_LOG=metrics-activity + +# Prometheus +PROMETHEUS_ENDPOINT=http://localhost:9090 ``` -> ๐Ÿ’ก **Tip**: Copy `local.env` to `.env` and modify as needed for your environment. +> Tip: Copy `local.env` to `.env` and modify as needed for your environment. ### ๐Ÿณ Docker Deployment @@ -206,20 +185,16 @@ python -m uvicorn webapi.main:app --reload ``` ## ๐Ÿ“ API Documentation - -- **Swagger UI**: `http://localhost:8009/docs` -- **ReDoc**: `http://localhost:8009/redoc` -- **OpenAPI JSON**: `http://localhost:8009/openapi.json` +- Swagger UI: `http://localhost:8009/docs` +- ReDoc: `http://localhost:8009/redoc` +- OpenAPI JSON: `http://localhost:8009/openapi.json` ## โš ๏ธ Important Notes - -- Date format: `YYYY-MM-DD` -- Max query range: 365 days +- Date format: `YYYY-MM-DD` (single-digit month/day also accepted by API) - Default `product_id`: "freeleaps" -- Requires StarRocks database access +- Requires StarRocks database access and/or Prometheus endpoint ## ๐Ÿ› Troubleshooting - | Issue | Solution | |-------|----------| | Port in use | `docker stop $(docker ps -q --filter ancestor=metrics:latest)` | diff --git a/apps/metrics/backend/infra/database_client.py b/apps/metrics/backend/infra/external_service/starrocks_client.py similarity index 99% rename from apps/metrics/backend/infra/database_client.py rename to apps/metrics/backend/infra/external_service/starrocks_client.py index 84db909..7b639af 100644 --- a/apps/metrics/backend/infra/database_client.py +++ b/apps/metrics/backend/infra/external_service/starrocks_client.py @@ -88,12 +88,3 @@ class StarRocksClient: def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit""" self.disconnect() - - - - - - - - - diff --git a/apps/metrics/backend/services/registration_analytics_service.py b/apps/metrics/backend/services/registration_analytics_service.py index 1579286..eb843f0 100644 --- a/apps/metrics/backend/services/registration_analytics_service.py +++ b/apps/metrics/backend/services/registration_analytics_service.py @@ -1,7 +1,7 @@ from typing import List, Dict, Any from datetime import date, timedelta from loguru import logger -from backend.infra.database_client import StarRocksClient +from backend.infra.external_service.starrocks_client import StarRocksClient from backend.models.user_registration_models import UserRegistrationResponse, DailyRegisteredUsers @@ -33,7 +33,6 @@ class RegistrationService: raw_data = self.starrocks_client.get_daily_registered_users( start_date, end_date, product_id ) - # Convert to DailyRegisteredUsers objects daily_data = [ DailyRegisteredUsers( @@ -44,7 +43,6 @@ class RegistrationService: ) for row in raw_data ] - # Create date-to-count mapping data_dict = {str(item.date_id): item.registered_cnt for item in daily_data} diff --git a/apps/metrics/requirements.txt b/apps/metrics/requirements.txt index 96d4bfd..1422c6b 100644 --- a/apps/metrics/requirements.txt +++ b/apps/metrics/requirements.txt @@ -14,3 +14,4 @@ pytest==8.4.1 pytest-asyncio==0.21.2 pymysql==1.1.0 sqlalchemy==2.0.23 +python-dotenv diff --git a/apps/metrics/webapi/routes/metrics/__init__.py b/apps/metrics/webapi/routes/metrics/__init__.py index 5e05f09..68613d6 100644 --- a/apps/metrics/webapi/routes/metrics/__init__.py +++ b/apps/metrics/webapi/routes/metrics/__init__.py @@ -1,7 +1,7 @@ from fastapi import APIRouter -from webapi.routes.metrics.registration_metrics import router as registration_router +from webapi.routes.starrocks_metrics import api_router as starrocks_metrics_router from webapi.routes.prometheus_metrics import api_router as prometheus_metrics_router router = APIRouter() -router.include_router(registration_router, prefix="/metrics", tags=["registration-metrics"]) +router.include_router(starrocks_metrics_router, prefix="/metrics", tags=["starrocks-metrics"]) router.include_router(prometheus_metrics_router, prefix="/metrics", tags=["prometheus-metrics"]) diff --git a/apps/metrics/webapi/routes/metrics/registration_metrics.py b/apps/metrics/webapi/routes/metrics/registration_metrics.py index ebab21a..e69de29 100644 --- a/apps/metrics/webapi/routes/metrics/registration_metrics.py +++ b/apps/metrics/webapi/routes/metrics/registration_metrics.py @@ -1,229 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query -from datetime import date, datetime, timedelta -from typing import Optional -from loguru import logger -from backend.services.registration_analytics_service import RegistrationService -from backend.models.user_registration_models import UserRegistrationResponse, UserRegistrationQuery - -router = APIRouter(tags=["registration"]) - -# Initialize service -registration_service = RegistrationService() - - -@router.get("/daily-registered-users", response_model=UserRegistrationResponse) -async def get_daily_registered_users( - start_date: date = Query(..., description="Start date in YYYY-MM-DD format"), - end_date: date = Query(..., description="End date in YYYY-MM-DD format"), - product_id: str = Query("freeleaps", description="Product identifier") -): - """ - Get daily registered users count for a date range - - Returns two lists: - - dates: List of dates in YYYY-MM-DD format - - counts: List of daily registration counts - - Example: - - GET /api/metrics/daily-registered-users?start_date=2024-01-01&end_date=2024-01-07 - """ - try: - # Validate date range - if start_date > end_date: - raise HTTPException( - status_code=400, - detail="Start date must be before or equal to end date" - ) - - # Check date range is not too large (max 1 year) - if (end_date - start_date).days > 365: - raise HTTPException( - status_code=400, - detail="Date range cannot exceed 365 days" - ) - - logger.info(f"Querying registration data from {start_date} to {end_date} for product {product_id}") - - # Get data from service - result = registration_service.get_daily_registered_users( - start_date, end_date, product_id - ) - - logger.info(f"Successfully retrieved data for {len(result.dates)} days") - return result - - except HTTPException: - raise - except Exception as e: - logger.error(f"Failed to get daily registered users: {e}") - raise HTTPException( - status_code=500, - detail=f"Internal server error: {str(e)}" - ) - - -@router.get("/registration-summary") -async def get_registration_summary( - start_date: date = Query(..., description="Start date in YYYY-MM-DD format"), - end_date: date = Query(..., description="End date in YYYY-MM-DD format"), - product_id: str = Query("freeleaps", description="Product identifier") -): - """ - Get summary statistics for user registrations in a date range - - Returns summary statistics including: - - total_registrations: Total number of registrations - - average_daily: Average daily registrations - - max_daily: Maximum daily registrations - - min_daily: Minimum daily registrations - - days_with_registrations: Number of days with registrations - - total_days: Total number of days in range - """ - try: - # Validate date range - if start_date > end_date: - raise HTTPException( - status_code=400, - detail="Start date must be before or equal to end date" - ) - - if (end_date - start_date).days > 365: - raise HTTPException( - status_code=400, - detail="Date range cannot exceed 365 days" - ) - - logger.info(f"Querying registration summary from {start_date} to {end_date} for product {product_id}") - - # Get summary from service - summary = registration_service.get_registration_summary( - start_date, end_date, product_id - ) - - return summary - - except HTTPException: - raise - except Exception as e: - logger.error(f"Failed to get registration summary: {e}") - raise HTTPException( - status_code=500, - detail=f"Internal server error: {str(e)}" - ) - - -@router.get("/recent-registered-users", response_model=UserRegistrationResponse) -async def get_recent_registered_users( - days: int = Query(7, ge=1, le=365, description="Number of recent days to query"), - product_id: str = Query("freeleaps", description="Product identifier") -): - """ - Get daily registered users count for recent N days - - Returns registration data for the last N days from today - - Example: - - GET /api/metrics/recent-registered-users?days=7 - - GET /api/metrics/recent-registered-users?days=30&product_id=freeleaps - """ - try: - # Calculate date range - end_date = date.today() - start_date = end_date - timedelta(days=days-1) - - logger.info(f"Querying recent {days} days registration data from {start_date} to {end_date} for product {product_id}") - - # Get data from service - result = registration_service.get_daily_registered_users( - start_date, end_date, product_id - ) - - logger.info(f"Successfully retrieved recent {days} days data, total registrations: {result.total_registrations}") - return result - - except Exception as e: - logger.error(f"Failed to get recent registered users: {e}") - raise HTTPException( - status_code=500, - detail=f"Internal server error: {str(e)}" - ) - - -@router.get("/registered-users-by-days", response_model=UserRegistrationResponse) -async def get_registered_users_by_days( - start_date: date = Query(..., description="Start date in YYYY-MM-DD format"), - days: int = Query(..., ge=1, le=365, description="Number of days from start date"), - product_id: str = Query("freeleaps", description="Product identifier") -): - """ - Get daily registered users count starting from a specific date for N days - - Returns registration data for N days starting from the specified start date - - Example: - - GET /api/metrics/registered-users-by-days?start_date=2024-01-01&days=7 - - GET /api/metrics/registered-users-by-days?start_date=2024-09-01&days=30&product_id=freeleaps - """ - try: - # Calculate end date - end_date = start_date + timedelta(days=days-1) - - logger.info(f"Querying registration data from {start_date} for {days} days (until {end_date}) for product {product_id}") - - # Get data from service - result = registration_service.get_daily_registered_users( - start_date, end_date, product_id - ) - - logger.info(f"Successfully retrieved {days} days data from {start_date}, total registrations: {result.total_registrations}") - return result - - except Exception as e: - logger.error(f"Failed to get registered users by days: {e}") - raise HTTPException( - status_code=500, - detail=f"Internal server error: {str(e)}" - ) - - -@router.post("/daily-registered-users", response_model=UserRegistrationResponse) -async def get_daily_registered_users_post( - query: UserRegistrationQuery -): - """ - Get daily registered users count for a date range (POST method) - - Same as GET method but accepts parameters in request body - """ - try: - # Validate date range - if query.start_date > query.end_date: - raise HTTPException( - status_code=400, - detail="Start date must be before or equal to end date" - ) - - if (query.end_date - query.start_date).days > 365: - raise HTTPException( - status_code=400, - detail="Date range cannot exceed 365 days" - ) - - logger.info(f"Querying registration data from {query.start_date} to {query.end_date} for product {query.product_id}") - - # Get data from service - result = registration_service.get_daily_registered_users( - query.start_date, query.end_date, query.product_id - ) - - logger.info(f"Successfully retrieved data for {len(result.dates)} days") - return result - - except HTTPException: - raise - except Exception as e: - logger.error(f"Failed to get daily registered users: {e}") - raise HTTPException( - status_code=500, - detail=f"Internal server error: {str(e)}" - ) diff --git a/apps/metrics/webapi/routes/starrocks_metrics/__init__.py b/apps/metrics/webapi/routes/starrocks_metrics/__init__.py new file mode 100644 index 0000000..2fbae20 --- /dev/null +++ b/apps/metrics/webapi/routes/starrocks_metrics/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter +from .metrics_query import router as metrics_query_router +from .available_metrics import router as available_metrics_router +from .metric_info import router as metric_info_router + +api_router = APIRouter() +api_router.include_router(available_metrics_router, tags=["starrocks-metrics"]) +api_router.include_router(metrics_query_router, tags=["starrocks-metrics"]) +api_router.include_router(metric_info_router, tags=["starrocks-metrics"]) diff --git a/apps/metrics/webapi/routes/starrocks_metrics/available_metrics.py b/apps/metrics/webapi/routes/starrocks_metrics/available_metrics.py new file mode 100644 index 0000000..68c4314 --- /dev/null +++ b/apps/metrics/webapi/routes/starrocks_metrics/available_metrics.py @@ -0,0 +1,45 @@ +from fastapi import APIRouter, HTTPException + +from common.log.module_logger import ModuleLogger + +router = APIRouter() + +# Initialize logger +module_logger = ModuleLogger(__file__) + +# Product -> supported StarRocks-backed metrics +SUPPORTED_STARROCKS_METRICS_MAP = { + "freeleaps": [ + "daily_registered_users", + ], + "magicleaps": [ + "daily_registered_users", + ], +} + + +@router.get("/starrocks/product/{product_id}/available-metrics") +async def get_available_metrics(product_id: str): + """ + Get list of available StarRocks-backed metrics for a specific product. + + Args: + product_id: Product ID to get metrics for (required). + + Returns a list of metric names available via StarRocks for the specified product. + """ + await module_logger.log_info( + f"Getting StarRocks available metrics list for product_id: {product_id}" + ) + + if product_id not in SUPPORTED_STARROCKS_METRICS_MAP: + raise HTTPException(status_code=404, detail=f"Unknown product_id: {product_id}") + + metrics = SUPPORTED_STARROCKS_METRICS_MAP[product_id] + + return { + "product_id": product_id, + "available_metrics": metrics, + "total_count": len(metrics), + "description": f"List of StarRocks-backed metrics for product '{product_id}'", + } diff --git a/apps/metrics/webapi/routes/starrocks_metrics/metric_info.py b/apps/metrics/webapi/routes/starrocks_metrics/metric_info.py new file mode 100644 index 0000000..d376cd0 --- /dev/null +++ b/apps/metrics/webapi/routes/starrocks_metrics/metric_info.py @@ -0,0 +1,53 @@ +from fastapi import APIRouter, HTTPException + +from common.log.module_logger import ModuleLogger + +router = APIRouter() + +# Initialize logger +module_logger = ModuleLogger(__file__) + +# Product -> metric -> description +STARROCKS_METRIC_DESCRIPTIONS = { + "freeleaps": { + "daily_registered_users": "Daily registered users count from StarRocks table dws_daily_registered_users", + }, + "magicleaps": { + "daily_registered_users": "Daily registered users count from StarRocks table dws_daily_registered_users", + }, +} + + +@router.get("/starrocks/product/{product_id}/metric/{metric_name}/info") +async def get_metric_info( + product_id: str, + metric_name: str +): + """ + Get information about a specific StarRocks-backed metric. + + Args: + product_id: Product identifier for the product's data. + metric_name: Name of the StarRocks-backed metric. + """ + await module_logger.log_info( + f"Getting StarRocks metric info for metric '{metric_name}' from product '{product_id}'" + ) + + if product_id not in STARROCKS_METRIC_DESCRIPTIONS: + raise HTTPException(status_code=404, detail=f"Unknown product_id: {product_id}") + + product_metrics = STARROCKS_METRIC_DESCRIPTIONS[product_id] + if metric_name not in product_metrics: + raise HTTPException(status_code=404, detail=f"Unknown metric '{metric_name}' for product '{product_id}'") + + metric_info = { + "product_id": product_id, + "metric_name": metric_name, + "description": product_metrics[metric_name], + } + + return { + "metric_info": metric_info, + "description": f"Information about StarRocks metric '{metric_name}' in product '{product_id}'", + } diff --git a/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py b/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py new file mode 100644 index 0000000..976b9c2 --- /dev/null +++ b/apps/metrics/webapi/routes/starrocks_metrics/metrics_query.py @@ -0,0 +1,95 @@ +from fastapi import APIRouter +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, Field +from datetime import date + +from common.log.module_logger import ModuleLogger +from backend.services.registration_analytics_service import RegistrationService + + +class RegistrationDataPoint(BaseModel): + """Single data point in registration time series.""" + date: str = Field(..., description="Date in YYYY-MM-DD format") + value: int = Field(..., description="Number of registered users") + product_id: str = Field(..., description="Product identifier") + + +class RegistrationTimeSeriesResponse(BaseModel): + """Response model for registration time series data.""" + metric_name: str = Field(..., description="Name of the queried metric") + data_points: List[RegistrationDataPoint] = Field(..., description="List of data points") + total_points: int = Field(..., description="Total number of data points") + time_range: Dict[str, str] = Field(..., description="Start and end date of the query") + total_registrations: int = Field(..., description="Total number of registrations in the period") + + +class RegistrationQueryRequest(BaseModel): + """Request model for registration query.""" + product_id: str = Field("freeleaps", description="Product ID to identify which product's data to query") + start_date: str = Field(..., description="Start date in YYYY-MM-DD format") + end_date: str = Field(..., description="End date in YYYY-MM-DD format") + + +router = APIRouter() + +# Initialize service and logger +registration_service = RegistrationService() +module_logger = ModuleLogger(__file__) + + +@router.post("/starrocks/dru_query", response_model=RegistrationTimeSeriesResponse) +async def metrics_query( + request: RegistrationQueryRequest +): + """ + Query registration time series data. + + Returns XY curve data (time series) for user registrations within the given date range. + """ + await module_logger.log_info( + f"Querying registration data for product '{request.product_id}' from {request.start_date} to {request.end_date}") + + # Parse dates - handle both YYYY-M-D and YYYY-MM-DD formats + def parse_date(date_str: str) -> date: + try: + return date.fromisoformat(date_str) + except ValueError: + # Try to parse YYYY-M-D format and convert to YYYY-MM-DD + parts = date_str.split('-') + if len(parts) == 3: + year, month, day = parts + return date(int(year), int(month), int(day)) + raise ValueError(f"Invalid date format: {date_str}") + + start_date = parse_date(request.start_date) + end_date = parse_date(request.end_date) + + # Query the registration data + result = registration_service.get_daily_registered_users( + start_date=start_date, + end_date=end_date, + product_id=request.product_id + ) + + # Format response + response = RegistrationTimeSeriesResponse( + metric_name="daily_registered_users", + data_points=[ + RegistrationDataPoint( + date=date_str, + value=count, + product_id=request.product_id + ) + for date_str, count in zip(result.dates, result.counts) + ], + total_points=len(result.dates), + time_range={ + "start": request.start_date, + "end": request.end_date + }, + total_registrations=result.total_registrations + ) + + await module_logger.log_info( + f"Successfully queried registration data with {len(result.dates)} data points, total registrations: {result.total_registrations}") + return response