tracker, ui, backend: checking support for network timings

This commit is contained in:
nick-delirium 2025-04-04 16:53:34 +02:00 committed by Delirium
parent b1b21937ed
commit e92ba42d82
23 changed files with 278 additions and 176 deletions

View file

@ -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
}

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)
)

View file

@ -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)
)

View file

@ -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<IResourceRequest[]>([]);
const socketListRef = useRef<any[]>([]);
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<string, number[]> = {}
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(
<FetchDetailsModal

View file

@ -75,7 +75,7 @@ function FetchDetailsModal(props: Props) {
}
/>
{isXHR && <FetchTabs isSpot={isSpot} resource={resource} />}
<FetchTabs isSpot={isSpot} resource={resource} isXHR={isXHR} />
{rows && rows.length > 0 && (
<div className="flex justify-between absolute bottom-0 left-0 right-0 p-3 border-t bg-white">

View file

@ -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<string, string>;
@ -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<object | null>(null);
const [jsonResponse, setJsonResponse] = useState<object | null>(null);
@ -122,10 +130,13 @@ function FetchTabs({ resource, isSpot }: Props) {
<AnimatedSVG name={ICONS.NO_RESULTS} size={30} />
<div className="mt-6 text-base font-normal">
{t('Body is empty or not captured.')}{' '}
<a href="https://docs.openreplay.com/en/sdk/network-options" className="link" target="_blank">
<a
href="https://docs.openreplay.com/en/sdk/network-options"
className="link"
target="_blank"
>
{t('Configure')}
</a>
{' '}
</a>{' '}
{t(
'network capturing to get more out of Fetch/XHR requests.',
)}
@ -160,10 +171,13 @@ function FetchTabs({ resource, isSpot }: Props) {
<AnimatedSVG name={ICONS.NO_RESULTS} size={30} />
<div className="mt-6 text-base font-normal">
{t('Body is empty or not captured.')}{' '}
<a href="https://docs.openreplay.com/en/sdk/network-options" className="link" target="_blank">
<a
href="https://docs.openreplay.com/en/sdk/network-options"
className="link"
target="_blank"
>
{t('Configure')}
</a>
{' '}
</a>{' '}
{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 <div>
{resource.timings ? JSON.stringify(resource.timings, null, 2) : 'notihng :('}
</div>;
}
};
const usedTabs = isXHR ? TABS : RESOURCE_TABS;
return (
<div>
<Tabs tabs={TABS} active={activeTab} onClick={onTabClick} border />
<Tabs tabs={usedTabs} active={activeTab} onClick={onTabClick} border />
<div style={{ height: 'calc(100vh - 364px)', overflowY: 'auto' }}>
{renderActiveTab()}
</div>

View file

@ -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

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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
}

View file

@ -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<IResourceRequest | IResourceTiming>,
) => {
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<IResourceRequest | IResourceTiming>,
) => {
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: {},
});
}

View file

@ -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
# FREE 2, 35, 36, 65, 85, 86, 87, 88, 89

View file

@ -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,

View file

@ -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,
]
}

View file

@ -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))
}
})

View file

@ -123,19 +123,37 @@ export default function (app: App, opts: Partial<Options>): 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<Options>): 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,
),
)
}

View file

@ -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