[Dev] Teact: Add debug overlay

This commit is contained in:
Alexander Zinchuk 2023-01-22 18:12:46 +01:00
parent 07e7253b02
commit 56dbad1ef9
3 changed files with 118 additions and 5 deletions

View File

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

View File

@ -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<P extends Props = any> = (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);

109
src/util/debugOverlay.ts Normal file
View File

@ -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<string, {
value: number;
lastUpdateAt: number;
}> = {};
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}<br/>`;
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 }]) => ([
`<div style="background: #ff0000${factorToHex(value / (key.includes('renders') ? maxRenders : maxDuration))}">`,
` <span${lastUpdateAt > halfSecondAgo ? BG_GREEN : ''}>${key}: ${Math.round(value)}</span>`,
'</div>',
].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');
}