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

Run k6 against local Nuxt on PRs #4924

Merged
merged 7 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
92 changes: 92 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,86 @@ jobs:
env:
DEPLOYMENT_ENV: production

nuxt-load-test:
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe the load testing could be split to a separate action?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Potentially, and it's something I plan to look into when adding the job to run k6 against staging in the next PR after this one. The steps to run k6 on a local environment are so different than running it against a remote environment, I wanted to make sure I didn't abstract too early 🙂

name: Load test local frontend
runs-on: ubuntu-latest
needs:
- nuxt-build

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
install_recipe: node-install
locales: "test"

- name: Build and run Nuxt and Talkback
run: |
just frontend/run build
just frontend/run talkback &
env NUXT_PUBLIC_API_URL=http://127.0.0.1:49153/ just frontend/run start &

- name: Setup k6
uses: grafana/setup-k6-action@v1

- name: Wait for local Talkback to be available
# 10 seconds, talkback has a slow startup; this step will fail if Talkback never becomes available
run: npx wait-port -t 10000 :49153

- name: Wait for local Nuxt to be available
# 2 seconds, this step will fail if Nuxt never becomes available
run: npx wait-port -t 2000 http://127.0.0.1:8443/healthcheck

- name: Run k6 frontend all against local Nuxt
run: |
just k6 frontend all \
-e FRONTEND_URL=http://127.0.0.1:8443/ \
-e text_summary=/tmp/k6-summary.txt

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: k6-output
path: /tmp/k6-summary.txt

- name: Make comment body
shell: python
run: |
from pathlib import Path
summary = Path("/tmp/k6-summary.txt").read_text()
Path("/tmp/k6-summary-comment.txt").write_text(f"""
## Latest k6 run output[^update]

```
${summary}
```

[^update]: This comment will automatically update with new output each time k6 runs for this PR
""")

- uses: peter-evans/find-comment@v3
id: k6-summary-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Latest k6 run output

- name: Post comment summary
uses: peter-evans/create-or-update-comment@v4
# Do not comment on forks
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.owner.login == 'WordPress' &&
github.actor != 'dependabot[bot]'
with:
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body-path: /tmp/k6-summary-comment.txt
comment-id: ${{ steps.k6-summary-comment.outputs.comment-id }}

nuxt-checks:
name: Run Nuxt checks
if: |
Expand Down Expand Up @@ -1182,6 +1262,18 @@ jobs:
wait_time: 60 # check every minute
max_time: 1800 # allow up to 30 minutes for a deployment

- name: Setup k6
uses: grafana/setup-k6-action@v1

- name: Run k6 script
env:
K6_CLOUD_TOKEN: ${{ secrets.GC_K6_TOKEN }}
K6_SIGNING_SECRET: ${{ secrets.K6_SIGNING_SECRET }}
run: |
just k6 frontend all --out cloud \
-e FRONTEND_URL=https://staging.openverse.org/ \
-e signing_secret="$K6_SIGNING_SECRET"

deploy-api:
name: Deploy staging API
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions docker/dev_env/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ ENV PATH="${PNPM_BIN}:${N_PREFIX}/bin:${PDM_PYTHONS}:${HOME}/.local/bin:${PATH}"
# - nodejs: language runtime (includes npm but not Corepack)
# - docker*: used to interact with host Docker socket
# - postgresql*: required to connect to PostgreSQL databases
# - k6, words: used for load testing
# - k6: used for load testing
#
# pipx dependencies:
# - httpie: CLI HTTP client
Expand Down Expand Up @@ -65,7 +65,7 @@ RUN mkdir /pipx \
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
unzip \
postgresql postgresql-devel \
k6 words \
k6 \
&& dnf clean all \
&& pipx install --global \
httpie \
Expand Down
34 changes: 32 additions & 2 deletions frontend/test/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const port = 49153
const host = "https://api.openverse.org"

const urlPatterns = {
search: /\/(?<mediaType>images|audio|video|model-3d)\/*\?(?<query>[\w&=+]+)/,
search: /\/(?<mediaType>images|audio|video|model-3d)\/*\?(?<query>.*?)$/u,
thumb:
/\/(?<mediaType>images|audio|video|model-3d)\/(?<uuid>[\w-]{32,})\/thumb/,
related:
Expand Down Expand Up @@ -100,6 +100,8 @@ const getBodyUtil = (tape) =>
tape.res?.headers["content-encoding"]?.includes(key)
)?.[1] ?? BodyUtils.default

const MAX_PEAKS = 200

/**
* Transform any response values to use the talkback
* proxy instead of pointing directly upstream for
Expand Down Expand Up @@ -142,11 +144,34 @@ const tapeDecorator = (tape) => {
const bodyUtil = getBodyUtil(tape)
const responseBody = bodyUtil.read(tape.res.body).toString()

const fixedResponseBody = responseBody.replace(
let fixedResponseBody = responseBody.replace(
/https?:\/\/api.openverse.org/g,
`http://localhost:${port}`
)

if (
tape.req.url.includes("/audio/") &&
!tape.req.url.includes("/audio/stats")
) {
const responseBodyJson = JSON.parse(fixedResponseBody)

// The search or related requests
if (responseBodyJson.results) {
responseBodyJson.results.map((result) => {
if (result.peaks && result.peaks.length > MAX_PEAKS) {
result.peaks = result.peaks.slice(0, MAX_PEAKS)
}
})
// The single result requests
} else if (
responseBodyJson.peaks &&
responseBodyJson.peaks.length > MAX_PEAKS
) {
responseBodyJson.peaks = responseBodyJson.peaks.slice(0, MAX_PEAKS)
}
fixedResponseBody = JSON.stringify(responseBodyJson)
}

tape.res.body = Buffer.from(bodyUtil.save(fixedResponseBody))
return tape
}
Expand All @@ -164,6 +189,11 @@ const opts = /** @type {Partial<TalkbackOptions>} */ ({
summary: false,
tapeNameGenerator,
tapeDecorator,
responseDecorator: (tape, req, context) => {
// Log responses to make debugging easier
console.log(req.method, req.url, tape.res?.status, context.id)
return tape
},
})

const server = talkback(opts)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
meta: {
createdAt: '2024-09-20T02:47:13.176Z',
host: 'https://api.openverse.org',
resHumanReadable: true,
resUncompressed: true,
},
req: {
headers: {
connection: 'keep-alive',
},
url: '/v1/audio/?q=Dom+Quixote+de+la+Mancha&license=by&extension=mp3&source=jamendo&peaks=true',
method: 'GET',
body: '',
},
res: {
status: 200,
headers: {
date: [
'Fri, 20 Sep 2024 02:47:13 GMT',
],
'content-type': [
'application/json',
],
'transfer-encoding': [
'chunked',
],
connection: [
'keep-alive',
],
vary: [
'Accept, Authorization, origin, Accept-Encoding',
],
allow: [
'GET, HEAD, OPTIONS',
],
'x-ratelimit-limit-anon_burst': [
'20/min',
],
'x-ratelimit-available-anon_burst': [
'17',
],
'x-ratelimit-limit-anon_sustained': [
'200/day',
],
'x-ratelimit-available-anon_sustained': [
'166',
],
'x-frame-options': [
'DENY',
],
'x-content-type-options': [
'nosniff',
],
'referrer-policy': [
'same-origin',
],
'cross-origin-opener-policy': [
'same-origin',
],
'x-request-id': [
'f03692b6-0328-4603-9737-771e9100765e',
],
'cf-cache-status': [
'HIT',
],
age: [
'1144',
],
'last-modified': [
'Fri, 20 Sep 2024 02:28:09 GMT',
],
server: [
'cloudflare',
],
'cf-ray': [
'8c5e7d536b31274b-ADL',
],
'content-encoding': [
'br',
],
},
body: {
result_count: 0,
page_count: 0,
page_size: 20,
page: 1,
results: [],
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
meta: {
createdAt: '2024-09-20T02:47:12.052Z',
host: 'https://api.openverse.org',
resHumanReadable: true,
resUncompressed: true,
},
req: {
headers: {
connection: 'keep-alive',
},
url: '/v1/audio/?q=Dom+Quixote+de+la+Mancha&peaks=true',
method: 'GET',
body: '',
},
res: {
status: 200,
headers: {
date: [
'Fri, 20 Sep 2024 02:47:12 GMT',
],
'content-type': [
'application/json',
],
'transfer-encoding': [
'chunked',
],
connection: [
'keep-alive',
],
vary: [
'Accept, Authorization, origin, Accept-Encoding',
],
allow: [
'GET, HEAD, OPTIONS',
],
'x-ratelimit-limit-anon_burst': [
'20/min',
],
'x-ratelimit-available-anon_burst': [
'11',
],
'x-ratelimit-limit-anon_sustained': [
'200/day',
],
'x-ratelimit-available-anon_sustained': [
'6',
],
'x-frame-options': [
'DENY',
],
'x-content-type-options': [
'nosniff',
],
'referrer-policy': [
'same-origin',
],
'cross-origin-opener-policy': [
'same-origin',
],
'x-request-id': [
'43ea964c-6a59-4abb-8b12-8110e850d8fc',
],
'cf-cache-status': [
'HIT',
],
age: [
'1143',
],
'last-modified': [
'Fri, 20 Sep 2024 02:28:09 GMT',
],
server: [
'cloudflare',
],
'cf-ray': [
'8c5e7d4c599b274b-ADL',
],
'content-encoding': [
'br',
],
},
body: {
result_count: 0,
page_count: 0,
page_size: 20,
page: 1,
results: [],
},
},
}
Loading