Skip to content

Conversation

@ashokbytebytego
Copy link

Refactor: Separate Dynamic Profiling Endpoints from Metrics Router

Summary

This PR refactors the API routing structure by moving dynamic profiling endpoints from metrics_routes.py into a dedicated dynamicprofiling.py router. The change maintains complete backward compatibility by registering the new router under both /dynamicprofiling (new) and /metrics (legacy) prefixes until we make changes on agent side.

Motivation

Problem:

  • Dynamic profiling and metrics endpoints were mixed in metrics_routes.py

Solution:

  • Create dedicated dynamicprofiling.py router
  • Separate concerns: metrics vs. command control
  • Provide semantic API paths while maintaining backward compatibility

Changes

Files Modified

1. NEW: src/gprofiler/backend/routers/dynamicprofiling.py

Complete router with 4 endpoints migrated from metrics_routes:

Endpoint Method Purpose
/profile_request POST Create start/stop profiling requests
/heartbeat POST Agent heartbeat + command delivery
/command_completion POST Report command execution status
/profiling/host_status GET Query host profiling status

Includes:

  • All endpoint implementations
  • Helper functions (profiling_host_status_params, _create_slack_blocks)
  • Complete docstrings and type hints
  • Database operations and business logic

2. MODIFIED: src/gprofiler/backend/routers/metrics_routes.py

Removed:

  • 4 dynamic profiling endpoints
  • Dynamic profiling helper functions
  • Related imports

Retained:

  • All metrics endpoints unchanged:
    • /instance_type_count, /samples, /graph, /function_cpu
    • /graph/nodes_and_cores, /summary, /nodes_cores/summary
    • /cpu_trend, /html_metadata

Result: 794 → 236 lines (70% reduction)

3. MODIFIED: src/gprofiler/backend/routers/__init__.py

Added:

from backend.routers import dynamicprofiling

# New clean API
router.include_router(
    dynamicprofiling.router, 
    prefix="/dynamicprofiling", 
    tags=["dynamicprofiling"]
)

# Backward compatibility for existing agents
router.include_router(
    dynamicprofiling.router, 
    prefix="/metrics", 
    tags=["dynamicprofiling-legacy"]
)

API Changes

Endpoint Migration Table

Function Old Path New Path Status
Profile Request POST /api/metrics/profile_request POST /api/dynamicprofiling/profile_request Both work
Heartbeat POST /api/metrics/heartbeat POST /api/dynamicprofiling/heartbeat Both work
Command Completion POST /api/metrics/command_completion POST /api/dynamicprofiling/command_completion Both work
Host Status GET /api/metrics/profiling/host_status GET /api/dynamicprofiling/profiling/host_status Both work

Backward Compatibility

Zero Breaking Changes

FastAPI allows registering the same router under multiple prefixes:

  • Both /api/metrics/* and /api/dynamicprofiling/* call the same function
  • No code duplication
  • Single source of truth
  • Existing agents work without modification

Metrics Endpoints

Unchanged and Unaffected

All metrics endpoints remain at /api/metrics/*:

  • /api/metrics/samples
  • /api/metrics/graph
  • /api/metrics/cpu_trend
  • etc.

Benefits

Code Quality

  • ✅ Clear separation of concerns
  • ✅ Single Responsibility Principle
  • ✅ 70% reduction in metrics_routes.py size
  • ✅ Improved code navigation and discoverability

API Design

  • ✅ Semantic endpoint paths (/dynamicprofiling clearly indicates purpose)
  • ✅ Clean API structure for future development
  • ✅ Better developer onboarding experience

Backward Compatibility

  • ✅ Zero downtime migration
  • ✅ Agents can migrate at their own pace
  • ✅ Clear deprecation path for future

Testing

Environment

  • Local Docker Compose with all services
  • PostgreSQL with dynamic profiling schema
  • All services running and healthy

Test Results

8 endpoints tested (4 types × 2 paths):

# Endpoint Path Result
1 Profile Request (Start) /metrics/profile_request ✅ PASS
2 Profile Request (Start) /dynamicprofiling/profile_request ✅ PASS
3 Heartbeat (No Commands) /metrics/heartbeat ✅ PASS
4 Heartbeat (No Commands) /dynamicprofiling/heartbeat ✅ PASS
5 Command Completion /metrics/command_completion ✅ PASS
6 Command Completion /dynamicprofiling/command_completion ✅ PASS
7 Host Status (All) /metrics/profiling/host_status ✅ PASS
8 Host Status (Filtered) /dynamicprofiling/profiling/host_status?service_name=test ✅ PASS

Additional Validation:

  • ✅ Request/response format identical between old and new paths
  • ✅ Database operations work correctly
  • ✅ Metrics endpoints unaffected by migration
  • ✅ No performance degradation
  • ✅ Error handling and validation working

Example Test Commands

# Old path (backward compatibility)
curl -X POST http://localhost:8080/api/metrics/heartbeat \
  -H "Content-Type: application/json" \
  -d '{"hostname":"test-host","ip_address":"10.0.0.1","service_name":"test","status":"active"}'

# New path (preferred)
curl -X POST http://localhost:8080/api/dynamicprofiling/heartbeat \
  -H "Content-Type: application/json" \
  -d '{"hostname":"test-host","ip_address":"10.0.0.1","service_name":"test","status":"active"}'

# Both return identical responses

Migration Path

Phase 1 (This PR)

  • ✅ Both paths work simultaneously
  • ✅ No breaking changes
  • ✅ Existing agents continue using /metrics/*
  • ✅ New agents can use /dynamicprofiling/*

Phase 2 (Future)

  • Add deprecation warnings for /metrics/profile_request, etc.
  • Update agent documentation
  • Monitor usage metrics

Phase 3 (Future, after agent migration)

  • Remove legacy path registration
  • Only /dynamicprofiling/* remains

Developer Impact

For Agent Developers

Current agents: No changes required. Code continues to work.

# Existing code - works unchanged
response = requests.post(
    "http://backend:8080/api/metrics/heartbeat",
    json=heartbeat_data
)

New agents: Can use cleaner API.

# Recommended for new development
response = requests.post(
    "http://backend:8080/api/dynamicprofiling/heartbeat",
    json=heartbeat_data
)

For Backend Developers

Where to add new features:

  • Metrics features → metrics_routes.py
  • Dynamic profiling features → dynamicprofiling.py

Clear separation makes it obvious where code belongs.

Rollback Plan

If issues arise:

Option 1: Git Revert

git revert <commit-sha>

Option 2: Manual Rollback (< 10 minutes)

  1. Copy functions from dynamicprofiling.py to metrics_routes.py
  2. Delete dynamicprofiling.py
  3. Remove router registrations in __init__.py
  4. Restore imports

Technical Details

Implementation

No Code Duplication:

# Same router instance, registered twice with different prefixes
router.include_router(dynamicprofiling.router, prefix="/dynamicprofiling")
router.include_router(dynamicprofiling.router, prefix="/metrics")

Performance:

  • FastAPI router lookup is O(1)
  • No measurable performance impact
  • Response times identical between paths

Security:

  • No changes to authentication/authorization
  • No changes to input validation
  • Same security model maintained

File Statistics

Modified:
  src/gprofiler/backend/routers/__init__.py       (+4 lines)
  src/gprofiler/backend/routers/metrics_routes.py (-558 lines)

Added:
  src/gprofiler/backend/routers/dynamicprofiling.py (+607 lines)

Net change: +53 lines across 3 files

Documentation

  • API Docs: Auto-generated at /api/v1/docs (shows both paths)
  • OpenAPI Spec: Available at /api/v1/openapi.json
  • Related Docs: heartbeat_doc/README_HEARTBEAT.md

Separate dynamic profiling endpoints from metrics endpoints for better code organization while maintaining complete backward compatibility.

Changes: NEW dynamicprofiling.py with 4 endpoints, MODIFIED metrics_routes.py , MODIFIED __init__.py to register router under both /dynamicprofiling and /metrics prefixes

Testing: All 8 endpoints tested and verified (4 types x 2 paths)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants