-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding debugging+performance helpers (#1247)
* adding debugging+performance helpers * linting --------- Co-authored-by: Shane Osbourne <sosbourne@duckduckgo.com>
- Loading branch information
1 parent
3e17e6f
commit 22e3eb0
Showing
7 changed files
with
277 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { h } from 'preact'; | ||
import { useEffect, useRef, useState } from 'preact/hooks'; | ||
import { useTelemetry } from '../types.js'; | ||
import { useCustomizer } from '../customizer/Customizer.js'; | ||
import { Telemetry } from './telemetry.js'; | ||
|
||
export function DebugCustomized({ index }) { | ||
const [isOpen, setOpen] = useState(false); | ||
const telemetry = useTelemetry(); | ||
useCustomizer({ | ||
title: '🐞 Debug', | ||
id: 'debug', | ||
icon: 'shield', | ||
visibility: isOpen ? 'visible' : 'hidden', | ||
|
||
toggle: (_id) => setOpen((prev) => !prev), | ||
index, | ||
}); | ||
return ( | ||
<div> | ||
<Debug telemetry={telemetry} isOpen={isOpen} /> | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* @param {object} props | ||
* @param {import("./telemetry.js").Telemetry} props.telemetry | ||
* @param {boolean} props.isOpen | ||
*/ | ||
export function Debug({ telemetry, isOpen }) { | ||
/** @type {import("preact").Ref<HTMLTextAreaElement>} */ | ||
const textRef = useRef(null); | ||
useEvents(textRef, telemetry); | ||
return ( | ||
<div hidden={!isOpen}> | ||
<textarea style={{ width: '100%' }} rows={20} ref={textRef}></textarea> | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* @param {import("preact").RefObject<HTMLTextAreaElement>} ref | ||
* @param {import("./telemetry.js").Telemetry} telemetry | ||
*/ | ||
function useEvents(ref, telemetry) { | ||
useEffect(() => { | ||
if (!ref.current) return; | ||
const elem = ref.current; | ||
function handle(/** @type {CustomEvent<any>} */ { detail }) { | ||
elem.value += JSON.stringify(detail, null, 2) + '\n\n'; | ||
} | ||
for (const beforeElement of telemetry.eventStore) { | ||
elem.value += JSON.stringify(beforeElement, null, 2) + '\n\n'; | ||
} | ||
telemetry.eventStore = []; | ||
telemetry.storeEnabled = false; | ||
telemetry.eventTarget.addEventListener(Telemetry.EVENT_BROADCAST, handle); | ||
return () => { | ||
telemetry.eventTarget.removeEventListener(Telemetry.EVENT_BROADCAST, handle); | ||
telemetry.storeEnabled = true; | ||
}; | ||
}, [ref, telemetry]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/** | ||
* @import { Messaging } from "@duckduckgo/messaging" | ||
*/ | ||
export class Telemetry { | ||
static EVENT_REQUEST = 'TELEMETRY_EVENT_REQUEST'; | ||
static EVENT_RESPONSE = 'TELEMETRY_EVENT_RESPONSE'; | ||
static EVENT_SUBSCRIPTION = 'TELEMETRY_EVENT_SUBSCRIPTION'; | ||
static EVENT_SUBSCRIPTION_DATA = 'TELEMETRY_EVENT_SUBSCRIPTION_DATA'; | ||
static EVENT_NOTIFICATION = 'TELEMETRY_EVENT_NOTIFICATION'; | ||
static EVENT_BROADCAST = 'TELEMETRY_*'; | ||
|
||
eventTarget = new EventTarget(); | ||
/** @type {any[]} */ | ||
eventStore = []; | ||
storeEnabled = true; | ||
|
||
/** | ||
* @param now | ||
*/ | ||
constructor(now = Date.now()) { | ||
this.now = now; | ||
performance.mark('ddg-telemetry-init'); | ||
this._setupMessagingMarkers(); | ||
} | ||
|
||
_setupMessagingMarkers() { | ||
this.eventTarget.addEventListener(Telemetry.EVENT_REQUEST, (/** @type {CustomEvent<any>} */ { detail }) => { | ||
const named = `ddg request ${detail.method} ${detail.timestamp}`; | ||
performance.mark(named); | ||
this.broadcast(detail); | ||
}); | ||
this.eventTarget.addEventListener(Telemetry.EVENT_RESPONSE, (/** @type {CustomEvent<any>} */ { detail }) => { | ||
const reqNamed = `ddg request ${detail.method} ${detail.timestamp}`; | ||
const resNamed = `ddg response ${detail.method} ${detail.timestamp}`; | ||
performance.mark(resNamed); | ||
performance.measure(reqNamed, reqNamed, resNamed); | ||
this.broadcast(detail); | ||
}); | ||
this.eventTarget.addEventListener(Telemetry.EVENT_SUBSCRIPTION, (/** @type {CustomEvent<any>} */ { detail }) => { | ||
const named = `ddg subscription ${detail.method} ${detail.timestamp}`; | ||
performance.mark(named); | ||
this.broadcast(detail); | ||
}); | ||
this.eventTarget.addEventListener(Telemetry.EVENT_SUBSCRIPTION_DATA, (/** @type {CustomEvent<any>} */ { detail }) => { | ||
const named = `ddg subscription data ${detail.method} ${detail.timestamp}`; | ||
performance.mark(named); | ||
this.broadcast(detail); | ||
}); | ||
this.eventTarget.addEventListener(Telemetry.EVENT_NOTIFICATION, (/** @type {CustomEvent<any>} */ { detail }) => { | ||
const named = `ddg notification ${detail.method} ${detail.timestamp}`; | ||
performance.mark(named); | ||
this.broadcast(detail); | ||
}); | ||
} | ||
|
||
broadcast(payload) { | ||
if (this.eventStore.length >= 50) { | ||
this.eventStore = []; | ||
} | ||
if (this.storeEnabled) { | ||
this.eventStore.push(structuredClone(payload)); | ||
} | ||
this.eventTarget.dispatchEvent(new CustomEvent(Telemetry.EVENT_BROADCAST, { detail: payload })); | ||
} | ||
|
||
measureFromPageLoad(marker, measure = 'measure__' + Date.now()) { | ||
if (!performance.getEntriesByName(marker).length) { | ||
performance.mark(marker); | ||
performance.measure(measure, 'ddg-telemetry-init', marker); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @implements Messaging | ||
*/ | ||
class MessagingObserver { | ||
/** @type {Map<string, number>} */ | ||
observed = new Map(); | ||
|
||
/** | ||
* @param {import("@duckduckgo/messaging").Messaging} messaging | ||
* @param {EventTarget} eventTarget | ||
*/ | ||
constructor(messaging, eventTarget) { | ||
this.messaging = messaging; | ||
this.messagingContext = messaging.messagingContext; | ||
this.transport = messaging.transport; | ||
this.eventTarget = eventTarget; | ||
} | ||
|
||
/** | ||
* @param {string} method | ||
* @param {Record<string, any>} params | ||
*/ | ||
request(method, params) { | ||
const timestamp = Date.now(); | ||
const json = { | ||
kind: 'request', | ||
method, | ||
params, | ||
timestamp, | ||
}; | ||
this.record(Telemetry.EVENT_REQUEST, json); | ||
return ( | ||
this.messaging | ||
.request(method, params) | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
.then((x) => { | ||
const resJson = { | ||
kind: 'response', | ||
method, | ||
result: x, | ||
timestamp, | ||
}; | ||
this.record(Telemetry.EVENT_RESPONSE, resJson); | ||
return x; | ||
}) | ||
); | ||
} | ||
|
||
/** | ||
* @param {string} method | ||
* @param {Record<string, any>} params | ||
*/ | ||
notify(method, params) { | ||
const json = { | ||
kind: 'notification', | ||
method, | ||
params, | ||
}; | ||
this.record(Telemetry.EVENT_NOTIFICATION, json); | ||
return this.messaging.notify(method, params); | ||
} | ||
|
||
/** | ||
* @param method | ||
* @param callback | ||
* @return {function(): void} | ||
*/ | ||
subscribe(method, callback) { | ||
const timestamp = Date.now(); | ||
const json = { | ||
kind: 'subscription', | ||
method, | ||
timestamp, | ||
}; | ||
|
||
this.record(Telemetry.EVENT_SUBSCRIPTION, json); | ||
return this.messaging.subscribe(method, (params) => { | ||
const json = { | ||
kind: 'subscription data', | ||
method, | ||
timestamp, | ||
params, | ||
}; | ||
this.record(Telemetry.EVENT_SUBSCRIPTION_DATA, json); | ||
callback(params); | ||
}); | ||
} | ||
|
||
/** | ||
* @param {string} name | ||
* @param {Record<string, any>} detail | ||
*/ | ||
record(name, detail) { | ||
this.eventTarget.dispatchEvent(new CustomEvent(name, { detail })); | ||
} | ||
} | ||
|
||
/** | ||
* @param {Messaging} messaging | ||
* @return {{telemetry: Telemetry, messaging: MessagingObserver}} | ||
*/ | ||
export function install(messaging) { | ||
const telemetry = new Telemetry(); | ||
const observedMessaging = new MessagingObserver(messaging, telemetry.eventTarget); | ||
return { telemetry, messaging: observedMessaging }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters