Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/python_response_time/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
class Settings(BaseSettings):
"""Configuration settings for the HTTP benchmark."""

LOG_TO_STDOUT: Annotated[
bool, Field(description="Whether to log to stdout (console)", strict=False)
] = True

TARGET_URL: Annotated[
str, Field(description="Target endpoint for benchmarking")
] = "https://httpbin.org/get"
list[str], Field(description="List of target endpoints for benchmarking")
] = ["https://httpbin.org/get", "https://httpbin.org/status/200"]
NUM_REQUESTS: Annotated[
int, Field(gt=0, le=1_000_000, description="Total number of requests")
] = 10
Expand All @@ -23,7 +27,7 @@ class Settings(BaseSettings):
] = 3.0
REQUEST_DELAY: Annotated[
float, Field(gt=0, le=60, description="Delay between requests in seconds")
] = 0.1
] = 2.0
LOG_LEVEL: Annotated[
str, Field(pattern="^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$")
] = "INFO"
Expand Down
6 changes: 4 additions & 2 deletions src/python_response_time/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ def setup_logger(level: str = "INFO"):
logger: Configured Loguru logger instance.

"""
from .config import app_settings

logger.remove()
if level.upper() == "SILENT":
if not getattr(app_settings, "LOG_TO_STDOUT", True):
logger.add(
"app.log",
serialize=True,
level="ERROR",
level=level,
rotation="1 MB",
retention="10 days",
compression="zip",
Expand Down
146 changes: 77 additions & 69 deletions src/python_response_time/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
def run_app(console: Console, shutdown_event: Event) -> None:
"""Run the main benchmark loop."""
console.print("[bold cyan]HTTP Benchmark Starting...[/bold cyan]")
console.print(f"Target: {app_settings.TARGET_URL}")
console.print(f"Targets: {app_settings.TARGET_URL}")
console.print(f"Requests: {app_settings.NUM_REQUESTS}")
console.print(f"Delay: {app_settings.REQUEST_DELAY}s")
console.print(f"SSL Verify: {app_settings.VERIFY_SSL}\n")
Expand All @@ -34,76 +34,84 @@ def run_app(console: Console, shutdown_event: Event) -> None:
for i in range(app_settings.NUM_REQUESTS):
if shutdown_event.is_set():
break
request_id = i + 1
logger.info({"event": "request_start", "request": request_id})
start_time = time.perf_counter()
try:
response = session.get(
str(app_settings.TARGET_URL),
timeout=(
app_settings.CONNECT_TIMEOUT,
app_settings.READ_TIMEOUT,
),
verify=app_settings.VERIFY_SSL,
)
elapsed = time.perf_counter() - start_time
console.print(
f"{request_id:>4} | "
f"{response.status_code} | "
f"{elapsed * 1000:.2f} ms"
)
for url in app_settings.TARGET_URL:
request_id = f"{i + 1}-{url}"
logger.info(
{
"event": "request_complete",
"request": request_id,
"status": response.status_code,
"response_time_ms": elapsed * 1000,
}
)
REQUEST_COUNT.labels(status=str(response.status_code)).inc()
REQUEST_LATENCY.labels(status=str(response.status_code)).observe(
elapsed
)
except ConnectTimeout:
console.print(f"{request_id:>4} | CONNECT_TIMEOUT")
logger.warning(
{
"event": "connect_timeout",
"request": request_id,
}
)
REQUEST_COUNT.labels(status="connect_timeout").inc()
except ReadTimeout:
console.print(f"{request_id:>4} | READ_TIMEOUT")
logger.warning(
{
"event": "read_timeout",
"request": request_id,
}
)
REQUEST_COUNT.labels(status="read_timeout").inc()
except SSLError as e:
console.print(f"{request_id:>4} | SSL_ERROR")
logger.error(
{
"event": "ssl_error",
"request": request_id,
"details": str(e),
}
)
REQUEST_COUNT.labels(status="ssl_error").inc()
except RequestException as e:
console.print(f"{request_id:>4} | ERROR")
logger.error(
{
"event": "request_error",
"request": request_id,
"details": str(e),
}
{"event": "request_start", "request": request_id, "url": url}
)
REQUEST_COUNT.labels(status="error").inc()
if app_settings.REQUEST_DELAY > 0:
sleep_interruptible(app_settings.REQUEST_DELAY, shutdown_event)
start_time = time.perf_counter()
try:
response = session.get(
str(url),
timeout=(
app_settings.CONNECT_TIMEOUT,
app_settings.READ_TIMEOUT,
),
verify=app_settings.VERIFY_SSL,
)
elapsed = time.perf_counter() - start_time
console.print(
f"{request_id:>8} | "
f"{response.status_code} | "
f"{elapsed * 1000:.2f} ms"
)
logger.info(
{
"event": "request_complete",
"request": request_id,
"url": url,
"status": response.status_code,
"response_time_ms": elapsed * 1000,
}
)
REQUEST_COUNT.labels(status=str(response.status_code)).inc()
REQUEST_LATENCY.labels(status=str(response.status_code)).observe(
elapsed
)
except ConnectTimeout:
console.print(f"{request_id:>8} | CONNECT_TIMEOUT")
logger.warning(
{
"event": "connect_timeout",
"request": request_id,
"url": url,
}
)
REQUEST_COUNT.labels(status="connect_timeout").inc()
except ReadTimeout:
console.print(f"{request_id:>8} | READ_TIMEOUT")
logger.warning(
{
"event": "read_timeout",
"request": request_id,
"url": url,
}
)
REQUEST_COUNT.labels(status="read_timeout").inc()
except SSLError as e:
console.print(f"{request_id:>8} | SSL_ERROR")
logger.error(
{
"event": "ssl_error",
"request": request_id,
"url": url,
"details": str(e),
}
)
REQUEST_COUNT.labels(status="ssl_error").inc()
except RequestException as e:
console.print(f"{request_id:>8} | ERROR")
logger.error(
{
"event": "request_error",
"request": request_id,
"url": url,
"details": str(e),
}
)
REQUEST_COUNT.labels(status="error").inc()
if app_settings.REQUEST_DELAY > 0:
sleep_interruptible(app_settings.REQUEST_DELAY, shutdown_event)
finally:
session.close()
console.print("\n[green]Benchmark stopped gracefully[/green]")
Expand Down
11 changes: 6 additions & 5 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from python_response_time.core import app_settings


def test_target_url():
def test_target_urls():
"""TARGET_URL should be a non-empty string starting with http(s)."""
url = app_settings.TARGET_URL
assert isinstance(url, str)
assert url.startswith("http")
assert len(url) > 0
urls = app_settings.TARGET_URL
assert isinstance(urls, list)
assert all(isinstance(url, str) for url in urls)
assert all(url.startswith("http") for url in urls)
assert all(len(url) > 0 for url in urls)


def test_num_requests():
Expand Down
Loading
Loading