mirror of
https://github.com/remsky/Kokoro-FastAPI.git
synced 2025-08-05 16:48:53 +00:00
Added a PSR test based on Locust.
This commit is contained in:
parent
29066f7c9f
commit
bae2d4e392
4 changed files with 124 additions and 1 deletions
24
api/psr/README.md
Normal file
24
api/psr/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
## Introduction
|
||||
This is a simple PSR test written in locust. It simulates users that execute the following scenario:
|
||||
- call API to convert text to speech using streaming API
|
||||
- play the generated audio file
|
||||
- repeat
|
||||
|
||||
## Get Started
|
||||
Install the packages required by the PSR test:
|
||||
```
|
||||
uv pip install -e ".[psr]"
|
||||
```
|
||||
|
||||
## Run the test
|
||||
Start Kokoro-FastAPI first, then start Locust:
|
||||
```
|
||||
set HOST=http://localhost:8880
|
||||
locust -f ./api/psr/openai_load_test.py -H %HOST% --users 1 --spawn-rate 0.1 --run-time 5m KokoroOpenAPiClient
|
||||
```
|
||||
|
||||
1. Use the Locust UI on http://localhost:8089 to start the test.
|
||||
2. Set the number of users to a value supported by your machine (e.g.: Intel i9 + nvidia 3050 could support 10 users).
|
||||
3. Set the user ramp up to a desired value (e.g.: 0.1 means one user every 10 seconds)
|
||||
4. Click Advanced and set the desired test duration (Locust will run user simulations for that duration).
|
||||
5. Click Start.
|
90
api/psr/openai_load_test.py
Normal file
90
api/psr/openai_load_test.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
from time import strftime
|
||||
|
||||
from locust import HttpUser, task, events
|
||||
from locust.runners import MasterRunner
|
||||
from loguru import logger
|
||||
from atomicx import AtomicInt
|
||||
|
||||
TEXT_SAMPLES = [
|
||||
{"audio_duration": 24, "text": "Andy is from Canada, but with his long blond hair, incredibly mellow demeanor, and wardrobe of faded T-shirts, shorts, and sandals, you would think he grew up surfing on the coast of Malibu. Having worked with Endel Tulving as an undergraduate, Andy reasoned that episodic memory gives us access to a tangible memory from a specific place and time, leading us to feel confident that we are reliving a genuine moment from the past."},
|
||||
{"audio_duration": 13, "text": "In contrast, familiarity can be strong, such as the sense of certainty that you have seen something or someone before, or it can be weak, such as a hunch or educated guess; either way, it doesn’t give us anything specific to hold on to."},
|
||||
{"audio_duration": 10, "text": "Andy was presenting his research, and I might have been a bit too direct when I voiced my skepticism that familiarity was anything more than weak episodic memory."},
|
||||
{"audio_duration": 25, "text": "I anticipated some blowback, but instead of getting defensive, Andy completely disarmed me by responding, “You should be skeptical!” After an afternoon of spirited debate, we decided to team up on an experiment to test the idea that we could identify a brain area responsible for the sense of familiarity. To sweeten the pot, we decided to make a “beer bet”—if his prediction turned out to be right, I’d buy him a beer and vice versa."},
|
||||
{"audio_duration": 8, "text": "For instance, if a subject imagined what it would be like to stuff an armadillo in a shoebox, that would create a distinctive episodic memory."},
|
||||
{"audio_duration": 5, "text": "(At this point, I’ve lost so many beer bets to Andy, it will take years to pay off my tab.)"},
|
||||
{"audio_duration": 23, "text": "We didn’t yet have a working MRI scanner in the lab at Berkeley, but Mark finessed a space for us at a clinical MRI facility in the Martinez VA Medical Center, about halfway between Berkeley and Davis. The Martinez scanner was set up for routine clinical scans and didn’t have state-of-the-art performance specs, so I had to MacGyver it, tweaking our procedures every way I could think of to turn this Ford Pinto into a Ferrari."},
|
||||
{"audio_duration": 1, "text": "“Can it fit in a shoebox?”)."},
|
||||
{"audio_duration": 9, "text": "Sure enough, activity in the hippocampus spiked when people saw a word and formed a memory that later helped them recall something about 109the context."},
|
||||
{"audio_duration": 20, "text": "This argument was based on the fact that animals with damage to the hippocampus, as well as the patients with developmental amnesia such as those studied by Faraneh Vargha-Khadem, seemed to do fine on “recognition memory” tests that required them to tell the difference between objects they had seen before and ones that were new."},
|
||||
{"audio_duration": 6, "text": "Rats, like human infants, tend to be more interested in exploring things that are new to them than in things they have seen before."},
|
||||
{"audio_duration": 28, "text": "安迪来自加拿大,但凭借他那头长长的金发、非常温和的举止和褪色的T恤、短裤和凉鞋的衣橱,你会认为他是在马里布海岸冲浪长大的。作为本科生与恩德尔·图尔文合作后,安迪推测情景记忆让我们能够访问来自特定地点和时间的具体记忆,使我们感到自信,仿佛正在重温过去的真实时刻。"},
|
||||
]
|
||||
|
||||
VOICES: list[str] = [
|
||||
"bf_emma",
|
||||
"af_alloy",
|
||||
"bf_emma(1)+af_alloy(1)",
|
||||
]
|
||||
|
||||
HOST = os.environ.get("HOST", "http://localhost:8880")
|
||||
URL_PATH = os.environ.get("URL_PATH", "/v1/audio/speech")
|
||||
OUTPUT_FORMAT = os.environ.get("KOKORO_OUTPUT_FORMAT", "opus")
|
||||
|
||||
user_id = itertools.count()
|
||||
concurrency = AtomicInt()
|
||||
|
||||
@events.test_start.add_listener
|
||||
def on_test_start(environment):
|
||||
if isinstance(environment.runner, MasterRunner):
|
||||
logger.info("A new test is starting.")
|
||||
|
||||
@events.test_stop.add_listener
|
||||
def on_test_stop(environment):
|
||||
if isinstance(environment.runner, MasterRunner):
|
||||
logger.info(f"The test stopped.")
|
||||
|
||||
class KokoroOpenAPiClient(HttpUser):
|
||||
def __init__(self, environment):
|
||||
super().__init__(environment)
|
||||
self.voice: str = random.choice(VOICES)
|
||||
self.audio_duration = 0
|
||||
self.id = next(user_id)
|
||||
logging.info(f"User {self.id} initialized.")
|
||||
|
||||
def wait_time(self):
|
||||
return self.audio_duration
|
||||
|
||||
@task
|
||||
def call_rest(self):
|
||||
current_concurrency = concurrency.inc()
|
||||
try:
|
||||
sample = random.choice(TEXT_SAMPLES)
|
||||
text = sample.get("text")
|
||||
self.audio_duration = sample.get("audio_duration")
|
||||
|
||||
logging.info(f"{strftime('%X')} User {self.id} is executing, next execution in {self.audio_duration} sec, concurrency: {current_concurrency + 1}.")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-store",
|
||||
}
|
||||
|
||||
body = {
|
||||
"input": text,
|
||||
"voice": self.voice,
|
||||
"response_format": OUTPUT_FORMAT,
|
||||
"stream": True,
|
||||
"speed": 1
|
||||
}
|
||||
|
||||
self.client.post(URL_PATH, headers=headers, json=body, timeout=4)
|
||||
finally:
|
||||
events.request.fire(
|
||||
request_type="Concurrency",
|
||||
name="Concurrency metric",
|
||||
response_time=concurrency.dec(),
|
||||
response_length=0)
|
5
api/psr/start_locust.cmd
Normal file
5
api/psr/start_locust.cmd
Normal file
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
|
||||
set HOST=http://localhost:8880
|
||||
|
||||
locust -f ./openai_load_test.py -H %HOST% --users 1 --spawn-rate 0.1 --run-time 5m KokoroOpenAPiClient
|
|
@ -55,7 +55,11 @@ test = [
|
|||
"httpx==0.26.0",
|
||||
"pytest-asyncio==0.25.3",
|
||||
"tomli>=2.0.1",
|
||||
"jinja2>=3.1.6"
|
||||
"jinja2>=3.1.6",
|
||||
]
|
||||
psr = [
|
||||
"locust>=2.37.11",
|
||||
"atomicx>=0.0.14",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
|
Loading…
Add table
Reference in a new issue