diff --git a/app/client/package.json b/app/client/package.json index 6ad9d2f312..9b7cd05f65 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -16,8 +16,8 @@ "@craco/craco": "^5.6.1", "@manaflair/redux-batch": "^1.0.0", "@optimizely/optimizely-sdk": "^4.0.0", - "@sentry/react": "^5.22.3", - "@sentry/tracing": "^5.22.3", + "@sentry/react": "^5.24.2", + "@sentry/tracing": "^5.24.2", "@sentry/webpack-plugin": "^1.12.1", "@types/chance": "^1.0.7", "@types/lodash": "^4.14.120", diff --git a/app/client/src/components/designSystems/appsmith/help/DocumentationSearch.tsx b/app/client/src/components/designSystems/appsmith/help/DocumentationSearch.tsx index d65df0a592..273a97dc6d 100644 --- a/app/client/src/components/designSystems/appsmith/help/DocumentationSearch.tsx +++ b/app/client/src/components/designSystems/appsmith/help/DocumentationSearch.tsx @@ -23,7 +23,12 @@ import { import { Icon } from "@blueprintjs/core"; import moment from "moment"; -const { algolia, appVersion, cloudHosting } = getAppsmithConfigs(); +const { + algolia, + appVersion, + cloudHosting, + intercomAppID, +} = getAppsmithConfigs(); const searchClient = algoliasearch(algolia.apiId, algolia.apiKey); const OenLinkIcon = HelpIcons.OPEN_LINK; @@ -108,6 +113,11 @@ const DefaultHelpMenuItem = (props: { id={props.item.id} onClick={() => { if (props.item.link) window.open(props.item.link, "_blank"); + if (props.item.id === "intercom-trigger") { + if (cloudHosting && intercomAppID && window.Intercom) { + window.Intercom("show"); + } + } props.onSelect(); }} > diff --git a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx index ccace89270..05b5daf046 100644 --- a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx +++ b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx @@ -73,7 +73,6 @@ class HelpModal extends React.Component { // eslint-disable-next-line @typescript-eslint/camelcase user_id: user?.username, // eslint-disable-next-line @typescript-eslint/camelcase - custom_launcher_selector: "#intercom-trigger", name: user?.name, email: user?.email, }); diff --git a/app/client/src/components/editorComponents/Sidebar.tsx b/app/client/src/components/editorComponents/Sidebar.tsx index c82d9b1695..25692ae48a 100644 --- a/app/client/src/components/editorComponents/Sidebar.tsx +++ b/app/client/src/components/editorComponents/Sidebar.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import ExplorerSidebar from "pages/Editor/Explorer"; import { PanelStack, Classes } from "@blueprintjs/core"; import { Colors } from "constants/Colors"; +import * as Sentry from "@sentry/react"; const SidebarWrapper = styled.div` background-color: ${Colors.MINE_SHAFT}; @@ -32,4 +33,4 @@ export const Sidebar = memo(() => { Sidebar.displayName = "Sidebar"; -export default Sidebar; +export default Sentry.withProfiler(Sidebar); diff --git a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx index 10c38b1f4d..e68975be60 100644 --- a/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx +++ b/app/client/src/pages/AppViewer/AppViewerPageContainer.tsx @@ -17,6 +17,9 @@ import { getCurrentPageName, } from "selectors/editorSelectors"; import ConfirmRunModal from "pages/Editor/ConfirmRunModal"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; const Section = styled.section` background: ${props => props.theme.colors.bodyBG}; @@ -108,11 +111,18 @@ class AppViewerPageContainer extends Component { } } -const mapStateToProps = (state: AppState) => ({ - isFetchingPage: getIsFetchingPage(state), - widgets: getCanvasWidgetDsl(state), - currentPageName: getCurrentPageName(state), -}); +const mapStateToProps = (state: AppState) => { + PerformanceTracker.startTracking( + PerformanceTransactionName.GENERATE_VIEW_MODE_PROPS, + ); + const props = { + isFetchingPage: getIsFetchingPage(state), + widgets: getCanvasWidgetDsl(state), + currentPageName: getCurrentPageName(state), + }; + PerformanceTracker.stopTracking(); + return props; +}; const mapDispatchToProps = (dispatch: any) => ({ fetchPage: (pageId: string, bustCache = false) => diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index edd5c3895b..bc2cdbb7b7 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -29,6 +29,10 @@ import Spinner from "components/editorComponents/Spinner"; import styled from "styled-components"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import { changeApi } from "actions/apiPaneActions"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; +import * as Sentry from "@sentry/react"; const LoadingContainer = styled(CenteredWrapper)` height: 50%; @@ -157,7 +161,6 @@ class ApiEditor extends React.Component { match={this.props.match} /> ); - return (
{ const apiAction = getActionById(state, props); const apiName = getApiName(state, props.match.params.apiId); const { isDeleting, isRunning, isCreating } = state.ui.apiPane; - return { + PerformanceTracker.startTracking( + PerformanceTransactionName.GENERATE_API_PROPS, + ); + const apiEditorState = { actions: state.entities.actions, currentApplication: getCurrentApplication(state), currentPageName: getCurrentPageName(state), @@ -229,6 +235,8 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { isCreating, isEditorInitialized: getIsEditorInitialized(state), }; + PerformanceTracker.stopTracking(); + return apiEditorState; }; const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ @@ -240,4 +248,6 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ changeAPIPage: (actionId: string) => dispatch(changeApi(actionId)), }); -export default connect(mapStateToProps, mapDispatchToProps)(ApiEditor); +export default Sentry.withProfiler( + connect(mapStateToProps, mapDispatchToProps)(ApiEditor), +); diff --git a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx index ff367c25ba..cfecce8382 100644 --- a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx +++ b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import EntityProperty, { EntityPropertyProps } from "./EntityProperty"; import { isFunction } from "lodash"; import { entityDefinitions } from "utils/autocomplete/EntityDefinitions"; @@ -10,6 +10,10 @@ import { } from "entities/DataTree/dataTreeFactory"; import { useSelector } from "react-redux"; import { evaluateDataTreeWithoutFunctions } from "selectors/dataTreeSelectors"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; +import * as Sentry from "@sentry/react"; export const EntityProperties = (props: { entityType: ENTITY_TYPE; @@ -18,6 +22,12 @@ export const EntityProperties = (props: { step: number; entity?: any; }) => { + PerformanceTracker.startTracking( + PerformanceTransactionName.ENTITY_EXPLORER_ENTITY, + ); + useEffect(() => { + PerformanceTracker.stopTracking(); + }); let entity: any; const dataTree: DataTree = useSelector(evaluateDataTreeWithoutFunctions); if (props.isCurrentPage && dataTree[props.entityName]) { @@ -27,7 +37,6 @@ export const EntityProperties = (props: { } else { return null; } - let config: any; let entityProperties: Array = []; switch (props.entityType) { @@ -87,4 +96,4 @@ export const EntityProperties = (props: { ); }; -export default EntityProperties; +export default Sentry.withProfiler(EntityProperties); diff --git a/app/client/src/pages/Editor/PropertyPane/index.tsx b/app/client/src/pages/Editor/PropertyPane/index.tsx index 74a03c1865..f4749366c0 100644 --- a/app/client/src/pages/Editor/PropertyPane/index.tsx +++ b/app/client/src/pages/Editor/PropertyPane/index.tsx @@ -27,6 +27,9 @@ import PropertyControl from "pages/Editor/PropertyPane/PropertyControl"; import AnalyticsUtil from "utils/AnalyticsUtil"; import * as log from "loglevel"; import PaneWrapper from "pages/common/PaneWrapper"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; const PropertySectionLabel = styled.div` color: ${props => props.theme.colors.paneSectionLabel}; @@ -224,12 +227,17 @@ class PropertyPane extends Component< } const mapStateToProps = (state: AppState): PropertyPaneProps => { - return { + PerformanceTracker.startTracking( + PerformanceTransactionName.GENERATE_PROPERTY_PANE_PROPS, + ); + const props = { propertySections: getPropertyConfig(state), widgetId: getCurrentWidgetId(state), widgetProperties: getWidgetPropsForPropertyPane(state), isVisible: getIsPropertyPaneVisible(state), }; + PerformanceTracker.stopTracking(); + return props; }; const mapDispatchToProps = (dispatch: any): PropertyPaneFunctions => { diff --git a/app/client/src/pages/Editor/WidgetsEditor.tsx b/app/client/src/pages/Editor/WidgetsEditor.tsx index b0c27a46c6..9b73236392 100644 --- a/app/client/src/pages/Editor/WidgetsEditor.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor.tsx @@ -18,6 +18,9 @@ import { getCanvasClassName } from "utils/generators"; import { flashElementById } from "utils/helpers"; import { useParams } from "react-router"; import { fetchPage } from "actions/pageActions"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; const EditorWrapper = styled.div` display: flex; @@ -46,6 +49,9 @@ const CanvasContainer = styled.section` /* eslint-disable react/display-name */ const WidgetsEditor = () => { + PerformanceTracker.startTracking( + PerformanceTransactionName.GENERATE_WIDGET_EDITOR_PROPS, + ); const { focusWidget, selectWidget } = useWidgetSelection(); const params = useParams<{ applicationId: string; pageId: string }>(); const dispatch = useDispatch(); @@ -55,6 +61,10 @@ const WidgetsEditor = () => { const currentPageId = useSelector(getCurrentPageId); const currentPageName = useSelector(getCurrentPageName); + useEffect(() => { + PerformanceTracker.stopTracking(PerformanceTransactionName.CLOSE_API); + }); + // Switch page useEffect(() => { if (currentPageId !== params.pageId && !!params.pageId) { @@ -101,6 +111,7 @@ const WidgetsEditor = () => { node = ; } log.debug("Canvas rendered"); + PerformanceTracker.stopTracking(); return ( diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx index de253e8c90..5b962bccc8 100644 --- a/app/client/src/pages/Editor/routes.tsx +++ b/app/client/src/pages/Editor/routes.tsx @@ -28,6 +28,9 @@ import { } from "utils/hooks/dragResizeHooks"; import { closeAllModals } from "actions/widgetActions"; import { useDispatch } from "react-redux"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; const Wrapper = styled.div<{ isVisible: boolean }>` position: absolute; @@ -78,6 +81,7 @@ class EditorsRouter extends React.Component< } handleClose = (e: React.MouseEvent) => { + PerformanceTracker.startTracking(PerformanceTransactionName.CLOSE_API); e.stopPropagation(); const { applicationId, pageId } = this.props.match.params; this.setState({ diff --git a/app/client/src/pages/common/AppRoute.tsx b/app/client/src/pages/common/AppRoute.tsx index d35351e414..4bef0352d7 100644 --- a/app/client/src/pages/common/AppRoute.tsx +++ b/app/client/src/pages/common/AppRoute.tsx @@ -1,8 +1,8 @@ -import React, { useEffect } from "react"; +import React from "react"; import { Route } from "react-router-dom"; import AnalyticsUtil from "utils/AnalyticsUtil"; import * as Sentry from "@sentry/react"; -import { useSelector, connect } from "react-redux"; +import { connect } from "react-redux"; import { getThemeDetails } from "selectors/themeSelectors"; import { AppState } from "reducers"; import { ThemeMode } from "reducers/uiReducers/themeReducer"; diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index dfb8ae4599..8b14ed8cd6 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -49,6 +49,9 @@ import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants"; import { RestAction } from "entities/Action"; import { getCurrentOrgId } from "selectors/organizationSelectors"; import log from "loglevel"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; function* syncApiParamsSaga( actionPayload: ReduxActionWithMeta, @@ -57,7 +60,7 @@ function* syncApiParamsSaga( const field = actionPayload.meta.field; const value = actionPayload.payload; const padQueryParams = { key: "", value: "" }; - + PerformanceTracker.startTracking(PerformanceTransactionName.SYNC_PARAMS_SAGA); if (field === "actionConfiguration.path") { if (value.indexOf("?") > -1) { const paramsString = value.substr(value.indexOf("?") + 1); @@ -122,6 +125,7 @@ function* syncApiParamsSaga( ), ); } + PerformanceTracker.stopTracking(); } function* initializeExtraFormDataSaga() { @@ -162,6 +166,7 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) { // // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // // @ts-ignore // document.activeElement.blur(); + PerformanceTracker.startTracking(PerformanceTransactionName.CHANGE_API_SAGA); const { id } = actionPayload.payload; const action = yield select(getAction, id); if (!action) return; @@ -187,6 +192,7 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) { id, ); } + PerformanceTracker.stopTracking(); } function* updateFormFields( diff --git a/app/client/src/selectors/dataTreeSelectors.ts b/app/client/src/selectors/dataTreeSelectors.ts index 252959c1d6..7b2dc945cd 100644 --- a/app/client/src/selectors/dataTreeSelectors.ts +++ b/app/client/src/selectors/dataTreeSelectors.ts @@ -7,39 +7,50 @@ import { getWidgets, getWidgetsMeta } from "sagas/selectors"; import * as log from "loglevel"; import "url-search-params-polyfill"; import { getPageList } from "./appViewSelectors"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; -export const getUnevaluatedDataTree = (withFunctions?: boolean) => - createSelector( - getActionsForCurrentPage, - getWidgets, - getWidgetsMeta, - getPageList, - getAppData, - (actions, widgets, widgetsMeta, pageListPayload, appData) => { - const pageList = pageListPayload || []; - return DataTreeFactory.create( - { - actions, - widgets, - widgetsMeta, - pageList, - appData, - }, - withFunctions, - ); - }, - ); +export const getUnevaluatedDataTree = createSelector( + getActionsForCurrentPage, + getWidgets, + getWidgetsMeta, + getPageList, + getAppData, + (actions, widgets, widgetsMeta, pageListPayload, appData) => { + PerformanceTracker.startTracking( + PerformanceTransactionName.CONSTRUCT_UNEVAL_TREE, + ); + const pageList = pageListPayload || []; + const unevalTree = DataTreeFactory.create( + { + actions, + widgets, + widgetsMeta, + pageList, + appData, + }, + true, + ); + PerformanceTracker.stopTracking(); + return unevalTree; + }, +); -export const evaluateDataTree = (withFunctions?: boolean) => - createSelector( - getUnevaluatedDataTree(withFunctions), - (dataTree: DataTree): DataTree => { - return getEvaluatedDataTree(dataTree); - }, - ); +export const evaluateDataTree = createSelector( + getUnevaluatedDataTree, + (dataTree: DataTree): DataTree => { + PerformanceTracker.startTracking( + PerformanceTransactionName.DATA_TREE_EVALUATION, + ); + const evalDataTree = getEvaluatedDataTree(dataTree); + PerformanceTracker.stopTracking(); + return evalDataTree; + }, +); -export const evaluateDataTreeWithFunctions = evaluateDataTree(true); -export const evaluateDataTreeWithoutFunctions = evaluateDataTree(true); +export const evaluateDataTreeWithFunctions = evaluateDataTree; +export const evaluateDataTreeWithoutFunctions = evaluateDataTree; // For autocomplete. Use actions cached responses if // there isn't a response already diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index 7b96dc4f9d..f81a924fb0 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -20,6 +20,9 @@ import { DataTreeWidget } from "entities/DataTree/dataTreeFactory"; import { getActions } from "sagas/selectors"; import * as log from "loglevel"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig; const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar; @@ -107,6 +110,9 @@ export const getCanvasWidgetDsl = createSelector( entities: AppState["entities"], evaluatedDataTree, ): ContainerWidgetProps => { + PerformanceTracker.startTracking( + PerformanceTransactionName.CONSTRUCT_CANVAS_DSL, + ); log.debug("Evaluating data tree to get canvas widgets"); log.debug({ evaluatedDataTree }); const widgets = { ...entities.canvasWidgets }; @@ -121,6 +127,7 @@ export const getCanvasWidgetDsl = createSelector( const denormalizedWidgets = CanvasWidgetsNormalizer.denormalize("0", { canvasWidgets: widgets, }); + PerformanceTracker.stopTracking(); return denormalizedWidgets; }, ); diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index b7084ecf44..76236fd896 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -15,12 +15,14 @@ import { DataTreeWidget, ENTITY_TYPE, } from "entities/DataTree/dataTreeFactory"; -import * as log from "loglevel"; import equal from "fast-deep-equal/es6"; import WidgetFactory from "utils/WidgetFactory"; import { AppToaster } from "components/editorComponents/ToastComponent"; import { ToastType } from "react-toastify"; import { Action } from "entities/Action"; +import PerformanceTracker, { + PerformanceTransactionName, +} from "utils/PerformanceTracker"; export const removeBindingsFromActionObject = (obj: Action) => { const string = JSON.stringify(obj); @@ -261,52 +263,57 @@ let dependencyTreeCache: any = {}; let cachedDataTreeString = ""; export function getEvaluatedDataTree(dataTree: DataTree): DataTree { - const totalStart = performance.now(); // Create Dependencies DAG - const createDepsStart = performance.now(); const dataTreeString = JSON.stringify(dataTree); // Stringify before doing a fast equals because the data tree has functions and fast equal will always treat those as changed values // Better solve will be to prune functions - if (!equal(dataTreeString, cachedDataTreeString)) { + const shouldCreateDependencyTree = !equal( + dataTreeString, + cachedDataTreeString, + ); + PerformanceTracker.startTracking( + PerformanceTransactionName.CREATE_DEPENDENCIES, + { isCacheMiss: shouldCreateDependencyTree }, + ); + if (shouldCreateDependencyTree) { cachedDataTreeString = dataTreeString; dependencyTreeCache = createDependencyTree(dataTree); } - const createDepsEnd = performance.now(); const { dependencyMap, sortedDependencies, dependencyTree, } = dependencyTreeCache; - + PerformanceTracker.stopTracking(); // Evaluate Tree - const evaluatedTreeStart = performance.now(); + PerformanceTracker.startTracking( + PerformanceTransactionName.SORTED_DEPENDENCY_EVALUATION, + { + dependencies: sortedDependencies, + dependencyCount: sortedDependencies.length, + dataTreeSize: cachedDataTreeString.length, + }, + ); const evaluatedTree = dependencySortedEvaluateDataTree( dataTree, dependencyMap, sortedDependencies, ); - const evaluatedTreeEnd = performance.now(); + PerformanceTracker.stopTracking(); // Set Loading Widgets - const loadingTreeStart = performance.now(); + PerformanceTracker.startTracking( + PerformanceTransactionName.SET_WIDGET_LOADING, + ); const treeWithLoading = setTreeLoading(evaluatedTree, dependencyTree); - const loadingTreeEnd = performance.now(); + PerformanceTracker.stopTracking(); // Validate Widgets + PerformanceTracker.startTracking( + PerformanceTransactionName.VALIDATE_DATA_TREE, + ); const validated = getValidatedTree(treeWithLoading); - - // End counting total time - const endStart = performance.now(); - - // Log time taken and count - const timeTaken = { - total: (endStart - totalStart).toFixed(2), - createDeps: (createDepsEnd - createDepsStart).toFixed(2), - evaluate: (evaluatedTreeEnd - evaluatedTreeStart).toFixed(2), - loading: (loadingTreeEnd - loadingTreeStart).toFixed(2), - }; - log.debug("data tree evaluated"); - log.debug(timeTaken); + PerformanceTracker.stopTracking(); // dataTreeCache = validated; return validated; } @@ -572,7 +579,6 @@ function evaluateDynamicProperty( if (isCacheHit && cacheObj) { return cacheObj.evaluated; } else { - log.debug("eval " + propertyPath); const dynamicResult = getDynamicValue(unEvalPropertyValue, currentTree); dynamicPropValueCache.set(propertyPath, { evaluated: dynamicResult.result, diff --git a/app/client/src/utils/PerformanceTracker.ts b/app/client/src/utils/PerformanceTracker.ts new file mode 100644 index 0000000000..1042534d81 --- /dev/null +++ b/app/client/src/utils/PerformanceTracker.ts @@ -0,0 +1,180 @@ +import * as Sentry from "@sentry/react"; +import { Span, SpanStatus } from "@sentry/tracing"; +import _ from "lodash"; +import * as log from "loglevel"; + +export enum PerformanceTransactionName { + DEPLOY_APPLICATION = "DEPLOY_APPLICATION", + RUN_ACTION = "RUN_ACTION", + PAGE_SWITCH_EDIT = "PAGE_SWITCH_EDIT", + PAGE_SWITCH_VIEW = "PAGE_SWITCH_VIEW", + CREATE_ACTION = "CREATE_ACTION", + CURL_IMPORT = "CURL_IMPORT", + EXECUTE_WIDGET_ACTION = "EXECUTE_WIDGET_ACTION", + RUN_ACTION_WAIT_FOR_SAVE = "RUN_ACTION_WAIT_FOR_SAVE", + DATA_TREE_EVALUATION = "DATA_TREE_EVALUATION", + CONSTRUCT_UNEVAL_TREE = "CONSTRUCT_UNEVAL_TREE", + CONSTRUCT_CANVAS_DSL = "CONSTRUCT_CANVAS_DSL", + CREATE_DEPENDENCIES = "CREATE_DEPENDENCIES", + SORTED_DEPENDENCY_EVALUATION = "SORTED_DEPENDENCY_EVALUATION", + SET_WIDGET_LOADING = "SET_WIDGET_LOADING", + VALIDATE_DATA_TREE = "VALIDATE_DATA_TREE", + EXECUTE_PAGE_LOAD_ACTIONS = "EXECUTE_PAGE_LOAD_ACTIONS", + SAVE_PAGE_LAYOUT = "SAVE_PAGE_LAYOUT", + SAVE_ACTION = "SAVE_ACTION", + EVALUATE_BINDING = "EVALUATE_BINDING", + GENERATE_PROPERTY_PANE_PROPS = "GENERATE_PROPERTY_PANE_PROPS", + GENERATE_VIEW_MODE_PROPS = "GENERATE_VIEW_MODE_PROPS", + GENERATE_WIDGET_EDITOR_PROPS = "GENERATE_WIDGET_EDITOR_PROPS", + ENTITY_EXPLORER_ENTITY = "ENTITY_EXPLORER_ENTITY", + CLOSE_API = "CLOSE_API", + OPEN_API = "OPEN_API", + CANVAS_MOUNT = "CANVAS_MOUNT", + GENERATE_API_PROPS = "GENERATE_API_PROPS", + CHANGE_API_SAGA = "CHANGE_API_SAGA", + SYNC_PARAMS_SAGA = "SYNC_PARAMS_SAGA", +} + +export enum PerformanceTagNames { + PAGE_ID = "pageId", + APP_ID = "appId", + APP_MODE = "appMode", + TRANSACTION_SUCCESS = "transaction.success", +} + +export interface PerfLog { + sentrySpan: Span; + skipLog?: boolean; + eventName: string; +} + +class PerformanceTracker { + private static perfLogQueue: PerfLog[] = []; + + static startTracking = ( + eventName: PerformanceTransactionName, + data?: any, + skipLog = false, + ) => { + const currentTransaction = Sentry.getCurrentHub() + .getScope() + ?.getTransaction(); + if ( + PerformanceTracker.perfLogQueue.length === 0 && + currentTransaction !== undefined && + currentTransaction.status === SpanStatus.Ok + ) { + PerformanceTracker.perfLogQueue.push({ + sentrySpan: currentTransaction, + skipLog: skipLog, + eventName: eventName, + }); + } + if (PerformanceTracker.perfLogQueue.length === 0) { + if (!skipLog) { + log.debug( + PerformanceTracker.generateSpaces( + PerformanceTracker.perfLogQueue.length + 1, + ) + + eventName + + " Track Transaction ", + ); + } + const newTransaction = Sentry.startTransaction({ name: eventName }); + Sentry.getCurrentHub().configureScope(scope => + scope.setSpan(newTransaction), + ); + PerformanceTracker.perfLogQueue.push({ + sentrySpan: newTransaction, + skipLog: skipLog, + eventName: eventName, + }); + } else { + if (!skipLog) { + log.debug( + PerformanceTracker.generateSpaces( + PerformanceTracker.perfLogQueue.length + 1, + ) + + eventName + + " Track Span ", + ); + } + const currentPerfLog = + PerformanceTracker.perfLogQueue[ + PerformanceTracker.perfLogQueue.length - 1 + ]; + const currentRunningSpan = currentPerfLog.sentrySpan; + const span = currentRunningSpan.startChild({ op: eventName, data: data }); + PerformanceTracker.perfLogQueue.push({ + sentrySpan: span, + skipLog: skipLog, + eventName: eventName, + }); + } + }; + + static stopTracking = ( + data?: any, + eventName?: PerformanceTransactionName, + ) => { + if (eventName) { + let index = -1; + _.forEach(PerformanceTracker.perfLogQueue, (perfLog, i) => { + if (perfLog.eventName === eventName) { + index = i; + } + if (index !== -1 && i >= index) { + const currentSpan = perfLog.sentrySpan; + currentSpan.finish(); + if (!perfLog?.skipLog) { + PerformanceTracker.printDuration( + perfLog.eventName, + PerformanceTracker.perfLogQueue.length + 1, + currentSpan.startTimestamp, + currentSpan.endTimestamp, + ); + } + } + }); + PerformanceTracker.perfLogQueue = PerformanceTracker.perfLogQueue.splice( + index, + ); + } else { + const perfLog = PerformanceTracker.perfLogQueue.pop(); + if (perfLog) { + const currentRunningSpan = perfLog?.sentrySpan; + currentRunningSpan.setData("endData", data); + currentRunningSpan.finish(); + if (!perfLog?.skipLog) { + PerformanceTracker.printDuration( + perfLog.eventName, + PerformanceTracker.perfLogQueue.length + 1, + currentRunningSpan.startTimestamp, + currentRunningSpan.endTimestamp, + ); + } + } + } + }; + + static generateSpaces(num: number) { + let str = ""; + for (let i = 0; i < num; i++) { + str += "\t"; + } + return str; + } + + static printDuration( + eventName: string, + level: number, + startTime: number, + endTime?: number, + ) { + const duration = ((endTime || 0) - startTime) * 1000; + const spaces = PerformanceTracker.generateSpaces(level); + log.debug(spaces + eventName + " Finish Tracking in " + duration + "ms"); + } +} + +export default PerformanceTracker; diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 98cd74c4c7..d7a65c46e3 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -2719,14 +2719,14 @@ dependencies: any-observable "^0.3.0" -"@sentry/browser@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e" - integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA== +"@sentry/browser@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.24.2.tgz#e2c2786dbf07699ee12f12babf0138d633abc494" + integrity sha512-P/uZC/VrLRpU7MVEJnlZK5+AkEmuHu+mns5gC91Z4gjn7GamjR/CaXVedHGw/15ZrsQiAiwoWwuxpv4Ypd/+SA== dependencies: - "@sentry/core" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/core" "5.24.2" + "@sentry/types" "5.24.2" + "@sentry/utils" "5.24.2" tslib "^1.9.3" "@sentry/cli@^1.55.0": @@ -2740,69 +2740,69 @@ progress "^2.0.3" proxy-from-env "^1.1.0" -"@sentry/core@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7" - integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw== +"@sentry/core@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.24.2.tgz#1724652855c0887a690c3fc6acd2519d4072b511" + integrity sha512-nuAwCGU1l9hgMinl5P/8nIQGRXDP2FI9cJnq5h1qiP/XIOvJkJz2yzBR6nTyqr4vBth0tvxQJbIpDNGd7vHJLg== dependencies: - "@sentry/hub" "5.22.3" - "@sentry/minimal" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/hub" "5.24.2" + "@sentry/minimal" "5.24.2" + "@sentry/types" "5.24.2" + "@sentry/utils" "5.24.2" tslib "^1.9.3" -"@sentry/hub@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5" - integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw== +"@sentry/hub@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.24.2.tgz#64a02fd487599945e488ae23aba4ce4df44ee79e" + integrity sha512-xmO1Ivvpb5Qr9WgekinuZZlpl9Iw7iPETUe84HQOhUrXf+2gKO+LaUYMMsYSVDwXQEmR6/tTMyOtS6iavldC6w== dependencies: - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/types" "5.24.2" + "@sentry/utils" "5.24.2" tslib "^1.9.3" -"@sentry/minimal@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440" - integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA== +"@sentry/minimal@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.24.2.tgz#14e8b136842398a32987459f0574359b6dc57a1f" + integrity sha512-biFpux5bI3R8xiD/Zzvrk1kRE6bqPtfWXmZYAHRtaUMCAibprTKSY9Ta8QYHynOAEoJ5Akedy6HUsEkK5DoZfA== dependencies: - "@sentry/hub" "5.22.3" - "@sentry/types" "5.22.3" + "@sentry/hub" "5.24.2" + "@sentry/types" "5.24.2" tslib "^1.9.3" -"@sentry/react@^5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.22.3.tgz#ed692f9e2aff718da6cd15d2941ddda4f1d63385" - integrity sha512-Or/tLayuxpOJhIWOXiDKdaJQZ981uRS9NT0QcPvU+Si1qTElSqtH1zB94GlwhgpglkbmLPiYq6VPrG2HOiZ79Q== +"@sentry/react@^5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.24.2.tgz#781ae4274ad5c3148b8f80b613c1b0a73f763e47" + integrity sha512-iVti69qCMFztgP2E0LMx6V+3+ppKdylMJalWnwMt4LyL9idnnuiwFzMCA9g3QEvXRCTSuqEO39Dk4XYXyxi8pA== dependencies: - "@sentry/browser" "5.22.3" - "@sentry/minimal" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/browser" "5.24.2" + "@sentry/minimal" "5.24.2" + "@sentry/types" "5.24.2" + "@sentry/utils" "5.24.2" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@^5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.22.3.tgz#9b5a376e3164c007a22e8642ec094104468cac0c" - integrity sha512-Zp59kMCk5v56ZAyErqjv/QvGOWOQ5fRltzeVQVp8unIDTk6gEFXfhwPsYHOokJe1mfkmrgPDV6xAkYgtL3KCDQ== +"@sentry/tracing@^5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.24.2.tgz#a36b4f9bf699c5e07e99a148360091c8e727c51f" + integrity sha512-1uDgvGGVF8lb3hRXbhNnns+8DBUKjhRKOFR5Z3RExjrDFYTDbHmoNtV73Q12Ra+Iht9HTZnIBOqYD3oSZIbJ0w== dependencies: - "@sentry/hub" "5.22.3" - "@sentry/minimal" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/hub" "5.24.2" + "@sentry/minimal" "5.24.2" + "@sentry/types" "5.24.2" + "@sentry/utils" "5.24.2" tslib "^1.9.3" -"@sentry/types@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18" - integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw== +"@sentry/types@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.24.2.tgz#e2c25d1e75d8dbec5dbbd9a309a321425b61c2ca" + integrity sha512-HcOK00R0tQG5vzrIrqQ0jC28+z76jWSgQCzXiessJ5SH/9uc6NzdO7sR7K8vqMP2+nweCHckFohC8G0T1DLzuQ== -"@sentry/utils@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f" - integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw== +"@sentry/utils@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.24.2.tgz#90b7dff939bbbf4bb8edcac6aac2d04a0552af80" + integrity sha512-oPGde4tNEDHKk0Cg9q2p0qX649jLDUOwzJXHKpd0X65w3A6eJByDevMr8CSzKV9sesjrUpxqAv6f9WWlz185tA== dependencies: - "@sentry/types" "5.22.3" + "@sentry/types" "5.24.2" tslib "^1.9.3" "@sentry/webpack-plugin@^1.12.1":