From 8e1f6127f0f4a60a4ae9a0e9ad0cb945acc32528 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 4 Apr 2025 16:53:34 +0200 Subject: [PATCH] tracker, ui, backend: checking support for network timings --- backend/pkg/messages/messages.go | 16 +- backend/pkg/messages/read-message.go | 12 +- ee/connectors/msgcodec/messages.py | 8 +- ee/connectors/msgcodec/messages.pyx | 10 +- ee/connectors/msgcodec/msgcodec.py | 7 +- ee/connectors/msgcodec/msgcodec.pyx | 7 +- .../DevTools/NetworkPanel/NetworkPanel.tsx | 47 ++++-- .../FetchDetailsModal/FetchDetailsModal.tsx | 2 +- .../components/FetchTabs/FetchTabs.tsx | 39 +++-- frontend/app/player/web/TabManager.ts | 1 + .../web/messages/RawMessageReader.gen.ts | 4 +- .../app/player/web/messages/filters.gen.ts | 2 +- .../app/player/web/messages/message.gen.ts | 5 +- frontend/app/player/web/messages/raw.gen.ts | 4 +- .../player/web/messages/tracker-legacy.gen.ts | 4 +- .../app/player/web/messages/tracker.gen.ts | 146 +++++++++--------- frontend/app/player/web/types/resource.ts | 40 ++++- mobs/messages.rb | 30 +++- tracker/tracker/src/common/messages.gen.ts | 14 +- tracker/tracker/src/main/app/messages.gen.ts | 17 +- tracker/tracker/src/main/modules/img.ts | 2 +- tracker/tracker/src/main/modules/timing.ts | 31 +++- .../src/webworker/MessageEncoder.gen.ts | 6 +- 23 files changed, 278 insertions(+), 176 deletions(-) diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index bccd0f210..29ecfcca7 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -1466,7 +1466,7 @@ func (msg *SetNodeAttributeDict) TypeID() int { return 52 } -type ResourceTimingDeprecated struct { +type ResourceTimingDeprecatedDeprecated struct { message Timestamp uint64 Duration uint64 @@ -1478,7 +1478,7 @@ type ResourceTimingDeprecated struct { Initiator string } -func (msg *ResourceTimingDeprecated) Encode() []byte { +func (msg *ResourceTimingDeprecatedDeprecated) Encode() []byte { buf := make([]byte, 81+len(msg.URL)+len(msg.Initiator)) buf[0] = 53 p := 1 @@ -1493,11 +1493,11 @@ func (msg *ResourceTimingDeprecated) Encode() []byte { return buf[:p] } -func (msg *ResourceTimingDeprecated) Decode() Message { +func (msg *ResourceTimingDeprecatedDeprecated) Decode() Message { return msg } -func (msg *ResourceTimingDeprecated) TypeID() int { +func (msg *ResourceTimingDeprecatedDeprecated) TypeID() int { return 53 } @@ -2424,7 +2424,7 @@ func (msg *UnbindNodes) TypeID() int { return 115 } -type ResourceTiming struct { +type ResourceTimingDeprecated struct { message Timestamp uint64 Duration uint64 @@ -2438,7 +2438,7 @@ type ResourceTiming struct { Cached bool } -func (msg *ResourceTiming) Encode() []byte { +func (msg *ResourceTimingDeprecated) Encode() []byte { buf := make([]byte, 101+len(msg.URL)+len(msg.Initiator)) buf[0] = 116 p := 1 @@ -2455,11 +2455,11 @@ func (msg *ResourceTiming) Encode() []byte { return buf[:p] } -func (msg *ResourceTiming) Decode() Message { +func (msg *ResourceTimingDeprecated) Decode() Message { return msg } -func (msg *ResourceTiming) TypeID() int { +func (msg *ResourceTimingDeprecated) TypeID() int { return 116 } diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 7f05c1500..4b4d9681f 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -873,9 +873,9 @@ func DecodeSetNodeAttributeDict(reader BytesReader) (Message, error) { return msg, err } -func DecodeResourceTimingDeprecated(reader BytesReader) (Message, error) { +func DecodeResourceTimingDeprecatedDeprecated(reader BytesReader) (Message, error) { var err error = nil - msg := &ResourceTimingDeprecated{} + msg := &ResourceTimingDeprecatedDeprecated{} if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } @@ -1500,9 +1500,9 @@ func DecodeUnbindNodes(reader BytesReader) (Message, error) { return msg, err } -func DecodeResourceTiming(reader BytesReader) (Message, error) { +func DecodeResourceTimingDeprecated(reader BytesReader) (Message, error) { var err error = nil - msg := &ResourceTiming{} + msg := &ResourceTimingDeprecated{} if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } @@ -2211,7 +2211,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { case 52: return DecodeSetNodeAttributeDict(reader) case 53: - return DecodeResourceTimingDeprecated(reader) + return DecodeResourceTimingDeprecatedDeprecated(reader) case 54: return DecodeConnectionInformation(reader) case 55: @@ -2283,7 +2283,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) { case 115: return DecodeUnbindNodes(reader) case 116: - return DecodeResourceTiming(reader) + return DecodeResourceTimingDeprecated(reader) case 117: return DecodeTabChange(reader) case 118: diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 94d62e1d1..119bd7bd7 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -71,7 +71,7 @@ class CreateDocument(Message): __id__ = 7 def __init__(self, ): - + class CreateElementNode(Message): @@ -500,7 +500,7 @@ class SetNodeAttributeDict(Message): self.value = value -class ResourceTimingDeprecated(Message): +class ResourceTimingDeprecatedDeprecated(Message): __id__ = 53 def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator): @@ -853,7 +853,7 @@ class UnbindNodes(Message): self.total_removed_percent = total_removed_percent -class ResourceTiming(Message): +class ResourceTimingDeprecated(Message): __id__ = 116 def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator, transferred_size, cached): @@ -1177,5 +1177,3 @@ class MobileIssueEvent(Message): self.context_string = context_string self.context = context self.payload = payload - - diff --git a/ee/connectors/msgcodec/messages.pyx b/ee/connectors/msgcodec/messages.pyx index 468d19059..24295f66c 100644 --- a/ee/connectors/msgcodec/messages.pyx +++ b/ee/connectors/msgcodec/messages.pyx @@ -104,11 +104,11 @@ cdef class SetViewportScroll(PyMessage): cdef class CreateDocument(PyMessage): cdef public int __id__ - + def __init__(self, ): self.__id__ = 7 - + cdef class CreateElementNode(PyMessage): @@ -743,7 +743,7 @@ cdef class SetNodeAttributeDict(PyMessage): self.value = value -cdef class ResourceTimingDeprecated(PyMessage): +cdef class ResourceTimingDeprecatedDeprecated(PyMessage): cdef public int __id__ cdef public unsigned long timestamp cdef public unsigned long duration @@ -1269,7 +1269,7 @@ cdef class UnbindNodes(PyMessage): self.total_removed_percent = total_removed_percent -cdef class ResourceTiming(PyMessage): +cdef class ResourceTimingDeprecated(PyMessage): cdef public int __id__ cdef public unsigned long timestamp cdef public unsigned long duration @@ -1764,5 +1764,3 @@ cdef class MobileIssueEvent(PyMessage): self.context_string = context_string self.context = context self.payload = payload - - diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index 9c4018587..0429cd67f 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -144,7 +144,7 @@ class MessageCodec(Codec): if message_id == 7: return CreateDocument( - + ) if message_id == 8: @@ -486,7 +486,7 @@ class MessageCodec(Codec): ) if message_id == 53: - return ResourceTimingDeprecated( + return ResourceTimingDeprecatedDeprecated( timestamp=self.read_uint(reader), duration=self.read_uint(reader), ttfb=self.read_uint(reader), @@ -767,7 +767,7 @@ class MessageCodec(Codec): ) if message_id == 116: - return ResourceTiming( + return ResourceTimingDeprecated( timestamp=self.read_uint(reader), duration=self.read_uint(reader), ttfb=self.read_uint(reader), @@ -1029,4 +1029,3 @@ class MessageCodec(Codec): context=self.read_string(reader), payload=self.read_string(reader) ) - diff --git a/ee/connectors/msgcodec/msgcodec.pyx b/ee/connectors/msgcodec/msgcodec.pyx index f1426fe17..c59dcd95b 100644 --- a/ee/connectors/msgcodec/msgcodec.pyx +++ b/ee/connectors/msgcodec/msgcodec.pyx @@ -242,7 +242,7 @@ cdef class MessageCodec: if message_id == 7: return CreateDocument( - + ) if message_id == 8: @@ -584,7 +584,7 @@ cdef class MessageCodec: ) if message_id == 53: - return ResourceTimingDeprecated( + return ResourceTimingDeprecatedDeprecated( timestamp=self.read_uint(reader), duration=self.read_uint(reader), ttfb=self.read_uint(reader), @@ -865,7 +865,7 @@ cdef class MessageCodec: ) if message_id == 116: - return ResourceTiming( + return ResourceTimingDeprecated( timestamp=self.read_uint(reader), duration=self.read_uint(reader), ttfb=self.read_uint(reader), @@ -1127,4 +1127,3 @@ cdef class MessageCodec: context=self.read_string(reader), payload=self.read_string(reader) ) - diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index cce5b1ee5..f2543ce02 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -1,5 +1,5 @@ /* eslint-disable i18next/no-literal-string */ -import { ResourceType, Timed } from 'Player'; +import { IResourceRequest, ResourceType, Timed } from 'Player'; import { WsChannel } from 'Player/web/messages'; import MobilePlayer from 'Player/mobile/IOSPlayer'; import WebPlayer from 'Player/web/WebPlayer'; @@ -400,8 +400,8 @@ export const NetworkPanelComp = observer( transferredSize: 0, }); - const originalListRef = useRef([]); - const socketListRef = useRef([]); + const originalListRef = useRef([]); + const socketListRef = useRef([]); const { sessionStore: { devTools }, @@ -433,18 +433,38 @@ export const NetworkPanelComp = observer( // Heaviest operation here, will create a final merged network list const processData = async () => { - const fetchUrls = new Set( - fetchList.map((ft) => { - return `${ft.name}-${Math.floor(ft.time / 100)}-${Math.floor(ft.duration / 100)}`; - }), - ); + const fetchUrlMap: Record = {} + const len = fetchList.length; + for (let i = 0; i < len; i++) { + const ft = fetchList[i] as any; + const key = `${ft.name}-${Math.round(ft.time / 10)}-${Math.round(ft.duration / 10)}` + if (fetchUrlMap[key]) { + fetchUrlMap[key].push(i); + } + fetchUrlMap[key] = [i]; + } // We want to get resources that aren't in fetch list - const filteredResources = await processInChunks(resourceList, (chunk) => - chunk.filter((res: any) => { - const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`; - return !fetchUrls.has(key); - }), + const filteredResources = await processInChunks(resourceList, (chunk) => { + const clearChunk = []; + for (const res of chunk) { + const key = `${res.name}-${Math.floor(res.time / 10)}-${Math.floor(res.duration / 10)}`; + const possibleRequests = fetchUrlMap[key] + if (possibleRequests && possibleRequests.length) { + for (const i of possibleRequests) { + fetchList[i].timings = res.timings; + } + fetchUrlMap[key] = []; + } else { + clearChunk.push(res); + } + } + return clearChunk; + }, + // chunk.filter((res: any) => { + // const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`; + // return !fetchUrls.has(key); + // }), BATCH_SIZE, 25, ); @@ -612,6 +632,7 @@ export const NetworkPanelComp = observer( return setSelectedWsChannel(socketMsgList); } + setIsDetailsModalActive(true); showModal( - {isXHR && } + {rows && rows.length > 0 && (
diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx index ebe8cf68a..cbf546cc0 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx @@ -9,10 +9,17 @@ import { TFunction } from 'i18next'; const HEADERS = 'HEADERS'; const REQUEST = 'REQUEST'; const RESPONSE = 'RESPONSE'; -const TABS = [HEADERS, REQUEST, RESPONSE].map((tab) => ({ +const TIMINGS = 'TIMINGS'; +const TABS = [HEADERS, REQUEST, RESPONSE, TIMINGS].map((tab) => ({ text: tab, key: tab, })); +const RESOURCE_TABS = [ + { + text: TIMINGS, + key: TIMINGS, + }, +]; type RequestResponse = { headers?: Record; @@ -76,10 +83,11 @@ function parseRequestResponse( interface Props { resource: { request: string; response: string }; isSpot?: boolean; + isXHR?: boolean; } -function FetchTabs({ resource, isSpot }: Props) { +function FetchTabs({ resource, isSpot, isXHR }: Props) { const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState(HEADERS); + const [activeTab, setActiveTab] = useState(isXHR ? HEADERS : TIMINGS); const onTabClick = (tab: string) => setActiveTab(tab); const [jsonRequest, setJsonRequest] = useState(null); const [jsonResponse, setJsonResponse] = useState(null); @@ -122,10 +130,13 @@ function FetchTabs({ resource, isSpot }: Props) {
{t('Body is empty or not captured.')}{' '} - + {t('Configure')} - - {' '} + {' '} {t( 'network capturing to get more out of Fetch/XHR requests.', )} @@ -160,10 +171,13 @@ function FetchTabs({ resource, isSpot }: Props) {
{t('Body is empty or not captured.')}{' '} - + {t('Configure')} - - {' '} + {' '} {t( 'network capturing to get more out of Fetch/XHR requests.', )} @@ -197,11 +211,16 @@ function FetchTabs({ resource, isSpot }: Props) { responseHeaders={responseHeaders} /> ); + case TIMINGS: + return
+ {resource.timings ? JSON.stringify(resource.timings, null, 2) : 'notihng :('} +
; } }; + const usedTabs = isXHR ? TABS : RESOURCE_TABS; return (
- +
{renderActiveTab()}
diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index 5674c0cd9..46f845a21 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -304,6 +304,7 @@ export default class TabSessionManager { Log(msg), ); break; + case MType.ResourceTimingDeprecatedDeprecated: case MType.ResourceTimingDeprecated: case MType.ResourceTiming: // TODO: merge `resource` and `fetch` lists into one here instead of UI diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 1b9105f88..0b4c0bb86 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -449,7 +449,7 @@ export default class RawMessageReader extends PrimitiveReader { const url = this.readString(); if (url === null) { return resetPointer() } const initiator = this.readString(); if (initiator === null) { return resetPointer() } return { - tp: MType.ResourceTimingDeprecated, + tp: MType.ResourceTimingDeprecatedDeprecated, timestamp, duration, ttfb, @@ -783,7 +783,7 @@ export default class RawMessageReader extends PrimitiveReader { const transferredSize = this.readUint(); if (transferredSize === null) { return resetPointer() } const cached = this.readBoolean(); if (cached === null) { return resetPointer() } return { - tp: MType.ResourceTiming, + tp: MType.ResourceTimingDeprecated, timestamp, duration, ttfb, diff --git a/frontend/app/player/web/messages/filters.gen.ts b/frontend/app/player/web/messages/filters.gen.ts index 0a3d5dca7..3ed9ab3df 100644 --- a/frontend/app/player/web/messages/filters.gen.ts +++ b/frontend/app/player/web/messages/filters.gen.ts @@ -7,4 +7,4 @@ const IOS_TYPES = [90,91,92,93,94,95,96,97,98,100,101,102,103,104,105,106,107,11 const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,34,35,37,38,49,50,51,43,52,54,55,57,58,59,60,61,67,68,69,70,71,72,73,74,75,76,77,113,114,117,118,119,122] export function isDOMType(t: MType) { return DOM_TYPES.includes(t) -} \ No newline at end of file +} diff --git a/frontend/app/player/web/messages/message.gen.ts b/frontend/app/player/web/messages/message.gen.ts index 7864d89bf..cf5f3fad7 100644 --- a/frontend/app/player/web/messages/message.gen.ts +++ b/frontend/app/player/web/messages/message.gen.ts @@ -40,7 +40,7 @@ import type { RawSetNodeAttributeDictDeprecated, RawStringDict, RawSetNodeAttributeDict, - RawResourceTimingDeprecated, + RawResourceTimingDeprecatedDeprecated, RawConnectionInformation, RawSetPageVisibility, RawLoadFontFace, @@ -65,7 +65,7 @@ import type { RawLongAnimationTask, RawSelectionChange, RawMouseThrashing, - RawResourceTiming, + RawResourceTimingDeprecated, RawTabChange, RawTabData, RawCanvasNode, @@ -245,4 +245,3 @@ export type MobileNetworkCall = RawMobileNetworkCall & Timed export type MobileSwipeEvent = RawMobileSwipeEvent & Timed export type MobileIssueEvent = RawMobileIssueEvent & Timed - diff --git a/frontend/app/player/web/messages/raw.gen.ts b/frontend/app/player/web/messages/raw.gen.ts index 9652db44c..39a5b434d 100644 --- a/frontend/app/player/web/messages/raw.gen.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -38,7 +38,7 @@ export const enum MType { SetNodeAttributeDictDeprecated = 51, StringDict = 43, SetNodeAttributeDict = 52, - ResourceTimingDeprecated = 53, + ResourceTimingDeprecatedDeprecated = 53, ConnectionInformation = 54, SetPageVisibility = 55, LoadFontFace = 57, @@ -63,7 +63,7 @@ export const enum MType { LongAnimationTask = 89, SelectionChange = 113, MouseThrashing = 114, - ResourceTiming = 116, + ResourceTimingDeprecated = 116, TabChange = 117, TabData = 118, CanvasNode = 119, diff --git a/frontend/app/player/web/messages/tracker-legacy.gen.ts b/frontend/app/player/web/messages/tracker-legacy.gen.ts index 21daaf8e2..521d60ceb 100644 --- a/frontend/app/player/web/messages/tracker-legacy.gen.ts +++ b/frontend/app/player/web/messages/tracker-legacy.gen.ts @@ -39,7 +39,7 @@ export const TP_MAP = { 51: MType.SetNodeAttributeDictDeprecated, 43: MType.StringDict, 52: MType.SetNodeAttributeDict, - 53: MType.ResourceTimingDeprecated, + 53: MType.ResourceTimingDeprecatedDeprecated, 54: MType.ConnectionInformation, 55: MType.SetPageVisibility, 57: MType.LoadFontFace, @@ -64,7 +64,7 @@ export const TP_MAP = { 89: MType.LongAnimationTask, 113: MType.SelectionChange, 114: MType.MouseThrashing, - 116: MType.ResourceTiming, + 116: MType.ResourceTimingDeprecated, 117: MType.TabChange, 118: MType.TabData, 119: MType.CanvasNode, diff --git a/frontend/app/player/web/messages/tracker.gen.ts b/frontend/app/player/web/messages/tracker.gen.ts index be69dea86..57dc429bc 100644 --- a/frontend/app/player/web/messages/tracker.gen.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -31,7 +31,7 @@ type TrSetViewportScroll = [ type TrCreateDocument = [ type: 7, - + ] type TrCreateElementNode = [ @@ -298,7 +298,7 @@ type TrSetNodeAttributeDict = [ value: string, ] -type TrResourceTimingDeprecated = [ +type TrResourceTimingDeprecatedDeprecated = [ type: 53, timestamp: number, duration: number, @@ -526,7 +526,7 @@ type TrUnbindNodes = [ totalRemovedPercent: number, ] -type TrResourceTiming = [ +type TrResourceTimingDeprecated = [ type: 116, timestamp: number, duration: number, @@ -597,14 +597,14 @@ export type TrackerMessage = TrTimestamp | TrSetPageLocationDeprecated | TrSetVi export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { - + case 0: { return { tp: MType.Timestamp, timestamp: tMsg[1], } } - + case 4: { return { tp: MType.SetPageLocationDeprecated, @@ -613,7 +613,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { navigationStart: tMsg[3], } } - + case 5: { return { tp: MType.SetViewportSize, @@ -621,7 +621,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { height: tMsg[2], } } - + case 6: { return { tp: MType.SetViewportScroll, @@ -629,14 +629,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { y: tMsg[2], } } - + case 7: { return { tp: MType.CreateDocument, - + } } - + case 8: { return { tp: MType.CreateElementNode, @@ -647,7 +647,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { svg: tMsg[5], } } - + case 9: { return { tp: MType.CreateTextNode, @@ -656,7 +656,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { index: tMsg[3], } } - + case 10: { return { tp: MType.MoveNode, @@ -665,14 +665,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { index: tMsg[3], } } - + case 11: { return { tp: MType.RemoveNode, id: tMsg[1], } } - + case 12: { return { tp: MType.SetNodeAttribute, @@ -681,7 +681,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[3], } } - + case 13: { return { tp: MType.RemoveNodeAttribute, @@ -689,7 +689,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { name: tMsg[2], } } - + case 14: { return { tp: MType.SetNodeData, @@ -697,7 +697,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { data: tMsg[2], } } - + case 16: { return { tp: MType.SetNodeScroll, @@ -706,7 +706,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { y: tMsg[3], } } - + case 18: { return { tp: MType.SetInputValue, @@ -715,7 +715,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { mask: tMsg[3], } } - + case 19: { return { tp: MType.SetInputChecked, @@ -723,7 +723,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { checked: tMsg[2], } } - + case 20: { return { tp: MType.MouseMove, @@ -731,7 +731,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { y: tMsg[2], } } - + case 21: { return { tp: MType.NetworkRequestDeprecated, @@ -745,7 +745,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[8], } } - + case 22: { return { tp: MType.ConsoleLog, @@ -753,7 +753,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[2], } } - + case 34: { return { tp: MType.StringDictGlobal, @@ -761,7 +761,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[2], } } - + case 35: { return { tp: MType.SetNodeAttributeDictGlobal, @@ -770,7 +770,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[3], } } - + case 37: { return { tp: MType.CssInsertRule, @@ -779,7 +779,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { index: tMsg[3], } } - + case 38: { return { tp: MType.CssDeleteRule, @@ -787,7 +787,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { index: tMsg[2], } } - + case 39: { return { tp: MType.Fetch, @@ -800,7 +800,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[7], } } - + case 40: { return { tp: MType.Profiler, @@ -810,7 +810,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { result: tMsg[4], } } - + case 41: { return { tp: MType.OTable, @@ -818,7 +818,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[2], } } - + case 44: { return { tp: MType.ReduxDeprecated, @@ -827,7 +827,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[3], } } - + case 45: { return { tp: MType.Vuex, @@ -835,7 +835,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { state: tMsg[2], } } - + case 46: { return { tp: MType.MobX, @@ -843,7 +843,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { payload: tMsg[2], } } - + case 47: { return { tp: MType.NgRx, @@ -852,7 +852,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[3], } } - + case 48: { return { tp: MType.GraphQlDeprecated, @@ -863,7 +863,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[5], } } - + case 49: { return { tp: MType.PerformanceTrack, @@ -873,7 +873,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { usedJSHeapSize: tMsg[4], } } - + case 50: { return { tp: MType.StringDictDeprecated, @@ -881,7 +881,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[2], } } - + case 51: { return { tp: MType.SetNodeAttributeDictDeprecated, @@ -890,7 +890,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { valueKey: tMsg[3], } } - + case 43: { return { tp: MType.StringDict, @@ -898,7 +898,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[2], } } - + case 52: { return { tp: MType.SetNodeAttributeDict, @@ -907,10 +907,10 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { value: tMsg[3], } } - + case 53: { return { - tp: MType.ResourceTimingDeprecated, + tp: MType.ResourceTimingDeprecatedDeprecated, timestamp: tMsg[1], duration: tMsg[2], ttfb: tMsg[3], @@ -921,7 +921,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { initiator: tMsg[8], } } - + case 54: { return { tp: MType.ConnectionInformation, @@ -929,14 +929,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { type: tMsg[2], } } - + case 55: { return { tp: MType.SetPageVisibility, hidden: tMsg[1], } } - + case 57: { return { tp: MType.LoadFontFace, @@ -946,14 +946,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { descriptors: tMsg[4], } } - + case 58: { return { tp: MType.SetNodeFocus, id: tMsg[1], } } - + case 59: { return { tp: MType.LongTask, @@ -966,7 +966,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { containerName: tMsg[7], } } - + case 60: { return { tp: MType.SetNodeAttributeURLBased, @@ -976,7 +976,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { baseURL: tMsg[4], } } - + case 61: { return { tp: MType.SetCssDataURLBased, @@ -985,7 +985,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { baseURL: tMsg[3], } } - + case 67: { return { tp: MType.CssInsertRuleURLBased, @@ -995,7 +995,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { baseURL: tMsg[4], } } - + case 68: { return { tp: MType.MouseClick, @@ -1007,7 +1007,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { normalizedY: tMsg[6], } } - + case 69: { return { tp: MType.MouseClickDeprecated, @@ -1017,7 +1017,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { selector: tMsg[4], } } - + case 70: { return { tp: MType.CreateIFrameDocument, @@ -1025,7 +1025,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { id: tMsg[2], } } - + case 71: { return { tp: MType.AdoptedSsReplaceURLBased, @@ -1034,7 +1034,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { baseURL: tMsg[3], } } - + case 73: { return { tp: MType.AdoptedSsInsertRuleURLBased, @@ -1044,7 +1044,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { baseURL: tMsg[4], } } - + case 75: { return { tp: MType.AdoptedSsDeleteRule, @@ -1052,7 +1052,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { index: tMsg[2], } } - + case 76: { return { tp: MType.AdoptedSsAddOwner, @@ -1060,7 +1060,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { id: tMsg[2], } } - + case 77: { return { tp: MType.AdoptedSsRemoveOwner, @@ -1068,7 +1068,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { id: tMsg[2], } } - + case 79: { return { tp: MType.Zustand, @@ -1076,7 +1076,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { state: tMsg[2], } } - + case 83: { return { tp: MType.NetworkRequest, @@ -1091,7 +1091,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { transferredBodySize: tMsg[9], } } - + case 84: { return { tp: MType.WsChannel, @@ -1103,7 +1103,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { messageType: tMsg[6], } } - + case 89: { return { tp: MType.LongAnimationTask, @@ -1115,7 +1115,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { scripts: tMsg[6], } } - + case 113: { return { tp: MType.SelectionChange, @@ -1124,17 +1124,17 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { selection: tMsg[3], } } - + case 114: { return { tp: MType.MouseThrashing, timestamp: tMsg[1], } } - + case 116: { return { - tp: MType.ResourceTiming, + tp: MType.ResourceTimingDeprecated, timestamp: tMsg[1], duration: tMsg[2], ttfb: tMsg[3], @@ -1147,21 +1147,21 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { cached: tMsg[10], } } - + case 117: { return { tp: MType.TabChange, tabId: tMsg[1], } } - + case 118: { return { tp: MType.TabData, tabId: tMsg[1], } } - + case 119: { return { tp: MType.CanvasNode, @@ -1169,14 +1169,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { timestamp: tMsg[2], } } - + case 120: { return { tp: MType.TagTrigger, tagId: tMsg[1], } } - + case 121: { return { tp: MType.Redux, @@ -1186,7 +1186,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { actionTime: tMsg[4], } } - + case 122: { return { tp: MType.SetPageLocation, @@ -1196,7 +1196,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { documentTitle: tMsg[4], } } - + case 123: { return { tp: MType.GraphQl, @@ -1207,7 +1207,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { duration: tMsg[5], } } - + default: return null } diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts index 50cf97c6b..b578b990e 100644 --- a/frontend/app/player/web/types/resource.ts +++ b/frontend/app/player/web/types/resource.ts @@ -65,7 +65,7 @@ export function getResourceName(url: string) { return url .split('/') .filter((s) => s !== '') - .pop(); + .pop() as string; } interface IResource { @@ -96,6 +96,15 @@ export interface IResourceTiming extends IResource { success: boolean; status: '2xx-3xx' | '4xx-5xx'; time: number; + timings: { + queueing: number; + dnsLookup: number; + initialConnection: number; + ssl: number; + ttfb: number; + contentDownload: number; + total: number; + }; } export interface IResourceRequest extends IResource { @@ -110,26 +119,31 @@ export interface IResourceRequest extends IResource { decodedBodySize?: number; } -const getGraphqlReqName = (resource: IResource) => { +const getGraphqlReqName = ( + resource: Partial, +) => { try { - if (!resource.request) return getResourceName(resource.url); + if (!resource.request) return getResourceName(resource.url ?? ''); const req = JSON.parse(resource.request); const body = JSON.parse(req.body); - return /query (\w+)/.exec(body.query)?.[1]; + return /query (\w+)/.exec(body.query)?.[1] as string; } catch (e) { - return getResourceName(resource.url); + return getResourceName(resource.url ?? ''); } }; -export const Resource = (resource: IResource) => { +export const Resource = ( + resource: Partial, +) => { const name = resource.type === 'graphql' ? getGraphqlReqName(resource) - : getResourceName(resource.url); + : getResourceName(resource.url ?? ''); + return { ...resource, name, - isRed: !resource.success || resource.error, // || resource.score >= RED_BOUND, + isRed: Boolean(!resource.success || resource.error), // || resource.score >= RED_BOUND, isYellow: false, // resource.score < RED_BOUND && resource.score >= YELLOW_BOUND, }; }; @@ -153,6 +167,15 @@ export function getResourceFromResourceTiming( success: !failed, status: !failed ? '2xx-3xx' : '4xx-5xx', time: Math.max(0, msg.timestamp - sessStart), + timings: { + queueing: msg.queueing, + dnsLookup: msg.dnsLookup, + initialConnection: msg.initialConnection, + ssl: msg.ssl, + ttfb: msg.ttfb, + contentDownload: msg.contentDownload, + total: msg.total, + }, }); } @@ -169,5 +192,6 @@ export function getResourceFromNetworkRequest( time: Math.max(0, msg.timestamp - sessStart), decodedBodySize: 'transferredBodySize' in msg ? msg.transferredBodySize : undefined, + timings: {}, }); } diff --git a/mobs/messages.rb b/mobs/messages.rb index 2cba1545e..e9fb97d54 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -309,7 +309,9 @@ message 52, 'SetNodeAttributeDict' do string 'Name' string 'Value' end -message 53, 'ResourceTimingDeprecated', :replayer => :devtools do + +# deprecated during 1.16.0 release +message 53, 'ResourceTimingDeprecatedDeprecated', :replayer => :devtools do uint 'Timestamp' uint 'Duration' uint 'TTFB' @@ -344,7 +346,7 @@ message 56, 'PerformanceTrackAggr', :tracker => false, :replayer => false do end # Since 4.1.7 / 1.9.0 -message 57, 'LoadFontFace' do +message 57, 'LoadFontFace' do uint 'ParentID' string 'Family' string 'Source' @@ -520,6 +522,25 @@ message 84, 'WSChannel', :replayer => :devtools do string 'MessageType' end +message 85, 'ResourceTiming', :replayer => :devtools do + uint 'Timestamp' + uint 'Duration' + uint 'TTFB' + uint 'HeaderSize' + uint 'EncodedBodySize' + uint 'DecodedBodySize' + string 'URL' + string 'Initiator' + uint 'TransferredSize' + boolean 'Cached' + uint 'Queueing' + uint 'DnsLookup' + uint 'InitialConnection' + uint 'SSL' + uint 'ContentDownload' + uint 'Total' +end + message 89, 'LongAnimationTask', :replayer => :devtools do string 'Name' int 'Duration' @@ -554,7 +575,8 @@ message 115, 'UnbindNodes', :replayer => false do uint 'TotalRemovedPercent' end -message 116, 'ResourceTiming', :replayer => :devtools do +#deprecated during 1.23.0 release +message 116, 'ResourceTimingDeprecated', :replayer => :devtools do uint 'Timestamp' uint 'Duration' uint 'TTFB' @@ -630,4 +652,4 @@ message 127, 'SessionSearch', :tracker => false, :replayer => false do uint 'Partition' end -# FREE 2, 35, 36, 65, 85, 86, 87, 88, 89 \ No newline at end of file +# FREE 2, 35, 36, 65, 85, 86, 87, 88, 89 diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index 683aacdf2..96e8a10dd 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -45,7 +45,7 @@ export declare const enum Type { SetNodeAttributeDictDeprecated = 51, StringDict = 43, SetNodeAttributeDict = 52, - ResourceTimingDeprecated = 53, + ResourceTimingDeprecatedDeprecated = 53, ConnectionInformation = 54, SetPageVisibility = 55, LoadFontFace = 57, @@ -75,7 +75,7 @@ export declare const enum Type { SelectionChange = 113, MouseThrashing = 114, UnbindNodes = 115, - ResourceTiming = 116, + ResourceTimingDeprecated = 116, TabChange = 117, TabData = 118, CanvasNode = 119, @@ -113,7 +113,7 @@ export type SetViewportScroll = [ export type CreateDocument = [ /*type:*/ Type.CreateDocument, - + ] export type CreateElementNode = [ @@ -380,8 +380,8 @@ export type SetNodeAttributeDict = [ /*value:*/ string, ] -export type ResourceTimingDeprecated = [ - /*type:*/ Type.ResourceTimingDeprecated, +export type ResourceTimingDeprecatedDeprecated = [ + /*type:*/ Type.ResourceTimingDeprecatedDeprecated, /*timestamp:*/ number, /*duration:*/ number, /*ttfb:*/ number, @@ -608,8 +608,8 @@ export type UnbindNodes = [ /*totalRemovedPercent:*/ number, ] -export type ResourceTiming = [ - /*type:*/ Type.ResourceTiming, +export type ResourceTimingDeprecated = [ + /*type:*/ Type.ResourceTimingDeprecated, /*timestamp:*/ number, /*duration:*/ number, /*ttfb:*/ number, diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index 90e543f31..694c050ac 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -50,11 +50,11 @@ export function SetViewportScroll( } export function CreateDocument( - + ): Messages.CreateDocument { return [ Messages.Type.CreateDocument, - + ] } @@ -548,7 +548,7 @@ export function SetNodeAttributeDict( ] } -export function ResourceTimingDeprecated( +export function ResourceTimingDeprecatedDeprecated( timestamp: number, duration: number, ttfb: number, @@ -557,9 +557,9 @@ export function ResourceTimingDeprecated( decodedBodySize: number, url: string, initiator: string, -): Messages.ResourceTimingDeprecated { +): Messages.ResourceTimingDeprecatedDeprecated { return [ - Messages.Type.ResourceTimingDeprecated, + Messages.Type.ResourceTimingDeprecatedDeprecated, timestamp, duration, ttfb, @@ -974,7 +974,7 @@ export function UnbindNodes( ] } -export function ResourceTiming( +export function ResourceTimingDeprecated( timestamp: number, duration: number, ttfb: number, @@ -985,9 +985,9 @@ export function ResourceTiming( initiator: string, transferredSize: number, cached: boolean, -): Messages.ResourceTiming { +): Messages.ResourceTimingDeprecated { return [ - Messages.Type.ResourceTiming, + Messages.Type.ResourceTimingDeprecated, timestamp, duration, ttfb, @@ -1096,4 +1096,3 @@ export function WebVitals( value, ] } - diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index c3cec1b97..2cfc3a988 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -60,7 +60,7 @@ export default function (app: App): void { const sendImgError = app.safe(function (img: HTMLImageElement): void { const resolvedSrc = resolveURL(img.src || '') // Src type is null sometimes. - is it true? if (isURL(resolvedSrc)) { - app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false)) + app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false, 0, 0, 0, 0, 0, 0)) } }) diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index ff3e4cfae..3d67c4824 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -123,19 +123,37 @@ export default function (app: App, opts: Partial): void { } const failed = entry.responseEnd === 0 || (entry.transferSize === 0 && entry.decodedBodySize === 0) + || (entry.responseStatus && entry.responseStatus >= 400) + + const timings = { + queueing: entry.domainLookupStart - entry.startTime, + dnsLookup: entry.domainLookupEnd - entry.domainLookupStart, + initialConnection: entry.connectEnd - entry.connectStart, + ssl: entry.secureConnectionStart > 0 + ? entry.connectEnd - entry.secureConnectionStart : 0, + ttfb: entry.responseStart - entry.requestStart, + contentDownload: entry.responseEnd - entry.responseStart, + total: entry.responseEnd - entry.startTime + }; if (failed) { app.send( ResourceTiming( entry.startTime + getTimeOrigin(), 0, - 0, + timings.ttfb, 0, 0, 0, entry.name, entry.initiatorType, 0, - true, + false, + timings.queueing, + timings.dnsLookup, + timings.initialConnection, + timings.ssl, + timings.contentDownload, + timings.total, ), ) } @@ -143,15 +161,20 @@ export default function (app: App, opts: Partial): void { ResourceTiming( entry.startTime + getTimeOrigin(), entry.duration, - entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, + timings.ttfb, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entry.name, entry.initiatorType, entry.transferSize, - // @ts-ignore (entry.responseStatus && entry.responseStatus === 304) || entry.transferSize === 0, + timings.queueing, + timings.dnsLookup, + timings.initialConnection, + timings.ssl, + timings.contentDownload, + timings.total, ), ) } diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index 20756d205..275a51c87 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -27,7 +27,7 @@ export default class MessageEncoder extends PrimitiveEncoder { break case Messages.Type.CreateDocument: - return true + return true break case Messages.Type.CreateElementNode: @@ -182,7 +182,7 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) break - case Messages.Type.ResourceTimingDeprecated: + case Messages.Type.ResourceTimingDeprecatedDeprecated: return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) break @@ -302,7 +302,7 @@ export default class MessageEncoder extends PrimitiveEncoder { return this.uint(msg[1]) break - case Messages.Type.ResourceTiming: + case Messages.Type.ResourceTimingDeprecated: return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) && this.uint(msg[9]) && this.boolean(msg[10]) break