diff --git a/index.html b/index.html
index 03aef6e..b8fe111 100644
--- a/index.html
+++ b/index.html
@@ -160,6 +160,21 @@
>
+
+
+
+ Calculates realistic "Total Page Load Time" by fetching assets
+
+
+
+
+
Real World Performance
+
+
+
Avg. Page Load Time
+
0ms
+
+
+
+
+
Bandwidth Usage
diff --git a/script.js b/script.js
index 5e22d0c..55d6fdb 100644
--- a/script.js
+++ b/script.js
@@ -62,6 +62,8 @@ class StressTestingTool {
requestsPerSecond: [],
workers: [], // Web Worker instances
workerStats: new Map(), // Stats per worker
+ pageLoadTimes: [], // All page load times for percentiles
+ totalAssetRequests: 0,
updateInterval: null,
chartUpdateInterval: null,
userErrorData: [],
@@ -164,6 +166,12 @@ class StressTestingTool {
errorsNetwork: document.getElementById("errorsNetwork"),
totalBandwidth: document.getElementById("totalBandwidth"),
+ // Page Load Simulation
+ simulateAssets: document.getElementById("simulateAssets"),
+ pageLoadSection: document.getElementById("pageLoadSection"),
+ avgPageLoadTime: document.getElementById("avgPageLoadTime"),
+ totalAssetRequests: document.getElementById("totalAssetRequests"),
+
// Request history
requestHistoryBody: document.getElementById("requestHistoryBody"),
@@ -747,6 +755,8 @@ class StressTestingTool {
this.elements.linksPerPage?.value || 10
);
}
+
+ this.config.simulateAssets = this.elements.simulateAssets?.checked || false;
}
resetState() {
@@ -768,6 +778,8 @@ class StressTestingTool {
};
this.state.totalBytesSent = 0;
this.state.totalBytesReceived = 0;
+ this.state.pageLoadTimes = [];
+ this.state.totalAssetRequests = 0;
this.state.requestHistory = [];
this.state.percentiles = { p50: 0, p95: 0, p99: 0 };
@@ -876,6 +888,20 @@ class StressTestingTool {
this.state.totalBytesReceived = bytesReceived;
this.state.errorsByCategory = errors;
this.state.responseTimes = allResponseTimes.slice(-1000); // Sample for percentiles
+
+ // Aggregate page load times and assets
+ let totalAssets = 0;
+ let newPageLoadTimes = [];
+ for (const stats of this.state.workerStats.values()) {
+ totalAssets += stats.totalAssetRequests || 0;
+ if (stats.pageLoadTimes && stats.pageLoadTimes.length > 0) {
+ newPageLoadTimes = newPageLoadTimes.concat(stats.pageLoadTimes);
+ }
+ }
+ this.state.totalAssetRequests = totalAssets;
+ if (newPageLoadTimes.length > 0) {
+ this.state.pageLoadTimes = this.state.pageLoadTimes.concat(newPageLoadTimes).slice(-1000);
+ }
}
addToRequestHistory(request) {
@@ -1010,6 +1036,19 @@ class StressTestingTool {
this.state.totalBytesSent + this.state.totalBytesReceived;
this.elements.totalBandwidth.textContent = formatBytes(totalBytes);
}
+
+ // Update simulation metrics
+ if (this.config.simulateAssets) {
+ this.elements.pageLoadSection.style.display = 'block';
+ this.elements.totalAssetRequests.textContent = this.state.totalAssetRequests.toLocaleString();
+
+ const avgPageLoad = this.state.pageLoadTimes.length > 0
+ ? Math.round(this.state.pageLoadTimes.reduce((a, b) => a + b, 0) / this.state.pageLoadTimes.length)
+ : 0;
+ this.elements.avgPageLoadTime.textContent = `${avgPageLoad}ms`;
+ } else {
+ this.elements.pageLoadSection.style.display = 'none';
+ }
}
updateCharts() {
@@ -1211,8 +1250,17 @@ class StressTestingTool {
"Error Threshold": this.state.errorThreshold
? `${this.state.errorThreshold.users} users at ${this.state.errorThreshold.time}s (${this.state.errorThreshold.errorRate}% error rate)`
: "No errors detected",
+ "Full Page Simulation": this.config.simulateAssets ? "Enabled" : "Disabled",
};
+ if (this.config.simulateAssets) {
+ const avgPageLoad = this.state.pageLoadTimes.length > 0
+ ? Math.round(this.state.pageLoadTimes.reduce((a, b) => a + b, 0) / this.state.pageLoadTimes.length)
+ : 0;
+ results["Average Page Load Time"] = `${avgPageLoad}ms`;
+ results["Total Asset Requests"] = this.state.totalAssetRequests.toLocaleString();
+ }
+
if (this.config.crawlerEnabled) {
results["Unique URLs Visited"] = this.state.visitedUrls.size;
}
diff --git a/worker.js b/worker.js
index 4396983..d779da0 100644
--- a/worker.js
+++ b/worker.js
@@ -14,6 +14,8 @@ let state = {
responseTimes: [],
bytesSent: 0,
bytesReceived: 0,
+ pageLoadTimes: [],
+ totalAssetRequests: 0,
errorsByCategory: {
"4xx": 0,
"5xx": 0,
@@ -89,7 +91,21 @@ async function runUser(id) {
let crawlDepth = 0;
while (state.active && Date.now() < endTime) {
+ const pageLoadStart = performance.now();
const result = await makeRequest(currentUrl);
+ let totalPageTime = result.responseTime;
+
+ // asset simulation
+ if (config.simulateAssets && result.success && result.body) {
+ const assets = extractAssets(result.body, currentUrl);
+ if (assets.length > 0) {
+ const assetResults = await fetchAssetsThrottled(assets);
+ const pageLoadEnd = performance.now();
+ totalPageTime = pageLoadEnd - pageLoadStart;
+ state.pageLoadTimes.push(totalPageTime);
+ state.totalAssetRequests += assets.length;
+ }
+ }
// Report individual request for history log (sampled)
if (Math.random() < 0.1 || config.userCount < 50) {
@@ -235,3 +251,68 @@ function extractRandomLink(html, baseUrl) {
} catch (e) { }
return null;
}
+
+function extractAssets(html, baseUrl) {
+ const assets = [];
+ try {
+ // Regex for scripts, links (css), and images
+ const scriptRegex = /