Skip to content

Commit

Permalink
✅ K6 (#1335)
Browse files Browse the repository at this point in the history
* Trap xk6 process and terminate properly

* xk6 dockerfile with tini

* Create and delete holder k6 scenario defaults

* xk6 statsd extention

* Docker compose

* k6: teardown file management

* k6: bake scripts into container

* 200+204 valid for deleting tenants

* Improved error handling and retry functionality

* waitForSSEProofDoneRequest from the holder's perspective
  • Loading branch information
wdbasson authored Feb 21, 2025
1 parent b07db08 commit f7421de
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 88 deletions.
18 changes: 15 additions & 3 deletions scripts/k6/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,24 @@ WORKDIR /app
RUN go install go.k6.io/xk6/cmd/[email protected]
RUN xk6 build v0.56.0 --output /app/xk6 \
--with github.com/avitalique/[email protected] \
--with github.com/phymbert/[email protected]
--with github.com/phymbert/[email protected] \
--with github.com/LeonAdato/xk6-output-statsd@latest

FROM docker.io/alpine:3
RUN apk add --no-cache bash

COPY --from=builder /app/xk6 /usr/local/bin/xk6

USER nobody
# Add Tini
ENV TINI_VERSION v0.19.0
RUN apk add --no-cache tini

ADD scripts /k6/scripts
ADD scenarios /k6/scenarios
ADD libs /k6/libs
ADD env.sh /k6/env.sh

ENTRYPOINT ["/usr/local/bin/xk6"]
ENTRYPOINT ["/bin/bash"]
WORKDIR /k6

USER nobody
46 changes: 46 additions & 0 deletions scripts/k6/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
services:
xk6:
build: .
container_name: xk6
image: xk6
tty: true
depends_on:
datadog:
condition: service_healthy
volumes:
# - ./output:/k6/output
# - .env.local:/k6/.env.local
- ../k6:/k6

entrypoint: /bin/bash
command: ./scripts/main.sh -c batch
environment:
- K6_STATSD_ADDR=datadog:8125
- K6_CONSOLE_OUTPUT_FANCY=true
- BASE_HOLDER_PREFIX=dev9
- BASE_VUS=20
- BASE_ITERATIONS=10
- TOTAL_BATCHES=5
- VERSION=latest
- ISSUERS=issuer1

datadog:
image: datadog/agent:latest
container_name: datadog
healthcheck:
test: ["CMD", "agent", "health"]
interval: 5s
timeout: 3s
retries: 5
env_file:
- .env.local # Only load API key from here
environment:
- DD_SITE=datadoghq.eu
- DD_DOGSTATSD_NON_LOCAL_TRAFFIC=1
- DD_HOSTNAME=k6-metrics
- DD_DOGSTATSD_STATS_ENABLE=true
- DD_PROCESS_AGENT_ENABLED=false
- DOCKER_CONTENT_TRUST=1
- DD_LOG_LEVEL=warn
ports:
- "8125:8125/udp"
56 changes: 47 additions & 9 deletions scripts/k6/libs/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export function deleteTenant(bearerToken, walletId) {

if (response.status === 204 || response.status === 200) {
// Request was successful
if (responseBody === null) {
// console.log(`Wallet ${walletId} deleted successfully.`);
if (responseBody === null || responseBody === "null") {
console.log(`Wallet ${walletId} deleted successfully.`);
} else {
console.error(
`Failed to delete wallet ${walletId}. Response body: ${responseBody}`
Expand Down Expand Up @@ -292,8 +292,10 @@ export function createCredential(
if (response.status >= 200 && response.status < 300) {
return response;
}
console.error(`Request failed with status ${response.status}`);
console.error(`Response body: ${response.body}`);
console.error(`createCredential request failed with status ${response.status}`);
// if (response.body) {
// console.error(`Response body: ${response.body}`);
// }
return response.body;
} catch (error) {
console.error(`Error accepting invitation: ${error.message}`);
Expand Down Expand Up @@ -792,10 +794,10 @@ export function genericWaitForSSEEvent({
sseUrlPath,
topic,
expectedState,
lookBack = 5,
maxRetries = 3,
retryDelay = 1,
maxEmptyPings = 1,
lookBack = 45,
maxRetries = 5,
retryDelay = 2,
maxEmptyPings = 3,
sseTag
}) {
const sseUrl = `${__ENV.CLOUDAPI_URL}/tenant/v1/sse/${walletId}/${sseUrlPath}/${threadId}/${eventType}?look_back=${lookBack}`;
Expand All @@ -809,6 +811,7 @@ export function genericWaitForSSEEvent({
let succeeded = false;
let failed = false;
let emptyPingCount = 0;
let hadEmptyPing = false;

const response = sse.open(
sseUrl,
Expand All @@ -820,11 +823,13 @@ export function genericWaitForSSEEvent({
client.on("event", (event) => {
if (!event.data || event.data.trim() === "") {
emptyPingCount++;
hadEmptyPing = true;
console.log(`VU ${__VU}: Iteration ${__ITER}: Empty ping received: ${emptyPingCount}`);
if (emptyPingCount >= maxEmptyPings) {
console.error(`VU ${__VU}: Iteration ${__ITER}: Failed after ${maxEmptyPings} empty pings`);
client.close();
failed = true;
return false; // Signal to exit the whole function
return false;
}
return;
}
Expand All @@ -835,6 +840,9 @@ export function genericWaitForSSEEvent({
eventData.topic === topic &&
eventData.payload &&
eventData.payload.state === expectedState) {
if (hadEmptyPing) {
console.log(`VU ${__VU}: Iteration ${__ITER}: Successfully received event after ${emptyPingCount} empty ping(s)`);
}
client.close();
succeeded = true;
}
Expand Down Expand Up @@ -883,6 +891,36 @@ export function genericWaitForSSEEvent({
return false;
}

export function retry(fn, retries = 3, delay = 2000) {
let attempts = 0;

while (attempts < retries) {
try {
const result = fn();
// If first attempt succeeds, just return the result without logging
if (attempts === 0) {
return result;
}
// For subsequent successful attempts, log the success
console.log(`VU ${__VU}: Iteration ${__ITER}: Succeeded on attempt ${attempts + 1}`);
return result;
} catch (e) {
attempts++;
// Only log from second attempt onwards
if (attempts > 1) {
console.warn(`VU ${__VU}: Iteration ${__ITER}: Attempt ${attempts} failed: ${e.message}`);
}

if (attempts >= retries) {
console.error(`VU ${__VU}: Iteration ${__ITER}: All ${retries} attempts failed`);
throw e;
}

sleep(delay / 1000);
}
}
}

// {
// "name": "load_pop",
// "version": "0.1.0",
Expand Down
45 changes: 32 additions & 13 deletions scripts/k6/scenarios/create-credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
genericWaitForSSEEvent,
getCredentialIdByThreadId,
getWalletIndex,
retry, // Add this import
} from "../libs/functions.js";

const vus = Number.parseInt(__ENV.VUS, 10);
const iterations = Number.parseInt(__ENV.ITERATIONS, 10);
const holderPrefix = __ENV.HOLDER_PREFIX;
const issuerPrefix = __ENV.ISSUER_PREFIX;
const outputPrefix = `${issuerPrefix}-${holderPrefix}`;
const version = __ENV.VERSION;

export const options = {
scenarios: {
Expand All @@ -36,13 +38,14 @@ export const options = {
"http_req_duration{scenario:default}": ["max>=0"],
"http_reqs{scenario:default}": ["count >= 0"],
"iteration_duration{scenario:default}": ["max>=0"],
checks: ["rate==1"],
checks: ["rate>0.99"],
// 'specific_function_reqs{my_custom_tag:specific_function}': ['count>=0'],
// 'specific_function_reqs{scenario:default}': ['count>=0'],
},
tags: {
test_run_id: "phased-issuance",
test_phase: "create-credentials",
version: `${version}`,
},
};

Expand Down Expand Up @@ -87,15 +90,21 @@ export default function (data) {

let createCredentialResponse;
try {
createCredentialResponse = createCredential(
bearerToken,
wallet.issuer_access_token,
wallet.issuer_credential_definition_id,
wallet.issuer_connection_id
);
createCredentialResponse = retry(() => {
const response = createCredential(
bearerToken,
wallet.issuer_access_token,
wallet.issuer_credential_definition_id,
wallet.issuer_connection_id
);
if (response.status !== 200) {
throw new Error(`Non-200 status: ${response.status}`);
}
return response;
}, 5, 2000);
} catch (error) {
// console.error(`Error creating credential: ${error.message}`);
createCredentialResponse = { status: 500, response: error.message };
console.error(`Failed after retries: ${error.message}`);
createCredentialResponse = error.response || error;
}

check(createCredentialResponse, {
Expand Down Expand Up @@ -140,10 +149,20 @@ export default function (data) {

const credentialId = getCredentialIdByThreadId(wallet.access_token, threadId);

const acceptCredentialResponse = acceptCredential(
wallet.access_token,
credentialId
);
let acceptCredentialResponse;
try {
acceptCredentialResponse = retry(() => {
const response = acceptCredential(wallet.access_token, credentialId);
if (response.status !== 200) {
throw new Error(`Non-200 status: ${response.status}`);
}
return response;
}, 5, 2000);
} catch (error) {
console.error(`Failed after retries: ${error.message}`);
acceptCredentialResponse = error.response || error;
}

check(acceptCredentialResponse, {
"Credential accepted successfully": (r) => {
if (r.status !== 200) {
Expand Down
12 changes: 7 additions & 5 deletions scripts/k6/scenarios/create-holders.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import file from "k6/x/file";
import { getBearerToken } from "../libs/auth.js";
import { createTenant } from "../libs/functions.js";

const vus = Number.parseInt(__ENV.VUS, 10);
const iterations = Number.parseInt(__ENV.ITERATIONS, 10);
const holderPrefix = __ENV.HOLDER_PREFIX;
const issuerPrefix = __ENV.ISSUER_PREFIX;
const sleepDuration = Number.parseInt(__ENV.SLEEP_DURATION, 0);
const vus = Number(__ENV.VUS || 1);
const iterations = Number(__ENV.ITERATIONS || 1);
const holderPrefix = __ENV.HOLDER_PREFIX || "holder";
const issuerPrefix = __ENV.ISSUER_PREFIX || "issuer";
const sleepDuration = Number(__ENV.SLEEP_DURATION || 0);
const outputPrefix = `${holderPrefix}`;
const version = __ENV.VERSION;

export const options = {
scenarios: {
Expand All @@ -40,6 +41,7 @@ export const options = {
tags: {
test_run_id: "phased-issuance",
test_phase: "create-holders",
version: `${version}`,
},
};

Expand Down
Loading

0 comments on commit f7421de

Please sign in to comment.