diff --git a/app/client/src/UITelemetry/generateTraces.ts b/app/client/src/UITelemetry/generateTraces.ts index 08b977a62dea..c051fa0840a6 100644 --- a/app/client/src/UITelemetry/generateTraces.ts +++ b/app/client/src/UITelemetry/generateTraces.ts @@ -76,15 +76,15 @@ export function startNestedSpan( return generatorTrace.startSpan(spanName, spanOptions, parentContext); } -export function endSpan(span: Span) { - span.end(); +export function endSpan(span?: Span) { + span?.end(); } export function setAttributesToSpan( - span: Span, - spanAttributes: SpanAttributes, + span?: Span, + spanAttributes: SpanAttributes = {}, ) { - span.setAttributes(spanAttributes); + span?.setAttributes(spanAttributes); } export function wrapFnWithParentTraceContext(parentSpan: Span, fn: () => any) { diff --git a/app/client/src/entities/Engine/AppEditorEngine.ts b/app/client/src/entities/Engine/AppEditorEngine.ts index 407c72fa0b54..c5c91ac69a8c 100644 --- a/app/client/src/entities/Engine/AppEditorEngine.ts +++ b/app/client/src/entities/Engine/AppEditorEngine.ts @@ -66,6 +66,8 @@ import { fetchAppThemesAction, fetchSelectedAppThemeAction, } from "actions/appThemingActions"; +import type { Span } from "@opentelemetry/api"; +import { endSpan, startNestedSpan } from "UITelemetry/generateTraces"; export default class AppEditorEngine extends AppEngine { constructor(mode: APP_MODE) { @@ -87,10 +89,17 @@ export default class AppEditorEngine extends AppEngine { * @param AppEnginePayload * @returns */ - public *setupEngine(payload: AppEnginePayload): any { - yield* super.setupEngine.call(this, payload); + public *setupEngine(payload: AppEnginePayload, rootSpan: Span): any { + const editorSetupSpan = startNestedSpan( + "AppEditorEngine.setupEngine", + rootSpan, + ); + + yield* super.setupEngine.call(this, payload, rootSpan); yield put(resetEditorSuccess()); CodemirrorTernService.resetServer(); + + endSpan(editorSetupSpan); } public startPerformanceTracking() { @@ -109,7 +118,13 @@ export default class AppEditorEngine extends AppEngine { toLoadPageId: string, applicationId: string, allResponses: EditConsolidatedApi, + rootSpan: Span, ) { + const loadPageThemesAndActionsSpan = startNestedSpan( + "AppEditorEngine.loadPageThemesAndActions", + rootSpan, + ); + const { currentTheme, customJSLibraries, @@ -157,13 +172,39 @@ export default class AppEditorEngine extends AppEngine { `Unable to fetch actions for the application: ${applicationId}`, ); + const waitForUserSpan = startNestedSpan( + "AppEditorEngine.waitForFetchUserSuccess", + rootSpan, + ); yield call(waitForFetchUserSuccess); + endSpan(waitForUserSpan); + + const waitForSegmentInitSpan = startNestedSpan( + "AppEditorEngine.waitForSegmentInit", + rootSpan, + ); yield call(waitForSegmentInit, true); + endSpan(waitForSegmentInitSpan); + + const waitForFetchEnvironmentsSpan = startNestedSpan( + "AppEditorEngine.waitForFetchEnvironments", + rootSpan, + ); yield call(waitForFetchEnvironments); + endSpan(waitForFetchEnvironmentsSpan); + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + endSpan(loadPageThemesAndActionsSpan); } - private *loadPluginsAndDatasources(allResponses: EditConsolidatedApi) { + private *loadPluginsAndDatasources( + allResponses: EditConsolidatedApi, + rootSpan: Span, + ) { + const loadPluginsAndDatasourcesSpan = startNestedSpan( + "AppEditorEngine.loadPluginsAndDatasources", + rootSpan, + ); const { mockDatasources, pluginFormConfigs } = allResponses || {}; const isAirgappedInstance = isAirgapped(); const currentWorkspaceId: string = yield select(getCurrentWorkspaceId); @@ -195,6 +236,9 @@ export default class AppEditorEngine extends AppEngine { [ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS], [ReduxActionErrorTypes.FETCH_PLUGIN_FORM_CONFIGS_ERROR], ); + + endSpan(loadPluginsAndDatasourcesSpan); + if (!pluginFormCall) throw new PluginFormConfigsNotFoundError( "Unable to fetch plugin form configs", @@ -205,17 +249,24 @@ export default class AppEditorEngine extends AppEngine { toLoadPageId: string, applicationId: string, allResponses: EditConsolidatedApi, + rootSpan: Span, ): any { yield call( this.loadPageThemesAndActions, toLoadPageId, applicationId, allResponses, + rootSpan, ); - yield call(this.loadPluginsAndDatasources, allResponses); + yield call(this.loadPluginsAndDatasources, allResponses, rootSpan); } - public *completeChore() { + public *completeChore(rootSpan: Span) { + const completeChoreSpan = startNestedSpan( + "AppEditorEngine.completeChore", + rootSpan, + ); + const isFirstTimeUserOnboardingComplete: boolean = yield select( getFirstTimeUserOnboardingComplete, ); @@ -272,9 +323,13 @@ export default class AppEditorEngine extends AppEngine { yield put({ type: ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS, }); + + endSpan(completeChoreSpan); } - public *loadGit(applicationId: string) { + public *loadGit(applicationId: string, rootSpan: Span) { + const loadGitSpan = startNestedSpan("AppEditorEngine.loadGit", rootSpan); + const branchInStore: string = yield select(getCurrentGitBranch); yield put( restoreRecentEntitiesRequest({ @@ -285,10 +340,13 @@ export default class AppEditorEngine extends AppEngine { // init of temporary remote url from old application yield put(remoteUrlInputValue({ tempRemoteUrl: "" })); // add branch query to path and fetch status + if (branchInStore) { history.replace(addBranchParam(branchInStore)); yield fork(this.loadGitInBackground); } + + endSpan(loadGitSpan); } private *loadGitInBackground() { diff --git a/app/client/src/entities/Engine/AppViewerEngine.ts b/app/client/src/entities/Engine/AppViewerEngine.ts index b67a87267f2d..5f3c023d8f68 100644 --- a/app/client/src/entities/Engine/AppViewerEngine.ts +++ b/app/client/src/entities/Engine/AppViewerEngine.ts @@ -34,7 +34,8 @@ import { fetchAppThemesAction, fetchSelectedAppThemeAction, } from "actions/appThemingActions"; - +import type { Span } from "@opentelemetry/api"; +import { endSpan, startNestedSpan } from "UITelemetry/generateTraces"; export default class AppViewerEngine extends AppEngine { constructor(mode: APP_MODE) { super(mode); @@ -50,16 +51,30 @@ export default class AppViewerEngine extends AppEngine { return; } - *completeChore() { + *completeChore(rootSpan: Span) { + const completeChoreSpan = startNestedSpan( + "AppViewerEngine.completeChore", + rootSpan, + ); + yield call(waitForWidgetConfigBuild); yield put({ type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS, }); yield spawn(reportSWStatus); + + endSpan(completeChoreSpan); } - *setupEngine(payload: AppEnginePayload) { - yield call(super.setupEngine.bind(this), payload); + *setupEngine(payload: AppEnginePayload, rootSpan: Span) { + const viewerSetupSpan = startNestedSpan( + "AppViewerEngine.setupEngine", + rootSpan, + ); + + yield call(super.setupEngine.bind(this), payload, rootSpan); + + endSpan(viewerSetupSpan); } startPerformanceTracking() { @@ -78,7 +93,13 @@ export default class AppViewerEngine extends AppEngine { toLoadPageId: string, applicationId: string, allResponses: DeployConsolidatedApi, + rootSpan: Span, ): any { + const loadAppEntitiesSpan = startNestedSpan( + "AppViewerEngine.loadAppEntities", + rootSpan, + ); + const { currentTheme, customJSLibraries, @@ -128,9 +149,29 @@ export default class AppViewerEngine extends AppEngine { `Unable to fetch actions for the application: ${applicationId}`, ); + const waitForUserSpan = startNestedSpan( + "AppViewerEngine.waitForFetchUserSuccess", + rootSpan, + ); yield call(waitForFetchUserSuccess); + endSpan(waitForUserSpan); + + const waitForSegmentSpan = startNestedSpan( + "AppViewerEngine.waitForSegmentInit", + rootSpan, + ); yield call(waitForSegmentInit, true); + endSpan(waitForSegmentSpan); + + const waitForEnvironmentsSpan = startNestedSpan( + "AppViewerEngine.waitForFetchEnvironments", + rootSpan, + ); yield call(waitForFetchEnvironments); + endSpan(waitForEnvironmentsSpan); + yield put(fetchAllPageEntityCompletion([executePageLoadActions()])); + + endSpan(loadAppEntitiesSpan); } } diff --git a/app/client/src/entities/Engine/index.ts b/app/client/src/entities/Engine/index.ts index a281fc67d8b1..6c8e9776a51f 100644 --- a/app/client/src/entities/Engine/index.ts +++ b/app/client/src/entities/Engine/index.ts @@ -19,6 +19,8 @@ import URLGeneratorFactory from "entities/URLRedirect/factory"; import { updateBranchLocally } from "actions/gitSyncActions"; import { getCurrentGitBranch } from "selectors/gitSyncSelectors"; import { restoreIDEEditorViewMode } from "actions/ideActions"; +import type { Span } from "@opentelemetry/api"; +import { endSpan, startNestedSpan } from "UITelemetry/generateTraces"; export interface AppEnginePayload { applicationId?: string; @@ -29,10 +31,14 @@ export interface AppEnginePayload { } export interface IAppEngine { - setupEngine(payload: AppEnginePayload): any; - loadAppData(payload: AppEnginePayload): any; + setupEngine(payload: AppEnginePayload, rootSpan: Span): any; + loadAppData(payload: AppEnginePayload, rootSpan: Span): any; loadAppURL(pageId: string, pageIdInUrl?: string): any; - loadAppEntities(toLoadPageId: string, applicationId: string): any; + loadAppEntities( + toLoadPageId: string, + applicationId: string, + rootSpan: Span, + ): any; loadGit(applicationId: string): any; completeChore(): any; } @@ -55,13 +61,19 @@ export default abstract class AppEngine { toLoadPageId: string, applicationId: string, allResponses: InitConsolidatedApi, + rootSpan: Span, ): any; - abstract loadGit(applicationId: string): any; + abstract loadGit(applicationId: string, rootSpan: Span): any; abstract startPerformanceTracking(): any; abstract stopPerformanceTracking(): any; - abstract completeChore(): any; + abstract completeChore(rootSpan: Span): any; - *loadAppData(payload: AppEnginePayload, allResponses: InitConsolidatedApi) { + *loadAppData( + payload: AppEnginePayload, + allResponses: InitConsolidatedApi, + rootSpan: Span, + ) { + const loadAppDataSpan = startNestedSpan("AppEngine.loadAppData", rootSpan); const { applicationId, branch, pageId } = payload; const { pages } = allResponses; const apiCalls: boolean = yield failFastApiCalls( @@ -82,8 +94,11 @@ export default abstract class AppEngine { ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR, ], ); - if (!apiCalls) + + if (!apiCalls) { throw new PageNotFoundError(`Cannot find page with id: ${pageId}`); + } + const application: ApplicationPayload = yield select(getCurrentApplication); const currentGitBranch: ReturnType = yield select(getCurrentGitBranch); @@ -97,25 +112,44 @@ export default abstract class AppEngine { application.applicationVersion, this._mode, ); + + endSpan(loadAppDataSpan); return { toLoadPageId, applicationId: application.id }; } - *setupEngine(payload: AppEnginePayload): any { + *setupEngine(payload: AppEnginePayload, rootSpan: Span): any { + const setupEngineSpan = startNestedSpan("AppEngine.setupEngine", rootSpan); + const { branch } = payload; yield put(updateBranchLocally(branch || "")); yield put(setAppMode(this._mode)); yield put(restoreIDEEditorViewMode()); yield put({ type: ReduxActionTypes.START_EVALUATION }); + + endSpan(setupEngineSpan); } - *loadAppURL(pageId: string, pageIdInUrl?: string) { + *loadAppURL({ + pageId, + pageIdInUrl, + rootSpan, + }: { + pageId: string; + pageIdInUrl?: string; + rootSpan: Span; + }) { try { if (!this._urlRedirect) return; + + const loadAppUrlSpan = startNestedSpan("AppEngine.loadAppURL", rootSpan); const newURL: string = yield call( this._urlRedirect.generateRedirectURL.bind(this), pageId, pageIdInUrl, ); + + endSpan(loadAppUrlSpan); + if (!newURL) return; history.replace(newURL); } catch (e) { diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index f474374a598f..9030278c551b 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -540,10 +540,11 @@ export default function* executePluginActionTriggerSaga( }, actionId, ); - span && - setAttributesToSpan(span, { - actionId: actionId, - }); + + setAttributesToSpan(span, { + actionId: actionId, + }); + const action = shouldBeDefined( yield select(getAction, actionId), `Action not found for id - ${actionId}`, @@ -1286,7 +1287,7 @@ function* executePageLoadActionsSaga( getLayoutOnLoadIssues, ); const actionCount = flatten(pageActions).length; - span && setAttributesToSpan(span, { numActions: actionCount }); + setAttributesToSpan(span, { numActions: actionCount }); // when cyclical depedency issue is there, // none of the page load actions would be executed PerformanceTracker.startAsyncTracking( @@ -1343,11 +1344,12 @@ function* executePluginActionSaga( ) { const actionId = pluginAction.id; const pluginActionNameToDisplay = getPluginActionNameToDisplay(pluginAction); - parentSpan && - setAttributesToSpan(parentSpan, { - actionId, - pluginName: pluginActionNameToDisplay, - }); + + setAttributesToSpan(parentSpan, { + actionId, + pluginName: pluginActionNameToDisplay, + }); + if (pluginAction.confirmBeforeExecute) { const modalPayload = { name: pluginActionNameToDisplay, diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 7598b4d4a66d..f6081ce82813 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -85,6 +85,11 @@ import type { Datasource } from "entities/Datasource"; import type { Plugin, PluginFormPayload } from "api/PluginApi"; import ConsolidatedPageLoadApi from "api/ConsolidatedPageLoadApi"; import { axiosConnectionAbortedCode } from "@appsmith/api/ApiUtils"; +import { + endSpan, + startNestedSpan, + startRootSpan, +} from "UITelemetry/generateTraces"; export const URL_CHANGE_ACTIONS = [ ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE, @@ -284,32 +289,56 @@ export function* getInitResponses({ } export function* startAppEngine(action: ReduxAction) { + const rootSpan = startRootSpan("startAppEngine", { + mode: action.payload.mode, + pageId: action.payload.pageId, + applicationId: action.payload.applicationId, + branch: action.payload.branch, + }); + try { const engine: AppEngine = AppEngineFactory.create( action.payload.mode, action.payload.mode, ); engine.startPerformanceTracking(); - yield call(engine.setupEngine, action.payload); + yield call(engine.setupEngine, action.payload, rootSpan); + + const getInitResponsesSpan = startNestedSpan( + "getInitResponsesSpan", + rootSpan, + ); + const allResponses: InitConsolidatedApi = yield call(getInitResponses, { ...action.payload, }); + + endSpan(getInitResponsesSpan); + yield put({ type: ReduxActionTypes.LINT_SETUP }); + const { applicationId, toLoadPageId } = yield call( engine.loadAppData, action.payload, allResponses, + rootSpan, ); - yield call(engine.loadAppURL, toLoadPageId, action.payload.pageId); + + yield call(engine.loadAppURL, { + pageId: toLoadPageId, + pageIdInUrl: action.payload.pageId, + rootSpan, + }); yield call( engine.loadAppEntities, toLoadPageId, applicationId, allResponses, + rootSpan, ); - yield call(engine.loadGit, applicationId); - yield call(engine.completeChore); + yield call(engine.loadGit, applicationId, rootSpan); + yield call(engine.completeChore, rootSpan); yield put(generateAutoHeightLayoutTreeAction(true, false)); engine.stopPerformanceTracking(); } catch (e) { @@ -317,6 +346,8 @@ export function* startAppEngine(action: ReduxAction) { if (e instanceof AppEngineApiError) return; Sentry.captureException(e); yield put(safeCrashAppRequest()); + } finally { + endSpan(rootSpan); } } diff --git a/app/client/src/sagas/__tests__/initSagas.test.ts b/app/client/src/sagas/__tests__/initSagas.test.ts index 1a8883cefbd6..4b365b9d9865 100644 --- a/app/client/src/sagas/__tests__/initSagas.test.ts +++ b/app/client/src/sagas/__tests__/initSagas.test.ts @@ -10,6 +10,7 @@ import type { AppEnginePayload } from "entities/Engine"; import { testSaga } from "redux-saga-test-plan"; import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import mockResponse from "./mockConsolidatedApiResponse.json"; +import { startRootSpan } from "UITelemetry/generateTraces"; jest.mock("../../api/Api", () => ({ __esModule: true, @@ -50,33 +51,40 @@ describe("tests the sagas in initSagas", () => { stopPerformanceTracking: jest.fn(), }; + const mockRootSpan = startRootSpan("startAppEngine"); + (AppEngineFactory.create as jest.Mock).mockReturnValue(engine); testSaga(startAppEngine, action) .next() - .call(engine.setupEngine, action.payload) + .call(engine.setupEngine, action.payload, mockRootSpan) .next() .call(getInitResponses, { ...action.payload }) .next(mockResponse.data) .put({ type: ReduxActionTypes.LINT_SETUP }) .next() - .call(engine.loadAppData, action.payload, mockResponse.data) + .call(engine.loadAppData, action.payload, mockResponse.data, mockRootSpan) .next({ applicationId: action.payload.applicationId, toLoadPageId: action.payload.pageId, }) - .call(engine.loadAppURL, action.payload.pageId, action.payload.pageId) + .call(engine.loadAppURL, { + pageId: action.payload.pageId, + pageIdInUrl: action.payload.pageId, + rootSpan: mockRootSpan, + }) .next() .call( engine.loadAppEntities, action.payload.pageId, action.payload.applicationId, mockResponse.data, + mockRootSpan, ) .next() - .call(engine.loadGit, action.payload.applicationId) + .call(engine.loadGit, action.payload.applicationId, mockRootSpan) .next() - .call(engine.completeChore) + .call(engine.completeChore, mockRootSpan) .next() .put(generateAutoHeightLayoutTreeAction(true, false)) .next()