From 312d96fb7a8943bb0170caa8f6bae63a55f18990 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 20 Sep 2021 15:48:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E5=9F=8B=E7=82=B9=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/i18n/langs/en-US/translation.json | 1 + shared/i18n/langs/zh-CN/translation.json | 1 + web/src/App.tsx | 5 +- .../modals/SettingsView/Performance.tsx | 38 ++++++++++++ .../components/modals/SettingsView/index.tsx | 6 ++ web/src/dev.ts | 2 + web/src/routes/Entry/index.tsx | 3 + web/src/routes/Invite/index.tsx | 2 + web/src/routes/Main/index.tsx | 3 + web/src/utils/measure-helper.ts | 60 +++++++++++++++++++ 10 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 web/src/components/modals/SettingsView/Performance.tsx create mode 100644 web/src/utils/measure-helper.ts diff --git a/shared/i18n/langs/en-US/translation.json b/shared/i18n/langs/en-US/translation.json index 5f0e2df2..ae0cc111 100644 --- a/shared/i18n/langs/en-US/translation.json +++ b/shared/i18n/langs/en-US/translation.json @@ -32,6 +32,7 @@ "k3fe97dcc": "System settings", "k41064134": "DM", "k419da0ef": "Message explanation", + "k4231ab36": "Performance statistics", "k424be044": "This invite expired in <2>{{date}}", "k42a44318": "Joined", "k42a98418": "File Service", diff --git a/shared/i18n/langs/zh-CN/translation.json b/shared/i18n/langs/zh-CN/translation.json index 2b78b50f..da138c48 100644 --- a/shared/i18n/langs/zh-CN/translation.json +++ b/shared/i18n/langs/zh-CN/translation.json @@ -32,6 +32,7 @@ "k3fe97dcc": "系统设置", "k41064134": "私信", "k419da0ef": "消息解释", + "k4231ab36": "性能统计", "k424be044": "该邀请将于 <2>{{date}} 过期", "k42a44318": "已加入", "k42a98418": "文件服务", diff --git a/web/src/App.tsx b/web/src/App.tsx index 8a1f7038..ef9a7fb6 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -6,6 +6,7 @@ import { Loadable } from './components/Loadable'; import { ConfigProvider as AntdProvider } from 'antd'; import { parseColorScheme } from './utils/color-scheme-helper'; import { Helmet } from 'react-helmet'; +import { useRecordMeasure } from './utils/measure-helper'; const MainRoute = Loadable(() => import('./routes/Main').then((module) => module.MainRoute) @@ -44,7 +45,7 @@ const AppProvider: React.FC = React.memo((props) => { }); AppProvider.displayName = 'AppProvider'; -export const AppContainer: React.FC = React.memo((props) => { +const AppContainer: React.FC = React.memo((props) => { const { colorScheme } = useColorScheme(); const { isDarkMode, extraSchemeName } = parseColorScheme(colorScheme); @@ -77,6 +78,8 @@ const AppHeader: React.FC = React.memo(() => { AppHeader.displayName = 'AppHeader'; export const App: React.FC = React.memo(() => { + useRecordMeasure('AppRenderStart'); + return ( diff --git a/web/src/components/modals/SettingsView/Performance.tsx b/web/src/components/modals/SettingsView/Performance.tsx new file mode 100644 index 00000000..cb481e70 --- /dev/null +++ b/web/src/components/modals/SettingsView/Performance.tsx @@ -0,0 +1,38 @@ +import { measure } from '@/utils/measure-helper'; +import React, { useMemo } from 'react'; + +export const SettingsPerformance: React.FC = React.memo(() => { + const { record, timeUsage } = useMemo( + () => ({ + record: measure.getRecord(), + timeUsage: measure.getTimeUsage(), + }), + [] + ); + + return ( +
+
+
Record:
+
+ {Object.entries(record).map(([n, t]) => ( +
+ {n}: {t} +
+ ))} +
+
+
+
TimeUsage:
+
+ {Object.entries(timeUsage).map(([n, t]) => ( +
+ {n}: {t} +
+ ))} +
+
+
+ ); +}); +SettingsPerformance.displayName = 'SettingsPerformance'; diff --git a/web/src/components/modals/SettingsView/index.tsx b/web/src/components/modals/SettingsView/index.tsx index 9c5e8ede..bd6f7d4e 100644 --- a/web/src/components/modals/SettingsView/index.tsx +++ b/web/src/components/modals/SettingsView/index.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useMemo } from 'react'; import { t } from 'tailchat-shared'; import { SettingsAbout } from './About'; import { SettingsAccount } from './Account'; +import { SettingsPerformance } from './Performance'; import { SettingsStatus } from './Status'; import { SettingsSystem } from './System'; @@ -41,6 +42,11 @@ export const SettingsView: React.FC = React.memo((props) => { title: t('服务状态'), content: , }, + { + type: 'item', + title: t('性能统计'), + content: , + }, { type: 'item', title: t('关于'), diff --git a/web/src/dev.ts b/web/src/dev.ts index 5b70929f..4fcee330 100644 --- a/web/src/dev.ts +++ b/web/src/dev.ts @@ -1,8 +1,10 @@ import { isDevelopment, request, version } from 'tailchat-shared'; +import { measure } from './utils/measure-helper'; if (isDevelopment === true) { (window as any).DEBUG = { request, version, + measure, }; } diff --git a/web/src/routes/Entry/index.tsx b/web/src/routes/Entry/index.tsx index ec4a9017..1f633b61 100644 --- a/web/src/routes/Entry/index.tsx +++ b/web/src/routes/Entry/index.tsx @@ -6,8 +6,11 @@ import clsx from 'clsx'; import styles from './index.module.less'; import loginPatternUrl from '@assets/images/login-pattern.svg'; import { RegisterView } from './RegisterView'; +import { useRecordMeasure } from '@/utils/measure-helper'; export const EntryRoute = React.memo(() => { + useRecordMeasure('AppEntryRenderStart'); + return (
{ const { inviteCode } = useParams<{ inviteCode: string }>(); + useRecordMeasure('AppInviteRenderStart'); return ( diff --git a/web/src/routes/Main/index.tsx b/web/src/routes/Main/index.tsx index db661811..b49e2c42 100644 --- a/web/src/routes/Main/index.tsx +++ b/web/src/routes/Main/index.tsx @@ -1,9 +1,12 @@ +import { useRecordMeasure } from '@/utils/measure-helper'; import React from 'react'; import { MainContent } from './Content'; import { Navbar } from './Navbar'; import { MainProvider } from './Provider'; export const MainRoute: React.FC = React.memo(() => { + useRecordMeasure('AppMainRenderStart'); + return (
diff --git a/web/src/utils/measure-helper.ts b/web/src/utils/measure-helper.ts new file mode 100644 index 00000000..76aa3056 --- /dev/null +++ b/web/src/utils/measure-helper.ts @@ -0,0 +1,60 @@ +import { useLayoutEffect } from 'react'; + +const records: Record = {}; + +/** + * 记录测量点 + * @param name 测量点唯一名 + */ +export function recordMeasure(name: string) { + if (!records[name]) { + records[name] = performance.now(); + } +} + +/** + * 记录测量点(hook) + * @param name 测量点唯一名 + */ +export function useRecordMeasure(name: string) { + useLayoutEffect(() => { + recordMeasure(name); + }, []); +} + +export const measure = { + getRecord: () => ({ ...records }), + getTimeUsage() { + let t = performance.timing; + + const usage = { + // DNS查询耗时 + dnsUsage: t.domainLookupEnd - t.domainLookupStart, + + // TCP链接耗时 + tcpUsage: t.connectEnd - t.connectStart, + + // request请求耗时 + requestUsage: t.responseEnd - t.responseStart, + + // 解析dom树耗时 + parseDOMUsage: t.domComplete - t.domInteractive, + + // 白屏时间 + firstPaintTime: t.responseStart - t.navigationStart, + + // domready时间 + domReadyTime: t.domContentLoadedEventEnd - t.navigationStart, + + // onload 时间 + onloadTime: t.loadEventEnd - t.navigationStart, + }; + + if ((t = performance.memory)) { + // js内存使用占比 + usage['jsHeapRatio'] = t.usedJSHeapSize / t.totalJSHeapSize; + } + + return usage; + }, +};