Skip to content

Commit

Permalink
feat(locust): add a load testing user
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexisBRENON committed Oct 11, 2024
1 parent 4b63bd1 commit fcf36b8
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 93 deletions.
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ sequenceDiagram
opt if fabric.prediction. isExploration
rect rgba(30, 183, 136, 0.66)
SSP -)+ GB: POST /
GB -->>- SSP: 200 OK
GB -->>- SSP: 204 No Response
end
end
Expand All @@ -110,7 +110,7 @@ sequenceDiagram

### 🏋️ Example

An integration example is provided through the [`locustfiles/root.py`](https://github.com/greenbids/greenbids-tailor-external/blob/main/locustfiles/root.py#L12).
An integration example is provided through the [`locustfiles/rtb.py`](https://github.com/greenbids/greenbids-tailor-external/blob/main/locustfiles/rtb.py#L8).
It highlights when the Greenbids Tailor service must be called during the ad request processing.
It also propose an example of features to pass in the payload (only for demonstrative purpose).

Expand All @@ -128,24 +128,24 @@ It will print you a summary of the test.
The following has been obtained on a laptop (AMD Ryzen 7 PRO, 16GB RAM) running the Python executable:

```text
Type Name # reqs # fails | Avg Min Max Med | req/s failures/s
--------|---------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
POST 56579 0(0.00%) | 1 0 123 1 | 462.71 0.00
PUT 282236 0(0.00%) | 1 0 140 2 | 2308.18 0.00
GET /healthz/liveness 4 0(0.00%) | 12 10 14 11 | 0.03 0.00
GET /healthz/readiness 3 0(0.00%) | 9 7 10 9 | 0.02 0.00
GET /healthz/startup 4 0(0.00%) | 9 6 13 8 | 0.03 0.00
--------|---------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
Aggregated 338826 0(0.00%) | 1 0 140 2 | 2770.99 0.00
Type Name # reqs # fails | Avg Min Max Med | req/s failures/s
--------|--------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
POST 16991 0(0.00%) | 4 0 125 5 | 386.06 0.00
PUT 84973 0(0.00%) | 4 0 125 4 | 1930.70 0.00
GET /healthz/liveness 1 0(0.00%) | 13 13 13 13 | 0.02 0.00
GET /healthz/readiness 2 0(0.00%) | 9 7 10 7 | 0.05 0.00
GET /healthz/startup 1 0(0.00%) | 9 9 9 9 | 0.02 0.00
--------|--------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
Aggregated 101968 0(0.00%) | 4 0 125 4 | 2316.85 0.00
Response time percentiles (approximated)
Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs
--------|-------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
POST 1 2 2 2 3 4 5 5 7 10 120 56579
PUT 2 2 2 3 3 4 5 5 7 100 140 282236
GET /healthz/liveness 14 14 14 14 14 14 14 14 14 14 14 4
GET /healthz/readiness 9 9 11 11 11 11 11 11 11 11 11 3
GET /healthz/startup 10 10 14 14 14 14 14 14 14 14 14 4
--------|-------------------------|--------|------|------|------|------|------|------|------|------|------|------|------
Aggregated 2 2 2 3 3 4 5 5 7 100 140 338826
Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs
--------|--------------------|--------|------|------|------|------|------|------|------|------|------|------|------
POST 5 5 6 6 6 7 7 8 9 120 130 16991
PUT 4 5 5 5 6 6 7 7 9 99 130 84973
GET /healthz/liveness 13 13 13 13 13 13 13 13 13 13 13 1
GET /healthz/readiness 11 11 11 11 11 11 11 11 11 11 11 2
GET /healthz/startup 9 9 9 9 9 9 9 9 9 9 9 1
--------|--------------------|--------|------|------|------|------|------|------|------|------|------|------|------
Aggregated 4 5 5 5 6 6 7 7 9 99 130 101968
```
37 changes: 37 additions & 0 deletions locustfiles/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import typing

import polyfactory


class Bidder(typing.TypedDict):
"""A simplified example of bidder specific parameters passed in an ad request."""

name: str
"""The name/ID of the bidder"""
user_id: str | None
"""What is the user ID of the current user on DSP's side (user sync)"""


class AdRequest(typing.TypedDict):
"""A simplified example of an ad request that an SSP may handle."""

bidders: list[Bidder]
"""The full list of elligible DSPs for the auction."""

hostname: str
"""Hostname of the publisher generating this ad request"""

device: str
"""Device used by the user visiting the publisher's site"""


class AdRequestFactory(polyfactory.factories.TypedDictFactory[AdRequest]):
"""Utility object to generate random AdRequests"""

__randomize_collection_length__ = True
__min_collection_length__ = 2
"""Minimal number of bidders"""
__max_collection_length__ = 15
"""Maximal number of bidders"""

pass
93 changes: 20 additions & 73 deletions locustfiles/root.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,34 @@
import random
import typing

import locust
import polyfactory

from _utils import AdRequestFactory


class Root(locust.FastHttpUser):
"""Traffic shaping testing."""

@locust.task
def handleAdRequest(self):
"""Here we simulate the way an SSP may integrate with the Greenbids Tailor service."""
weight = 1e-6

# Suppose that you got an ad request as input
def on_start(self) -> None:
ad_request = AdRequestFactory.build()

# Build a list of features for each bidder in the ad request
feature_maps = [
self.fabrics = [
{
"bidder": bidder["name"],
"userSynced": bidder.get("user_id") is not None,
"hostname": ad_request["hostname"],
"device": ad_request["device"],
# You may add whatever seems relevant to you here.
"SSP's_secret_ingredient": 42,
# Let's get in touch to allow us to craft a well suited model.
"featureMap": {
"bidder": bidder["name"],
"userSynced": bidder.get("user_id") is not None,
"hostname": ad_request["hostname"],
"device": ad_request["device"],
},
"prediction": {"isExploration": True, "score": 1, "threshold": -1},
"groundTruth": {"hasResponse": True},
}
for bidder in ad_request["bidders"]
]
# Do a single call to the Greenbids Tailor service
fabrics = self.client.put("", json=feature_maps).json()

# Do your regular calls here to send a bid requests to the selected bidders
for fabric in fabrics:
if not fabric["prediction"]["shouldSend"]:
# Skip any bidder that as too few response probability
continue

# For selected bidders, send them a bid request
# rsp = requests.post(bidder.url, json={...})

# Bidder may or may not return a bid (for test we simulate this randomly)
# hasResponse = (rsp.status_code != 204)
hasResponse = random.choice([True, False])

# Store the outcome in the fabric
fabric["groundTruth"] = dict(hasResponse=hasResponse)

# For a sample of calls, report the outcomes to the Greenbids Tailor POST endpoint
if fabrics[0]["prediction"]["isExploration"]:
# You may use a fire-and-forget mechanism
self.client.post("", json=fabrics)


class Bidder(typing.TypedDict):
"""A simplified example of bidder specific parameters passed in an ad request."""

name: str
"""The name/ID of the bidder"""
user_id: str | None
"""What is the user ID of the current user on DSP's side (user sync)"""


class AdRequest(typing.TypedDict):
"""A simplified example of an ad request that an SSP may handle."""

bidders: list[Bidder]
"""The full list of elligible DSPs for the auction."""

hostname: str
"""Hostname of the publisher generating this ad request"""

device: str
"""Device used by the user visiting the publisher's site"""


class AdRequestFactory(polyfactory.factories.TypedDictFactory[AdRequest]):
"""Utility object to generate random AdRequests"""
return super().on_start()

__randomize_collection_length__ = True
__min_collection_length__ = 2
"""Minimal number of bidders"""
__max_collection_length__ = 15
"""Maximal number of bidders"""
@locust.task(100)
def request(self):
self.client.put("", json=self.fabrics)

pass
@locust.task(20)
def report(self):
self.client.post("", json=self.fabrics)
55 changes: 55 additions & 0 deletions locustfiles/rtb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import random

import locust

from _utils import AdRequestFactory


class Rtb(locust.FastHttpUser):
"""Traffic shaping testing."""

@locust.task
def handleAdRequest(self):
"""Here we simulate the way an SSP may integrate with the Greenbids Tailor service."""

# Suppose that you got an ad request as input
ad_request = AdRequestFactory.build()

# Build a list of features for each bidder in the ad request
feature_maps = [
{
"featureMap": {
"bidder": bidder["name"],
"userSynced": bidder.get("user_id") is not None,
"hostname": ad_request["hostname"],
"device": ad_request["device"],
# You may add whatever seems relevant to you here.
"SSP's_secret_ingredient": 42,
# Let's get in touch to allow us to craft a well suited model.
}
}
for bidder in ad_request["bidders"]
]
# Do a single call to the Greenbids Tailor service
fabrics = self.client.put("", json=feature_maps).json()

# Do your regular calls here to send a bid requests to the selected bidders
for fabric in fabrics:
if not fabric["prediction"]["shouldSend"]:
# Skip any bidder that as too few response probability
continue

# For selected bidders, send them a bid request
# rsp = requests.post(bidder.url, json={...})

# Bidder may or may not return a bid (for test we simulate this randomly)
# hasResponse = (rsp.status_code != 204)
hasResponse = random.choice([True, False])

# Store the outcome in the fabric
fabric["groundTruth"] = dict(hasResponse=hasResponse)

# For a sample of calls, report the outcomes to the Greenbids Tailor POST endpoint
if fabrics[0]["prediction"]["isExploration"]:
# You may use a fire-and-forget mechanism
self.client.post("", json=fabrics)

0 comments on commit fcf36b8

Please sign in to comment.