From 4bf6671cc436cb89e39c6e641d0877fdaa8cff8a Mon Sep 17 00:00:00 2001 From: Vemparala Surya Vamsi <121419957+vsvamsi1@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:12:31 +0530 Subject: [PATCH] chore: Added profiling for widget components performance (#34912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Capturing telemetry of widget renders and only enabling the profiled build for community users. This should cause a minor performance overhead and in case performance degrades too much we can disable the widget profiled telemetry by disabling the new relic flag(set this to false `APPSMITH_NEW_RELIC_ACCOUNT_ENABLE`). Fixes #35184 > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.Sanity" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 38310d7341e7a2acb30491e478edaedbbc16a739 > Cypress dashboard. > Tags: `@tag.Sanity` > Spec: >
Fri, 26 Jul 2024 12:55:43 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced `WidgetProfiler` component to track rendering performance metrics. - Added a new function for enhanced telemetry span management. - **Improvements** - Updated build script to allow for profiled builds based on environment variables, improving performance analysis. - Implemented GZIP compression for OTLP trace data transmission, optimizing bandwidth usage. - **Refactor** - Removed `Sentry.withProfiler` for widget profiling, simplifying performance monitoring. --- app/client/build.sh | 8 +++++- app/client/src/UITelemetry/auto-otel-web.ts | 1 + app/client/src/UITelemetry/generateTraces.ts | 12 +++++++++ .../widgets/BaseWidgetHOC/WidgetProfiler.tsx | 27 +++++++++++++++++++ .../BaseWidgetHOC/withBaseWidgetHOC.ts | 4 --- app/client/src/widgets/withWidgetProps.tsx | 13 ++++++++- 6 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 app/client/src/widgets/BaseWidgetHOC/WidgetProfiler.tsx diff --git a/app/client/build.sh b/app/client/build.sh index 146c6d355f..bc8184cfd8 100755 --- a/app/client/build.sh +++ b/app/client/build.sh @@ -18,6 +18,12 @@ export REACT_APP_SENTRY_RELEASE=$GIT_SHA export REACT_APP_CLIENT_LOG_LEVEL=ERROR # Disable CRA built-in ESLint checks since we have our own config and a separate step for this export DISABLE_ESLINT_PLUGIN=true -craco --max-old-space-size=7168 build --config craco.build.config.js +if [ "$APPSMITH_CLOUD_HOSTING" == "true" ]; then + echo "Building profiled build" + craco --max-old-space-size=7168 build --profile --config craco.build.config.js --verbose +else + craco --max-old-space-size=7168 build --config craco.build.config.js +fi + echo "build finished" diff --git a/app/client/src/UITelemetry/auto-otel-web.ts b/app/client/src/UITelemetry/auto-otel-web.ts index 6365b99279..389e5e04fd 100644 --- a/app/client/src/UITelemetry/auto-otel-web.ts +++ b/app/client/src/UITelemetry/auto-otel-web.ts @@ -39,6 +39,7 @@ const tracerProvider = new WebTracerProvider({ const nrTracesExporter = new OTLPTraceExporter({ url: `${otlpEndpoint}/v1/traces`, + compression: CompressionAlgorithm.GZIP, headers: { "api-key": otlpLicenseKey, }, diff --git a/app/client/src/UITelemetry/generateTraces.ts b/app/client/src/UITelemetry/generateTraces.ts index c051fa0840..71b417a732 100644 --- a/app/client/src/UITelemetry/generateTraces.ts +++ b/app/client/src/UITelemetry/generateTraces.ts @@ -91,3 +91,15 @@ export function wrapFnWithParentTraceContext(parentSpan: Span, fn: () => any) { const parentContext = trace.setSpan(context.active(), parentSpan); return context.with(parentContext, fn); } + +export function startAndEndSpan( + spanName: string, + startTime: number, + difference: number, + spanAttributes: SpanAttributes = {}, +) { + const endTime = startTime + Math.floor(difference); + + const span = startRootSpan(spanName, spanAttributes, startTime); + span.end(endTime); +} diff --git a/app/client/src/widgets/BaseWidgetHOC/WidgetProfiler.tsx b/app/client/src/widgets/BaseWidgetHOC/WidgetProfiler.tsx new file mode 100644 index 0000000000..28deff6ce3 --- /dev/null +++ b/app/client/src/widgets/BaseWidgetHOC/WidgetProfiler.tsx @@ -0,0 +1,27 @@ +import React, { Profiler, useCallback } from "react"; +import { startAndEndSpan } from "UITelemetry/generateTraces"; +export const WidgetProfiler = ({ + children, + type, + widgetId, +}: { + children: React.ReactNode; + type: string; + widgetId: string; +}) => { + const onRender = useCallback( + (id: string, phase: string, actualDuration: number) => { + startAndEndSpan("widgetRender", Date.now(), actualDuration, { + widgetType: type, + // mount or update phase + phase, + }); + }, + [type], + ); + return ( + + {children} + + ); +}; diff --git a/app/client/src/widgets/BaseWidgetHOC/withBaseWidgetHOC.ts b/app/client/src/widgets/BaseWidgetHOC/withBaseWidgetHOC.ts index b37a01a3ec..454a91c054 100644 --- a/app/client/src/widgets/BaseWidgetHOC/withBaseWidgetHOC.ts +++ b/app/client/src/widgets/BaseWidgetHOC/withBaseWidgetHOC.ts @@ -3,7 +3,6 @@ import withMeta from "widgets/MetaHOC"; import { withLazyRender } from "widgets/withLazyRender"; import type BaseWidget from "widgets/BaseWidget"; import withWidgetProps from "widgets/withWidgetProps"; -import * as Sentry from "@sentry/react"; import { withLayoutSystemWidgetHOC } from "../../layoutSystems/withLayoutSystemWidgetHOC"; import { flow, identity } from "lodash"; @@ -26,8 +25,5 @@ export const withBaseWidgetHOC = ( // Adds/Enhances widget props withWidgetProps, - - // Wraps the widget to be profiled via sentry - Sentry.withProfiler, ])(Widget); }; diff --git a/app/client/src/widgets/withWidgetProps.tsx b/app/client/src/widgets/withWidgetProps.tsx index 991d38171d..0abe1b3806 100644 --- a/app/client/src/widgets/withWidgetProps.tsx +++ b/app/client/src/widgets/withWidgetProps.tsx @@ -49,6 +49,9 @@ import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import { isWidgetSelectedForPropertyPane } from "selectors/propertyPaneSelectors"; import WidgetFactory from "WidgetProvider/factory"; import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors"; +import { WidgetProfiler } from "./BaseWidgetHOC/WidgetProfiler"; +import { getAppsmithConfigs } from "@appsmith/configs"; +const { newRelic } = getAppsmithConfigs(); const WIDGETS_WITH_CHILD_WIDGETS = ["LIST_WIDGET", "FORM_WIDGET"]; const WIDGETS_REQUIRING_SELECTED_ANCESTRY = ["MODAL_WIDGET", "TABS_WIDGET"]; @@ -353,7 +356,15 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) { } } - return ; + if (!newRelic.enableNewRelic) { + return ; + } + + return ( + + + + ); } return WrappedPropsComponent;