diff --git a/src/components/httprequestviewer/HTTPRequestViewer.tsx b/src/components/httprequestviewer/HTTPRequestViewer.tsx index 5f6c52d..8ef8790 100644 --- a/src/components/httprequestviewer/HTTPRequestViewer.tsx +++ b/src/components/httprequestviewer/HTTPRequestViewer.tsx @@ -50,6 +50,7 @@ import { extractScreenshots, findUniqueScreenshots } from './lib/screenshotUtils import { processHTTPRequests } from './lib/httpRequestProcessor' import { analyzeCDN, analyzeQueueReason } from './lib/analysisUtils' import { addRequestPostProcessing } from './lib/requestPostProcessor' +import { assignConnectionNumbers } from './lib/connectionUtils' // Import types import type { HTTPRequest, ScreenshotEvent } from './types/httpRequest' @@ -121,7 +122,8 @@ export default function HTTPRequestViewer({ traceId }: HTTPRequestViewerProps) { const httpRequests = processHTTPRequests(traceData.traceEvents) const sortedRequests = sortRequests(httpRequests) const processedRequests = addRequestPostProcessing(sortedRequests, analyzeQueueReason, analyzeCDN) - return processedRequests + const requestsWithConnections = assignConnectionNumbers(processedRequests) + return requestsWithConnections }, [traceData]) // Extract and process screenshots with SSIM analysis diff --git a/src/components/httprequestviewer/RequestRowDetails.tsx b/src/components/httprequestviewer/RequestRowDetails.tsx index ad683ac..5964dbc 100644 --- a/src/components/httprequestviewer/RequestRowDetails.tsx +++ b/src/components/httprequestviewer/RequestRowDetails.tsx @@ -19,7 +19,7 @@ interface RequestRowDetailsProps { const RequestRowDetails: React.FC = ({ request }) => { return ( - +
{/* Request Details */} diff --git a/src/components/httprequestviewer/RequestRowSummary.tsx b/src/components/httprequestviewer/RequestRowSummary.tsx index c5c8c7a..4b5fa61 100644 --- a/src/components/httprequestviewer/RequestRowSummary.tsx +++ b/src/components/httprequestviewer/RequestRowSummary.tsx @@ -61,6 +61,12 @@ const RequestRowSummary: React.FC = ({ {truncateUrl(request.url)} + + {request.connectionNumber || '-'} + + + {request.requestNumberOnConnection || '-'} + {formatDuration(request.timing.startOffset)} diff --git a/src/components/httprequestviewer/RequestsTable.tsx b/src/components/httprequestviewer/RequestsTable.tsx index e4ba0c9..01bad2a 100644 --- a/src/components/httprequestviewer/RequestsTable.tsx +++ b/src/components/httprequestviewer/RequestsTable.tsx @@ -71,6 +71,16 @@ const RequestsTable: React.FC = ({ URL + + + Connection # + + + + + Request # + + Start Time diff --git a/src/components/httprequestviewer/lib/connectionUtils.ts b/src/components/httprequestviewer/lib/connectionUtils.ts new file mode 100644 index 0000000..c4c4952 --- /dev/null +++ b/src/components/httprequestviewer/lib/connectionUtils.ts @@ -0,0 +1,67 @@ +import type { HTTPRequest } from '../types/httpRequest' + +/** + * Assigns connection numbers and request numbers to HTTP requests based on: + * 1. Actual connectionId from Chrome DevTools trace data + * 2. Sequential numbering (1, 2, 3...) based on chronological first-seen order + * 3. Request ordering within each connection by start time + */ +export const assignConnectionNumbers = (requests: HTTPRequest[]): HTTPRequest[] => { + // Sort all requests by start time to process in chronological order + const chronologicalRequests = [...requests].sort((a, b) => a.timing.start - b.timing.start) + + // Track first-seen order of connection IDs + const connectionIdToNumber = new Map() + let nextConnectionNumber = 1 + + // First pass: assign connection numbers based on first-seen order + chronologicalRequests.forEach(request => { + if (request.connectionId !== undefined && !connectionIdToNumber.has(request.connectionId)) { + connectionIdToNumber.set(request.connectionId, nextConnectionNumber++) + } + }) + + // Group requests by connectionId and sort within each connection by start time + const requestsByConnection = new Map() + const requestsWithoutConnection: HTTPRequest[] = [] + + requests.forEach(request => { + if (request.connectionId !== undefined) { + if (!requestsByConnection.has(request.connectionId)) { + requestsByConnection.set(request.connectionId, []) + } + requestsByConnection.get(request.connectionId)!.push(request) + } else { + requestsWithoutConnection.push(request) + } + }) + + // Sort requests within each connection by start time + requestsByConnection.forEach(connectionRequests => { + connectionRequests.sort((a, b) => a.timing.start - b.timing.start) + }) + + // Assign connection numbers and request numbers + const processedRequests = requests.map(request => { + if (request.connectionId !== undefined) { + const connectionNumber = connectionIdToNumber.get(request.connectionId) || 0 + const connectionRequests = requestsByConnection.get(request.connectionId) || [] + const requestNumberOnConnection = connectionRequests.findIndex(r => r.requestId === request.requestId) + 1 + + return { + ...request, + connectionNumber, + requestNumberOnConnection + } + } else { + // Handle requests without connection ID (show as unknown) + return { + ...request, + connectionNumber: undefined, + requestNumberOnConnection: undefined + } + } + }) + + return processedRequests +} \ No newline at end of file diff --git a/src/components/httprequestviewer/lib/httpRequestProcessor.ts b/src/components/httprequestviewer/lib/httpRequestProcessor.ts index a99c115..aa76bdf 100644 --- a/src/components/httprequestviewer/lib/httpRequestProcessor.ts +++ b/src/components/httprequestviewer/lib/httpRequestProcessor.ts @@ -43,6 +43,11 @@ export const processHTTPRequests = (traceEvents: TraceEvent[]): HTTPRequest[] => request.resourceType = args.data.resourceType || '' request.priority = args.data.priority || '' request.timing.start = Math.min(request.timing.start, event.ts) + + // Extract connection ID if available + if (args.data.connectionId !== undefined) { + request.connectionId = args.data.connectionId + } break case 'ResourceReceiveResponse': @@ -52,6 +57,11 @@ export const processHTTPRequests = (traceEvents: TraceEvent[]): HTTPRequest[] => request.protocol = args.data.protocol request.responseHeaders = args.data.headers + // Extract connection ID if available (fallback if not set in SendRequest) + if (args.data.connectionId !== undefined && !request.connectionId) { + request.connectionId = args.data.connectionId + } + // Extract content-length from response headers if (request.responseHeaders) { const contentLengthHeader = request.responseHeaders.find( diff --git a/src/components/httprequestviewer/types/httpRequest.ts b/src/components/httprequestviewer/types/httpRequest.ts index 36d2e04..55364f3 100644 --- a/src/components/httprequestviewer/types/httpRequest.ts +++ b/src/components/httprequestviewer/types/httpRequest.ts @@ -30,6 +30,9 @@ export interface HTTPRequest { method: string resourceType: string priority: string + connectionId?: number + connectionNumber?: number + requestNumberOnConnection?: number statusCode?: number mimeType?: string protocol?: string diff --git a/src/components/shared/tooltipDefinitions.ts b/src/components/shared/tooltipDefinitions.ts index 0ffba8e..4e66cb3 100644 --- a/src/components/shared/tooltipDefinitions.ts +++ b/src/components/shared/tooltipDefinitions.ts @@ -12,6 +12,8 @@ export const TooltipType = { CONNECTION_TIME: 'CONNECTION_TIME', SERVER_LATENCY: 'SERVER_LATENCY', REQUEST_URL: 'REQUEST_URL', + CONNECTION_NUMBER: 'CONNECTION_NUMBER', + REQUEST_NUMBER: 'REQUEST_NUMBER', REQUEST_DURATION: 'REQUEST_DURATION', DATA_RATE: 'DATA_RATE', TOTAL_RESPONSE_TIME: 'TOTAL_RESPONSE_TIME', @@ -155,6 +157,27 @@ export const TOOLTIP_DEFINITIONS: Record = ] }, + [TooltipType.CONNECTION_NUMBER]: { + title: "Connection #", + description: "Sequential number assigned to each unique network connection based on first-seen order. HTTP/1.1 typically allows 6 connections per domain, HTTP/2 uses 1 connection with multiplexing.", + lighthouseRelation: "Connection limits can create bottlenecks in HTTP/1.1. HTTP/2 reduces this with multiplexing. HTTP/3 uses QUIC streams instead of traditional connections.", + calculation: "Based on Chrome's internal connectionId from trace data. For HTTP/3, this represents QUIC stream groupings rather than actual TCP connections.", + links: [ + { text: "HTTP Connection Management", url: "https://web.dev/articles/http-connection-management" }, + { text: "HTTP/3 and QUIC", url: "https://web.dev/articles/http3" } + ] + }, + + [TooltipType.REQUEST_NUMBER]: { + title: "Request #", + description: "Sequential number of this request within its connection/stream group. Shows request ordering for each unique connection ID.", + lighthouseRelation: "Request ordering affects resource loading priority. In HTTP/1.1, earlier requests block later ones. HTTP/2/3 allow parallel processing.", + calculation: "Incremental counter starting from 1 for each connection ID, ordered by request start time. For HTTP/3, represents ordering within QUIC stream groups.", + links: [ + { text: "HTTP/2 Multiplexing", url: "https://web.dev/articles/http2" } + ] + }, + [TooltipType.REQUEST_DURATION]: { title: "Request Duration", description: "Client-side time including queuing, network setup, and download. This is Total Response Time minus Server Latency.",