From 2f6f824efc9d867430c5099520ba9b526b0a3b6f Mon Sep 17 00:00:00 2001 From: Vemparala Surya Vamsi <121419957+vsvamsi1@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:09:02 +0530 Subject: [PATCH] chore: frontend and backend telemetry updates for execute flow #28800 and #28805 (#28936) ## Description 1. Added frontend and backend custom OTLP telemetry to track execute flow 2. Updated end vars in client side code to match with server sdk intialisation code. #### PR fixes following issue(s) Fixes #28800 and #28805 #### Type of change - Chore (housekeeping or task changes that don't impact user perception) #### How Has This Been Tested? - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --- .../docker/templates/nginx-app.conf.template | 2 + app/client/jest.config.js | 2 + app/client/public/index.html | 4 + .../src/{ => UITelemetry}/auto-otel-web.js | 30 ++++-- app/client/src/UITelemetry/generateTraces.ts | 89 +++++++++++++++++ app/client/src/actions/pluginActionActions.ts | 30 ++++-- app/client/src/api/ActionAPI.tsx | 20 +++- app/client/src/ce/configs/index.ts | 15 +++ app/client/src/ce/configs/types.ts | 2 + app/client/src/index.tsx | 2 +- .../sagas/ActionExecution/PluginActionSaga.ts | 90 ++++++++++++----- app/client/src/utils/WorkerUtil.ts | 9 +- .../Evaluation/handlers/updateActionData.ts | 1 + app/server/appsmith-server/pom.xml | 29 ++++++ .../appsmith/server/ServerApplication.java | 18 ++++ .../server/constants/EnvVariables.java | 1 + .../server/controllers/ActionController.java | 6 +- .../controllers/ce/ActionControllerCE.java | 15 ++- .../server/helpers/OtlpTelemetry.java | 99 +++++++++++++++++++ 19 files changed, 409 insertions(+), 55 deletions(-) rename app/client/src/{ => UITelemetry}/auto-otel-web.js (65%) create mode 100644 app/client/src/UITelemetry/generateTraces.ts create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/OtlpTelemetry.java diff --git a/app/client/docker/templates/nginx-app.conf.template b/app/client/docker/templates/nginx-app.conf.template index c21b7fe84d..5452e57a4a 100644 --- a/app/client/docker/templates/nginx-app.conf.template +++ b/app/client/docker/templates/nginx-app.conf.template @@ -53,6 +53,8 @@ server { sub_filter __APPSMITH_NEW_RELIC_BROWSER_AGENT_LICENSE_KEY__ '${APPSMITH_NEW_RELIC_BROWSER_AGENT_LICENSE_KEY}'; sub_filter __APPSMITH_NEW_RELIC_ACCOUNT_ENABLE__ '${APPSMITH_NEW_RELIC_ACCOUNT_ENABLE}'; sub_filter __APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY__ '${APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY}'; + sub_filter __APPSMITH_NEW_RELIC_OTEL_SERVICE_NAME__ '${APPSMITH_NEW_RELIC_OTEL_SERVICE_NAME}'; + sub_filter __APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT__ '${APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT}'; } diff --git a/app/client/jest.config.js b/app/client/jest.config.js index 956fb7d66d..1e5611382c 100644 --- a/app/client/jest.config.js +++ b/app/client/jest.config.js @@ -89,6 +89,8 @@ module.exports = { applicationId: parseConfig("__APPSMITH_NEW_RELIC_APPLICATION_ID__"), browserAgentlicenseKey: parseConfig("__APPSMITH_NEW_RELIC_BROWSER_AGENT_LICENSE_KEY__"), otlpLicenseKey: parseConfig("__APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY__"), + otlpServiceName: parseConfig("__APPSMITH_NEW_RELIC_OTEL_SERVICE_NAME__"), + otlpEndpoint:parseConfig("__APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT__") }, fusioncharts: { licenseKey: parseConfig("__APPSMITH_FUSIONCHARTS_LICENSE_KEY__"), diff --git a/app/client/public/index.html b/app/client/public/index.html index d7efcbc795..d1ecbd99e0 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -276,6 +276,10 @@ applicationId: parseConfig("__APPSMITH_NEW_RELIC_APPLICATION_ID__"), browserAgentlicenseKey: parseConfig("__APPSMITH_NEW_RELIC_BROWSER_AGENT_LICENSE_KEY__"), otlpLicenseKey: parseConfig("__APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY__"), + //OTLP following the naming convention of Sdk initialisation + otlpServiceName: parseConfig("__APPSMITH_NEW_RELIC_OTEL_SERVICE_NAME__"), + otlpEndpoint:parseConfig("__APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT__"), + }, fusioncharts: { licenseKey: parseConfig("__APPSMITH_FUSIONCHARTS_LICENSE_KEY__"), diff --git a/app/client/src/auto-otel-web.js b/app/client/src/UITelemetry/auto-otel-web.js similarity index 65% rename from app/client/src/auto-otel-web.js rename to app/client/src/UITelemetry/auto-otel-web.js index 6b895e630f..c2e7c5a6e9 100644 --- a/app/client/src/auto-otel-web.js +++ b/app/client/src/UITelemetry/auto-otel-web.js @@ -7,23 +7,22 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; import { getAppsmithConfigs } from "@appsmith/configs"; +import { W3CTraceContextPropagator } from "@opentelemetry/core"; const { newRelic } = getAppsmithConfigs(); -const { applicationId, otlpLicenseKey } = newRelic; - -const NEW_RELIC_OTLP_ENTITY_NAME = "Appsmith Frontend OTLP"; -const NEW_RELIC_OTLP_ENDPOINT = "https://otlp.nr-data.net:4318"; +const { applicationId, otlpEndpoint, otlpLicenseKey, otlpServiceName } = + newRelic; const provider = new WebTracerProvider({ resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: NEW_RELIC_OTLP_ENTITY_NAME, + [SemanticResourceAttributes.SERVICE_NAME]: otlpServiceName, [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: applicationId, [SemanticResourceAttributes.SERVICE_VERSION]: "1.0.0", }), }); const newRelicExporter = new OTLPTraceExporter({ - url: `${NEW_RELIC_OTLP_ENDPOINT}/v1/traces`, + url: `${otlpEndpoint}/v1/traces`, headers: { "api-key": otlpLicenseKey, }, @@ -43,9 +42,28 @@ const processor = new BatchSpanProcessor( exportTimeoutMillis: 30000, }, ); + +const W3C_OTLP_TRACE_HEADER = "traceparent"; +const CUSTOM_OTLP_TRACE_HEADER = "traceparent-otlp"; +//We are overriding the default header "traceparent" used for trace context because the browser +// agent shares the same header's distributed tracing +class CustomW3CTraceContextPropagator extends W3CTraceContextPropagator { + inject(context, carrier, setter) { + // Call the original inject method to get the default traceparent header + super.inject(context, carrier, setter); + + // Modify the carrier to use a different header + if (carrier[W3C_OTLP_TRACE_HEADER]) { + carrier[CUSTOM_OTLP_TRACE_HEADER] = carrier[W3C_OTLP_TRACE_HEADER]; + delete carrier[W3C_OTLP_TRACE_HEADER]; // Remove the original traceparent header + } + } +} + provider.addSpanProcessor(processor); provider.register({ contextManager: new ZoneContextManager(), + propagator: new CustomW3CTraceContextPropagator(), }); registerInstrumentations({ diff --git a/app/client/src/UITelemetry/generateTraces.ts b/app/client/src/UITelemetry/generateTraces.ts new file mode 100644 index 0000000000..bc273a6f15 --- /dev/null +++ b/app/client/src/UITelemetry/generateTraces.ts @@ -0,0 +1,89 @@ +import type { Span, Attributes, HrTime } from "@opentelemetry/api"; +import { SpanKind } from "@opentelemetry/api"; +import { context } from "@opentelemetry/api"; +import { trace } from "@opentelemetry/api"; + +const GENERATOR_TRACE = "generator-tracer"; +export function startRootSpan(spanName: string, spanAttributes?: Attributes) { + const tracer = trace.getTracer(GENERATOR_TRACE); + if (!spanName) { + return; + } + const attributes = spanAttributes ?? { attributes: spanAttributes }; + return tracer?.startSpan(spanName, { kind: SpanKind.CLIENT, ...attributes }); +} +export const generateContext = (span: Span) => { + return trace.setSpan(context.active(), span); +}; +export function startNestedSpan( + spanName: string, + parentSpan: Span, + spanAttributes?: Attributes, +) { + if (!spanName || !parentSpan) { + // do not generate nested span without parentSpan..we cannot generate context out of it + return; + } + + const parentContext = generateContext(parentSpan); + + const generatorTrace = trace.getTracer(GENERATOR_TRACE); + if (!spanAttributes) { + return generatorTrace.startSpan( + spanName, + { kind: SpanKind.CLIENT }, + parentContext, + ); + } + return generatorTrace.startSpan( + spanName, + { attributes: { kind: SpanKind.CLIENT, ...spanAttributes } }, + parentContext, + ); +} + +function convertHighResolutionTimeToEpoch(hr: HrTime) { + const epochInSeconds = hr[0]; + const millisecondFragment = Math.round(hr[1] / 1000000); + const epochInMilliseconds = epochInSeconds * 1000 + millisecondFragment; + return epochInMilliseconds; +} + +function addTraceToNewRelicSession(span: any) { + if ( + !span || + !span.startTime || + !span.endTime || + !span.name || + !(window as any)?.newrelic + ) { + return; + } + + //extract timestamp details from the span + //we have to convert it from HR timestamp to a regular epoch + const start = convertHighResolutionTimeToEpoch(span.startTime); + const end = convertHighResolutionTimeToEpoch(span.endTime); + const spanName = span.name; + + //the new relic window object is attached when the browser script + (window as any).newrelic.addToTrace({ name: spanName, start, end }); +} +export function endSpan(span?: Span) { + span?.end(); + + addTraceToNewRelicSession(span); +} +export function setAttributesToSpan(span: Span, spanAttributes: Attributes) { + if (!span) { + return; + } + span.setAttributes(spanAttributes); +} + +export function wrapFnWithParentTraceContext(parentSpan: Span, fn: () => any) { + const parentContext = trace.setSpan(context.active(), parentSpan); + return context.with(parentContext, fn); +} + +export type OtlpSpan = Span; diff --git a/app/client/src/actions/pluginActionActions.ts b/app/client/src/actions/pluginActionActions.ts index dd99f7c262..063088d2d0 100644 --- a/app/client/src/actions/pluginActionActions.ts +++ b/app/client/src/actions/pluginActionActions.ts @@ -14,6 +14,7 @@ import type { Action } from "entities/Action"; import { batchAction } from "actions/batchActions"; import type { ExecuteErrorPayload } from "constants/AppsmithActionConstants/ActionConstants"; import type { ModalInfo } from "reducers/uiReducers/modalActionReducer"; +import type { OtlpSpan } from "UITelemetry/generateTraces"; export const createActionRequest = (payload: Partial) => { return { @@ -345,17 +346,30 @@ export const bindDataOnCanvas = (payload: { }; }; +type actionDataPayload = { + entityName: string; + dataPath: string; + data: unknown; + dataPathRef?: string; +}[]; + +export interface updateActionDataPayloadType { + actionDataPayload: actionDataPayload; + parentSpan?: OtlpSpan; +} export const updateActionData = ( - payload: { - entityName: string; - dataPath: string; - data: unknown; - dataPathRef?: string; - }[], -) => { + payload: actionDataPayload, + parentSpan?: OtlpSpan, +): { + type: string; + payload: updateActionDataPayloadType; +} => { return { type: ReduxActionTypes.UPDATE_ACTION_DATA, - payload, + payload: { + actionDataPayload: payload, + parentSpan, + }, }; }; diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 6f88d53615..41d140e70b 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -8,6 +8,8 @@ import type { Action, ActionViewMode } from "entities/Action"; import type { APIRequest } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { omit } from "lodash"; +import type { OtlpSpan } from "UITelemetry/generateTraces"; +import { wrapFnWithParentTraceContext } from "UITelemetry/generateTraces"; export interface CreateActionRequest extends APIRequest { datasourceId: string; @@ -201,12 +203,10 @@ class ActionAPI extends API { static async deleteAction(id: string) { return API.delete(`${ActionAPI.url}/${id}`); } - - static async executeAction( + private static async executeApiCall( executeAction: FormData, timeout?: number, ): Promise> { - ActionAPI.abortActionExecutionTokenSource = axios.CancelToken.source(); return API.post(ActionAPI.url + "/execute", executeAction, undefined, { timeout: timeout || DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, headers: { @@ -218,6 +218,20 @@ class ActionAPI extends API { }); } + static async executeAction( + executeAction: FormData, + timeout?: number, + parentSpan?: OtlpSpan, + ): Promise> { + ActionAPI.abortActionExecutionTokenSource = axios.CancelToken.source(); + if (!parentSpan) { + return this.executeApiCall(executeAction, timeout); + } + return wrapFnWithParentTraceContext(parentSpan, async () => { + return await this.executeApiCall(executeAction, timeout); + }); + } + static async moveAction(moveRequest: MoveActionRequest) { return API.put(ActionAPI.url + "/move", moveRequest, undefined, { timeout: DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, diff --git a/app/client/src/ce/configs/index.ts b/app/client/src/ce/configs/index.ts index 2b11bc9808..786827665d 100644 --- a/app/client/src/ce/configs/index.ts +++ b/app/client/src/ce/configs/index.ts @@ -23,6 +23,8 @@ export interface INJECTED_CONFIGS { applicationId: string; browserAgentlicenseKey: string; otlpLicenseKey: string; + otlpServiceName: string; + otlpEndpoint: string; }; fusioncharts: { licenseKey: string; @@ -93,6 +95,9 @@ export const getConfigsFromEnvVars = (): INJECTED_CONFIGS => { browserAgentlicenseKey: process.env.APPSMITH_NEW_RELIC_BROWSER_AGENT_LICENSE_KEY || "", otlpLicenseKey: process.env.APPSMITH_NEW_RELIC_OTLP_LICENSE_KEY || "", + otlpEndpoint: process.env.APPSMITH_NEW_RELIC_OTEL_SERVICE_NAME || "", + otlpServiceName: + process.env.APPSMITH_NEW_RELIC_OTEL_EXPORTER_OTLP_ENDPOINT || "", }, logLevel: (process.env.REACT_APP_CLIENT_LOG_LEVEL as @@ -177,6 +182,14 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => { APPSMITH_FEATURE_CONFIGS?.newRelic.otlpLicenseKey, ); + const newRelicOtlpServiceName = getConfig( + ENV_CONFIG.newRelic.otlpServiceName, + APPSMITH_FEATURE_CONFIGS?.newRelic.otlpServiceName, + ); + const newRelicOtlpEndpoint = getConfig( + ENV_CONFIG.newRelic.otlpEndpoint, + APPSMITH_FEATURE_CONFIGS?.newRelic.otlpEndpoint, + ); const fusioncharts = getConfig( ENV_CONFIG.fusioncharts.licenseKey, APPSMITH_FEATURE_CONFIGS?.fusioncharts.licenseKey, @@ -257,6 +270,8 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => { applicationId: newRelicApplicationId.value, browserAgentlicenseKey: newRelicBrowserLicenseKey.value, otlpLicenseKey: newRelicOtlpLicenseKey.value, + otlpEndpoint: newRelicOtlpEndpoint.value, + otlpServiceName: newRelicOtlpServiceName.value, }, fusioncharts: { enabled: fusioncharts.enabled, diff --git a/app/client/src/ce/configs/types.ts b/app/client/src/ce/configs/types.ts index 50f3ff5848..649ca53a31 100644 --- a/app/client/src/ce/configs/types.ts +++ b/app/client/src/ce/configs/types.ts @@ -31,6 +31,8 @@ export interface AppsmithUIConfigs { applicationId: string; browserAgentlicenseKey: string; otlpLicenseKey: string; + otlpServiceName: string; + otlpEndpoint: string; }; segment: { enabled: boolean; diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 4c1ab26ae7..bc328eeb88 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -37,7 +37,7 @@ enableNewRelic && (async () => { try { await import( - /* webpackChunkName: "otlpTelemetry" */ "./auto-otel-web.js" + /* webpackChunkName: "otlpTelemetry" */ "./UITelemetry/auto-otel-web.js" ); } catch (e) { log.error("Error loading telemetry script", e); diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index a570663044..de2f904fd6 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -8,6 +8,7 @@ import { takeLatest, } from "redux-saga/effects"; import * as Sentry from "@sentry/react"; +import type { updateActionDataPayloadType } from "actions/pluginActionActions"; import { clearActionResponse, executePluginActionError, @@ -162,6 +163,12 @@ import { } from "@appsmith/selectors/environmentSelectors"; import { EVAL_WORKER_ACTIONS } from "@appsmith/workers/Evaluation/evalWorkerActions"; import { getIsActionCreatedInApp } from "@appsmith/utils/getIsActionCreatedInApp"; +import type { OtlpSpan } from "UITelemetry/generateTraces"; +import { + endSpan, + setAttributesToSpan, + startRootSpan, +} from "UITelemetry/generateTraces"; enum ActionResponseDataTypes { BINARY = "BINARY", @@ -498,6 +505,7 @@ export default function* executePluginActionTriggerSaga( pluginAction: TRunDescription, eventType: EventType, ) { + const span = startRootSpan("executePluginActionTriggerSaga"); const { payload: pluginPayload } = pluginAction; const { actionId, onError, params } = pluginPayload; if (getType(params) !== Types.OBJECT) { @@ -515,6 +523,10 @@ export default function* executePluginActionTriggerSaga( }, actionId, ); + span && + setAttributesToSpan(span, { + actionId: actionId, + }); const appMode: APP_MODE | undefined = yield select(getAppMode); const action = shouldBeDefined( yield select(getAction, actionId), @@ -564,6 +576,8 @@ export default function* executePluginActionTriggerSaga( action, pagination, params, + undefined, + span, ); const { isError, payload } = executePluginActionResponse; @@ -742,6 +756,7 @@ export function* runActionSaga( action?: Action; }>, ) { + const span = startRootSpan("runActionSaga"); const actionId = reduxAction.payload.id; const isSaving: boolean = yield select(isActionSaving(actionId)); const isDirty: boolean = yield select(isActionDirty(actionId)); @@ -810,6 +825,7 @@ export function* runActionSaga( paginationField, {}, true, + span, ); payload = executePluginActionResponse.payload; isError = executePluginActionResponse.isError; @@ -1075,7 +1091,7 @@ function* executeOnPageLoadJSAction(pageAction: PageAction) { } } -function* executePageLoadAction(pageAction: PageAction) { +function* executePageLoadAction(pageAction: PageAction, span?: OtlpSpan) { const currentEnvDetails: { id: string; name: string } = yield select( getCurrentEnvironmentDetails, ); @@ -1123,7 +1139,14 @@ function* executePageLoadAction(pageAction: PageAction) { try { const executePluginActionResponse: ExecutePluginActionResponse = - yield call(executePluginActionSaga, action); + yield call( + executePluginActionSaga, + action, + undefined, + undefined, + undefined, + span, + ); payload = executePluginActionResponse.payload; isError = executePluginActionResponse.isError; } catch (e) { @@ -1250,13 +1273,14 @@ function* executePageLoadAction(pageAction: PageAction) { } function* executePageLoadActionsSaga() { + const span = startRootSpan("executePageLoadActionsSaga"); try { const pageActions: PageAction[][] = yield select(getLayoutOnLoadActions); const layoutOnLoadActionErrors: LayoutOnLoadActionErrors[] = yield select( getLayoutOnLoadIssues, ); const actionCount = flatten(pageActions).length; - + span && setAttributesToSpan(span, { numActions: actionCount }); // when cyclical depedency issue is there, // none of the page load actions would be executed PerformanceTracker.startAsyncTracking( @@ -1267,7 +1291,9 @@ function* executePageLoadActionsSaga() { // Load all sets in parallel // @ts-expect-error: no idea how to type this yield* yield all( - actionSet.map((apiAction) => call(executePageLoadAction, apiAction)), + actionSet.map((apiAction) => + call(executePageLoadAction, apiAction, span), + ), ); } PerformanceTracker.stopAsyncTracking( @@ -1284,6 +1310,7 @@ function* executePageLoadActionsSaga() { kind: "error", }); } + endSpan(span); } interface ExecutePluginActionResponse { @@ -1301,9 +1328,14 @@ function* executePluginActionSaga( paginationField?: PaginationField, params?: Record, isUserInitiated?: boolean, + parentSpan?: OtlpSpan, ) { const actionId = pluginAction.id; - + parentSpan && + setAttributesToSpan(parentSpan, { + actionId, + pluginName: pluginAction?.name, + }); if (pluginAction.confirmBeforeExecute) { const modalPayload = { name: pluginAction.name, @@ -1372,7 +1404,8 @@ function* executePluginActionSaga( let payload = EMPTY_RESPONSE; let response: ActionExecutionResponse; try { - response = yield ActionAPI.executeAction(formData, timeout); + response = yield ActionAPI.executeAction(formData, timeout, parentSpan); + const isError = isErrorResponse(response); PerformanceTracker.stopAsyncTracking( PerformanceTransactionName.EXECUTE_ACTION, @@ -1389,13 +1422,16 @@ function* executePluginActionSaga( ); yield put( - updateActionData([ - { - entityName: pluginAction.name, - dataPath: "data", - data: isError ? undefined : payload.body, - }, - ]), + updateActionData( + [ + { + entityName: pluginAction.name, + dataPath: "data", + data: isError ? undefined : payload.body, + }, + ], + parentSpan, + ), ); // TODO: Plugins are not always fetched before on page load actions are executed. try { @@ -1451,13 +1487,16 @@ function* executePluginActionSaga( }), ); yield put( - updateActionData([ - { - entityName: pluginAction.name, - dataPath: "data", - data: EMPTY_RESPONSE.body, - }, - ]), + updateActionData( + [ + { + entityName: pluginAction.name, + dataPath: "data", + data: EMPTY_RESPONSE.body, + }, + ], + parentSpan, + ), ); if (e instanceof UserCancelledActionExecutionError) { // Case: user cancelled the request of file upload @@ -1587,18 +1626,15 @@ function* softRefreshActionsSaga() { } function* handleUpdateActionData( - action: ReduxAction<{ - entityName: string; - dataPath: string; - data: unknown; - dataPathRef?: string; - }>, + action: ReduxAction, ) { + const { actionDataPayload, parentSpan } = action.payload; yield call( evalWorker.request, EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA, - action.payload, + actionDataPayload, ); + endSpan(parentSpan); } export function* watchPluginActionExecutionSagas() { diff --git a/app/client/src/utils/WorkerUtil.ts b/app/client/src/utils/WorkerUtil.ts index a4596e8288..9d3ecdca13 100644 --- a/app/client/src/utils/WorkerUtil.ts +++ b/app/client/src/utils/WorkerUtil.ts @@ -5,7 +5,7 @@ import { uniqueId } from "lodash"; import log from "loglevel"; import type { TMessage } from "./MessageUtil"; import { MessageType, sendMessage } from "./MessageUtil"; -import { trace } from "@opentelemetry/api"; +import { endSpan, startRootSpan } from "UITelemetry/generateTraces"; /** * Wrap a webworker to provide a synchronous request-response semantic. @@ -165,11 +165,9 @@ export class GracefulWorkerService { this._channels.set(messageId, ch); const mainThreadStartTime = performance.now(); let timeTaken; + const span = startRootSpan(method); try { - const tracer = trace.getTracer("eval"); - const span = tracer?.startSpan(method); - sendMessage.call(this._Worker, { messageType: MessageType.REQUEST, body: { @@ -181,11 +179,12 @@ export class GracefulWorkerService { // The `this._broker` method is listening to events and will pass response to us over this channel. const response = yield take(ch); - span?.end(); timeTaken = response.timeTaken; const { data: responseData } = response; return responseData; } finally { + endSpan(span); + // Log perf of main thread and worker const mainThreadEndTime = performance.now(); const timeTakenOnMainThread = mainThreadEndTime - mainThreadStartTime; diff --git a/app/client/src/workers/Evaluation/handlers/updateActionData.ts b/app/client/src/workers/Evaluation/handlers/updateActionData.ts index 886cd02b81..240bcd2478 100644 --- a/app/client/src/workers/Evaluation/handlers/updateActionData.ts +++ b/app/client/src/workers/Evaluation/handlers/updateActionData.ts @@ -13,6 +13,7 @@ export interface UpdateActionProps { export default function (request: EvalWorkerSyncRequest) { const actionsDataToUpdate: UpdateActionProps[] = request.data; handleActionsDataUpdate(actionsDataToUpdate); + return true; } export function handleActionsDataUpdate(actionsToUpdate: UpdateActionProps[]) { diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml index 3b8ea0bd83..5aa1316f38 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -30,10 +30,39 @@ pom import + + io.opentelemetry + opentelemetry-bom + 1.22.0 + pom + import + + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-exporter-otlp + + + io.opentelemetry + opentelemetry-exporter-otlp-logs + 1.22.0-alpha + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.22.0-alpha +