From 56dbad1ef9f59eb9be045f130e1cfb0dc0fb1e3e Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 22 Jan 2023 18:12:46 +0100 Subject: [PATCH] [Dev] Teact: Add debug overlay --- src/hooks/useFlag.ts | 4 +- src/lib/teact/teact.ts | 10 ++-- src/util/debugOverlay.ts | 109 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/util/debugOverlay.ts diff --git a/src/hooks/useFlag.ts b/src/hooks/useFlag.ts index 7acd11b6c..3d8150cd6 100644 --- a/src/hooks/useFlag.ts +++ b/src/hooks/useFlag.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from '../lib/teact/teact'; -const useFlag = (initial = false): [boolean, NoneToVoidFunction, NoneToVoidFunction] => { - const [value, setValue] = useState(initial); +const useFlag = (initial = false, debugKey?: string): [boolean, NoneToVoidFunction, NoneToVoidFunction] => { + const [value, setValue] = useState(initial, debugKey); const setTrue = useCallback(() => { setValue(true); diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index 47f7883a8..ff7cc24b8 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -6,13 +6,12 @@ import { import { orderBy } from '../../util/iteratees'; import { getUnequalProps } from '../../util/arePropsShallowEqual'; import { handleError } from '../../util/handleError'; +import { incrementOverlayCounter } from '../../util/debugOverlay'; export type Props = AnyLiteral; export type FC

= (props: P) => any; // eslint-disable-next-line @typescript-eslint/naming-convention -export type FC_withDebug = FC & { - DEBUG_contentComponentName?: string; -}; +export type FC_withDebug = FC & { DEBUG_contentComponentName?: string }; export enum VirtualElementTypesEnum { Empty, @@ -339,6 +338,11 @@ export function renderComponent(componentInstance: ComponentInstance) { } DEBUG_components[componentName].renderTimes.push(duration); DEBUG_components[componentName].renderCount++; + + if (DEBUG_MORE) { + incrementOverlayCounter(`${componentName} renders`); + incrementOverlayCounter(`${componentName} duration`, duration); + } } } catch (err: any) { handleError(err); diff --git a/src/util/debugOverlay.ts b/src/util/debugOverlay.ts new file mode 100644 index 000000000..e30378c0e --- /dev/null +++ b/src/util/debugOverlay.ts @@ -0,0 +1,109 @@ +import { throttle } from './schedulers'; + +const KEYS_TO_IGNORE = new Set([ + 'TeactMemoWrapper renders', + 'TeactNContainer renders', + 'Button renders', +]); +const MIN_RENDERS_TO_SHOW = 5; +const MIN_DURATION_TO_SHOW = 2; +const BG_GREEN = ' style="background: lightgreen"'; + +let counters: Record = {}; + +const renderCountersThrottled = throttle(renderCounters, 500, false); + +let loggerEl: HTMLDivElement; + +export function debugToOverlay(text: string) { + if (!loggerEl) { + setupOverlay(); + } + + const date = new Date(); + const dateFormatted = `${date.toLocaleTimeString()}.${date.getMilliseconds()}`; + const wasAtBottom = loggerEl.scrollTop + 10 >= loggerEl.scrollHeight - loggerEl.offsetHeight; + + loggerEl.innerHTML += `${dateFormatted}: ${text}
`; + + if (wasAtBottom) { + loggerEl.scrollTop = loggerEl.scrollHeight; + } +} + +export function incrementOverlayCounter(key: string, value = 1) { + const now = Date.now(); + if (!counters[key]) { + counters[key] = { value, lastUpdateAt: now }; + } else { + counters[key].value += value; + counters[key].lastUpdateAt = now; + } + + renderCountersThrottled(); +} + +export function renderCounters() { + if (!loggerEl) { + setupOverlay(); + } + + const halfSecondAgo = Date.now() - 500; + const [maxRenders, maxDuration] = Object.entries(counters).reduce((acc, [key, { value }]) => { + if (KEYS_TO_IGNORE.has(key)) { + return acc; + } + + if (key.includes('renders') && value > acc[0]) { + acc[0] = value; + } + + if (key.includes('duration') && value > acc[1]) { + acc[1] = value; + } + + return acc; + }, [0, 0]); + + loggerEl.innerHTML = Object + .entries(counters) + .filter(([key, { value }]) => ( + (!KEYS_TO_IGNORE.has(key)) && ( + (key.includes('renders') && value > MIN_RENDERS_TO_SHOW) + || (key.includes('duration') && value > MIN_DURATION_TO_SHOW) + ) + )) + .sort((a, b) => ( + b[1].lastUpdateAt - a[1].lastUpdateAt + )) + .map(([key, { value, lastUpdateAt }]) => ([ + `

`, + ` halfSecondAgo ? BG_GREEN : ''}>${key}: ${Math.round(value)}`, + '
', + ].join('\n'))) + .join('\n'); +} + +function setupOverlay() { + loggerEl = document.createElement('div'); + loggerEl.style.cssText = 'position: absolute; left: 0; bottom: 25px; z-index: 9998; width: 260px; height: 200px;' + + ' border: 1px solid #555; background: rgba(255, 255, 255, 0.9); overflow: auto; font-size: 10px;'; + document.body.appendChild(loggerEl); + + const clearEl = document.createElement('a'); + clearEl.style.cssText = 'position: absolute; left: 222px; bottom: 198px; z-index: 9999; font-size: 20px; ' + + 'cursor: pointer;'; + clearEl.innerText = '🔄'; + clearEl.addEventListener('click', () => { + counters = {}; + renderCountersThrottled(); + }); + document.body.appendChild(clearEl); +} + +function factorToHex(factor: number) { + return Math.round(255 * factor).toString(16).padStart(2, '0'); +}