Compare commits
4 Commits
63550a42b4
...
f49a6de4db
Author | SHA1 | Date | |
---|---|---|---|
f49a6de4db | |||
69025c17ee | |||
b112fdf8ca | |||
4236ed0c55 |
@ -4,7 +4,7 @@ import styles from './HTTPRequestViewer.module.css'
|
||||
import type { HTTPRequest } from './types/httpRequest'
|
||||
|
||||
// Import utility functions
|
||||
import { formatDuration, formatSize } from './lib/formatUtils'
|
||||
import { formatDuration, formatSize, formatDataRate } from './lib/formatUtils'
|
||||
import {
|
||||
getStatusColor,
|
||||
getProtocolColor,
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getDurationColor,
|
||||
getTotalResponseTimeColor,
|
||||
getServerLatencyColor,
|
||||
getDataRateColor,
|
||||
getQueueAnalysisIcon,
|
||||
getCDNIcon,
|
||||
getCDNDisplayName
|
||||
@ -53,6 +54,11 @@ const RequestRowSummary: React.FC<RequestRowSummaryProps> = ({
|
||||
<td className={`${styles.tableCell} ${styles.priorityCell}`} style={{ color: getPriorityColor(request.priority) }}>
|
||||
{request.priority || '-'}
|
||||
</td>
|
||||
<td className={`${styles.tableCell} ${styles.urlCell}`}>
|
||||
<a href={request.url} target="_blank" rel="noopener noreferrer" className={styles.urlLink}>
|
||||
{truncateUrl(request.url)}
|
||||
</a>
|
||||
</td>
|
||||
<td className={`${styles.tableCell} ${styles.timeCell}`}>
|
||||
{formatDuration(request.timing.startOffset)}
|
||||
</td>
|
||||
@ -95,11 +101,7 @@ const RequestRowSummary: React.FC<RequestRowSummaryProps> = ({
|
||||
<td className={styles.serverLatencyCell} style={{ ...getServerLatencyColor(request.timing.serverLatency) }}>
|
||||
{formatDuration(request.timing.serverLatency)}
|
||||
</td>
|
||||
<td className={`${styles.tableCell} ${styles.urlCell}`}>
|
||||
<a href={request.url} target="_blank" rel="noopener noreferrer" className={styles.urlLink}>
|
||||
{truncateUrl(request.url)}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td className={styles.durationCell} style={{ ...getDurationColor(request.timing.duration) }}>
|
||||
{formatDuration(request.timing.duration)}
|
||||
</td>
|
||||
@ -109,6 +111,11 @@ const RequestRowSummary: React.FC<RequestRowSummaryProps> = ({
|
||||
{formatDuration(request.timing.totalResponseTime)}
|
||||
</td>
|
||||
|
||||
{/* Data Rate */}
|
||||
<td className={`${styles.tableCell} ${styles.right} ${styles.monospace}`} style={{ ...getDataRateColor(request.encodedDataLength, request.contentLength, request.timing.duration) }}>
|
||||
{formatDataRate(request.encodedDataLength, request.contentLength, request.timing.duration)}
|
||||
</td>
|
||||
|
||||
<td className={styles.sizeCell} style={{ ...getSizeColor(request.encodedDataLength) }}>
|
||||
{formatSize(request.encodedDataLength)}
|
||||
</td>
|
||||
|
@ -66,6 +66,11 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
Priority
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_URL}>
|
||||
URL
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.START_TIME}>
|
||||
Start Time
|
||||
@ -91,11 +96,6 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
Server Latency
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.left}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_URL}>
|
||||
URL
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.REQUEST_DURATION}>
|
||||
Duration
|
||||
@ -106,6 +106,11 @@ const RequestsTable: React.FC<RequestsTableProps> = ({
|
||||
Total Response Time
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.DATA_RATE}>
|
||||
Data Rate
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className={`${styles.tableHeaderCell} ${styles.right}`}>
|
||||
<Tooltip type={TooltipType.TRANSFER_SIZE}>
|
||||
Size
|
||||
|
@ -88,6 +88,32 @@ export const getServerLatencyColor = (microseconds?: number) => {
|
||||
return HIGHLIGHTING_CONFIG.COLORS.SERVER_LATENCY.DEFAULT
|
||||
}
|
||||
|
||||
export const getDataRateColor = (transferSize?: number, contentLength?: number, durationMicroseconds?: number) => {
|
||||
if (!durationMicroseconds || durationMicroseconds <= 0) return {}
|
||||
|
||||
// Prefer content-length over transfer size for more accurate data rate calculation
|
||||
const bytes = contentLength && contentLength > 0 ? contentLength : transferSize
|
||||
if (!bytes) return {}
|
||||
|
||||
// Convert duration from microseconds to seconds
|
||||
const durationSeconds = durationMicroseconds / 1000000
|
||||
|
||||
// Calculate bytes per second
|
||||
const bytesPerSecond = bytes / durationSeconds
|
||||
|
||||
// Convert to standard units for comparison
|
||||
const mbps = bytesPerSecond / (1024 * 1024) // MB/s
|
||||
const kbps = bytesPerSecond / 1024 // KB/s
|
||||
|
||||
if (mbps >= 1) {
|
||||
return { backgroundColor: '#e8f5e8', color: '#2e7d32' } // Green for >= 1 MB/s
|
||||
} else if (kbps >= 100) {
|
||||
return { backgroundColor: '#fff8e1', color: '#f57c00' } // Yellow for 100 KB/s - 1 MB/s
|
||||
} else {
|
||||
return { backgroundColor: '#ffebee', color: '#c62828' } // Red for < 100 KB/s
|
||||
}
|
||||
}
|
||||
|
||||
export const getQueueAnalysisIcon = (analysis?: QueueAnalysis): string => {
|
||||
if (!analysis) return ''
|
||||
|
||||
|
@ -10,4 +10,22 @@ export const formatSize = (bytes?: number): string => {
|
||||
if (bytes < 1024) return `${bytes}B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)}MB`
|
||||
}
|
||||
|
||||
export const formatDataRate = (transferSize?: number, contentLength?: number, durationMicroseconds?: number): string => {
|
||||
if (!durationMicroseconds || durationMicroseconds <= 0) return 'N/A'
|
||||
|
||||
// Prefer content-length over transfer size for more accurate data rate calculation
|
||||
const bytes = contentLength && contentLength > 0 ? contentLength : transferSize
|
||||
if (!bytes) return 'N/A'
|
||||
|
||||
// Convert duration from microseconds to seconds
|
||||
const durationSeconds = durationMicroseconds / 1000000
|
||||
|
||||
// Calculate bytes per second
|
||||
const bytesPerSecond = bytes / durationSeconds
|
||||
|
||||
if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(0)} B/s`
|
||||
if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`
|
||||
return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`
|
||||
}
|
@ -121,42 +121,21 @@ export const calculateDurations = (request: HTTPRequest): void => {
|
||||
lastDataTimestamp = request.events.finishLoading.ts
|
||||
}
|
||||
|
||||
// Chrome DevTools Duration Calculation:
|
||||
// Duration = Total time from request start to completion (excludes queuing)
|
||||
// This matches Chrome DevTools "Duration" column in Network tab
|
||||
|
||||
if (timingData?.requestTime && timingData?.sendStart !== undefined && timingData?.receiveHeadersEnd !== undefined) {
|
||||
// Chrome DevTools standard method: sendStart to receiveHeadersEnd
|
||||
// This excludes queuing/DNS/connection time and measures actual network time
|
||||
const networkDurationMs = timingData.receiveHeadersEnd - timingData.sendStart
|
||||
request.timing.duration = networkDurationMs * 1000 // Convert to microseconds
|
||||
|
||||
// If we have response body data, extend duration to last data chunk
|
||||
if (lastDataTimestamp) {
|
||||
const sendStartTimeUs = (timingData.requestTime * 1000000) + (timingData.sendStart * 1000)
|
||||
const bodyDuration = lastDataTimestamp - sendStartTimeUs
|
||||
|
||||
// Use the longer of header completion or body completion
|
||||
request.timing.duration = Math.max(request.timing.duration, bodyDuration)
|
||||
}
|
||||
|
||||
} else if (timingData?.requestTime && timingData?.receiveHeadersEnd !== undefined) {
|
||||
// Fallback: use receiveHeadersEnd from requestTime (includes all phases)
|
||||
request.timing.duration = timingData.receiveHeadersEnd * 1000 // Convert to microseconds
|
||||
|
||||
} else if (lastDataTimestamp && timingData?.requestTime && timingData?.sendStart !== undefined) {
|
||||
// Fallback: sendStart to last data (body download completion)
|
||||
const sendStartTimeUs = (timingData.requestTime * 1000000) + (timingData.sendStart * 1000)
|
||||
request.timing.duration = lastDataTimestamp - sendStartTimeUs
|
||||
|
||||
} else if (lastDataTimestamp && timingData?.requestTime) {
|
||||
// Final fallback: requestTime to last data (includes everything)
|
||||
const requestTimeUs = timingData.requestTime * 1000000
|
||||
request.timing.duration = lastDataTimestamp - requestTimeUs
|
||||
// Calculate Download Time (response body transfer time)
|
||||
// This is the time from first response byte to last data chunk
|
||||
if (timingData?.receiveHeadersStart !== undefined && lastDataTimestamp) {
|
||||
const receiveHeadersStartUs = (timingData.requestTime * 1000000) + (timingData.receiveHeadersStart * 1000)
|
||||
request.timing.downloadTime = Math.max(0, lastDataTimestamp - receiveHeadersStartUs)
|
||||
}
|
||||
|
||||
// Note: Duration will be calculated after Total Response Time and Server Latency are available
|
||||
|
||||
// Calculate Total Response Time (wall clock time from request initiation to completion)
|
||||
if (request.events.sendRequest && lastDataTimestamp) {
|
||||
// Use the earliest request start time (timing.start) which includes queuing, not sendRequest.ts
|
||||
if (request.timing.start && lastDataTimestamp) {
|
||||
request.timing.totalResponseTime = lastDataTimestamp - request.timing.start
|
||||
} else if (request.events.sendRequest && lastDataTimestamp) {
|
||||
// Fallback to sendRequest timestamp if timing.start unavailable
|
||||
request.timing.totalResponseTime = lastDataTimestamp - request.events.sendRequest.ts
|
||||
} else if (request.timing.queueTime && request.timing.duration) {
|
||||
// Fallback: sum all known components
|
||||
@ -180,6 +159,12 @@ export const calculateDurations = (request: HTTPRequest): void => {
|
||||
request.timing.totalResponseTime = totalTime
|
||||
}
|
||||
|
||||
// Calculate Duration as Client-Side Time (Total Response Time - Server Latency)
|
||||
// This represents time spent on the client side: queuing, network setup, and download
|
||||
if (request.timing.totalResponseTime && request.timing.serverLatency) {
|
||||
request.timing.duration = Math.max(0, request.timing.totalResponseTime - request.timing.serverLatency)
|
||||
}
|
||||
|
||||
// Fallback to trace event timestamps if timing data unavailable
|
||||
if (!request.timing.duration && request.timing.end) {
|
||||
request.timing.duration = request.timing.end - request.timing.start
|
||||
|
@ -46,6 +46,7 @@ export interface HTTPRequest {
|
||||
end?: number
|
||||
duration?: number
|
||||
totalResponseTime?: number
|
||||
downloadTime?: number
|
||||
queueTime?: number
|
||||
serverLatency?: number
|
||||
networkDuration?: number
|
||||
|
@ -13,6 +13,7 @@ export const TooltipType = {
|
||||
SERVER_LATENCY: 'SERVER_LATENCY',
|
||||
REQUEST_URL: 'REQUEST_URL',
|
||||
REQUEST_DURATION: 'REQUEST_DURATION',
|
||||
DATA_RATE: 'DATA_RATE',
|
||||
TOTAL_RESPONSE_TIME: 'TOTAL_RESPONSE_TIME',
|
||||
TRANSFER_SIZE: 'TRANSFER_SIZE',
|
||||
CONTENT_LENGTH: 'CONTENT_LENGTH',
|
||||
@ -156,14 +157,25 @@ export const TOOLTIP_DEFINITIONS: Record<TooltipTypeValues, TooltipDefinition> =
|
||||
|
||||
[TooltipType.REQUEST_DURATION]: {
|
||||
title: "Request Duration",
|
||||
description: "Total time from request start to completion, including all network phases and data transfer.",
|
||||
lighthouseRelation: "Long durations for critical resources directly impact LCP, FCP, and Speed Index. Background resource duration affects overall performance score.",
|
||||
calculation: "responseEnd - requestStart from Navigation Timing API.",
|
||||
description: "Client-side time including queuing, network setup, and download. This is Total Response Time minus Server Latency.",
|
||||
lighthouseRelation: "Long durations for critical resources directly impact LCP, FCP, and Speed Index. This represents time the browser spends waiting and downloading.",
|
||||
calculation: "Total Response Time - Server Latency. Includes queuing, DNS, connection, SSL, and download time.",
|
||||
links: [
|
||||
{ text: "Optimize Resource Loading", url: "https://web.dev/articles/critical-rendering-path" }
|
||||
]
|
||||
},
|
||||
|
||||
[TooltipType.DATA_RATE]: {
|
||||
title: "Data Rate",
|
||||
description: "Download speed calculated as transfer size divided by request duration. Indicates network throughput for this resource.",
|
||||
lighthouseRelation: "Low data rates may indicate network bottlenecks affecting resource loading speed, impacting LCP and overall performance scores.",
|
||||
calculation: "Transfer Size ÷ Request Duration. Shows effective throughput in KB/s or MB/s.",
|
||||
links: [
|
||||
{ text: "Network Performance", url: "https://web.dev/articles/fast" },
|
||||
{ text: "Optimize Resource Loading", url: "https://web.dev/articles/critical-rendering-path" }
|
||||
]
|
||||
},
|
||||
|
||||
[TooltipType.TOTAL_RESPONSE_TIME]: {
|
||||
title: "Total Response Time",
|
||||
description: "Complete time from navigation start to request completion. Shows request timing in context of page load.",
|
||||
|
Loading…
Reference in New Issue
Block a user