Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PostgresUser to examples #2836

Merged
merged 16 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
21 changes: 21 additions & 0 deletions examples/postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Overview

Read the instruction below for your specific database

## PostgreSQL

### How to run the test

- Prerequisites:

- `psycopg3` - https://www.psycopg.org/psycopg3/docs/basic/install.html

- Set your environment variables for:

- PGHOST
- PGPORT
- PGDATABASE
- PGUSER
- PGPASSWORD

- Run locust as usual, see https://docs.locust.io/en/stable/quickstart.html
38 changes: 38 additions & 0 deletions examples/postgres/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from locust import TaskSet, between, task
from locust.contrib.postgres import PostgresUser

import os
import random


class UserTasks(TaskSet):
@task
def run_select_query(self):
self.client.execute_query(
self.user.conn_string,
"SELECT * FROM loadtesting.invoice WHERE amount > 500",
)

@task(3)
def run_update_query(self):
random_amount = random.randint(1, 12)
self.client.execute_query(
self.user.conn_string,
f"UPDATE loadtesting.invoice SET amount={random_amount} WHERE amount < 10",
)


class PostgresLocust(PostgresUser):
tasks = [UserTasks]
min_wait = 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the wait_time stuff and just leave it as the default, to simplify the example.

You can skip the taskset and put the tasks directly under the user too.

max_wait = 3
wait_time = between(min_wait, max_wait)

# Use environment variables or default values
PGHOST = os.getenv("PGHOST", "localhost")
PGPORT = os.getenv("PGPORT", "5432")
PGDATABASE = os.getenv("PGDATABASE", "loadtesting_db")
PGUSER = os.getenv("PGUSER", "postgres")
PGPASSWORD = os.getenv("PGPASSWORD", "postgres")

conn_string = f"postgresql://{PGUSER}:{PGPASSWORD}@{PGHOST}:{PGPORT}/{PGDATABASE}"
49 changes: 49 additions & 0 deletions locust/contrib/postgres.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from locust import TaskSet, User, events

import time

import psycopg


def create_conn(conn_string):
Copy link
Collaborator

@cyberw cyberw Aug 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method is too small to make sense! The code is easier to understand if you just call .connect directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, it looks a lot cleaner calling the psycopg.connect directly

return psycopg.connect(conn_string)


def execute_query(conn_string, query):
db_conn = create_conn(conn_string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this create a new connection on each query? That sounds very wasteful. Probably you wanna do that only when the client object is created.

return db_conn.cursor().execute(query)


class PostgresClient:
def __getattr__(self, name):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think you need the whole __getattr__ song and dance :)

Just define the execute_query method directly on the PostgresClient class.

def request_handler(*args, **kwargs):
start_time = time.time()
try:
execute_query(*args, **kwargs)
response_time = int((time.time() - start_time) * 1000)
events.request.fire(
request_type="postgres_success",
name=name,
response_time=response_time,
response_length=0,
)
except Exception as e:
response_time = int((time.time() - start_time) * 1000)
events.request.fire(
request_type="postgres_failure",
name=name,
response_time=response_time,
response_length=0,
exception=e,
)
print(f"error: {e}")

return request_handler


class PostgresUser(User):
abstract = True

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = PostgresClient()