Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7bcd602
NRL-1841 Enable public mode for consumer performance tests
sandyforresternhs Dec 5, 2025
17c690d
NRL-1841 Remove comments
sandyforresternhs Dec 5, 2025
a69ad75
NRL-1841 Store public test config in tmp file
sandyforresternhs Dec 8, 2025
2d58983
NRL-1841 Remove default consumer behaviour from getFullUrl
sandyforresternhs Dec 8, 2025
0bf7aff
Merge branch 'develop' into feature/SAFO6-NRL-1841-performance-tests-…
sandyforresternhs Jan 5, 2026
48e7f01
NRL-1841 Remove comment
sandyforresternhs Jan 6, 2026
9a9b2fb
NRL-1841 Update command for running perftests in pipeline
sandyforresternhs Jan 6, 2026
a1756d3
NRL-1841 Update command for running perftests in pipeline
sandyforresternhs Jan 6, 2026
91db15f
NRL-1841 Integrate public config into perftest tests WIP
sandyforresternhs Jan 7, 2026
0776a63
NRL-1841 Enable perftest-consumer-public in makefile
sandyforresternhs Jan 8, 2026
123a1a4
NRL-1841 Remove commented code
sandyforresternhs Jan 8, 2026
cd45774
NRL-1841 Remove comments and pointer type
sandyforresternhs Jan 9, 2026
ca1304d
NRL-1841 Remove redundant makefile command
sandyforresternhs Jan 9, 2026
fd4aaee
Merge branch 'develop' into feature/SAFO6-NRL-1841-performance-tests-…
sandyforresternhs Jan 9, 2026
b8a5f0f
NRL-1841 Update Makefile command
sandyforresternhs Jan 13, 2026
964aa45
NRL-1841 Minor fix - run with poetry
anjalitrace2-nhs Jan 14, 2026
cc6938f
NRL-1841 Consumer perf tests working for both internal & public modes
anjalitrace2-nhs Jan 14, 2026
2f052be
Merge branch 'develop' of github.com:NHSDigital/NRLF into feature/SAF…
anjalitrace2-nhs Jan 15, 2026
15f102e
NRL-1841 Producer perf tests run in public mode, same as the consumer…
anjalitrace2-nhs Jan 15, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/pr-env-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,10 @@ jobs:
run: make test-performance-prepare TF_WORKSPACE_NAME=${{ needs.set-environment-id.outputs.environment_id }}

- name: Run Performance Test - Baseline
run: make test-performance-baseline HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
run: make test-performance-baseline-internal HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev

- name: Run Performance Test - Stress
run: make test-performance-stress HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
run: make test-performance-stress-internal HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev

- name: Process Performance Test Outputs
run: make test-performance-output
Expand Down
96 changes: 84 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,58 @@ test-performance-prepare:
mkdir -p $(DIST_PATH)
PYTHONPATH=. poetry run python tests/performance/environment.py setup $(TF_WORKSPACE_NAME)

test-performance: check-warn test-performance-baseline test-performance-stress ## Run the performance tests
test-performance-internal: check-warn test-performance-baseline-internal test-performance-stress-internal ## Run the performance tests against the internal access points

test-performance-baseline:
@echo "Running consumer performance baseline test"
k6 run --out csv=$(DIST_PATH)/consumer-baseline.csv tests/performance/consumer/baseline.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)

test-performance-stress:
@echo "Running consumer performance stress test"
test-performance-baseline-internal: check-warn ## Run the performance baseline tests for the internal access points
@echo "Running internal consumer performance baseline test"
TEST_CONNECT_MODE=internal \
TEST_STACK_DOMAIN=$(shell terraform -chdir=terraform/infrastructure output -raw domain 2>/dev/null) \
k6 run --out csv=$(DIST_PATH)/consumer-baseline.csv tests/performance/consumer/baseline.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)

test-performance-baseline-public: check-warn ## Run the baseline performance tests for the external access points
@echo "Fetching public mode configuration and bearer token..."
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
trap "rm -f $$CONFIG_FILE" EXIT; \
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
echo "Running consumer performance baseline test against the external access points"; \
TEST_CONNECT_MODE=public \
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
TEST_CONFIG_FILE=$$CONFIG_FILE \
k6 run --out csv=$(DIST_PATH)/consumer-baseline-public.csv tests/performance/consumer/baseline.js -e ENV_TYPE=$(ENV_TYPE)

test-performance-stress-internal: ## Run the performance stress tests for the internal access points
@echo "Running internal consumer performance stress test"
k6 run --out csv=$(DIST_PATH)/consumer-stress.csv tests/performance/consumer/stress.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)

test-performance-soak:
@echo "Running consumer performance soak test"
test-performance-stress-public: check-warn ## Run the stress performance tests for the external access points
@echo "Fetching public mode configuration and bearer token..."
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
trap "rm -f $$CONFIG_FILE" EXIT; \
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
echo "Running consumer performance stress test against the external access points"; \
TEST_CONNECT_MODE=public \
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
TEST_CONFIG_FILE=$$CONFIG_FILE \
k6 run --out csv=$(DIST_PATH)/consumer-stress-public.csv tests/performance/consumer/stress.js -e ENV_TYPE=$(ENV_TYPE)

test-performance-soak-internal:
@echo "Running internal consumer performance soak test"
k6 run --out csv=$(DIST_PATH)/consumer-soak.csv tests/performance/consumer/soak.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)

test-performance-soak-public: check-warn ## Run the soak performance tests for the external access points
@echo "Fetching public mode configuration and bearer token..."
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
trap "rm -f $$CONFIG_FILE" EXIT; \
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
echo "Running consumer performance soak test against the external access points"; \
TEST_CONNECT_MODE=public \
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
TEST_CONFIG_FILE=$$CONFIG_FILE \
k6 run --out csv=$(DIST_PATH)/consumer-soak-public.csv tests/performance/consumer/soak.js -e ENV_TYPE=$(ENV_TYPE)

test-performance-output: ## Process outputs from the performance tests
@echo "Processing performance test outputs"
poetry run python tests/performance/process_results.py baseline $(DIST_PATH)/consumer-baseline.csv
Expand Down Expand Up @@ -273,15 +311,49 @@ perftest-prepare: ## Prepare input files for producer & consumer perf tests
# cp "${DIST_PATH}/nft/seed-pointers-extract-${PERFTEST_TABLE_NAME}.csv" "${DIST_PATH}/seed-pointers-extract.csv"
PYTHONPATH=. poetry run python ./tests/performance/generate_producer_distributions.py

perftest-producer: ## Run producer perf tests
perftest-producer-internal: ## Run producer perf tests
@echo "Running producer performance tests with HOST=$(PERFTEST_HOST) and ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"
k6 run tests/performance/producer/perftest.js -e HOST=$(PERFTEST_HOST) -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)

perftest-consumer: ## Run consumer perf tests
perftest-producer-public: check-warn ## Run the producer perftests for the external access points
@echo "Fetching public mode configuration and bearer token..."
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
trap "rm -f $$CONFIG_FILE" EXIT; \
PYTHONPATH=. poetry run python tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
echo "Running public producer perftests with ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"; \
TEST_CONNECT_MODE=public \
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
TEST_CONFIG_FILE=$$CONFIG_FILE \
k6 run tests/performance/producer/perftest.js -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)

perftest-consumer-internal:
@echo "Running consumer performance tests with HOST=$(PERFTEST_HOST) and ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"
k6 run tests/performance/consumer/perftest.js -e HOST=$(PERFTEST_HOST) -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)

perftest-generate-pointer-table-extract: ## Refresh the perf test input files in s3. Can be expensive to run on large tables
perftest-consumer-public: check-warn ## Run the consumer perftests for the external access points
@echo "Fetching public mode configuration and bearer token..."
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
trap "rm -f $$CONFIG_FILE" EXIT; \
PYTHONPATH=. poetry run python tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
echo "Running public consumer perftests with ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"; \
TEST_CONNECT_MODE=public \
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
TEST_CONFIG_FILE=$$CONFIG_FILE \
k6 run tests/performance/consumer/perftest.js -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)

perftest-prep-generate-producer-data:
@echo "Generating producer reference with PERFTEST_TABLE_NAME=$(PERFTEST_TABLE_NAME) and DIST_PATH=$(DIST_PATH)"
mkdir -p $(DIST_PATH)
PYTHONPATH=. poetry run python tests/performance/perftest_environment.py generate_producer_data --output_dir="$(DIST_PATH)"

perftest-prep-extract-consumer-data:
@echo "Generating consumer reference with PERFTEST_TABLE_NAME=$(PERFTEST_TABLE_NAME) and DIST_PATH=$(DIST_PATH)"
mkdir -p $(DIST_PATH)
PYTHONPATH=. poetry run python tests/performance/perftest_environment.py extract_consumer_data --output_dir="$(DIST_PATH)"

perftest-generate-pointer-table-extract:
@echo "Generating pointer table extract with PERFTEST_TABLE_NAME=$(PERFTEST_TABLE_NAME) and DIST_PATH=$(DIST_PATH)"
rm -rf "${DIST_PATH}/nft"
mkdir -p "${DIST_PATH}/nft"
Expand Down
18 changes: 15 additions & 3 deletions tests/performance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ You will need to generate pointer permissions the first time performance tests a
```sh
# In project root
make perftest-generate-permissions # makes a bunch of json permission files for test organisations
make build # will take all permissions & create nrlf_permissions.zip file
make get-s3-perms ENV=perftest # will take all permissions & create nrlf_permissions.zip file
make build

# apply this new permissions zip file to your environment
cd ./terraform/infrastructure
Expand All @@ -71,9 +72,20 @@ make perftest-prepare PERFTEST_TABLE_NAME=nhsd-nrlf--perftest-baseline-pointers-

### Run tests

#### Internal mode

```sh
make perftest-consumer-internal ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
make perftest-producer-internal ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
```

#### Public mode

Via apigee proxies - most similar to a supplier

```sh
make perftest-consumer ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
make perftest-producer ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
make perftest-consumer-public ENV=perftest
make perftest-producer-public ENV=perftest
```

## Seed data
Expand Down
2 changes: 1 addition & 1 deletion tests/performance/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const NHS_NUMBERS = REFERENCE_DATA["nhs_numbers"];

// filter only 736253001, 736253002, 1363501000000100, 861421000000109, 749001000000101 for now
export const FILTERED_POINTER_TYPES = [
"736253001",
// "736253001",
"736253002",
"1363501000000100",
"861421000000109",
Expand Down
99 changes: 33 additions & 66 deletions tests/performance/consumer/client.js
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these need applying to the client_perftest.js file rather than in here? For the new perf tests

Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
import { getHeaders, getFullUrl } from "../test-config.js";
import {
POINTER_TYPES,
CATEGORIES,
NHS_NUMBERS,
POINTER_IDS,
POINTER_TYPES,
ODS_CODE,
CATEGORIES,
} from "../constants.js";
import http from "k6/http";
import { check } from "k6";

function getHeaders(odsCode = ODS_CODE) {
return {
"Content-Type": "application/fhir+json",
"X-Request-Id": "K6PerformanceTest",
"NHSD-Correlation-Id": "K6PerformanceTest",
"NHSD-Connection-Metadata": JSON.stringify({
"nrl.ods-code": odsCode,
"nrl.pointer-types": POINTER_TYPES.map(
(type) => `http://snomed.info/sct|${type}`
),
"nrl.app-id": "K6PerformanceTest",
}),
"NHSD-Client-RP-Details": JSON.stringify({
"developer.app.name": "K6PerformanceTest",
"developer.app.id": "K6PerformanceTest",
}),
};
}

function checkResponse(res) {
const is_success = check(res, { "status is 200": (r) => r.status === 200 });
if (!is_success) {
Expand All @@ -41,25 +23,22 @@ export function countDocumentReference() {
const identifier = encodeURIComponent(
`https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}`
);
const res = http.get(
`https://${__ENV.HOST}/consumer/DocumentReference?_summary=count&subject:identifier=${identifier}`,
{
headers: getHeaders(),
}
);

const path = `/DocumentReference?_summary=count&subject:identifier=${identifier}`;
const res = http.get(getFullUrl(path, "consumer"), {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}

export function readDocumentReference() {
const choice = Math.floor(Math.random() * POINTER_IDS.length);
const id = POINTER_IDS[choice];

const res = http.get(
`https://${__ENV.HOST}/consumer/DocumentReference/${id}`,
{
headers: getHeaders(),
}
);
const path = `/DocumentReference/${id}`;
const res = http.get(getFullUrl(path, "consumer"), {
headers: getHeaders(ODS_CODE, "consumer"),
});

checkResponse(res);
}
Expand All @@ -74,12 +53,10 @@ export function searchDocumentReference() {
);
const type = encodeURIComponent(`http://snomed.info/sct|${pointer_type}`);

const res = http.get(
`https://${__ENV.HOST}/consumer/DocumentReference?subject:identifier=${identifier}&type=${type}`,
{
headers: getHeaders(),
}
);
const path = `/DocumentReference?subject:identifier=${identifier}&type=${type}`;
const res = http.get(getFullUrl(path, "consumer"), {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}

Expand All @@ -95,12 +72,10 @@ export function searchDocumentReferenceByCategory() {
`http://snomed.info/sct|${randomCategory}`
);

const res = http.get(
`https://${__ENV.HOST}/consumer/DocumentReference?subject:identifier=${identifier}&category=${category}`,
{
headers: getHeaders(),
}
);
const path = `/DocumentReference?subject:identifier=${identifier}&category=${category}`;
const res = http.get(getFullUrl(path, "consumer"), {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}

Expand All @@ -114,13 +89,10 @@ export function searchPostDocumentReference() {
type: `http://snomed.info/sct|${pointer_type}`,
});

const res = http.post(
`https://${__ENV.HOST}/consumer/DocumentReference/_search`,
body,
{
headers: getHeaders(),
}
);
const path = `/DocumentReference/_search`;
const res = http.post(getFullUrl(path, "consumer"), body, {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}

Expand All @@ -133,13 +105,10 @@ export function searchPostDocumentReferenceByCategory() {
category: `http://snomed.info/sct|${category}`,
});

const res = http.post(
`https://${__ENV.HOST}/consumer/DocumentReference/_search`,
body,
{
headers: getHeaders(),
}
);
const path = `/DocumentReference/_search`;
const res = http.post(getFullUrl(path, "consumer"), body, {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}

Expand All @@ -150,12 +119,10 @@ export function countPostDocumentReference() {
const body = JSON.stringify({
"subject:identifier": `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}`,
});
const res = http.post(
`https://${__ENV.HOST}/consumer/DocumentReference/_search?_summary=count`,
body,
{
headers: getHeaders(),
}
);

const path = `/DocumentReference/_search?_summary=count`;
const res = http.post(getFullUrl(path, "consumer"), body, {
headers: getHeaders(ODS_CODE, "consumer"),
});
checkResponse(res);
}
Loading