tracker, ui, backend: checking support for network timings
This commit is contained in:
parent
b1b21937ed
commit
e92ba42d82
23 changed files with 278 additions and 176 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -910,7 +910,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
|||
|
||||
case 53: {
|
||||
return {
|
||||
tp: MType.ResourceTimingDeprecated,
|
||||
tp: MType.ResourceTimingDeprecatedDeprecated,
|
||||
timestamp: tMsg[1],
|
||||
duration: tMsg[2],
|
||||
ttfb: tMsg[3],
|
||||
|
|
@ -1134,7 +1134,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
|||
|
||||
case 116: {
|
||||
return {
|
||||
tp: MType.ResourceTiming,
|
||||
tp: MType.ResourceTimingDeprecated,
|
||||
timestamp: tMsg[1],
|
||||
duration: tMsg[2],
|
||||
ttfb: tMsg[3],
|
||||
|
|
|
|||
|
|
@ -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: {},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue