Browser performance degredation while ramping up to 25 VUs despite low host CPU and memory utlization
Brief summary
We're observing significant performance degradation when running browser tests with grafana/k6:latest-with-browser.
During a gradual ramp from 1 to 25 VUs (adding 1 VU every 2 minutes), end-user transaction response times steadily increase while throughput begins to decline.
Based on our investigation, this does not appear to be a traditional infrastructure bottleneck:
- Host CPU is not saturated
- Host memory is not exhausted
- Network bandwidth is not saturated
- Backend API response times remain relatively low
- Increasing the container PID limit to
65535did not improve the behavior
We also operate a separate browser testing platform based on Selenium + Chrome that executes the same application workflow and does not exhibit similar degradation, which makes us suspect the issue may be related to:
- Browser-module scalability
- Chromium integration
- CDP communication overhead
- Browser process management
- Browser-side event processing
The behavior appears directionally similar to the discussion in #4628, where browser-heavy applications generated substantial CDP traffic and browser-side overhead.
Environment
Image:
grafana/k6:latest-with-browser
k6 version:
v1.7.1+dirty (commit/9f82e6f1fc-dirty, go1.26.1, linux/amd64)
Browser:
Chromium 146.0.7680.153 (bundled Alpine version)
Execution model:
- k6/browser
- One browser context per iteration
- One page per iterationLoad profile
We gradually ramp from 1 to 25 VUs, holding each load level for approximately 2 minutes before adding another VU.
Observed behavior
As VUs increase:
- Transaction response times trend upward
- Throughput begins to decrease
- Browser steps take progressively longer to complete
- Degradation becomes noticeable around ~25 VUs
- Higher loads can eventually result in failures and timeouts
This occurs while:
- CPU remains below saturation
- Memory remains available
- Network bandwidth remains available
- Backend APIs continue responding quickly
Because degradation begins before infrastructure resources are exhausted, we currently do not believe CPU, memory, or network capacity are the primary bottleneck.
Comparison with a Selenium-based solution
We also operate another browser testing platform using:
- Selenium
- Chrome
- Chrome DevTools Protocol (CDP)
k6 architecture
k6
└── ChromiumBoth k6 and Chromium run inside the same container using grafana/k6:latest-with-browser.
Selenium architecture
Selenium Container
│
└── CDP
│
Chrome ContainerThe browser runs in its own dedicated container and Selenium communicates with it remotely.
Observed differences
Using Selenium
- No comparable response-time degradation up to approximately 100 users
- Same application and user journey
- Similar browser flags
- Similar container sizing
- Lower CPU utilization but higher memory utilization
Using k6/browser
- Response times steadily increase during ramp-up
- Throughput decreases
- Degradation steadily increases from 1 to 25 browser VUs
- Behavior persists after raising PID limits
We recognize this is not a perfect apples-to-apples comparison because the architectures differ. However, the difference makes us wonder whether browser-module architecture or CDP-related overhead could be contributing to the observed scaling behavior.
How transaction response times are measured
We are attempting to measure end-user perceived response time, not just network request duration.
Our transaction helper:
- Starts timing immediately before a user action
- Executes the browser action
- Waits for the expected UI state
- Records elapsed time
Example timing logic:
const start = Date.now();
await action();
const duration = Date.now() - start;Example transaction:
const t2 = await transaction('02_Click_PRA', async () => {
const header = page.locator(
'//h2[contains(text(), "Example Header")]'
);
await header.click();
const startButton = page.locator(
'//button[contains(text(), "Example Label")]'
);
await startButton.waitFor({
state: 'visible',
timeout: 30000,
});
});The timer starts immediately before the click and stops when the expected element becomes visible.
As a result, the transaction duration includes:
- User interaction
- Browser rendering
- DOM updates
- AngularJS processing
- SurveyJS rendering
- JavaScript execution
- XHR/fetch activity
- Any browser-module overhead that occurs before the UI becomes ready
This same methodology is used in our Selenium-based implementation.
Application under test
The application is a client-heavy AngularJS SPA that includes:
- AngularJS
- SurveyJS
- Knockout
- Angular UI Bootstrap
- ngResource
- ngAnimate
- ngFileUpload
- Multiple custom components and plugins
One of the primary application bundles is:
/apps/pra/bundle.js
~2.45 MB (uncompressed)This is a relatively large client-side application with significant rendering and state-management activity.
HAR observations
Backend responses appear healthy
Representative API calls during the affected transaction:
GET /api/forecasts/lobs ~27.6 ms
GET /api/project/list ~36.6 msBoth requests returned HTTP 200 and showed low server-side wait times.
Significant browser-side queueing/blocking
Several HAR entries show browser-side queueing/blocking times larger than actual server processing times.
Examples:
/api/platforms
~130 ms total
~110 ms queueing
trumbowyg-ng.min.js
~98 ms total
~68 ms blocked
trumbowyg.table.min.js
~94 ms total
~51 ms blockedIn many cases, most observed latency appears to occur on the browser side rather than in backend processing.
Landing page observations
Initial application load appears relatively healthy:
DOMContentLoaded ~270 ms
Load Event ~305 msWe did not observe obvious backend bottlenecks or unusually slow initial page-load activity.
Why we suspect browser-side or browser-integration overhead
Several observations point away from infrastructure limitations:
- CPU is available when degradation begins
- Memory is available when degradation begins
- PID limit increases had no effect
- Backend APIs remain fast
- HAR captures show significant browser-side queueing/blocking
- A Selenium + Chrome implementation running the same workflow scales considerably further before exhibiting similar symptoms
Because the application, workflow, and browser technology are largely the same, we are interested in whether the behavior could be related to:
- Browser-module scalability characteristics
- Chromium process management
- CDP communication overhead
- Browser event processing overhead
- Architectural differences between bundled Chromium and remotely controlled Chromium
Thank you for taking a look. We'd appreciate any guidance on whether this behavior aligns with known browser-module limitations or if there are additional diagnostics we should collect.
k6 version
v1.7.1+dirty (commit/9f82e6f1fc-dirty, go1.26.1, linux/amd64)
OS
RHEL7
Docker version and image (if applicable)
grafana/k6:latest-with-browser
Steps to reproduce the problem
At the moment we do not have a minimal public reproduction.
The behavior is consistently observed in our environment using a browser-heavy AngularJS SPA.
High-level reproduction steps:
- Run a k6/browser test using
grafana/k6:latest-with-browser. - Launch one Chromium instance per browser VU using the standard k6/browser APIs.
- Execute an end-user workflow that:
- Navigates through a client-heavy AngularJS application
- Performs UI interactions
- Waits for UI elements to become visible before proceeding
- Gradually ramp load from 1 to 25 VUs, adding approximately 1 VU every 2 minutes.
- Measure end-user transaction duration from user action until the expected UI state becomes available.
- Observe transaction response times and throughput as VU count increases.
Expected behaviour
- Transaction response times remain relatively stable as VUs increase, provided CPU, memory, network, and backend resources remain available.
Actual behaviour
Actual result:
- Transaction response times steadily increase as VU count rises.
- Throughput begins to decrease.
- Timeouts/failures may occur at higher load levels.
- The degradation occurs despite CPU, memory, and backend systems remaining healthy.
Source: grafana/k6