From: Sam Mirazi Date: Mon, 2 Jun 2025 04:17:51 +0000 (-0700) Subject: v14 X-Git-Url: https://git.josue.xyz/?a=commitdiff_plain;h=8f1d5e9b9eec6256cad38578c991ddd75515767f;p=fastapi-vs-flask%2F.git v14 --- diff --git a/app_fastapi/__pycache__/app.cpython-312.pyc b/app_fastapi/__pycache__/app.cpython-312.pyc index b0f8d18..1e2f5ce 100644 Binary files a/app_fastapi/__pycache__/app.cpython-312.pyc and b/app_fastapi/__pycache__/app.cpython-312.pyc differ diff --git a/run_benchmark_table.py b/run_benchmark_table.py new file mode 100644 index 0000000..0bb3e7f --- /dev/null +++ b/run_benchmark_table.py @@ -0,0 +1,240 @@ +import subprocess +import time +import re +import requests # pip install requests +from rich.console import Console +from rich.table import Table +import sys +import os + +# --- Configuration ------------------------------------------------------ +FLASK_SERVER_URL = "http://127.0.0.1:3000/" +FASTAPI_SERVER_URL = "http://127.0.0.1:8000/" +BENCHMARK_SCRIPT_PATH = "benchmark/run_benchmark.py" +NUM_REQUESTS_EXPECTED = 100 +PYTHON_EXE = sys.executable + +# ------------------------------------------------------------------------ +console = Console() + +# -------------------------- helpers ------------------------------------- +def start_server(command_args, health_check_url, server_name, cwd=None): + """Start server and wait until a 200 health check is returned.""" + console.print(f"[yellow]Starting {server_name} server…[/yellow]") + + # --- STREAM HANDLING: inherit console so the child can always write + popen_kwargs = dict(cwd=cwd, text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + + # run either as "python -m uvicorn ..." or plain exe + if "uvicorn" in command_args[0] and not command_args[0].endswith(".exe"): + process = subprocess.Popen([PYTHON_EXE, "-m"] + command_args, **popen_kwargs) + else: + process = subprocess.Popen(command_args, **popen_kwargs) + + max_wait = 30 + start_t = time.time() + while time.time() - start_t < max_wait: + try: + if requests.get(health_check_url, timeout=3).status_code == 200: + console.print(f"[green]{server_name} ready.[/green]") + return process + except requests.RequestException: + time.sleep(0.3) + console.print(f"[red]{server_name} failed to start within {max_wait}s.[/red]") + process.terminate() + return None + +def stop_server(proc, name): + if not proc: + return + console.print(f"[yellow]Stopping {name}…[/yellow]") + proc.terminate() + try: + proc.wait(timeout=8) + except subprocess.TimeoutExpired: + proc.kill() + console.print(f"[green]{name} stopped.[/green]") + +def run_benchmark_script(framework_arg): + console.print(f"Running benchmark for [bold]{framework_arg}[/bold]…") + cmd = [PYTHON_EXE, BENCHMARK_SCRIPT_PATH, framework_arg] + + if framework_arg.lower() == "flask": + final_summary_line = None + requests_done_count = 0 + progress_line_printed = False + try: + # Ensure encoding is specified for Popen for consistent text handling + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True, encoding='utf-8') + + if process.stdout: + for line in iter(process.stdout.readline, ''): + line = line.strip() + if not line: + continue + + if line.startswith("REQ_STATUS:"): + requests_done_count += 1 + # Using carriage return to update the line in place + print(f"\rFlask progress: Handled {requests_done_count}/{NUM_REQUESTS_EXPECTED} requests...", end="", flush=True) + progress_line_printed = True + elif line.startswith("[DIAG-BRB-FLASK]"): + if progress_line_printed: + # Clear the progress line before printing diagnostic output + print("\r" + " " * 80 + "\r", end="", flush=True) + print(line, flush=True) # Print diagnostic line + if progress_line_printed: + # Reprint the progress line after diagnostic output + print(f"\rFlask progress: Handled {requests_done_count}/{NUM_REQUESTS_EXPECTED} requests...", end="", flush=True) + elif "Final Flask benchmark summary:" in line: + final_summary_line = line + if progress_line_printed: + # Clear the progress line before finishing + print("\r" + " " * 80 + "\r", end="", flush=True) + # The summary line itself will be printed by the main logic if needed, or parsed + + process.stdout.close() + + # After the loop, if progress was printed, clear it finally + # This handles cases where the process ends without a final summary line immediately after progress + if progress_line_printed and not final_summary_line: + print("\r" + " " * 80 + "\r", end="", flush=True) + + stderr_output_list = [] + if process.stderr: + for line in iter(process.stderr.readline, ''): + line = line.strip() + if line: + stderr_output_list.append(line) + process.stderr.close() + + process.wait(timeout=600) + + if process.returncode != 0: + console.print(f"[red]{framework_arg} benchmark script failed with return code {process.returncode}[/red]") + if stderr_output_list: + console.print("[red]STDERR:[/red]") + for err_line in stderr_output_list: + console.print(f"[red]{err_line}[/red]") + return None + + if final_summary_line: + return final_summary_line + else: + console.print(f"[red]Could not find the final summary line for {framework_arg} in Popen benchmark output.[/red]") + if stderr_output_list: + console.print("[red]STDERR output during Popen execution was:[/red]") + for err_line in stderr_output_list: + console.print(f"[red]{err_line}[/red]") + return None + + except subprocess.TimeoutExpired: + console.print(f"[red]Benchmark for {framework_arg} (Popen path) timed out.[/red]") + if process.poll() is None: # Check if process is still running + process.kill() + process.wait() + return None + except Exception as e: + console.print(f"[red]An unexpected error occurred while running Popen benchmark for {framework_arg}: {e}[/red]") + return None + + else: # For FastAPI or any other framework not needing live progress + try: + result = subprocess.run(cmd, text=True, capture_output=True, timeout=600, check=False, encoding='utf-8') + if result.returncode != 0: + console.print(f"[red]{framework_arg} benchmark failed with subprocess.run.[/red]") + if result.stderr: + console.print(f"STDERR:\n{result.stderr.strip()}") + return None + + if result.stdout and result.stdout.strip(): + lines = result.stdout.strip().splitlines() + if lines: + return lines[-1] # Return the last line, expected to be the summary + else: + console.print(f"[red]No lines in stdout from {framework_arg} benchmark script (subprocess.run path).[/red]") + return None + else: + console.print(f"[red]No stdout from {framework_arg} benchmark script (subprocess.run path).[/red]") + if result.stderr and result.stderr.strip(): + console.print(f"STDERR:\n{result.stderr.strip()}") + return None + except subprocess.TimeoutExpired: + console.print(f"[red]Benchmark for {framework_arg} (subprocess.run path) timed out.[/red]") + return None + except Exception as e: + console.print(f"[red]An unexpected error occurred while running subprocess.run benchmark for {framework_arg}: {e}[/red]") + return None + +def parse_benchmark(line): + m = re.search(r"(\d+)/(\d+) successful requests in ([\d.]+) seconds", line) + if not m: + return None + succ, total, tsec = map(float, m.groups()) + return {"successful": f"{int(succ)}/{int(total)}", "total_time": tsec} + +def display_table(rows): + tbl = Table(title="Benchmark Summary", show_lines=True, header_style="bold magenta") + tbl.add_column("Framework", style="cyan") + tbl.add_column("Server", style="white") + tbl.add_column("Delay", style="green") + tbl.add_column("#Reqs", justify="right") + tbl.add_column("Success", justify="right") + tbl.add_column("Total s", justify="right", style="yellow") + tbl.add_column("Avg s/req", justify="right", style="blue") + for r in rows: + avg = r["total_time"] / NUM_REQUESTS_EXPECTED + tbl.add_row(r["framework"], r["config"], r["delay"], + str(NUM_REQUESTS_EXPECTED), r["successful"], + f"{r['total_time']:.2f}", f"{avg:.3f}") + console.print(tbl) + +# --------------------------- scenarios ---------------------------------- +SCENARIOS = [ + { + "name": "FastAPI", + "config": "Uvicorn, async", + "delay": "0.3 s asyncio.sleep", + "cmd": ["uvicorn", "app_fastapi.app:app", "--host", "0.0.0.0", + "--port", "8000", "--log-level", "warning"], + "url": FASTAPI_SERVER_URL, + "bench_arg": "fastapi", + }, + { + "name": "Flask", + "config": "Single-threaded", + "delay": "0.3 s time.sleep", + "cmd": [PYTHON_EXE, "app_flask/flask_application.py"], + "url": FLASK_SERVER_URL, + "bench_arg": "flask", + } +] + +# ----------------------------- main ------------------------------------- +if __name__ == "__main__": + console.print("[bold underline]Automated Web Framework Benchmark[/bold underline]\n") + results = [] + root = os.getcwd() + + for i, sc in enumerate(SCENARIOS, 1): + console.rule(f"[cyan]Scenario {i}/{len(SCENARIOS)} – {sc['name']}[/cyan]") + srv = start_server(sc["cmd"], sc["url"], sc["name"], cwd=root) + if not srv: + continue + try: + if sc["name"].lower() == "flask": + time.sleep(2) # tiny grace period + line = run_benchmark_script(sc["bench_arg"]) + parsed = parse_benchmark(line) if line else None + if parsed: + results.append({"framework": sc["name"], "config": sc["config"], + "delay": sc["delay"], **parsed}) + finally: + stop_server(srv, sc["name"]) + console.print() + + if results: + display_table(results) + console.print("\n[bold]Benchmark run finished.[/bold]") \ No newline at end of file diff --git a/show_benchmark_table.py b/show_benchmark_table.py deleted file mode 100644 index 0bb3e7f..0000000 --- a/show_benchmark_table.py +++ /dev/null @@ -1,240 +0,0 @@ -import subprocess -import time -import re -import requests # pip install requests -from rich.console import Console -from rich.table import Table -import sys -import os - -# --- Configuration ------------------------------------------------------ -FLASK_SERVER_URL = "http://127.0.0.1:3000/" -FASTAPI_SERVER_URL = "http://127.0.0.1:8000/" -BENCHMARK_SCRIPT_PATH = "benchmark/run_benchmark.py" -NUM_REQUESTS_EXPECTED = 100 -PYTHON_EXE = sys.executable - -# ------------------------------------------------------------------------ -console = Console() - -# -------------------------- helpers ------------------------------------- -def start_server(command_args, health_check_url, server_name, cwd=None): - """Start server and wait until a 200 health check is returned.""" - console.print(f"[yellow]Starting {server_name} server…[/yellow]") - - # --- STREAM HANDLING: inherit console so the child can always write - popen_kwargs = dict(cwd=cwd, text=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT) - - # run either as "python -m uvicorn ..." or plain exe - if "uvicorn" in command_args[0] and not command_args[0].endswith(".exe"): - process = subprocess.Popen([PYTHON_EXE, "-m"] + command_args, **popen_kwargs) - else: - process = subprocess.Popen(command_args, **popen_kwargs) - - max_wait = 30 - start_t = time.time() - while time.time() - start_t < max_wait: - try: - if requests.get(health_check_url, timeout=3).status_code == 200: - console.print(f"[green]{server_name} ready.[/green]") - return process - except requests.RequestException: - time.sleep(0.3) - console.print(f"[red]{server_name} failed to start within {max_wait}s.[/red]") - process.terminate() - return None - -def stop_server(proc, name): - if not proc: - return - console.print(f"[yellow]Stopping {name}…[/yellow]") - proc.terminate() - try: - proc.wait(timeout=8) - except subprocess.TimeoutExpired: - proc.kill() - console.print(f"[green]{name} stopped.[/green]") - -def run_benchmark_script(framework_arg): - console.print(f"Running benchmark for [bold]{framework_arg}[/bold]…") - cmd = [PYTHON_EXE, BENCHMARK_SCRIPT_PATH, framework_arg] - - if framework_arg.lower() == "flask": - final_summary_line = None - requests_done_count = 0 - progress_line_printed = False - try: - # Ensure encoding is specified for Popen for consistent text handling - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True, encoding='utf-8') - - if process.stdout: - for line in iter(process.stdout.readline, ''): - line = line.strip() - if not line: - continue - - if line.startswith("REQ_STATUS:"): - requests_done_count += 1 - # Using carriage return to update the line in place - print(f"\rFlask progress: Handled {requests_done_count}/{NUM_REQUESTS_EXPECTED} requests...", end="", flush=True) - progress_line_printed = True - elif line.startswith("[DIAG-BRB-FLASK]"): - if progress_line_printed: - # Clear the progress line before printing diagnostic output - print("\r" + " " * 80 + "\r", end="", flush=True) - print(line, flush=True) # Print diagnostic line - if progress_line_printed: - # Reprint the progress line after diagnostic output - print(f"\rFlask progress: Handled {requests_done_count}/{NUM_REQUESTS_EXPECTED} requests...", end="", flush=True) - elif "Final Flask benchmark summary:" in line: - final_summary_line = line - if progress_line_printed: - # Clear the progress line before finishing - print("\r" + " " * 80 + "\r", end="", flush=True) - # The summary line itself will be printed by the main logic if needed, or parsed - - process.stdout.close() - - # After the loop, if progress was printed, clear it finally - # This handles cases where the process ends without a final summary line immediately after progress - if progress_line_printed and not final_summary_line: - print("\r" + " " * 80 + "\r", end="", flush=True) - - stderr_output_list = [] - if process.stderr: - for line in iter(process.stderr.readline, ''): - line = line.strip() - if line: - stderr_output_list.append(line) - process.stderr.close() - - process.wait(timeout=600) - - if process.returncode != 0: - console.print(f"[red]{framework_arg} benchmark script failed with return code {process.returncode}[/red]") - if stderr_output_list: - console.print("[red]STDERR:[/red]") - for err_line in stderr_output_list: - console.print(f"[red]{err_line}[/red]") - return None - - if final_summary_line: - return final_summary_line - else: - console.print(f"[red]Could not find the final summary line for {framework_arg} in Popen benchmark output.[/red]") - if stderr_output_list: - console.print("[red]STDERR output during Popen execution was:[/red]") - for err_line in stderr_output_list: - console.print(f"[red]{err_line}[/red]") - return None - - except subprocess.TimeoutExpired: - console.print(f"[red]Benchmark for {framework_arg} (Popen path) timed out.[/red]") - if process.poll() is None: # Check if process is still running - process.kill() - process.wait() - return None - except Exception as e: - console.print(f"[red]An unexpected error occurred while running Popen benchmark for {framework_arg}: {e}[/red]") - return None - - else: # For FastAPI or any other framework not needing live progress - try: - result = subprocess.run(cmd, text=True, capture_output=True, timeout=600, check=False, encoding='utf-8') - if result.returncode != 0: - console.print(f"[red]{framework_arg} benchmark failed with subprocess.run.[/red]") - if result.stderr: - console.print(f"STDERR:\n{result.stderr.strip()}") - return None - - if result.stdout and result.stdout.strip(): - lines = result.stdout.strip().splitlines() - if lines: - return lines[-1] # Return the last line, expected to be the summary - else: - console.print(f"[red]No lines in stdout from {framework_arg} benchmark script (subprocess.run path).[/red]") - return None - else: - console.print(f"[red]No stdout from {framework_arg} benchmark script (subprocess.run path).[/red]") - if result.stderr and result.stderr.strip(): - console.print(f"STDERR:\n{result.stderr.strip()}") - return None - except subprocess.TimeoutExpired: - console.print(f"[red]Benchmark for {framework_arg} (subprocess.run path) timed out.[/red]") - return None - except Exception as e: - console.print(f"[red]An unexpected error occurred while running subprocess.run benchmark for {framework_arg}: {e}[/red]") - return None - -def parse_benchmark(line): - m = re.search(r"(\d+)/(\d+) successful requests in ([\d.]+) seconds", line) - if not m: - return None - succ, total, tsec = map(float, m.groups()) - return {"successful": f"{int(succ)}/{int(total)}", "total_time": tsec} - -def display_table(rows): - tbl = Table(title="Benchmark Summary", show_lines=True, header_style="bold magenta") - tbl.add_column("Framework", style="cyan") - tbl.add_column("Server", style="white") - tbl.add_column("Delay", style="green") - tbl.add_column("#Reqs", justify="right") - tbl.add_column("Success", justify="right") - tbl.add_column("Total s", justify="right", style="yellow") - tbl.add_column("Avg s/req", justify="right", style="blue") - for r in rows: - avg = r["total_time"] / NUM_REQUESTS_EXPECTED - tbl.add_row(r["framework"], r["config"], r["delay"], - str(NUM_REQUESTS_EXPECTED), r["successful"], - f"{r['total_time']:.2f}", f"{avg:.3f}") - console.print(tbl) - -# --------------------------- scenarios ---------------------------------- -SCENARIOS = [ - { - "name": "FastAPI", - "config": "Uvicorn, async", - "delay": "0.3 s asyncio.sleep", - "cmd": ["uvicorn", "app_fastapi.app:app", "--host", "0.0.0.0", - "--port", "8000", "--log-level", "warning"], - "url": FASTAPI_SERVER_URL, - "bench_arg": "fastapi", - }, - { - "name": "Flask", - "config": "Single-threaded", - "delay": "0.3 s time.sleep", - "cmd": [PYTHON_EXE, "app_flask/flask_application.py"], - "url": FLASK_SERVER_URL, - "bench_arg": "flask", - } -] - -# ----------------------------- main ------------------------------------- -if __name__ == "__main__": - console.print("[bold underline]Automated Web Framework Benchmark[/bold underline]\n") - results = [] - root = os.getcwd() - - for i, sc in enumerate(SCENARIOS, 1): - console.rule(f"[cyan]Scenario {i}/{len(SCENARIOS)} – {sc['name']}[/cyan]") - srv = start_server(sc["cmd"], sc["url"], sc["name"], cwd=root) - if not srv: - continue - try: - if sc["name"].lower() == "flask": - time.sleep(2) # tiny grace period - line = run_benchmark_script(sc["bench_arg"]) - parsed = parse_benchmark(line) if line else None - if parsed: - results.append({"framework": sc["name"], "config": sc["config"], - "delay": sc["delay"], **parsed}) - finally: - stop_server(srv, sc["name"]) - console.print() - - if results: - display_table(results) - console.print("\n[bold]Benchmark run finished.[/bold]") \ No newline at end of file