diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c1dd66c..39703ba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,6 +44,7 @@ jobs:
- name: Test
run: pytest --cov=app --cov-report=term-missing -v
env:
+ TEST_DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test_db
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USER: test
@@ -51,4 +52,6 @@ jobs:
POSTGRES_DB: test_db
REDIS_HOST: localhost
REDIS_PORT: 6379
+ TEST_REDIS_HOST: localhost
+ TEST_REDIS_PORT: 6379
ADMIN_KEY: ci-admin-key
diff --git a/README.md b/README.md
index df4d05c..f65b24b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# RoboSense API
+**배포 주소**: http://3.39.87.132
+
## 부제
```
High-Performance Sensor Data Processing API for Robotics
@@ -96,9 +98,12 @@ RobosenseAPI/
│ ├── main.py # FastAPI 앱 (랜딩페이지 서빙 포함)
│ ├── database.py # DB 연결 및 세션 관리
│ ├── redis_client.py # Redis 클라이언트
-│ ├── context.py # 컨텍스트 관리
-│ ├── middleware.py # 미들웨어
-│ ├── logging_config.py # 로깅 설정
+│ ├── auth.py # API Key 생성 및 검증
+│ ├── context.py # ContextVar 기반 request_id 관리
+│ ├── exceptions.py # 커스텀 예외 클래스
+│ ├── metrics.py # 요청 지표 수집
+│ ├── middleware.py # Request ID 주입, 응답시간 측정
+│ ├── logging_config.py # request_id 로그 필터
│ ├── models/
│ │ ├── __init__.py
│ │ ├── db_models.py # SQLAlchemy ORM 모델
@@ -107,9 +112,11 @@ RobosenseAPI/
│ │ └── enum.py # Enum 타입 정의
│ ├── routes/
│ │ ├── __init__.py
-│ │ ├── sensor_routes.py # 센서 데이터 API
+│ │ ├── sensor_routes.py # 센서 데이터 API + 배치 워커
│ │ ├── robot_routes.py # 로봇 관리 API
-│ │ └── stats_routes.py # 통계 API
+│ │ ├── stats_routes.py # 통계 API
+│ │ ├── admin_routes.py # API Key 발급/폐기 (관리자 전용)
+│ │ └── demo_routes.py # 랜딩페이지 공개 조회 (인증 불필요)
│ └── utils/
│ └── retry.py # 재시도 유틸리티
├── cpp_modules/ # C++ 연산 모듈
diff --git a/app/routes/admin_routes.py b/app/routes/admin_routes.py
index 31c4878..dc831c3 100644
--- a/app/routes/admin_routes.py
+++ b/app/routes/admin_routes.py
@@ -1,6 +1,8 @@
from fastapi import APIRouter, Depends, status
+from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
+from typing import Optional
from app.database import get_db
from app.models.db_models import ApiKey
@@ -9,14 +11,18 @@
router = APIRouter(prefix="/admin", tags=["관리"])
+class ApiKeyCreate(BaseModel):
+ robot_id: Optional[int] = None
+
+
@router.post("/api-keys", status_code=status.HTTP_201_CREATED)
async def issue_api_key(
- body: dict = None,
+ body: ApiKeyCreate = ApiKeyCreate(),
admin_key: str = Depends(verify_admin_key),
db: AsyncSession = Depends(get_db),
):
"""API Key 발급. 평문 키는 이 응답에서만 확인 가능."""
- robot_id = body.get("robot_id") if body else None
+ robot_id = body.robot_id
plain_key, salt, key_hash = generate_api_key()
api_key = ApiKey(key_hash=key_hash, salt=salt, robot_id=robot_id)
diff --git a/app/routes/sensor_routes.py b/app/routes/sensor_routes.py
index f8152a1..6d1bc0a 100644
--- a/app/routes/sensor_routes.py
+++ b/app/routes/sensor_routes.py
@@ -9,7 +9,6 @@
from app.auth import verify_api_key
from app.exceptions import SensorNotFoundError
import logging
-import time
import asyncio
import json
import math
@@ -19,14 +18,12 @@
router = APIRouter()
logger = logging.getLogger(__name__)
-last_invalidation = {}
sensor_queue = asyncio.Queue()
@router.post('/api/sensors', status_code=status.HTTP_201_CREATED)
async def collect_sensor_data(data: SensorDataCreate, _key=Depends(verify_api_key)):
redis = get_redis()
- now = time.time()
sensors_to_add = [
SensorData(
@@ -52,13 +49,13 @@ async def collect_sensor_data(data: SensorDataCreate, _key=Depends(verify_api_ke
await redis.ltrim(key, 0, 4)
await redis.expire(key, 10)
- if now - last_invalidation.get(data.robot_id, 0) > 10:
+ # 무효화 스로틀은 Redis로 공유. 프로세스 로컬 dict는 replica마다 따로 놀아
+ # "10초에 1번"이 프로세스 수만큼 중복 무효화됨. SETNX로 윈도우를 선점한 1개만 삭제.
+ if await redis.set(f"invalidate:robot:{data.robot_id}:detail", "1", nx=True, ex=10):
await redis.delete(f"robot:{data.robot_id}:detail")
- last_invalidation[data.robot_id] = now
- if now - last_invalidation.get('stats', 0) > 60:
+ if await redis.set("invalidate:stats", "1", nx=True, ex=60):
await redis.delete("stats:recent")
- last_invalidation['stats'] = now
return {
"status": "success",
@@ -79,6 +76,12 @@ async def check_filter_data(
_key=Depends(verify_api_key)):
query = select(SensorData).order_by(SensorData.id.desc())
+ # tz 없는 입력은 UTC로 간주 — TIMESTAMPTZ 컬럼과 비교 시 타임존 어긋남 방지
+ if start_time and start_time.tzinfo is None:
+ start_time = start_time.replace(tzinfo=timezone.utc)
+ if end_time and end_time.tzinfo is None:
+ end_time = end_time.replace(tzinfo=timezone.utc)
+
if robot_id:
query = query.where(SensorData.robot_id == robot_id)
if sensor_type:
diff --git a/index.html b/index.html
index 8d36c6b..c78a522 100644
--- a/index.html
+++ b/index.html
@@ -292,13 +292,13 @@
Registered Robots
function checkServer(){var d=document.getElementById('statusDot'),t=document.getElementById('statusText');fetch(B+'/health_check').then(function(r){if(r.ok){d.className='status-dot online';t.textContent='Server Online';}else{d.className='status-dot offline';t.textContent='Unhealthy';}}).catch(function(){d.className='status-dot offline';t.textContent='Server Offline';});}
checkServer();
function toggle(h){var b=h.nextElementSibling,t=h.querySelector('.endpoint-toggle');b.classList.toggle('open');t.style.transform=b.classList.contains('open')?'rotate(180deg)':'';}
-function showResp(id,s,d,m){var b=document.getElementById(id),ok=s>=200&&s<300;b.className='response-box visible';b.innerHTML=''+(typeof d==='string'?d:JSON.stringify(d,null,2))+'
';}
+function showResp(id,s,d,m){var b=document.getElementById(id),ok=s>=200&&s<300;b.className='response-box visible';b.innerHTML='
';b.querySelector('.status-code').textContent=s;b.querySelector('.response-time').textContent=m+'ms';b.querySelector('.response-body-output').textContent=(typeof d==='string'?d:JSON.stringify(d,null,2));}
function showLoad(id){var b=document.getElementById(id);b.className='response-box visible';b.innerHTML='';}
-function showErr(id,m){var b=document.getElementById(id);b.className='response-box visible';b.innerHTML=''+m+'
';}
+function showErr(id,m){var b=document.getElementById(id);b.className='response-box visible';b.innerHTML='
';b.querySelector('.response-body-output').textContent=m;}
function api(method,path,body){var s=performance.now(),o={method:method,headers:{'Content-Type':'application/json'}};if(body)o.body=JSON.stringify(body);return fetch(B+path,o).then(function(r){var m=Math.round(performance.now()-s);return r.json().then(function(d){return{status:r.status,data:d,ms:m};}).catch(function(){return r.text().then(function(t){return{status:r.status,data:t,ms:m};});});});}
function unlock(){['lock-post-sensor','lock-get-sensor','lock-filter-sensor','lock-stats'].forEach(function(id){document.getElementById(id).classList.add('unlocked');});}
function lock(){['lock-post-sensor','lock-get-sensor','lock-filter-sensor','lock-stats'].forEach(function(id){document.getElementById(id).classList.remove('unlocked');});}
-function updateRL(){var el=document.getElementById('registeredRobots');if(!R.length){el.innerHTML='아직 등록된 로봇이 없습니다. ';return;}el.innerHTML=R.map(function(r){return'#'+r.id+' '+r.name+'
';}).join('');}
+function updateRL(){var el=document.getElementById('registeredRobots');if(!R.length){el.innerHTML='아직 등록된 로봇이 없습니다. ';return;}el.innerHTML='';R.forEach(function(r){var chip=document.createElement('div');chip.className='robot-chip';var idSpan=document.createElement('span');idSpan.className='chip-id';idSpan.textContent='#'+r.id;var nameSpan=document.createElement('span');nameSpan.className='chip-name';nameSpan.textContent=r.name;chip.appendChild(idSpan);chip.appendChild(nameSpan);el.appendChild(chip);});}
var MODELS=['Warehouse-Bot-v2','Patrol-Bot-X1','Cargo-Mover-3K','Scout-Mini','HeavyLift-900','SpeedRunner-S','Inspector-Pro','CleanBot-Z','Picker-Alpha','Navigator-V5'];
var autoTimer=null,autoSentN=0,autoErrN=0,autoTotalMs=0;
diff --git a/landingpage.html b/landingpage.html
deleted file mode 100644
index 16004bd..0000000
--- a/landingpage.html
+++ /dev/null
@@ -1,782 +0,0 @@
-
-
-
-
-
-RoboSense API — Landing Page
-
-
-
-
-
-
-
- ● High-Performance Sensor Data API
-
- RoboSense API
- 물류 창고 자율주행 로봇을 위한 고성능 센서 데이터 처리 API.
- 실시간 수집 · C++ 최적화 · Redis 캐싱 · 비동기 배치 처리
-
-
-
-
-
- Architecture
- 시스템 아키텍처
- Nginx 리버스 프록시 뒤에 다중 FastAPI 인스턴스가 비동기로 동작하며, Redis 캐싱과 PostgreSQL을 통해 높은 처리량과 낮은 지연 시간을 달성합니다.
-
-
Robots센서 데이터 전송
-
→
-
Nginx로드 밸런서
-
→
-
FastAPI ×N비동기 처리
-
→
-
Redis캐시 / TTL
-
→
-
PostgreSQL영구 저장
-
-
-
-
-
-
FastAPI + Uvicorn 비동기 ASGI 서버, 타입 힌트 기반 자동 문서화
-
PostgreSQL 16 asyncpg 드라이버, JSONB 센서 데이터, 복합 인덱스
-
Redis 7 Cache-Aside 패턴, TTL 기반 자동 만료, 캐시 워밍
-
C++ 17 + pybind11 센서 직렬화, 이동평균 필터링, null 비율 계산
-
Nginx 리버스 프록시, 로드 밸런싱, GZip 압축
-
Docker Compose 멀티 컨테이너 오케스트레이션, 환경 일관성
-
AWS EC2 실제 배포 환경에서 TPS 부하 테스트
-
Locust 점진적 부하 테스트 (100 → 5,000 동시 접속)
-
-
-
-
-
- Usage Flow
- API 사용 흐름
- 모든 센서 데이터 및 통계 기능은 로봇 등록 후 사용할 수 있습니다.
-
-
-
⚠
-
Step 1 필수 — 센서 데이터 수집, 필터링, 통계 등 모든 기능은 로봇 등록(POST /api/robots) 이후에만 사용할 수 있습니다.
-
-
-
-
-
1
-
로봇 등록
-
POST /api/robots
-
-
→
-
-
2
-
센서 데이터 수집
-
POST /api/sensors
-
-
→
-
-
3
-
조회 & 필터링
-
GET /api/sensors/*
-
-
→
-
-
4
-
통계 확인
-
GET /api/stats
-
-
-
-
-
-
로봇 관리
-
-
-
-
-
Request Body
-
- Field Type Note
- name 필수 string 로봇 이름 (중복 불가)
- modelstring 로봇 모델명
- statusenum active / inactive / maintenance
- battery_levelint 0 ~ 100
-
-
Response 201
-
{
- "id": 1,
- "name": "Robot-A",
- "model": "Warehouse-Bot-v2",
- "status": "active",
- "battery_level": 100,
- "last_seen": null,
- "created_at": "2026-03-21T09:00:00"
-}
-
-
-
-
-
-
-
Query Parameters
-
- Param Type Note
- statusstring active / inactive / maintenance
-
-
Response 200
-
[
- { "id": 1, "name": "Robot-A", "status": "active", ... },
- { "id": 2, "name": "Robot-B", "status": "inactive", ... }
-]
-
-
-
-
-
-
-
Response 200
-
{
- "id": 1,
- "name": "Robot-A",
- "model": "Warehouse-Bot-v2",
- "status": "active",
- "battery_level": 85,
- "recent_sensors": [
- { "sensor_type": "IMU", "timestamp": "...", "raw_data": {...} },
- { "sensor_type": "GPS", "timestamp": "...", "raw_data": null }
- ]
-}
-
-
-
-
-
-
-
Request Body
-
- Field Type Note
- status 필수 enum active / inactive / maintenance
- battery_level 필수 int 0 ~ 100
-
-
-
-
-
-
-
-
센서 데이터
-
-
-
-
-
Request Body
-
- Field Type Note
- robot_id 필수 int 등록된 로봇 ID
- timestamp 필수 datetime 측정 시각
- sensors 필수 array 센서 배열 (IMU, GPS, LiDAR)
- sensors[].dataobject | null null 허용 (신호 끊김)
-
-
Request Example
-
{
- "robot_id": 1,
- "timestamp": "2026-03-21T10:30:00",
- "sensors": [
- { "sensor_type": "IMU", "data": {"acceleration": {"x": 1.2, "y": -0.5, "z": 9.8}} },
- { "sensor_type": "GPS", "data": null },
- { "sensor_type": "LiDAR", "data": {"distance": 3.5, "angle": 45} }
- ]
-}
-
Response 201
-
{ "status": "success", "robot_id": 1, "queued_count": 3 }
-
-
-
-
-
-
-
Query Parameters
-
- Param Type Note
- robot_idint 로봇 필터
- sensor_typestring IMU / GPS / LiDAR
- cursor_idint 페이징 커서
- limitint 기본 100
-
-
Response 200
-
{
- "data": [ ... ],
- "next_cursor": 450,
- "has_more": true
-}
-
-
-
-
-
-
-
Query Parameters
-
- Param Type Note
- robot_id 필수 int 로봇 ID
- sensor_type 필수 string IMU / GPS / LiDAR
- field 필수 string 예: acceleration.x
- window_size 필수 int 이동평균 윈도우 크기
-
-
Response 200
-
{
- "robot_id": 1,
- "sensor_type": "IMU",
- "field": "acceleration.x",
- "original_data": [1.2, 1.3, 1.1, 1.4, 1.0],
- "filtered_data": [1.2, 1.2, 1.27],
- "window_size": 3
-}
-
-
-
-
-
-
-
Response 200
-
{
- "id": 42,
- "robot_id": 1,
- "sensor_type": "IMU",
- "timestamp": "2026-03-21T10:30:00",
- "raw_data": {"acceleration": {"x": 1.2, "y": -0.5, "z": 9.8}},
- "created_at": "2026-03-21T10:30:01"
-}
-
-
-
-
-
-
-
통계
-
-
-
-
-
Query Parameters
-
- Param Type Note
- start_timedatetime 기본: 1시간 전
- end_timedatetime 기본: 현재
-
-
Response 200
-
{
- "timestamp": "2026-03-21T10:30:00Z",
- "time_range": { "start": "...", "end": "..." },
- "null_rates": {
- "IMU": 0.02,
- "GPS": 0.15,
- "LiDAR": 0.01
- },
- "robot_summary": {
- "total_robots": 12,
- "active": 10,
- "inactive": 2,
- "maintenance": 0,
- "status_details": [
- { "robot_id": 3, "status": "inactive", "last_seen": "..." }
- ]
- }
-}
-
-
-
-
-
-
-
시스템
-
-
-
-
-
Response 200 (healthy)
-
{ "status": "healthy", "db": true, "redis": true }
-
Response 503 (unhealthy)
-
{ "status": "unhealthy", "db": false, "redis": true }
-
-
-
-
-
-
-
- Performance
- 성능 최적화 포인트
- 극한 조건에서의 성능 최적화를 목표로 설계되었습니다.
-
-
-
Batch
-
asyncio Queue + COPY 프로토콜 비동기 배치 커밋
-
-
-
C++
-
pybind11 연동 센서 직렬화 & 필터링
-
-
-
Cache
-
Redis Cache-Aside TTL 10s ~ 60s 자동 갱신
-
-
-
GZip
-
응답 압축 미들웨어 1KB 이상 자동 압축
-
-
-
-
-
-
-
-
-
-
diff --git a/profiles/flame.svg b/profiles/flame.svg
new file mode 100644
index 0000000..9d892bd
--- /dev/null
+++ b/profiles/flame.svg
@@ -0,0 +1,491 @@
+py-spy record -o /app/profiles/flame.svg --pid 1 --duration 60 Reset Zoom Search _deliver_cancellation (anyio/_backends/_asyncio.py:589) (6 samples, 0.10%) decode (json/decoder.py:337) (10 samples, 0.17%) raw_decode (json/decoder.py:353) (9 samples, 0.15%) _json_decoder (sqlalchemy/dialects/postgresql/asyncpg.py:1194) (14 samples, 0.24%) loads (json/__init__.py:346) (14 samples, 0.24%) batch_commit_worker (app/routes/sensor_routes.py:211) (13 samples, 0.22%) copy_records_to_table (asyncpg/connection.py:1127) (11 samples, 0.19%) encode (json/encoder.py:200) (10 samples, 0.17%) iterencode (json/encoder.py:258) (8 samples, 0.14%) batch_commit_worker (app/routes/sensor_routes.py:212) (20 samples, 0.34%) <listcomp> (app/routes/sensor_routes.py:212) (20 samples, 0.34%) sensor_to_tuple (app/routes/sensor_routes.py:184) (19 samples, 0.32%) dumps (json/__init__.py:231) (11 samples, 0.19%) greenlet_spawn (sqlalchemy/util/_concurrency_py3k.py:184) (6 samples, 0.10%) _check (asyncpg/connresource.py:20) (15 samples, 0.25%) rollback (asyncpg/transaction.py:221) (13 samples, 0.22%) greenlet_spawn (sqlalchemy/util/_concurrency_py3k.py:196) (67 samples, 1.13%) _rollback_and_discard (sqlalchemy/dialects/postgresql/asyncpg.py:862) (66 samples, 1.12%) rollback (asyncpg/transaction.py:227) (47 samples, 0.80%) __rollback (asyncpg/transaction.py:206) (40 samples, 0.68%) execute (asyncpg/connection.py:354) (35 samples, 0.59%) close (sqlalchemy/ext/asyncio/session.py:1016) (86 samples, 1.46%) coro (starlette/middleware/base.py:142) (7 samples, 0.12%) __init__ (gzip.py:205) (14 samples, 0.24%) _write_gzip_header (gzip.py:246) (10 samples, 0.17%) __call__ (starlette/middleware/gzip.py:25) (52 samples, 0.88%) __init__ (starlette/middleware/gzip.py:126) (47 samples, 0.80%) __init__ (gzip.py:213) (20 samples, 0.34%) close (gzip.py:334) (18 samples, 0.30%) close (gzip.py:335) (6 samples, 0.10%) __call__ (starlette/middleware/gzip.py:129) (33 samples, 0.56%) __call__ (starlette/middleware/exceptions.py:59) (7 samples, 0.12%) __call__ (fastapi/middleware/asyncexitstack.py:16) (25 samples, 0.42%) matches (fastapi/routing.py:723) (6 samples, 0.10%) app (starlette/routing.py:733) (18 samples, 0.30%) wrapped_app (starlette/_exception_handler.py:31) (11 samples, 0.19%) __aexit__ (sqlalchemy/ext/asyncio/session.py:1070) (8 samples, 0.14%) __aexit__ (sqlalchemy/ext/asyncio/session.py:1071) (9 samples, 0.15%) get_db (app/database.py:29) (19 samples, 0.32%) __aexit__ (contextlib.py:217) (30 samples, 0.51%) __aexit__ (contextlib.py:728) (33 samples, 0.56%) app (fastapi/routing.py:102) (46 samples, 0.78%) app (fastapi/routing.py:104) (11 samples, 0.19%) _cleanup (sqlalchemy/orm/state.py:558) (6 samples, 0.10%) __aenter__ (anyio/_backends/_asyncio.py:731) (7 samples, 0.12%) __aexit__ (anyio/_backends/_asyncio.py:750) (14 samples, 0.24%) __aexit__ (anyio/_backends/_asyncio.py:789) (8 samples, 0.14%) __init__ (anyio/_backends/_asyncio.py:724) (8 samples, 0.14%) __new__ (anyio/_backends/_asyncio.py:399) (7 samples, 0.12%) create_task_group (anyio/_backends/_asyncio.py:2442) (9 samples, 0.15%) receive_or_disconnect (starlette/middleware/base.py:117) (59 samples, 1.00%) create_task_group (anyio/_core/_tasks.py:163) (12 samples, 0.20%) create_task (asyncio/tasks.py:384) (8 samples, 0.14%) _spawn (anyio/_backends/_asyncio.py:884) (12 samples, 0.20%) receive_or_disconnect (starlette/middleware/base.py:124) (30 samples, 0.51%) start_soon (anyio/_backends/_asyncio.py:904) (30 samples, 0.51%) wrap (starlette/middleware/base.py:120) (22 samples, 0.37%) wrapped_receive (starlette/middleware/base.py:84) (10 samples, 0.17%) _format_callbacks (asyncio/base_futures.py:35) (7 samples, 0.12%) format_cb (asyncio/base_futures.py:32) (7 samples, 0.12%) _task_repr_info (asyncio/base_tasks.py:10) (11 samples, 0.19%) _future_repr_info (asyncio/base_futures.py:58) (9 samples, 0.15%) _task_repr_info (asyncio/base_tasks.py:18) (11 samples, 0.19%) cancel (anyio/_backends/_asyncio.py:630) (27 samples, 0.46%) wrapper (reprlib.py:21) (25 samples, 0.42%) _task_repr (asyncio/base_tasks.py:28) (25 samples, 0.42%) receive_or_disconnect (starlette/middleware/base.py:125) (60 samples, 1.02%) wrap (starlette/middleware/base.py:121) (38 samples, 0.64%) cancel (anyio/_backends/_asyncio.py:636) (10 samples, 0.17%) stream (starlette/requests.py:228) (157 samples, 2.66%) st.. body (starlette/requests.py:243) (170 samples, 2.88%) bo.. app (fastapi/routing.py:373) (176 samples, 2.98%) app.. app (fastapi/routing.py:376) (13 samples, 0.22%) get_content_maintype (email/message.py:628) (7 samples, 0.12%) app (fastapi/routing.py:382) (8 samples, 0.14%) decode (json/decoder.py:337) (16 samples, 0.27%) raw_decode (json/decoder.py:353) (10 samples, 0.17%) app (fastapi/routing.py:385) (24 samples, 0.41%) json (starlette/requests.py:251) (21 samples, 0.36%) loads (json/__init__.py:346) (19 samples, 0.32%) solve_dependencies (fastapi/dependencies/utils.py:619) (6 samples, 0.10%) __delitem__ (starlette/datastructures.py:609) (8 samples, 0.14%) solve_dependencies (fastapi/dependencies/utils.py:620) (18 samples, 0.30%) solve_dependencies (fastapi/dependencies/utils.py:625) (10 samples, 0.17%) __getitem__ (typing.py:1653) (6 samples, 0.10%) __init__ (starlette/datastructures.py:397) (19 samples, 0.32%) solve_dependencies (fastapi/dependencies/utils.py:685) (23 samples, 0.39%) query_params (starlette/requests.py:132) (23 samples, 0.39%) solve_dependencies (fastapi/dependencies/utils.py:691) (6 samples, 0.10%) solve_dependencies (fastapi/dependencies/utils.py:645) (44 samples, 0.75%) asynccontextmanager (contextlib.py:332) (7 samples, 0.12%) _solve_generator (fastapi/dependencies/utils.py:579) (14 samples, 0.24%) add (_weakrefset.py:88) (9 samples, 0.15%) __call__ (sqlalchemy/orm/session.py:5114) (6 samples, 0.10%) __init__ (sqlalchemy/ext/asyncio/session.py:257) (7 samples, 0.12%) __init__ (sqlalchemy/ext/asyncio/session.py:269) (7 samples, 0.12%) __init__ (weakref.py:353) (24 samples, 0.41%) enter_async_context (contextlib.py:650) (105 samples, 1.78%) e.. __aenter__ (contextlib.py:210) (103 samples, 1.74%) get_db (app/database.py:29) (91 samples, 1.54%) __call__ (sqlalchemy/orm/session.py:5121) (67 samples, 1.13%) __init__ (sqlalchemy/ext/asyncio/session.py:270) (50 samples, 0.85%) __init__ (sqlalchemy/orm/session.py:1798) (30 samples, 0.51%) __setitem__ (weakref.py:167) (28 samples, 0.47%) solve_dependencies (fastapi/dependencies/utils.py:668) (123 samples, 2.08%) s.. _solve_generator (fastapi/dependencies/utils.py:582) (108 samples, 1.83%) _.. is_sequence_field (fastapi/_compat/main.py:207) (9 samples, 0.15%) _get_multidict_value (fastapi/dependencies/utils.py:756) (36 samples, 0.61%) is_sequence_field (fastapi/_compat/main.py:209) (26 samples, 0.44%) is_sequence_field (fastapi/_compat/v2.py:430) (26 samples, 0.44%) field_annotation_is_sequence (fastapi/_compat/shared.py:87) (22 samples, 0.37%) field_annotation_is_sequence (fastapi/_compat/shared.py:90) (18 samples, 0.30%) _annotation_is_sequence (fastapi/_compat/shared.py:80) (13 samples, 0.22%) lenient_issubclass (fastapi/_compat/shared.py:70) (13 samples, 0.22%) __subclasscheck__ (typing.py:1606) (7 samples, 0.12%) field_annotation_is_sequence (fastapi/_compat/shared.py:90) (6 samples, 0.10%) request_params_to_args (fastapi/dependencies/utils.py:815) (49 samples, 0.83%) _get_multidict_value (fastapi/dependencies/utils.py:767) (11 samples, 0.19%) is_sequence_field (fastapi/_compat/main.py:209) (8 samples, 0.14%) is_sequence_field (fastapi/_compat/v2.py:430) (8 samples, 0.14%) field_annotation_is_sequence (fastapi/_compat/shared.py:87) (7 samples, 0.12%) request_params_to_args (fastapi/dependencies/utils.py:820) (6 samples, 0.10%) request_params_to_args (fastapi/dependencies/utils.py:823) (7 samples, 0.12%) request_params_to_args (fastapi/dependencies/utils.py:843) (21 samples, 0.36%) _get_multidict_value (fastapi/dependencies/utils.py:767) (11 samples, 0.19%) is_sequence_field (fastapi/_compat/main.py:209) (8 samples, 0.14%) is_sequence_field (fastapi/_compat/v2.py:430) (8 samples, 0.14%) field_annotation_is_sequence (fastapi/_compat/shared.py:87) (7 samples, 0.12%) request_params_to_args (fastapi/dependencies/utils.py:848) (6 samples, 0.10%) _validate_value_with_model_field (fastapi/dependencies/utils.py:742) (9 samples, 0.15%) validate (fastapi/_compat/v2.py:178) (8 samples, 0.14%) validate_python (pydantic/type_adapter.py:441) (7 samples, 0.12%) request_params_to_args (fastapi/dependencies/utils.py:849) (13 samples, 0.22%) solve_dependencies (fastapi/dependencies/utils.py:687) (115 samples, 1.95%) s.. solve_dependencies (fastapi/dependencies/utils.py:645) (302 samples, 5.12%) solve_.. _async_ping (sqlalchemy/dialects/postgresql/asyncpg.py:824) (8 samples, 0.14%) transaction (asyncpg/connection.py:303) (6 samples, 0.10%) start (asyncpg/transaction.py:146) (22 samples, 0.37%) execute (asyncpg/connection.py:354) (21 samples, 0.36%) _async_ping (sqlalchemy/dialects/postgresql/asyncpg.py:825) (34 samples, 0.58%) _do_execute (asyncpg/connection.py:2013) (8 samples, 0.14%) _async_ping (sqlalchemy/dialects/postgresql/asyncpg.py:827) (50 samples, 0.85%) fetchrow (asyncpg/connection.py:752) (49 samples, 0.83%) _execute (asyncpg/connection.py:1873) (44 samples, 0.75%) __execute (asyncpg/connection.py:1970) (38 samples, 0.64%) _do_execute (asyncpg/connection.py:2033) (25 samples, 0.42%) <lambda> (asyncpg/connection.py:1951) (6 samples, 0.10%) _async_ping (sqlalchemy/dialects/postgresql/asyncpg.py:829) (20 samples, 0.34%) rollback (asyncpg/transaction.py:227) (15 samples, 0.25%) __rollback (asyncpg/transaction.py:206) (13 samples, 0.22%) execute (asyncpg/connection.py:354) (9 samples, 0.15%) _start_transaction (sqlalchemy/dialects/postgresql/asyncpg.py:843) (7 samples, 0.12%) start (asyncpg/transaction.py:146) (26 samples, 0.44%) execute (asyncpg/connection.py:354) (22 samples, 0.37%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:520) (43 samples, 0.73%) _start_transaction (sqlalchemy/dialects/postgresql/asyncpg.py:848) (33 samples, 0.56%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:526) (6 samples, 0.10%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:531) (11 samples, 0.19%) __bind_execute (asyncpg/prepared_stmt.py:268) (19 samples, 0.32%) __do_execute (asyncpg/prepared_stmt.py:257) (19 samples, 0.32%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:550) (29 samples, 0.49%) fetch (asyncpg/prepared_stmt.py:177) (22 samples, 0.37%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:551) (6 samples, 0.10%) _prepare_and_execute (sqlalchemy/dialects/postgresql/asyncpg.py:553) (14 samples, 0.24%) match (re/__init__.py:166) (13 samples, 0.22%) wait_for (asyncio/tasks.py:466) (8 samples, 0.14%) wait_for (asyncio/tasks.py:467) (9 samples, 0.15%) _ensure_future (asyncio/tasks.py:663) (7 samples, 0.12%) wait_for (asyncio/tasks.py:470) (25 samples, 0.42%) ensure_future (asyncio/tasks.py:659) (24 samples, 0.41%) _ensure_future (asyncio/tasks.py:680) (15 samples, 0.25%) greenlet_spawn (sqlalchemy/util/_concurrency_py3k.py:196) (299 samples, 5.06%) greenl.. execute (sqlalchemy/ext/asyncio/session.py:449) (340 samples, 5.76%) execute.. greenlet_spawn (sqlalchemy/util/_concurrency_py3k.py:203) (21 samples, 0.36%) verify_api_key (app/auth.py:39) (348 samples, 5.89%) verify_.. __init__ (sqlalchemy/sql/elements.py:2000) (22 samples, 0.37%) safe_construct (sqlalchemy/sql/elements.py:5555) (11 samples, 0.19%) __new__ (sqlalchemy/sql/elements.py:5462) (9 samples, 0.15%) _bind_param (sqlalchemy/sql/elements.py:4669) (35 samples, 0.59%) expect (sqlalchemy/sql/coercions.py:396) (57 samples, 0.97%) _literal_coercion (sqlalchemy/sql/coercions.py:804) (40 samples, 0.68%) _boolean_compare (sqlalchemy/sql/default_comparator.py:120) (60 samples, 1.02%) __init__ (sqlalchemy/sql/elements.py:3947) (13 samples, 0.22%) __hash__ (sqlalchemy/sql/annotation.py:367) (11 samples, 0.19%) _boolean_compare (sqlalchemy/sql/default_comparator.py:134) (23 samples, 0.39%) _construct_for_op (sqlalchemy/sql/elements.py:2914) (21 samples, 0.36%) __eq__ (sqlalchemy/sql/annotation.py:371) (97 samples, 1.64%) __eq__ (sqlalchemy/sql/operators.py:584) (96 samples, 1.63%) operate (sqlalchemy/sql/elements.py:1535) (91 samples, 1.54%) __eq__ (sqlalchemy/sql/operators.py:584) (90 samples, 1.52%) operate (sqlalchemy/sql/type_api.py:210) (87 samples, 1.47%) __eq__ (sqlalchemy/sql/operators.py:584) (105 samples, 1.78%) _.. operate (sqlalchemy/orm/attributes.py:453) (104 samples, 1.76%) __eq__ (sqlalchemy/sql/operators.py:584) (102 samples, 1.73%) operate (sqlalchemy/orm/properties.py:479) (102 samples, 1.73%) expect (sqlalchemy/sql/coercions.py:386) (7 samples, 0.12%) <listcomp> (sqlalchemy/sql/selectable.py:5404) (22 samples, 0.37%) __init__ (sqlalchemy/sql/selectable.py:5403) (27 samples, 0.46%) __init__ (sqlalchemy/sql/selectable.py:5407) (6 samples, 0.10%) select (sqlalchemy/sql/_selectable_constructors.py:538) (39 samples, 0.66%) _generative (sqlalchemy/sql/base.py:283) (6 samples, 0.10%) verify_api_key (app/auth.py:40) (162 samples, 2.74%) ve.. where (<string>:2) (13 samples, 0.22%) _allrows (sqlalchemy/engine/result.py:546) (6 samples, 0.10%) _soft_close (sqlalchemy/engine/result.py:2338) (7 samples, 0.12%) _allrows (sqlalchemy/engine/result.py:548) (11 samples, 0.19%) _fetchall_impl (sqlalchemy/engine/result.py:1681) (9 samples, 0.15%) _fetchall_impl (sqlalchemy/engine/result.py:2277) (9 samples, 0.15%) all (sqlalchemy/engine/result.py:1774) (19 samples, 0.32%) verify_api_key (app/auth.py:42) (21 samples, 0.36%) solve_dependencies (fastapi/dependencies/utils.py:674) (569 samples, 9.64%) solve_dependen.. verify_api_key (app/auth.py:45) (17 samples, 0.29%) hash_api_key (app/auth.py:28) (15 samples, 0.25%) _get_multidict_value (fastapi/dependencies/utils.py:756) (6 samples, 0.10%) request_params_to_args (fastapi/dependencies/utils.py:815) (8 samples, 0.14%) solve_dependencies (fastapi/dependencies/utils.py:684) (18 samples, 0.30%) _is_model_class (fastapi/_compat/main.py:359) (8 samples, 0.14%) request_body_to_args (fastapi/dependencies/utils.py:965) (18 samples, 0.30%) solve_dependencies (fastapi/dependencies/utils.py:702) (63 samples, 1.07%) request_body_to_args (fastapi/dependencies/utils.py:975) (42 samples, 0.71%) _validate_value_with_model_field (fastapi/dependencies/utils.py:742) (41 samples, 0.69%) validate (fastapi/_compat/v2.py:178) (40 samples, 0.68%) validate_python (pydantic/type_adapter.py:441) (38 samples, 0.64%) app (fastapi/routing.py:420) (1,021 samples, 17.29%) app (fastapi/routing.py:420) execute_command (redis/asyncio/client.py:725) (9 samples, 0.15%) call_with_retry (redis/asyncio/retry.py:50) (9 samples, 0.15%) _send_command_parse_response (redis/asyncio/client.py:700) (7 samples, 0.12%) parse_response (redis/asyncio/client.py:746) (7 samples, 0.12%) read_response (redis/asyncio/connection.py:607) (7 samples, 0.12%) read_response (redis/_parsers/resp2.py:82) (7 samples, 0.12%) check_filter_sensor_data (app/routes/sensor_routes.py:117) (15 samples, 0.25%) check_filter_sensor_data (app/routes/sensor_routes.py:135) (7 samples, 0.12%) loads (json/__init__.py:346) (6 samples, 0.10%) __init__ (<string>:2) (6 samples, 0.10%) set (sqlalchemy/orm/attributes.py:1281) (6 samples, 0.10%) __set__ (sqlalchemy/orm/attributes.py:540) (18 samples, 0.30%) set (sqlalchemy/orm/attributes.py:1290) (10 samples, 0.17%) <listcomp> (app/routes/sensor_routes.py:29) (51 samples, 0.86%) __init__ (<string>:4) (38 samples, 0.64%) _initialize_instance (sqlalchemy/orm/state.py:594) (28 samples, 0.47%) _declarative_constructor (sqlalchemy/orm/decl_base.py:2182) (24 samples, 0.41%) <listcomp> (app/routes/sensor_routes.py:33) (14 samples, 0.24%) collect_sensor_data (app/routes/sensor_routes.py:28) (70 samples, 1.19%) collect_sensor_data (app/routes/sensor_routes.py:45) (9 samples, 0.15%) dumps (json/__init__.py:231) (37 samples, 0.63%) encode (json/encoder.py:200) (34 samples, 0.58%) iterencode (json/encoder.py:258) (24 samples, 0.41%) acquire (asyncio/locks.py:106) (7 samples, 0.12%) __aenter__ (asyncio/locks.py:15) (21 samples, 0.36%) get_connection (redis/asyncio/connection.py:1194) (27 samples, 0.46%) ensure_connection (redis/asyncio/connection.py:1231) (6 samples, 0.10%) timeout (asyncio/timeouts.py:145) (9 samples, 0.15%) get_connection (redis/asyncio/connection.py:1198) (45 samples, 0.76%) ensure_connection (redis/asyncio/connection.py:1237) (37 samples, 0.63%) can_read_destructive (redis/asyncio/connection.py:574) (36 samples, 0.61%) can_read_destructive (redis/_parsers/base.py:433) (34 samples, 0.58%) execute_command (redis/asyncio/client.py:720) (83 samples, 1.41%) send_command (redis/asyncio/connection.py:567) (14 samples, 0.24%) _send_command_parse_response (redis/asyncio/client.py:699) (38 samples, 0.64%) send_command (redis/asyncio/connection.py:568) (24 samples, 0.41%) readuntil (asyncio/streams.py:658) (11 samples, 0.19%) _readline (redis/_parsers/base.py:468) (18 samples, 0.30%) readline (asyncio/streams.py:566) (17 samples, 0.29%) _read_response (redis/_parsers/resp2.py:90) (25 samples, 0.42%) read_response (redis/_parsers/resp2.py:82) (32 samples, 0.54%) read_response (redis/asyncio/connection.py:607) (38 samples, 0.64%) parse_response (redis/asyncio/client.py:746) (50 samples, 0.85%) execute_command (redis/asyncio/client.py:725) (113 samples, 1.91%) e.. call_with_retry (redis/asyncio/retry.py:50) (110 samples, 1.86%) c.. _send_command_parse_response (redis/asyncio/client.py:700) (60 samples, 1.02%) release (redis/asyncio/connection.py:1254) (10 samples, 0.17%) execute_command (redis/asyncio/client.py:735) (15 samples, 0.25%) collect_sensor_data (app/routes/sensor_routes.py:48) (270 samples, 4.57%) colle.. __aenter__ (asyncio/locks.py:15) (7 samples, 0.12%) get_connection (redis/asyncio/connection.py:1194) (12 samples, 0.20%) ensure_connection (redis/asyncio/connection.py:1231) (12 samples, 0.20%) connect (redis/asyncio/connection.py:298) (9 samples, 0.15%) can_read_destructive (redis/_parsers/base.py:433) (18 samples, 0.30%) get_connection (redis/asyncio/connection.py:1198) (37 samples, 0.63%) ensure_connection (redis/asyncio/connection.py:1237) (23 samples, 0.39%) can_read_destructive (redis/asyncio/connection.py:574) (22 samples, 0.37%) execute_command (redis/asyncio/client.py:720) (57 samples, 0.97%) send_command (redis/asyncio/connection.py:567) (12 samples, 0.20%) pack_command (redis/asyncio/connection.py:655) (9 samples, 0.15%) _send_command_parse_response (redis/asyncio/client.py:699) (38 samples, 0.64%) send_command (redis/asyncio/connection.py:568) (22 samples, 0.37%) _readline (redis/_parsers/base.py:468) (15 samples, 0.25%) readline (asyncio/streams.py:566) (13 samples, 0.22%) _read_response (redis/_parsers/resp2.py:90) (21 samples, 0.36%) read_response (redis/asyncio/connection.py:607) (33 samples, 0.56%) read_response (redis/_parsers/resp2.py:82) (28 samples, 0.47%) parse_response (redis/asyncio/client.py:746) (41 samples, 0.69%) parse_response (redis/asyncio/client.py:758) (9 samples, 0.15%) __contains__ (redis/client.py:92) (6 samples, 0.10%) parse_response (redis/asyncio/client.py:761) (9 samples, 0.15%) execute_command (redis/asyncio/client.py:725) (118 samples, 2.00%) e.. call_with_retry (redis/asyncio/retry.py:50) (113 samples, 1.91%) c.. _send_command_parse_response (redis/asyncio/client.py:700) (69 samples, 1.17%) parse_response (redis/asyncio/client.py:762) (7 samples, 0.12%) dispatch_async (redis/event.py:115) (12 samples, 0.20%) release (redis/asyncio/connection.py:1254) (22 samples, 0.37%) execute_command (redis/asyncio/client.py:735) (25 samples, 0.42%) collect_sensor_data (app/routes/sensor_routes.py:49) (208 samples, 3.52%) col.. __aenter__ (asyncio/locks.py:15) (10 samples, 0.17%) get_connection (redis/asyncio/connection.py:1194) (14 samples, 0.24%) ensure_connection (redis/asyncio/connection.py:1231) (7 samples, 0.12%) can_read_destructive (redis/_parsers/base.py:433) (17 samples, 0.29%) get_connection (redis/asyncio/connection.py:1198) (36 samples, 0.61%) ensure_connection (redis/asyncio/connection.py:1237) (24 samples, 0.41%) can_read_destructive (redis/asyncio/connection.py:574) (23 samples, 0.39%) execute_command (redis/asyncio/client.py:720) (62 samples, 1.05%) send_packed_command (redis/asyncio/connection.py:542) (6 samples, 0.10%) writelines (asyncio/streams.py:349) (6 samples, 0.10%) send_command (redis/asyncio/connection.py:567) (14 samples, 0.24%) pack_command (redis/asyncio/connection.py:648) (8 samples, 0.14%) _send_command_parse_response (redis/asyncio/client.py:699) (48 samples, 0.81%) send_command (redis/asyncio/connection.py:568) (31 samples, 0.53%) _readline (redis/_parsers/base.py:468) (23 samples, 0.39%) readline (asyncio/streams.py:566) (19 samples, 0.32%) _read_response (redis/_parsers/resp2.py:90) (26 samples, 0.44%) read_response (redis/_parsers/resp2.py:82) (31 samples, 0.53%) parse_response (redis/asyncio/client.py:746) (41 samples, 0.69%) read_response (redis/asyncio/connection.py:607) (39 samples, 0.66%) execute_command (redis/asyncio/client.py:725) (114 samples, 1.93%) e.. call_with_retry (redis/asyncio/retry.py:50) (112 samples, 1.90%) c.. _send_command_parse_response (redis/asyncio/client.py:700) (57 samples, 0.97%) dispatch_async (redis/event.py:115) (7 samples, 0.12%) execute_command (redis/asyncio/client.py:735) (16 samples, 0.27%) release (redis/asyncio/connection.py:1254) (11 samples, 0.19%) collect_sensor_data (app/routes/sensor_routes.py:50) (204 samples, 3.46%) col.. get_connection (redis/asyncio/connection.py:1198) (20 samples, 0.34%) ensure_connection (redis/asyncio/connection.py:1237) (18 samples, 0.30%) can_read_destructive (redis/asyncio/connection.py:574) (18 samples, 0.30%) can_read_destructive (redis/_parsers/base.py:433) (14 samples, 0.24%) execute_command (redis/asyncio/client.py:720) (29 samples, 0.49%) _send_command_parse_response (redis/asyncio/client.py:699) (13 samples, 0.22%) send_command (redis/asyncio/connection.py:568) (9 samples, 0.15%) _read_response (redis/_parsers/resp2.py:90) (7 samples, 0.12%) read_response (redis/_parsers/resp2.py:82) (8 samples, 0.14%) parse_response (redis/asyncio/client.py:746) (11 samples, 0.19%) read_response (redis/asyncio/connection.py:607) (9 samples, 0.15%) execute_command (redis/asyncio/client.py:725) (37 samples, 0.63%) call_with_retry (redis/asyncio/retry.py:50) (34 samples, 0.58%) _send_command_parse_response (redis/asyncio/client.py:700) (18 samples, 0.30%) collect_sensor_data (app/routes/sensor_routes.py:54) (88 samples, 1.49%) wrapper (redis/utils.py:417) (8 samples, 0.14%) get_connection (redis/asyncio/connection.py:1194) (6 samples, 0.10%) get_connection (redis/asyncio/connection.py:1198) (7 samples, 0.12%) ensure_connection (redis/asyncio/connection.py:1237) (6 samples, 0.10%) can_read_destructive (redis/asyncio/connection.py:574) (6 samples, 0.10%) execute_command (redis/asyncio/client.py:720) (14 samples, 0.24%) _send_command_parse_response (redis/asyncio/client.py:699) (14 samples, 0.24%) send_command (redis/asyncio/connection.py:568) (9 samples, 0.15%) read_response (redis/asyncio/connection.py:607) (6 samples, 0.10%) read_response (redis/_parsers/resp2.py:82) (6 samples, 0.10%) _read_response (redis/_parsers/resp2.py:90) (6 samples, 0.10%) parse_response (redis/asyncio/client.py:746) (7 samples, 0.12%) execute_command (redis/asyncio/client.py:725) (32 samples, 0.54%) call_with_retry (redis/asyncio/retry.py:50) (28 samples, 0.47%) _send_command_parse_response (redis/asyncio/client.py:700) (13 samples, 0.22%) collect_sensor_data (app/routes/sensor_routes.py:57) (65 samples, 1.10%) wrapper (redis/utils.py:417) (8 samples, 0.14%) app (fastapi/routing.py:430) (1,015 samples, 17.19%) app (fastapi/routing.py:430) run_endpoint_function (fastapi/routing.py:316) (1,005 samples, 17.02%) run_endpoint_function (fas.. serialize_response (fastapi/routing.py:267) (17 samples, 0.29%) validate (fastapi/_compat/v2.py:178) (17 samples, 0.29%) validate_python (pydantic/type_adapter.py:441) (16 samples, 0.27%) serialize_response (fastapi/routing.py:285) (18 samples, 0.30%) serialize (fastapi/_compat/v2.py:200) (18 samples, 0.30%) dump_python (pydantic/type_adapter.py:605) (18 samples, 0.30%) jsonable_encoder (fastapi/encoders.py:275) (6 samples, 0.10%) jsonable_encoder (fastapi/encoders.py:294) (9 samples, 0.15%) app (fastapi/routing.py:452) (64 samples, 1.08%) serialize_response (fastapi/routing.py:305) (27 samples, 0.46%) __init__ (starlette/responses.py:46) (37 samples, 0.63%) render (starlette/responses.py:192) (34 samples, 0.58%) dumps (json/__init__.py:238) (28 samples, 0.47%) encode (json/encoder.py:200) (28 samples, 0.47%) iterencode (json/encoder.py:258) (25 samples, 0.42%) app (fastapi/routing.py:464) (45 samples, 0.76%) __init__ (starlette/responses.py:189) (45 samples, 0.76%) __init__ (starlette/responses.py:47) (6 samples, 0.10%) app (fastapi/routing.py:106) (2,446 samples, 41.43%) app (fastapi/routing.py:106) app (fastapi/routing.py:467) (6 samples, 0.10%) __call__ (starlette/responses.py:157) (13 samples, 0.22%) sender (starlette/_exception_handler.py:39) (9 samples, 0.15%) sender (starlette/_exception_handler.py:39) (9 samples, 0.15%) send (anyio/streams/memory.py:247) (13 samples, 0.22%) checkpoint (anyio/lowlevel.py:39) (12 samples, 0.20%) send_nowait (anyio/streams/memory.py:224) (9 samples, 0.15%) send_with_compression (starlette/middleware/gzip.py:68) (27 samples, 0.46%) send_no_error (starlette/middleware/base.py:134) (26 samples, 0.44%) send (anyio/streams/memory.py:249) (12 samples, 0.20%) create_event (anyio/_backends/_asyncio.py:2446) (7 samples, 0.12%) send (anyio/streams/memory.py:252) (13 samples, 0.22%) __new__ (anyio/_core/_synchronization.py:85) (9 samples, 0.15%) wait (asyncio/locks.py:210) (7 samples, 0.12%) send (anyio/streams/memory.py:255) (11 samples, 0.19%) wait (anyio/_backends/_asyncio.py:1812) (11 samples, 0.19%) send_with_compression (starlette/middleware/gzip.py:69) (36 samples, 0.61%) send_no_error (starlette/middleware/base.py:134) (34 samples, 0.58%) apply_compression (starlette/middleware/gzip.py:133) (6 samples, 0.10%) write (gzip.py:289) (6 samples, 0.10%) send_with_compression (starlette/middleware/gzip.py:72) (10 samples, 0.17%) app (fastapi/routing.py:107) (102 samples, 1.73%) __call__ (starlette/responses.py:164) (87 samples, 1.47%) sender (starlette/_exception_handler.py:39) (83 samples, 1.41%) sender (starlette/_exception_handler.py:39) (82 samples, 1.39%) app (fastapi/routing.py:120) (2,647 samples, 44.83%) app (fastapi/routing.py:120) wrapped_app (starlette/_exception_handler.py:42) (2,620 samples, 44.38%) wrapped_app (starlette/_exception_handler.py:42) coro (starlette/middleware/base.py:144) (2,910 samples, 49.29%) coro (starlette/middleware/base.py:144) __call__ (starlette/middleware/gzip.py:29) (2,834 samples, 48.00%) __call__ (starlette/middleware/gzip.py:29) __call__ (starlette/middleware/gzip.py:130) (2,787 samples, 47.21%) __call__ (starlette/middleware/gzip.py:130) __call__ (starlette/middleware/gzip.py:46) (2,780 samples, 47.09%) __call__ (starlette/middleware/gzip.py:46) __call__ (starlette/middleware/exceptions.py:63) (2,758 samples, 46.71%) __call__ (starlette/middleware/exceptions.py:63) wrapped_app (starlette/_exception_handler.py:42) (2,743 samples, 46.46%) wrapped_app (starlette/_exception_handler.py:42) __call__ (fastapi/middleware/asyncexitstack.py:18) (2,714 samples, 45.97%) __call__ (fastapi/middleware/asyncexitstack.py:18) __call__ (starlette/routing.py:716) (2,702 samples, 45.77%) __call__ (starlette/routing.py:716) app (starlette/routing.py:736) (2,676 samples, 45.33%) app (starlette/routing.py:736) handle (starlette/routing.py:290) (2,665 samples, 45.14%) handle (starlette/routing.py:290) data_received (asyncio/streams.py:285) (11 samples, 0.19%) feed_data (asyncio/streams.py:504) (19 samples, 0.32%) _wakeup_waiter (asyncio/streams.py:478) (10 samples, 0.17%) data_received (asyncio/streams.py:287) (31 samples, 0.53%) data_received (uvicorn/protocols/http/httptools_impl.py:168) (6 samples, 0.10%) on_headers_complete (uvicorn/protocols/http/httptools_impl.py:253) (8 samples, 0.14%) on_headers_complete (uvicorn/protocols/http/httptools_impl.py:290) (6 samples, 0.10%) data_received (uvicorn/protocols/http/httptools_impl.py:171) (65 samples, 1.10%) digest (hmac.py:159) (21 samples, 0.36%) _init_hmac (hmac.py:67) (6 samples, 0.10%) new (hmac.py:184) (11 samples, 0.19%) __init__ (hmac.py:60) (9 samples, 0.15%) release (asyncpg/pool.py:239) (14 samples, 0.24%) reset (asyncpg/connection.py:1567) (14 samples, 0.24%) timeout (asyncio/timeouts.py:146) (14 samples, 0.24%) __init__ (asyncio/timeouts.py:40) (14 samples, 0.24%) __contains__ (uvicorn/middleware/proxy_headers.py:109) (7 samples, 0.12%) __contains__ (uvicorn/middleware/proxy_headers.py:117) (16 samples, 0.27%) ip_address (ipaddress.py:45) (15 samples, 0.25%) __init__ (ipaddress.py:1319) (13 samples, 0.22%) _ip_int_from_string (ipaddress.py:1209) (10 samples, 0.17%) __call__ (uvicorn/middleware/proxy_headers.py:34) (30 samples, 0.51%) __call__ (starlette/middleware/cors.py:81) (15 samples, 0.25%) __call__ (starlette/middleware/cors.py:82) (15 samples, 0.25%) get (<frozen _collections_abc>:774) (14 samples, 0.24%) __call__ (starlette/middleware/base.py:101) (8 samples, 0.14%) __call__ (starlette/middleware/base.py:106) (7 samples, 0.12%) __init__ (starlette/middleware/base.py:29) (6 samples, 0.10%) create_event (anyio/_backends/_asyncio.py:2446) (6 samples, 0.10%) __call__ (starlette/middleware/base.py:108) (18 samples, 0.30%) __new__ (anyio/_core/_synchronization.py:85) (11 samples, 0.19%) __init__ (<string>:4) (7 samples, 0.12%) __call__ (typing.py:1289) (9 samples, 0.15%) __new__ (anyio/_core/_streams.py:51) (15 samples, 0.25%) __call__ (starlette/middleware/base.py:189) (27 samples, 0.46%) __new__ (anyio/_core/_streams.py:52) (8 samples, 0.14%) __call__ (starlette/middleware/base.py:191) (12 samples, 0.20%) __enter__ (anyio/_backends/_asyncio.py:428) (9 samples, 0.15%) __aenter__ (anyio/_backends/_asyncio.py:731) (16 samples, 0.27%) __enter__ (anyio/_backends/_asyncio.py:425) (6 samples, 0.10%) current_task (asyncio/tasks.py:38) (6 samples, 0.10%) __aexit__ (anyio/_backends/_asyncio.py:750) (27 samples, 0.46%) __init__ (anyio/_backends/_asyncio.py:724) (6 samples, 0.10%) create_task_group (anyio/_backends/_asyncio.py:2442) (8 samples, 0.14%) __call__ (starlette/middleware/base.py:192) (64 samples, 1.08%) create_task_group (anyio/_core/_tasks.py:163) (10 samples, 0.17%) dispatch (app/middleware.py:13) (16 samples, 0.27%) uuid4 (uuid.py:723) (10 samples, 0.17%) _spawn (anyio/_backends/_asyncio.py:874) (14 samples, 0.24%) get_callable_name (anyio/_backends/_asyncio.py:348) (12 samples, 0.20%) create_task (asyncio/tasks.py:384) (14 samples, 0.24%) call_next (starlette/middleware/base.py:148) (41 samples, 0.69%) start_soon (anyio/_backends/_asyncio.py:904) (41 samples, 0.69%) _spawn (anyio/_backends/_asyncio.py:884) (18 samples, 0.30%) receive (anyio/streams/memory.py:115) (29 samples, 0.49%) checkpoint (anyio/lowlevel.py:39) (28 samples, 0.47%) receive (anyio/streams/memory.py:117) (6 samples, 0.10%) __init__ (anyio/_backends/_asyncio.py:1800) (17 samples, 0.29%) __init__ (asyncio/locks.py:168) (13 samples, 0.22%) create_event (anyio/_backends/_asyncio.py:2446) (19 samples, 0.32%) receive (anyio/streams/memory.py:120) (26 samples, 0.44%) __new__ (anyio/_core/_synchronization.py:85) (21 samples, 0.36%) __call__ (typing.py:1289) (9 samples, 0.15%) __init__ (<string>:3) (7 samples, 0.12%) get_current_task (anyio/_core/_testing.py:63) (7 samples, 0.12%) receive (anyio/streams/memory.py:121) (14 samples, 0.24%) receive (anyio/streams/memory.py:125) (22 samples, 0.37%) wait (anyio/_backends/_asyncio.py:1812) (22 samples, 0.37%) wait (asyncio/locks.py:210) (19 samples, 0.32%) _get_loop (asyncio/mixins.py:16) (16 samples, 0.27%) call_next (starlette/middleware/base.py:151) (109 samples, 1.85%) c.. dispatch (app/middleware.py:15) (164 samples, 2.78%) di.. dispatch (app/middleware.py:19) (8 samples, 0.14%) _log (logging/__init__.py:1622) (10 samples, 0.17%) makeRecord (logging/__init__.py:1601) (19 samples, 0.32%) _log (logging/__init__.py:1632) (21 samples, 0.36%) handle (logging/__init__.py:974) (9 samples, 0.15%) formatTime (logging/__init__.py:624) (6 samples, 0.10%) formatTime (logging/__init__.py:628) (6 samples, 0.10%) format (logging/__init__.py:689) (13 samples, 0.22%) format (logging/__init__.py:690) (6 samples, 0.10%) emit (logging/__init__.py:1110) (24 samples, 0.41%) format (logging/__init__.py:953) (23 samples, 0.39%) emit (logging/__init__.py:1113) (48 samples, 0.81%) dispatch (app/middleware.py:21) (126 samples, 2.13%) d.. info (logging/__init__.py:1489) (124 samples, 2.10%) i.. _log (logging/__init__.py:1634) (91 samples, 1.54%) handle (logging/__init__.py:1644) (90 samples, 1.52%) callHandlers (logging/__init__.py:1706) (86 samples, 1.46%) handle (logging/__init__.py:978) (75 samples, 1.27%) path (starlette/datastructures.py:87) (6 samples, 0.10%) dispatch (app/middleware.py:24) (15 samples, 0.25%) url (starlette/requests.py:103) (8 samples, 0.14%) __call__ (starlette/middleware/base.py:193) (346 samples, 5.86%) __call_.. _log (logging/__init__.py:1632) (8 samples, 0.14%) makeRecord (logging/__init__.py:1601) (8 samples, 0.14%) formatMessage (uvicorn/logging.py:106) (6 samples, 0.10%) formatMessage (uvicorn/logging.py:117) (10 samples, 0.17%) formatMessage (uvicorn/logging.py:65) (7 samples, 0.12%) copy (copy.py:102) (8 samples, 0.14%) copy (copy.py:82) (6 samples, 0.10%) format (logging/__init__.py:690) (48 samples, 0.81%) formatMessage (uvicorn/logging.py:98) (28 samples, 0.47%) copy (copy.py:92) (10 samples, 0.17%) emit (logging/__init__.py:1110) (57 samples, 0.97%) format (logging/__init__.py:953) (56 samples, 0.95%) send (uvicorn/protocols/http/httptools_impl.py:476) (98 samples, 1.66%) info (logging/__init__.py:1489) (96 samples, 1.63%) _log (logging/__init__.py:1634) (83 samples, 1.41%) handle (logging/__init__.py:1644) (83 samples, 1.41%) callHandlers (logging/__init__.py:1706) (82 samples, 1.39%) handle (logging/__init__.py:978) (80 samples, 1.36%) emit (logging/__init__.py:1114) (22 samples, 0.37%) flush (logging/__init__.py:1094) (22 samples, 0.37%) send (uvicorn/protocols/http/httptools_impl.py:480) (10 samples, 0.17%) get_path_with_query_string (uvicorn/protocols/utils.py:53) (9 samples, 0.15%) __call__ (starlette/middleware/base.py:223) (131 samples, 2.22%) _.. _send (starlette/middleware/errors.py:161) (129 samples, 2.18%) _.. receive (anyio/streams/memory.py:115) (9 samples, 0.15%) checkpoint (anyio/lowlevel.py:39) (9 samples, 0.15%) body_stream (starlette/middleware/base.py:174) (16 samples, 0.27%) __anext__ (anyio/abc/_streams.py:41) (16 samples, 0.27%) receive (anyio/streams/memory.py:117) (6 samples, 0.10%) __call__ (starlette/middleware/base.py:232) (28 samples, 0.47%) __call__ (starlette/middleware/base.py:238) (9 samples, 0.15%) _send (starlette/middleware/errors.py:161) (8 samples, 0.14%) __call__ (starlette/middleware/base.py:194) (180 samples, 3.05%) __c.. __call__ (starlette/middleware/base.py:241) (11 samples, 0.19%) _send (starlette/middleware/errors.py:161) (11 samples, 0.19%) send (uvicorn/protocols/http/httptools_impl.py:549) (10 samples, 0.17%) on_response_complete (uvicorn/protocols/http/httptools_impl.py:332) (9 samples, 0.15%) run_asgi (uvicorn/protocols/http/httptools_impl.py:409) (769 samples, 13.03%) run_asgi (uvicorn/pr.. __call__ (uvicorn/middleware/proxy_headers.py:60) (734 samples, 12.43%) __call__ (uvicorn/m.. __call__ (fastapi/applications.py:1139) (732 samples, 12.40%) __call__ (fastapi/a.. __call__ (starlette/applications.py:107) (729 samples, 12.35%) __call__ (starlett.. __call__ (starlette/middleware/errors.py:164) (723 samples, 12.25%) __call__ (starlett.. __call__ (starlette/middleware/cors.py:85) (684 samples, 11.59%) __call__ (starlet.. run_asgi (uvicorn/protocols/http/httptools_impl.py:414) (6 samples, 0.10%) error (logging/__init__.py:1518) (6 samples, 0.10%) <module> (uvicorn:8) (5,075 samples, 85.96%) <module> (uvicorn:8) __call__ (click/core.py:1485) (5,075 samples, 85.96%) __call__ (click/core.py:1485) main (click/core.py:1406) (5,075 samples, 85.96%) main (click/core.py:1406) invoke (click/core.py:1269) (5,075 samples, 85.96%) invoke (click/core.py:1269) invoke (click/core.py:824) (5,075 samples, 85.96%) invoke (click/core.py:824) main (uvicorn/main.py:471) (5,075 samples, 85.96%) main (uvicorn/main.py:471) run (uvicorn/main.py:593) (5,075 samples, 85.96%) run (uvicorn/main.py:593) run (uvicorn/server.py:67) (5,075 samples, 85.96%) run (uvicorn/server.py:67) asyncio_run (uvicorn/_compat.py:30) (5,075 samples, 85.96%) asyncio_run (uvicorn/_compat.py:30) run (asyncio/runners.py:118) (5,075 samples, 85.96%) run (asyncio/runners.py:118) wrap (starlette/middleware/base.py:120) (8 samples, 0.14%) wait (anyio/_backends/_asyncio.py:1812) (8 samples, 0.14%) expunge_all (sqlalchemy/orm/session.py:2588) (7 samples, 0.12%) _detach_states (sqlalchemy/orm/state.py:474) (12 samples, 0.20%) _close_impl (sqlalchemy/orm/session.py:2573) (33 samples, 0.56%) expunge_all (sqlalchemy/orm/session.py:2592) (17 samples, 0.29%) close (sqlalchemy/orm/session.py:1417) (24 samples, 0.41%) close (sqlalchemy/engine/base.py:2597) (24 samples, 0.41%) _do_close (sqlalchemy/engine/base.py:2735) (23 samples, 0.39%) _close_impl (sqlalchemy/engine/base.py:2721) (23 samples, 0.39%) _connection_rollback_impl (sqlalchemy/engine/base.py:2713) (23 samples, 0.39%) _rollback_impl (sqlalchemy/engine/base.py:1128) (14 samples, 0.24%) do_rollback (sqlalchemy/engine/default.py:712) (11 samples, 0.19%) _finalize_fairy (sqlalchemy/pool/base.py:1034) (16 samples, 0.27%) checkin (sqlalchemy/pool/base.py:771) (13 samples, 0.22%) _return_conn (sqlalchemy/pool/base.py:456) (13 samples, 0.22%) _do_return_conn (sqlalchemy/pool/impl.py:144) (12 samples, 0.20%) put (sqlalchemy/util/queue.py:289) (12 samples, 0.20%) put_nowait (sqlalchemy/util/queue.py:281) (11 samples, 0.19%) _checkin (sqlalchemy/pool/base.py:1386) (30 samples, 0.51%) close (sqlalchemy/engine/base.py:1258) (32 samples, 0.54%) close (sqlalchemy/pool/base.py:1511) (31 samples, 0.53%) close (sqlalchemy/orm/session.py:1419) (34 samples, 0.58%) close (sqlalchemy/orm/session.py:2507) (128 samples, 2.17%) c.. _close_impl (sqlalchemy/orm/session.py:2576) (77 samples, 1.30%) close (<string>:2) (77 samples, 1.30%) _go (sqlalchemy/orm/state_changes.py:137) (75 samples, 1.27%) close (sqlalchemy/orm/session.py:1432) (8 samples, 0.14%) expect (sqlalchemy/sql/coercions.py:416) (6 samples, 0.10%) _execute_internal (sqlalchemy/orm/session.py:2147) (10 samples, 0.17%) __get__ (sqlalchemy/event/base.py:451) (16 samples, 0.27%) _for_instance (sqlalchemy/event/base.py:185) (9 samples, 0.15%) _for_class (sqlalchemy/event/base.py:181) (7 samples, 0.12%) _execute_internal (sqlalchemy/orm/session.py:2174) (25 samples, 0.42%) orm_pre_session_exec (sqlalchemy/orm/context.py:506) (7 samples, 0.12%) orm_pre_session_exec (sqlalchemy/orm/context.py:507) (6 samples, 0.10%) _execute_internal (sqlalchemy/orm/session.py:2228) (28 samples, 0.47%) __init__ (weakref.py:369) (7 samples, 0.12%) _take_snapshot (sqlalchemy/orm/session.py:1089) (9 samples, 0.15%) __init__ (sqlalchemy/orm/session.py:958) (32 samples, 0.54%) _take_snapshot (sqlalchemy/orm/session.py:1092) (11 samples, 0.19%) _connection_for_bind (sqlalchemy/orm/session.py:2107) (45 samples, 0.76%) _autobegin_t (sqlalchemy/orm/session.py:1879) (44 samples, 0.75%) get (sqlalchemy/util/queue.py:311) (12 samples, 0.20%) await_only (sqlalchemy/util/_concurrency_py3k.py:132) (7 samples, 0.12%) _do_get (sqlalchemy/pool/impl.py:156) (21 samples, 0.36%) get (sqlalchemy/util/queue.py:312) (7 samples, 0.12%) checkout (sqlalchemy/pool/base.py:711) (27 samples, 0.46%) _checkout (sqlalchemy/pool/base.py:1264) (40 samples, 0.68%) _checkout (sqlalchemy/pool/base.py:1301) (9 samples, 0.15%) _do_ping_w_event (sqlalchemy/engine/default.py:729) (7 samples, 0.12%) do_ping (sqlalchemy/dialects/postgresql/asyncpg.py:1160) (6 samples, 0.10%) ping (sqlalchemy/dialects/postgresql/asyncpg.py:816) (6 samples, 0.10%) __init__ (sqlalchemy/engine/base.py:143) (63 samples, 1.07%) raw_connection (sqlalchemy/engine/base.py:3309) (62 samples, 1.05%) connect (sqlalchemy/pool/base.py:447) (58 samples, 0.98%) _checkout (sqlalchemy/pool/base.py:1318) (7 samples, 0.12%) __get__ (sqlalchemy/event/base.py:451) (6 samples, 0.10%) _connection_for_bind (sqlalchemy/orm/session.py:1187) (84 samples, 1.42%) connect (sqlalchemy/engine/base.py:3285) (84 samples, 1.42%) __init__ (sqlalchemy/engine/base.py:164) (12 samples, 0.20%) _connection_for_bind (sqlalchemy/orm/session.py:1256) (6 samples, 0.10%) _go (sqlalchemy/orm/state_changes.py:137) (106 samples, 1.80%) _.. _execute_internal (sqlalchemy/orm/session.py:2239) (156 samples, 2.64%) _e.. _connection_for_bind (sqlalchemy/orm/session.py:2108) (109 samples, 1.85%) _.. _connection_for_bind (<string>:2) (109 samples, 1.85%) _.. _execute_on_connection (sqlalchemy/sql/elements.py:524) (8 samples, 0.14%) _gen_cache_key (sqlalchemy/sql/cache_key.py:246) (6 samples, 0.10%) _generated_cache_key_traversal (<string>:24) (23 samples, 0.39%) _generated_cache_key_traversal (<string>:31) (7 samples, 0.12%) __get__ (sqlalchemy/util/langhelpers.py:1226) (6 samples, 0.10%) _gen_cache_key (sqlalchemy/sql/cache_key.py:272) (45 samples, 0.76%) _gen_cache_key (sqlalchemy/sql/cache_key.py:272) (8 samples, 0.14%) _gen_cache_key (sqlalchemy/sql/cache_key.py:244) (11 samples, 0.19%) _gen_cache_key (sqlalchemy/sql/cache_key.py:293) (34 samples, 0.58%) _gen_cache_key (sqlalchemy/sql/cache_key.py:335) (53 samples, 0.90%) <listcomp> (sqlalchemy/sql/cache_key.py:336) (52 samples, 0.88%) _generate_cache_key (sqlalchemy/sql/cache_key.py:382) (116 samples, 1.96%) _.. oneshot (sqlalchemy/util/langhelpers.py:1351) (126 samples, 2.13%) o.. _generate_cache_key (sqlalchemy/sql/cache_key.py:413) (123 samples, 2.08%) _.. _compile_w_cache (sqlalchemy/sql/elements.py:695) (138 samples, 2.34%) _.. get (sqlalchemy/util/_collections.py:531) (10 samples, 0.17%) _execute_clauseelement (sqlalchemy/engine/base.py:1633) (154 samples, 2.61%) _e.. _compile_w_cache (sqlalchemy/sql/elements.py:712) (12 samples, 0.20%) _execute_clauseelement (sqlalchemy/engine/base.py:1639) (6 samples, 0.10%) _init_compiled (sqlalchemy/engine/default.py:1416) (6 samples, 0.10%) <listcomp> (sqlalchemy/engine/default.py:1417) (6 samples, 0.10%) _init_compiled (sqlalchemy/engine/default.py:1438) (8 samples, 0.14%) _execute_context (sqlalchemy/engine/base.py:1815) (27 samples, 0.46%) execute (sqlalchemy/dialects/postgresql/asyncpg.py:585) (17 samples, 0.29%) await_only (sqlalchemy/util/_concurrency_py3k.py:132) (14 samples, 0.24%) _exec_single_context (sqlalchemy/engine/base.py:1967) (19 samples, 0.32%) do_execute (sqlalchemy/engine/default.py:952) (18 samples, 0.30%) _setup_result_proxy (sqlalchemy/engine/default.py:1875) (14 samples, 0.24%) orm_execute_statement (sqlalchemy/orm/context.py:306) (261 samples, 4.42%) orm_e.. execute (sqlalchemy/engine/base.py:1419) (258 samples, 4.37%) execu.. _execute_on_connection (sqlalchemy/sql/elements.py:527) (250 samples, 4.23%) _exec.. _execute_clauseelement (sqlalchemy/engine/base.py:1641) (81 samples, 1.37%) _execute_context (sqlalchemy/engine/base.py:1846) (49 samples, 0.83%) _exec_single_context (sqlalchemy/engine/base.py:1983) (23 samples, 0.39%) orm_setup_cursor_result (sqlalchemy/orm/context.py:606) (8 samples, 0.14%) _instance_processor (sqlalchemy/orm/loading.py:812) (24 samples, 0.41%) _instance_processor (sqlalchemy/orm/loading.py:975) (7 samples, 0.12%) row_processor (sqlalchemy/orm/context.py:2786) (47 samples, 0.80%) instances (sqlalchemy/orm/loading.py:114) (52 samples, 0.88%) <listcomp> (sqlalchemy/orm/loading.py:115) (51 samples, 0.86%) __init__ (sqlalchemy/engine/result.py:281) (8 samples, 0.14%) instances (sqlalchemy/orm/loading.py:204) (17 samples, 0.29%) _raw_all_rows (sqlalchemy/engine/result.py:540) (7 samples, 0.12%) _fetchall_impl (sqlalchemy/engine/cursor.py:2252) (6 samples, 0.10%) chunks (sqlalchemy/orm/loading.py:220) (25 samples, 0.42%) _raw_all_rows (sqlalchemy/engine/result.py:541) (18 samples, 0.30%) <listcomp> (sqlalchemy/engine/result.py:541) (17 samples, 0.29%) __init__ (sqlalchemy/orm/state.py:204) (31 samples, 0.53%) new_instance (sqlalchemy/orm/instrumentation.py:509) (50 samples, 0.85%) __init__ (sqlalchemy/orm/state.py:206) (10 samples, 0.17%) _instance (sqlalchemy/orm/loading.py:1115) (54 samples, 0.91%) _instance (sqlalchemy/orm/loading.py:1117) (6 samples, 0.10%) _instance (sqlalchemy/orm/loading.py:1146) (7 samples, 0.12%) instances (sqlalchemy/orm/loading.py:262) (103 samples, 1.74%) chunks (sqlalchemy/orm/loading.py:224) (74 samples, 1.25%) <listcomp> (sqlalchemy/orm/loading.py:224) (74 samples, 1.25%) all (5,904 samples, 100%) execute (sqlalchemy/orm/session.py:2351) (697 samples, 11.81%) execute (sqlalchem.. _execute_internal (sqlalchemy/orm/session.py:2249) (466 samples, 7.89%) _execute_in.. orm_execute_statement (sqlalchemy/orm/context.py:309) (204 samples, 3.46%) orm.. orm_setup_cursor_result (sqlalchemy/orm/context.py:616) (188 samples, 3.18%) orm..
\ No newline at end of file
diff --git a/scripts/mock_data.py b/scripts/mock_data.py
index 8723974..b94ef9f 100644
--- a/scripts/mock_data.py
+++ b/scripts/mock_data.py
@@ -4,9 +4,12 @@
import time
BASE_URL = "http://localhost:8000"
+API_KEY = "" # 발급받은 API Key 입력 (POST /api/robots, /api/sensors 필요)
TOTAL_ROBOT = 10
TOTAL_REQUESTS = 1000
+HEADERS = {"X-API-Key": API_KEY} if API_KEY else {}
+
def create_robot() :
"""로봇 10대 생성"""
print("로봇 생성")
@@ -21,7 +24,8 @@ def create_robot() :
response = requests.post(
f"{BASE_URL}/api/robots",
- json = robot_data
+ json = robot_data,
+ headers = HEADERS,
)
if response.status_code == 201 :
@@ -96,6 +100,7 @@ def create_sensor_data() :
response = requests.post(
f"{BASE_URL}/api/sensors",
json=payload,
+ headers=HEADERS,
timeout=5
)
diff --git a/test/conftest.py b/test/conftest.py
index f5e4714..faee725 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -156,9 +156,22 @@ async def override_get_db():
app.dependency_overrides[get_db] = override_get_db
# get_redis는 직접 호출이므로 모듈 변수 교체
- fake_redis = FakeRedis()
+ # TEST_REDIS_HOST 있으면 실제 Redis(CI), 없으면 FakeRedis(로컬 기본)
+ test_redis_host = os.getenv("TEST_REDIS_HOST")
+ if test_redis_host:
+ from redis.asyncio import Redis
+ test_redis = Redis(
+ host=test_redis_host,
+ port=int(os.getenv("TEST_REDIS_PORT", "6379")),
+ db=0,
+ decode_responses=True,
+ encoding="utf-8",
+ )
+ await test_redis.flushdb()
+ else:
+ test_redis = FakeRedis()
original_redis = redis_module.redis_client
- redis_module.redis_client = fake_redis
+ redis_module.redis_client = test_redis
# asyncpg_pool은 health_check에서 사용 — mock 주입
mock_pool = MagicMock()
@@ -183,5 +196,8 @@ async def override_get_db():
# 정리
app.dependency_overrides.clear()
+ if test_redis_host:
+ await test_redis.flushdb()
+ await test_redis.aclose()
redis_module.redis_client = original_redis
db_module.asyncpg_pool = original_pool
diff --git a/test/locustfile.py b/test/locustfile.py
index 2d1a487..951608f 100644
--- a/test/locustfile.py
+++ b/test/locustfile.py
@@ -1,8 +1,11 @@
from locust import HttpUser, task, constant
import random
import time
+import os
from datetime import datetime
+API_KEY = os.getenv("LOCUST_API_KEY", "8_pE3-Gxz788YhXeX7lZUKt9W4AH9wVe6LZFCuq-f10")
+
def generate_sensor_payload(robot_id, timestamp) :
"""페이로드 생성"""
@@ -56,6 +59,7 @@ class User(HttpUser) :
def on_start(self) :
"""시작 시 로봇 ID 할당"""
+ self.client.headers.update({"X-API-Key": API_KEY})
self.robot_id = random.randint(1, 10)
@task(100)
diff --git a/test/puttest.py b/test/puttest.py
deleted file mode 100644
index c64416d..0000000
--- a/test/puttest.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# test_put.py 만들어서 실행
-import requests
-
-# 1. 로봇 1번 현재 상태 확인
-response = requests.get("http://localhost/api/robots/1")
-print("변경 전:", response.json())
-
-# 2. PUT으로 상태 변경
-response = requests.put(
- "http://localhost/api/robots/1",
- json={
- "status": "maintenance",
- "battery_level": 50
- }
-)
-print("PUT 응답:", response.status_code, response.text)
-
-# 3. 다시 확인
-response = requests.get("http://localhost/api/robots/1")
-print("변경 후:", response.json())
\ No newline at end of file
diff --git a/test/test_single_request.py b/test/test_single_request.py
deleted file mode 100644
index e318efd..0000000
--- a/test/test_single_request.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# 성능 확인용
-
-import requests
-import time
-
-url = "http://localhost:8000/api/sensors"
-
-data = {
- "robot_id": 1,
- "timestamp": "2024-01-04T10:30:00",
- "sensors": [
- {
- "sensor_type": "IMU",
- "data": {
- "acceleration": {"x": 1.2, "y": -0.5, "z": 9.8},
- "gyroscope": {"x": 0.1, "y": 0.2, "z": 0.3}
- }
- },
- {
- "sensor_type": "GPS",
- "data": {
- "latitude": 37.5665,
- "longitude": 126.9780
- }
- },
- {
- "sensor_type": "LiDAR",
- "data": {
- "distance": 3.5,
- "angle": 45
- }
- }
- ]
-}
-
-print("10번 연속 요청 시작...\n")
-
-times = []
-for i in range(10):
- start = time.time()
- response = requests.post(url, json=data)
- elapsed = (time.time() - start) * 1000
- times.append(elapsed)
- print(f"{i+1}번째 요청: {elapsed:.2f}ms (응답: {response.status_code})")
-
-print(f"\n평균: {sum(times) / len(times):.2f}ms")
-print(f"최소: {min(times):.2f}ms")
-print(f"최대: {max(times):.2f}ms")
\ No newline at end of file