From 9a99e4694845a6be16690e8e20a0095300a3b320 Mon Sep 17 00:00:00 2001 From: JoshuaNam Date: Wed, 11 Mar 2026 00:12:47 +0000 Subject: [PATCH 1/6] feat: add K6 load test for deployed NodeBB instance --- eslint.config.mjs | 1 + test/load-test.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/load-test.js diff --git a/eslint.config.mjs b/eslint.config.mjs index 47cfa158f5..f26710566b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,6 +29,7 @@ export default defineConfig([ 'test/files/', '*.min.js', 'install/docker/', + 'test/load-test.js', ], }, // tests diff --git a/test/load-test.js b/test/load-test.js new file mode 100644 index 0000000000..71a6ad5bbd --- /dev/null +++ b/test/load-test.js @@ -0,0 +1,48 @@ +import http from "k6/http"; +import { check, sleep } from "k6"; + +export const options = { + stages: [ + { duration: "10s", target: 5 }, + { duration: "20s", target: 10 }, + { duration: "10s", target: 0 }, + ], + thresholds: { + http_req_duration: ["p(95)<500"], + http_req_failed: ["rate<0.1"], + }, +}; + +const BASE_URL = "http://17313-team11.s3d.cmu.edu:4567"; + +export default function () { + const homeRes = http.get(`${BASE_URL}/`); + check(homeRes, { + "homepage returns 200": (r) => r.status === 200, + "homepage loads within 500ms": (r) => r.timings.duration < 500, + }); + + sleep(1); + + const categoriesRes = http.get(`${BASE_URL}/api/categories`); + check(categoriesRes, { + "categories API returns 200": (r) => r.status === 200, + "categories API loads within 500ms": (r) => r.timings.duration < 500, + }); + + sleep(1); + + const generalRes = http.get(`${BASE_URL}/category/2/general-discussion`); + check(generalRes, { + "General Discussion returns 200": (r) => r.status === 200, + }); + + sleep(1); + + const announcementsRes = http.get(`${BASE_URL}/category/1/announcements`); + check(announcementsRes, { + "Announcements returns 200": (r) => r.status === 200, + }); + + sleep(1); +} From c9959abdcd6d495986507bd63adf036d0bedf9b1 Mon Sep 17 00:00:00 2001 From: JoshuaNam Date: Wed, 11 Mar 2026 00:22:47 +0000 Subject: [PATCH 2/6] added k6 test results to a txt file --- k6-results.txt | 186 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 k6-results.txt diff --git a/k6-results.txt b/k6-results.txt new file mode 100644 index 0000000000..ef8254abb1 --- /dev/null +++ b/k6-results.txt @@ -0,0 +1,186 @@ + + /\ Grafana /‾‾/ + /\ / \ |\ __ / / + / \/ \ | |/ / / ‾‾\ + / \ | ( | (‾) | + / __________ \ |_|\_\ \_____/ + + + execution: local + script: test/load-test.js + output: - + + scenarios: (100.00%) 1 scenario, 10 max VUs, 1m10s max duration (incl. graceful stop): + * default: Up to 10 looping VUs for 40s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + +running (0m01.0s), 01/10 VUs, 0 complete and 0 interrupted iterations +default [ 2% ] 01/10 VUs 01.0s/40.0s + +running (0m02.0s), 01/10 VUs, 0 complete and 0 interrupted iterations +default [ 5% ] 01/10 VUs 02.0s/40.0s + +running (0m03.0s), 02/10 VUs, 0 complete and 0 interrupted iterations +default [ 7% ] 02/10 VUs 03.0s/40.0s + +running (0m04.0s), 02/10 VUs, 0 complete and 0 interrupted iterations +default [ 10% ] 02/10 VUs 04.0s/40.0s + +running (0m05.0s), 02/10 VUs, 1 complete and 0 interrupted iterations +default [ 12% ] 02/10 VUs 05.0s/40.0s + +running (0m06.0s), 03/10 VUs, 1 complete and 0 interrupted iterations +default [ 15% ] 03/10 VUs 06.0s/40.0s + +running (0m07.0s), 03/10 VUs, 2 complete and 0 interrupted iterations +default [ 17% ] 03/10 VUs 07.0s/40.0s + +running (0m08.0s), 04/10 VUs, 2 complete and 0 interrupted iterations +default [ 20% ] 04/10 VUs 08.0s/40.0s + +running (0m09.0s), 04/10 VUs, 3 complete and 0 interrupted iterations +default [ 22% ] 04/10 VUs 09.0s/40.0s + +running (0m10.0s), 04/10 VUs, 4 complete and 0 interrupted iterations +default [ 25% ] 04/10 VUs 10.0s/40.0s + +running (0m11.0s), 05/10 VUs, 5 complete and 0 interrupted iterations +default [ 27% ] 05/10 VUs 11.0s/40.0s + +running (0m12.0s), 05/10 VUs, 6 complete and 0 interrupted iterations +default [ 30% ] 05/10 VUs 12.0s/40.0s + +running (0m13.0s), 05/10 VUs, 7 complete and 0 interrupted iterations +default [ 32% ] 05/10 VUs 13.0s/40.0s + +running (0m14.0s), 05/10 VUs, 8 complete and 0 interrupted iterations +default [ 35% ] 05/10 VUs 14.0s/40.0s + +running (0m15.0s), 06/10 VUs, 9 complete and 0 interrupted iterations +default [ 37% ] 06/10 VUs 15.0s/40.0s + +running (0m16.0s), 06/10 VUs, 10 complete and 0 interrupted iterations +default [ 40% ] 06/10 VUs 16.0s/40.0s + +running (0m17.0s), 06/10 VUs, 11 complete and 0 interrupted iterations +default [ 42% ] 06/10 VUs 17.0s/40.0s + +running (0m18.0s), 06/10 VUs, 13 complete and 0 interrupted iterations +default [ 45% ] 06/10 VUs 18.0s/40.0s + +running (0m19.0s), 07/10 VUs, 15 complete and 0 interrupted iterations +default [ 47% ] 07/10 VUs 19.0s/40.0s + +running (0m20.0s), 07/10 VUs, 16 complete and 0 interrupted iterations +default [ 50% ] 07/10 VUs 20.0s/40.0s + +running (0m21.0s), 07/10 VUs, 17 complete and 0 interrupted iterations +default [ 52% ] 07/10 VUs 21.0s/40.0s + +running (0m22.0s), 07/10 VUs, 19 complete and 0 interrupted iterations +default [ 55% ] 07/10 VUs 22.0s/40.0s + +running (0m23.0s), 08/10 VUs, 22 complete and 0 interrupted iterations +default [ 57% ] 08/10 VUs 23.0s/40.0s + +running (0m24.0s), 08/10 VUs, 23 complete and 0 interrupted iterations +default [ 60% ] 08/10 VUs 24.0s/40.0s + +running (0m25.0s), 08/10 VUs, 24 complete and 0 interrupted iterations +default [ 62% ] 08/10 VUs 25.0s/40.0s + +running (0m26.0s), 08/10 VUs, 25 complete and 0 interrupted iterations +default [ 65% ] 08/10 VUs 26.0s/40.0s + +running (0m27.0s), 09/10 VUs, 29 complete and 0 interrupted iterations +default [ 67% ] 09/10 VUs 27.0s/40.0s + +running (0m28.0s), 09/10 VUs, 31 complete and 0 interrupted iterations +default [ 70% ] 09/10 VUs 28.0s/40.0s + +running (0m29.0s), 09/10 VUs, 32 complete and 0 interrupted iterations +default [ 72% ] 09/10 VUs 29.0s/40.0s + +running (0m30.0s), 09/10 VUs, 33 complete and 0 interrupted iterations +default [ 75% ] 09/10 VUs 30.0s/40.0s + +running (0m31.0s), 10/10 VUs, 38 complete and 0 interrupted iterations +default [ 77% ] 10/10 VUs 31.0s/40.0s + +running (0m32.0s), 10/10 VUs, 39 complete and 0 interrupted iterations +default [ 80% ] 10/10 VUs 32.0s/40.0s + +running (0m33.0s), 10/10 VUs, 41 complete and 0 interrupted iterations +default [ 82% ] 10/10 VUs 33.0s/40.0s + +running (0m34.0s), 10/10 VUs, 41 complete and 0 interrupted iterations +default [ 85% ] 10/10 VUs 34.0s/40.0s + +running (0m35.0s), 06/10 VUs, 47 complete and 0 interrupted iterations +default [ 87% ] 06/10 VUs 35.0s/40.0s + +running (0m36.0s), 05/10 VUs, 49 complete and 0 interrupted iterations +default [ 90% ] 05/10 VUs 36.0s/40.0s + +running (0m37.0s), 05/10 VUs, 50 complete and 0 interrupted iterations +default [ 92% ] 05/10 VUs 37.0s/40.0s + +running (0m38.0s), 04/10 VUs, 51 complete and 0 interrupted iterations +default [ 95% ] 04/10 VUs 38.0s/40.0s + +running (0m39.0s), 04/10 VUs, 52 complete and 0 interrupted iterations +default [ 97% ] 04/10 VUs 39.0s/40.0s + +running (0m40.0s), 02/10 VUs, 54 complete and 0 interrupted iterations +default [ 100% ] 02/10 VUs 40.0s/40.0s + +running (0m41.0s), 01/10 VUs, 55 complete and 0 interrupted iterations +default ↓ [ 100% ] 02/10 VUs 40s + +running (0m42.0s), 01/10 VUs, 55 complete and 0 interrupted iterations +default ↓ [ 100% ] 02/10 VUs 40s + + + █ THRESHOLDS + + http_req_duration + ✓ 'p(95)<500' p(95)=84.97ms + + http_req_failed + ✓ 'rate<0.1' rate=0.00% + + + █ TOTAL RESULTS + + checks_total.......: 336 7.911576/s + checks_succeeded...: 100.00% 336 out of 336 + checks_failed......: 0.00% 0 out of 336 + + ✓ homepage returns 200 + ✓ homepage loads within 500ms + ✓ categories API returns 200 + ✓ categories API loads within 500ms + ✓ General Discussion returns 200 + ✓ Announcements returns 200 + + HTTP + http_req_duration..............: avg=57.03ms min=19.15ms med=60.61ms max=176.64ms p(90)=75.9ms p(95)=84.97ms + { expected_response:true }...: avg=57.03ms min=19.15ms med=60.61ms max=176.64ms p(90)=75.9ms p(95)=84.97ms + http_req_failed................: 0.00% 0 out of 224 + http_reqs......................: 224 5.274384/s + + EXECUTION + iteration_duration.............: avg=4.24s min=4.19s med=4.23s max=4.4s p(90)=4.31s p(95)=4.34s + iterations.....................: 56 1.318596/s + vus............................: 1 min=1 max=10 + vus_max........................: 10 min=10 max=10 + + NETWORK + data_received..................: 8.7 MB 204 kB/s + data_sent......................: 40 kB 952 B/s + + + + +running (0m42.5s), 00/10 VUs, 56 complete and 0 interrupted iterations +default ✓ [ 100% ] 00/10 VUs 40s From e10f65ea63b943cdc7f6f0233c6a9a055f9eb011 Mon Sep 17 00:00:00 2001 From: JoshuaNam Date: Wed, 11 Mar 2026 00:30:11 +0000 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20exclude=20K6=20load=20test=20from=20?= =?UTF-8?q?Mocha=20=E2=80=94=20K6=20scripts=20use=20their=20own=20runtime?= =?UTF-8?q?=20and=20cannot=20be=20resolved=20by=20Node.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mocharc.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mocharc.yml b/.mocharc.yml index 16d8518d1b..a602d06ac6 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -2,3 +2,5 @@ reporter: dot timeout: 25000 exit: true bail: true +ignore: + - test/load-test.js From 9fd77473f99fd39955e82f194b51a8c3d0034929 Mon Sep 17 00:00:00 2001 From: topshenyi-web Date: Mon, 16 Mar 2026 21:37:44 -0400 Subject: [PATCH 4/6] here goes nothing... --- .github/workflows/k6.yaml | 71 +++++++++++++++++++++++++++++++++++++++ test/load-test.js | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/k6.yaml diff --git a/.github/workflows/k6.yaml b/.github/workflows/k6.yaml new file mode 100644 index 0000000000..4d208b4943 --- /dev/null +++ b/.github/workflows/k6.yaml @@ -0,0 +1,71 @@ +name: Run k6 + +on: + push: + branches: + - main + - master + - develop + pull_request: + branches: + - main + - master + - develop + +jobs: + protocol: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - run: cp install/package.json package.json + + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: NPM Install + uses: bahmutov/npm-install@v1 + with: + useLockFile: false + + - name: Setup on Redis + env: + SETUP: >- + { + "url": "http://127.0.0.1:4567/forum", + "secret": "abcdef", + "admin:username": "admin", + "admin:email": "test@example.org", + "admin:password": "hAN3Eg8W", + "admin:password:confirm": "hAN3Eg8W", + + "database": "redis", + "redis:host": "127.0.0.1", + "redis:port": 6379, + "redis:password": "", + "redis:database": 0 + } + CI: >- + { + "host": "127.0.0.1", + "database": 1, + "port": 6379 + } + run: | + node app --setup="${SETUP}" --ci="${CI}" + - name: Build NodeBB + run: ./nodebb build + - name: Start NodeBB server + run: | + ./nodebb start + sleep 20 + - uses: grafana/setup-k6-action@v1 + - uses: grafana/run-k6-action@v1 + with: + path: | + ./test/load-test.js + - name: Stop NodeBB server + if: always() + run: ./nodebb stop diff --git a/test/load-test.js b/test/load-test.js index 71a6ad5bbd..0d9f0aaad3 100644 --- a/test/load-test.js +++ b/test/load-test.js @@ -13,7 +13,7 @@ export const options = { }, }; -const BASE_URL = "http://17313-team11.s3d.cmu.edu:4567"; +const BASE_URL = "http://127.0.0.1:4567"; export default function () { const homeRes = http.get(`${BASE_URL}/`); From 2ceccb1d8e21361c0ee7981e9cb708256af5bd81 Mon Sep 17 00:00:00 2001 From: topshenyi-web Date: Mon, 16 Mar 2026 21:41:26 -0400 Subject: [PATCH 5/6] fix: fix test by adding redis --- .github/workflows/k6.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/k6.yaml b/.github/workflows/k6.yaml index 4d208b4943..0dc9c3d8a7 100644 --- a/.github/workflows/k6.yaml +++ b/.github/workflows/k6.yaml @@ -15,6 +15,18 @@ on: jobs: protocol: runs-on: ubuntu-latest + services: + redis: + image: "redis:8.0.1" + # Set health checks to wait until redis has started + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps port 6379 on service container to the host + - 6379:6379 steps: - uses: actions/checkout@v2 From 846ff669c8e021889e84a5d743cedd25f795095a Mon Sep 17 00:00:00 2001 From: topshenyi-web Date: Mon, 16 Mar 2026 21:48:57 -0400 Subject: [PATCH 6/6] chore: remove k6 output file --- k6-results.txt | 186 ------------------------------------------------- 1 file changed, 186 deletions(-) delete mode 100644 k6-results.txt diff --git a/k6-results.txt b/k6-results.txt deleted file mode 100644 index ef8254abb1..0000000000 --- a/k6-results.txt +++ /dev/null @@ -1,186 +0,0 @@ - - /\ Grafana /‾‾/ - /\ / \ |\ __ / / - / \/ \ | |/ / / ‾‾\ - / \ | ( | (‾) | - / __________ \ |_|\_\ \_____/ - - - execution: local - script: test/load-test.js - output: - - - scenarios: (100.00%) 1 scenario, 10 max VUs, 1m10s max duration (incl. graceful stop): - * default: Up to 10 looping VUs for 40s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) - - -running (0m01.0s), 01/10 VUs, 0 complete and 0 interrupted iterations -default [ 2% ] 01/10 VUs 01.0s/40.0s - -running (0m02.0s), 01/10 VUs, 0 complete and 0 interrupted iterations -default [ 5% ] 01/10 VUs 02.0s/40.0s - -running (0m03.0s), 02/10 VUs, 0 complete and 0 interrupted iterations -default [ 7% ] 02/10 VUs 03.0s/40.0s - -running (0m04.0s), 02/10 VUs, 0 complete and 0 interrupted iterations -default [ 10% ] 02/10 VUs 04.0s/40.0s - -running (0m05.0s), 02/10 VUs, 1 complete and 0 interrupted iterations -default [ 12% ] 02/10 VUs 05.0s/40.0s - -running (0m06.0s), 03/10 VUs, 1 complete and 0 interrupted iterations -default [ 15% ] 03/10 VUs 06.0s/40.0s - -running (0m07.0s), 03/10 VUs, 2 complete and 0 interrupted iterations -default [ 17% ] 03/10 VUs 07.0s/40.0s - -running (0m08.0s), 04/10 VUs, 2 complete and 0 interrupted iterations -default [ 20% ] 04/10 VUs 08.0s/40.0s - -running (0m09.0s), 04/10 VUs, 3 complete and 0 interrupted iterations -default [ 22% ] 04/10 VUs 09.0s/40.0s - -running (0m10.0s), 04/10 VUs, 4 complete and 0 interrupted iterations -default [ 25% ] 04/10 VUs 10.0s/40.0s - -running (0m11.0s), 05/10 VUs, 5 complete and 0 interrupted iterations -default [ 27% ] 05/10 VUs 11.0s/40.0s - -running (0m12.0s), 05/10 VUs, 6 complete and 0 interrupted iterations -default [ 30% ] 05/10 VUs 12.0s/40.0s - -running (0m13.0s), 05/10 VUs, 7 complete and 0 interrupted iterations -default [ 32% ] 05/10 VUs 13.0s/40.0s - -running (0m14.0s), 05/10 VUs, 8 complete and 0 interrupted iterations -default [ 35% ] 05/10 VUs 14.0s/40.0s - -running (0m15.0s), 06/10 VUs, 9 complete and 0 interrupted iterations -default [ 37% ] 06/10 VUs 15.0s/40.0s - -running (0m16.0s), 06/10 VUs, 10 complete and 0 interrupted iterations -default [ 40% ] 06/10 VUs 16.0s/40.0s - -running (0m17.0s), 06/10 VUs, 11 complete and 0 interrupted iterations -default [ 42% ] 06/10 VUs 17.0s/40.0s - -running (0m18.0s), 06/10 VUs, 13 complete and 0 interrupted iterations -default [ 45% ] 06/10 VUs 18.0s/40.0s - -running (0m19.0s), 07/10 VUs, 15 complete and 0 interrupted iterations -default [ 47% ] 07/10 VUs 19.0s/40.0s - -running (0m20.0s), 07/10 VUs, 16 complete and 0 interrupted iterations -default [ 50% ] 07/10 VUs 20.0s/40.0s - -running (0m21.0s), 07/10 VUs, 17 complete and 0 interrupted iterations -default [ 52% ] 07/10 VUs 21.0s/40.0s - -running (0m22.0s), 07/10 VUs, 19 complete and 0 interrupted iterations -default [ 55% ] 07/10 VUs 22.0s/40.0s - -running (0m23.0s), 08/10 VUs, 22 complete and 0 interrupted iterations -default [ 57% ] 08/10 VUs 23.0s/40.0s - -running (0m24.0s), 08/10 VUs, 23 complete and 0 interrupted iterations -default [ 60% ] 08/10 VUs 24.0s/40.0s - -running (0m25.0s), 08/10 VUs, 24 complete and 0 interrupted iterations -default [ 62% ] 08/10 VUs 25.0s/40.0s - -running (0m26.0s), 08/10 VUs, 25 complete and 0 interrupted iterations -default [ 65% ] 08/10 VUs 26.0s/40.0s - -running (0m27.0s), 09/10 VUs, 29 complete and 0 interrupted iterations -default [ 67% ] 09/10 VUs 27.0s/40.0s - -running (0m28.0s), 09/10 VUs, 31 complete and 0 interrupted iterations -default [ 70% ] 09/10 VUs 28.0s/40.0s - -running (0m29.0s), 09/10 VUs, 32 complete and 0 interrupted iterations -default [ 72% ] 09/10 VUs 29.0s/40.0s - -running (0m30.0s), 09/10 VUs, 33 complete and 0 interrupted iterations -default [ 75% ] 09/10 VUs 30.0s/40.0s - -running (0m31.0s), 10/10 VUs, 38 complete and 0 interrupted iterations -default [ 77% ] 10/10 VUs 31.0s/40.0s - -running (0m32.0s), 10/10 VUs, 39 complete and 0 interrupted iterations -default [ 80% ] 10/10 VUs 32.0s/40.0s - -running (0m33.0s), 10/10 VUs, 41 complete and 0 interrupted iterations -default [ 82% ] 10/10 VUs 33.0s/40.0s - -running (0m34.0s), 10/10 VUs, 41 complete and 0 interrupted iterations -default [ 85% ] 10/10 VUs 34.0s/40.0s - -running (0m35.0s), 06/10 VUs, 47 complete and 0 interrupted iterations -default [ 87% ] 06/10 VUs 35.0s/40.0s - -running (0m36.0s), 05/10 VUs, 49 complete and 0 interrupted iterations -default [ 90% ] 05/10 VUs 36.0s/40.0s - -running (0m37.0s), 05/10 VUs, 50 complete and 0 interrupted iterations -default [ 92% ] 05/10 VUs 37.0s/40.0s - -running (0m38.0s), 04/10 VUs, 51 complete and 0 interrupted iterations -default [ 95% ] 04/10 VUs 38.0s/40.0s - -running (0m39.0s), 04/10 VUs, 52 complete and 0 interrupted iterations -default [ 97% ] 04/10 VUs 39.0s/40.0s - -running (0m40.0s), 02/10 VUs, 54 complete and 0 interrupted iterations -default [ 100% ] 02/10 VUs 40.0s/40.0s - -running (0m41.0s), 01/10 VUs, 55 complete and 0 interrupted iterations -default ↓ [ 100% ] 02/10 VUs 40s - -running (0m42.0s), 01/10 VUs, 55 complete and 0 interrupted iterations -default ↓ [ 100% ] 02/10 VUs 40s - - - █ THRESHOLDS - - http_req_duration - ✓ 'p(95)<500' p(95)=84.97ms - - http_req_failed - ✓ 'rate<0.1' rate=0.00% - - - █ TOTAL RESULTS - - checks_total.......: 336 7.911576/s - checks_succeeded...: 100.00% 336 out of 336 - checks_failed......: 0.00% 0 out of 336 - - ✓ homepage returns 200 - ✓ homepage loads within 500ms - ✓ categories API returns 200 - ✓ categories API loads within 500ms - ✓ General Discussion returns 200 - ✓ Announcements returns 200 - - HTTP - http_req_duration..............: avg=57.03ms min=19.15ms med=60.61ms max=176.64ms p(90)=75.9ms p(95)=84.97ms - { expected_response:true }...: avg=57.03ms min=19.15ms med=60.61ms max=176.64ms p(90)=75.9ms p(95)=84.97ms - http_req_failed................: 0.00% 0 out of 224 - http_reqs......................: 224 5.274384/s - - EXECUTION - iteration_duration.............: avg=4.24s min=4.19s med=4.23s max=4.4s p(90)=4.31s p(95)=4.34s - iterations.....................: 56 1.318596/s - vus............................: 1 min=1 max=10 - vus_max........................: 10 min=10 max=10 - - NETWORK - data_received..................: 8.7 MB 204 kB/s - data_sent......................: 40 kB 952 B/s - - - - -running (0m42.5s), 00/10 VUs, 56 complete and 0 interrupted iterations -default ✓ [ 100% ] 00/10 VUs 40s