This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-client-js.git
The following commit(s) were added to refs/heads/master by this push:
new 86cf96f feat: disconnect observers and add new URLs to report for web
vitals (#142)
86cf96f is described below
commit 86cf96f0a4d18334e8fd75e864e82aba95bec825
Author: Fine0830 <[email protected]>
AuthorDate: Fri Sep 27 14:23:16 2024 +0800
feat: disconnect observers and add new URLs to report for web vitals (#142)
---
README.md | 7 ++--
src/monitor.ts | 9 ++--
src/performance/index.ts | 93 +++++++++++++++++++++++++++---------------
src/performance/type.ts | 8 +++-
src/services/constant.ts | 2 +
src/services/eventsListener.ts | 24 +++++++++++
src/services/report.ts | 22 +++++-----
src/types.ts | 1 -
test/docker/Dockerfile.test-ui | 2 +-
test/docker/index.js | 1 -
test/env | 2 +-
11 files changed, 114 insertions(+), 57 deletions(-)
diff --git a/README.md b/README.md
index 4366943..0988862 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ The register method supports the following parameters.
|jsErrors|Boolean|Support js errors monitoring|false|true|
|apiErrors|Boolean|Support API errors monitoring|false|true|
|resourceErrors|Boolean|Support resource errors monitoring|false|true|
-|useFmp|Boolean|Collect FMP (first meaningful paint) data of the first
screen|false|false|
+|useFmp|Boolean|Collect FMP (first meaningful paint) data of the first screen.
Deprecated: This is no longer recommended. Please use the useWebVitals instead.
|false|false|
|enableSPA|Boolean|Monitor the page hashchange event and report PV, which is
suitable for [single page application
scenarios](https://github.com/apache/skywalking-client-js#spa-page).
|false|false|
|autoTracePerf|Boolean|Support sending of performance data
automatically.|false|true|
|vue|Vue|Support vue2 errors monitoring. Deprecated: This is no longer
recommended. Please use the [Catching errors in
frames](https://github.com/apache/skywalking-client-js#catching-errors-in-frames-including-react-angular-vue)
scenario instead. |false|undefined|
@@ -61,6 +61,7 @@ The register method supports the following parameters.
|noTraceOrigins|(string \| RegExp)[]|Origin in the `noTraceOrigins` list will
not be traced.|false|[]|
|traceTimeInterval|Number|Support setting time interval to report
segments.|false|60000|
|customTags|Array|Custom Tags|false|-|
+|useWebVitals|Boolean|Collect three core web vitals|false|false|
## Collect Metrics Manually
Use the `setPerformance` method to report metrics at the moment of page loaded
or any other moment meaningful.
@@ -77,7 +78,7 @@ ClientMonitor.setPerformance({
service: 'browser-app',
serviceVersion: '1.0.0',
pagePath: location.href,
- useFmp: true
+ useWebVitals: true
});
```
@@ -100,7 +101,7 @@ app.on('routeChange', function (next) {
service: 'browser-app',
serviceVersion: '1.0.0',
pagePath: location.href,
- useFmp: true
+ useWebVitals: true
});
});
```
diff --git a/src/monitor.ts b/src/monitor.ts
index 89a7a58..01e8090 100644
--- a/src/monitor.ts
+++ b/src/monitor.ts
@@ -27,7 +27,7 @@ const ClientMonitor = {
apiErrors: true,
resourceErrors: true,
autoTracePerf: true, // trace performance detail
- useFmp: false, // use first meaningful paint
+ useWebVitals: false,
enableSPA: false,
traceSDKInternal: false,
detailMode: true,
@@ -84,7 +84,6 @@ const ClientMonitor = {
this.customOptions = {
...this.customOptions,
...configs,
- useFmp: false,
};
this.validateOptions();
this.performance(this.customOptions);
@@ -139,7 +138,7 @@ const ClientMonitor = {
apiErrors,
resourceErrors,
autoTracePerf,
- useFmp,
+ useWebVitals,
enableSPA,
traceSDKInternal,
detailMode,
@@ -173,8 +172,8 @@ const ClientMonitor = {
if (typeof autoTracePerf !== 'boolean') {
this.customOptions.autoTracePerf = true;
}
- if (typeof useFmp !== 'boolean') {
- this.customOptions.useFmp = false;
+ if (typeof useWebVitals !== 'boolean') {
+ this.customOptions.useWebVitals = false;
}
if (typeof enableSPA !== 'boolean') {
this.customOptions.enableSPA = false;
diff --git a/src/performance/index.ts b/src/performance/index.ts
index f750270..2ce3baa 100644
--- a/src/performance/index.ts
+++ b/src/performance/index.ts
@@ -17,15 +17,30 @@
import {CustomOptionsType} from '../types';
import Report from '../services/report';
-import {prerenderChangeListener} from "../services/eventsListener";
+import {prerenderChangeListener, onHidden, runOnce, idlePeriod} from
"../services/eventsListener";
import pagePerf from './perf';
import FMP from './fmp';
import {observe} from "../services/observe";
-import {LCPMetric, FIDMetric} from "./type";
+import {LCPMetric, FIDMetric, CLSMetric} from "./type";
import {LayoutShift} from "../services/types";
import {getVisibilityObserver} from '../services/getVisibilityObserver';
import {getActivationStart} from '../services/getNavigationEntry';
+const handler = {
+ set(target: {[key: string]: unknown}, prop: string, value: unknown) {
+ target[prop] = value;
+ const source: {[key: string]: unknown} = {
+ ...target,
+ collector: undefined,
+ useWebVitals: undefined,
+ };
+ if (target.useWebVitals && !isNaN(Number(target.fmpTime)) &&
!isNaN(Number(target.lcpTime)) && !isNaN(Number(target.clsTime))) {
+ new TracePerf().reportPerf(source, String(target.collector));
+ }
+ return true;
+ }
+};
+const reportedMetricNames: Record<string, boolean> = {};
class TracePerf {
private options: CustomOptionsType = {
pagePath: '',
@@ -34,7 +49,7 @@ class TracePerf {
collector: ''
};
private perfInfo = {};
- private coreWebMetrics: {[key: string]: string | number | undefined} = {};
+ private coreWebMetrics: Record<string, unknown> = {};
public getPerf(options: CustomOptionsType) {
this.options = options;
this.perfInfo = {
@@ -42,7 +57,7 @@ class TracePerf {
serviceVersion: options.serviceVersion,
service: options.service,
}
- this.coreWebMetrics = new Proxy({...this.perfInfo, collector:
options.collector}, handler);
+ this.coreWebMetrics = new Proxy({...this.perfInfo, collector:
options.collector, useWebVitals: options.useWebVitals}, handler);
// trace and report perf data and pv to serve when page loaded
if (document.readyState === 'complete') {
this.getBasicPerf();
@@ -63,8 +78,6 @@ class TracePerf {
this.LCP();
this.FID();
this.CLS();
- }
- if (this.options.useFmp) {
const {fmpTime} = await new FMP();
this.coreWebMetrics.fmpTime = Math.floor(fmpTime);
}
@@ -86,11 +99,10 @@ class TracePerf {
entry.startTime - firstEntry.startTime < 5000
) {
partValue += entry.value;
- entryList.push(entry);
} else {
partValue = entry.value;
- entryList = [entry];
}
+ entryList.push(entry);
}
});
if (partValue > clsTime) {
@@ -98,7 +110,15 @@ class TracePerf {
}
};
- observe('layout-shift', handleEntries);
+ const obs = observe('layout-shift', handleEntries);
+
+ if (!obs) {
+ return;
+ }
+ onHidden(() => {
+ handleEntries(obs.takeRecords() as CLSMetric['entries']);
+ obs!.disconnect();
+ });
}
private LCP() {
prerenderChangeListener(() => {
@@ -112,7 +132,21 @@ class TracePerf {
}
};
- observe('largest-contentful-paint', processEntries);
+ const obs = observe('largest-contentful-paint', processEntries);
+ if (!obs) {
+ return;
+ }
+ const disconnect = runOnce(() => {
+ if (!reportedMetricNames['lcp']) {
+ processEntries(obs!.takeRecords() as LCPMetric['entries']);
+ obs!.disconnect();
+ reportedMetricNames['lcp'] = true;
+ }
+ });
+ ['keydown', 'click'].forEach((type) => {
+ addEventListener(type, () => idlePeriod(disconnect), true);
+ });
+ onHidden(disconnect);
})
}
private FID() {
@@ -126,15 +160,24 @@ class TracePerf {
fidTime,
...this.perfInfo,
};
- this.reportPerf(perfInfo);
+ new Report('WEBINTERACTION',
this.options.collector).sendByXhr(perfInfo);
}
};
const processEntries = (entries: FIDMetric['entries']) => {
entries.forEach(processEntry);
};
-
- observe('first-input', processEntries);
+ const obs = observe('first-input', processEntries);
+ if (!obs) {
+ return;
+ }
+
+ onHidden(
+ runOnce(() => {
+ processEntries(obs.takeRecords() as FIDMetric['entries']);
+ obs.disconnect();
+ }),
+ );
})
}
private getBasicPerf() {
@@ -144,17 +187,17 @@ class TracePerf {
...perfDetail,
...this.perfInfo,
};
- this.reportPerf({...perfInfo, isPV: true});
+ new Report('PERF', this.options.collector).sendByXhr(perfInfo);
+ // clear perf data
+ this.clearPerf();
}
- public reportPerf(data: {[key: string]: number | string | boolean},
collector?: string) {
+ public reportPerf(data: {[key: string]: unknown}, collector: string) {
const perf = {
...data,
...this.perfInfo
};
- new Report('PERF', collector || this.options.collector).sendByXhr(perf);
- // clear perf data
- this.clearPerf();
+ new Report('WEBVITALS', collector).sendByXhr(perf);
}
private clearPerf() {
@@ -166,17 +209,3 @@ class TracePerf {
}
export default new TracePerf();
-
-const handler = {
- set(target: {[key: string]: number | string | undefined}, prop: string,
value: number | string | undefined) {
- target[prop] = value;
- if (!isNaN(Number(target.fmpTime)) && !isNaN(Number(target.lcpTime)) &&
!isNaN(Number(target.clsTime))) {
- const source: {[key: string]: number | string | undefined} = {
- ...target,
- collector: undefined,
- };
- new TracePerf().reportPerf(source, String(target.collector));
- }
- return true;
- }
-};
diff --git a/src/performance/type.ts b/src/performance/type.ts
index 81d06b6..ade739c 100644
--- a/src/performance/type.ts
+++ b/src/performance/type.ts
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {LargestContentfulPaint} from "../services/types";
+import {LargestContentfulPaint, LayoutShift} from "../services/types";
export interface ICalScore {
dpss: ICalScore[];
st: number;
@@ -50,4 +50,8 @@ export interface LCPMetric {
export interface FIDMetric {
name: 'FID';
entries: PerformanceEventTiming[];
-}
\ No newline at end of file
+}
+export interface CLSMetric {
+ name: 'CLS';
+ entries: LayoutShift[];
+}
diff --git a/src/services/constant.ts b/src/services/constant.ts
index d30d326..9556f7b 100644
--- a/src/services/constant.ts
+++ b/src/services/constant.ts
@@ -31,6 +31,8 @@ export enum ReportTypes {
ERROR = '/browser/errorLog',
ERRORS = '/browser/errorLogs',
PERF = '/browser/perfData',
+ WEBVITALS = '/browser/perfData/webVitals',
+ WEBINTERACTION = '/browser/perfData/webInteraction',
SEGMENT = '/v3/segment',
SEGMENTS = '/v3/segments',
}
diff --git a/src/services/eventsListener.ts b/src/services/eventsListener.ts
index 5c863d1..b7ebaf3 100644
--- a/src/services/eventsListener.ts
+++ b/src/services/eventsListener.ts
@@ -29,3 +29,27 @@ export function onHidden (cb: () => void) {
}
});
};
+
+export function runOnce (callback: () => void) {
+ let called = false;
+ return () => {
+ if (!called) {
+ callback();
+ called = true;
+ }
+ };
+};
+
+export function idlePeriod(callback: () => void): number {
+ const func = window.requestIdleCallback || window.setTimeout;
+
+ let handle = -1;
+ callback = runOnce(callback);
+ if (document.visibilityState === 'hidden') {
+ callback();
+ } else {
+ handle = func(callback);
+ onHidden(callback);
+ }
+ return handle;
+};
diff --git a/src/services/report.ts b/src/services/report.ts
index 3a7cff2..2a3028c 100644
--- a/src/services/report.ts
+++ b/src/services/report.ts
@@ -19,17 +19,17 @@ class Report {
private url: string = '';
constructor(type: string, collector: string) {
- if (type === 'ERROR') {
- this.url = collector + ReportTypes.ERROR;
- } else if (type === 'ERRORS') {
- this.url = collector + ReportTypes.ERRORS;
- } else if (type === 'SEGMENT') {
- this.url = collector + ReportTypes.SEGMENT;
- } else if (type === 'SEGMENTS') {
- this.url = collector + ReportTypes.SEGMENTS;
- } else if (type === 'PERF') {
- this.url = collector + ReportTypes.PERF;
- }
+ const typesMap: Record<string, string> = {
+ ERROR: ReportTypes.ERROR,
+ ERRORS: ReportTypes.ERRORS,
+ SEGMENT: ReportTypes.SEGMENT,
+ SEGMENTS: ReportTypes.SEGMENTS,
+ PERF: ReportTypes.PERF,
+ WEBVITALS: ReportTypes.WEBVITALS,
+ WEBINTERACTION: ReportTypes.WEBINTERACTION,
+ };
+
+ this.url = `${collector}${typesMap[type]}`;
}
public sendByFetch(data: any) {
diff --git a/src/types.ts b/src/types.ts
index 9c6eb3e..353bc50 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -20,7 +20,6 @@ export interface CustomOptionsType extends
CustomReportOptions {
apiErrors?: boolean;
resourceErrors?: boolean;
autoTracePerf?: boolean;
- useFmp?: boolean;
enableSPA?: boolean;
vue?: any;
traceSDKInternal?: boolean;
diff --git a/test/docker/Dockerfile.test-ui b/test/docker/Dockerfile.test-ui
index ecbb0fe..e19c8f0 100644
--- a/test/docker/Dockerfile.test-ui
+++ b/test/docker/Dockerfile.test-ui
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FROM node:10.23 AS builder
+FROM node:18.12 AS builder
ADD . /skywalking-client-js
WORKDIR /skywalking-client-js
diff --git a/test/docker/index.js b/test/docker/index.js
index eae9167..73220e0 100644
--- a/test/docker/index.js
+++ b/test/docker/index.js
@@ -22,7 +22,6 @@ ClientMonitor.register({
pagePath: 'index.html',
serviceVersion: 'v1.0.0',
vue: Vue,
- useFmp: true,
traceTimeInterval: 2000,
});
diff --git a/test/env b/test/env
index 4cf6a6b..4fd01b7 100644
--- a/test/env
+++ b/test/env
@@ -14,6 +14,6 @@
# limitations under the License.
SW_AGENT_PYTHON_COMMIT=c8479000eb729cc86509222fd48b942edcaaca74
-SW_AGENT_CLIENT_JS_TEST_COMMIT=4f1eb1dcdbde3ec4a38534bf01dded4ab5d2f016
+SW_AGENT_CLIENT_JS_TEST_COMMIT=d144d7713b84a00bc8f1a1ecb16fdc41ce657eef
SW_CTL_COMMIT=d2f1cff71f3ea9f325ff1c0d99dd0c40a35e527c