diff --git a/app/client/src/UITelemetry/generateTraces.ts b/app/client/src/UITelemetry/generateTraces.ts index b7e358f6a32b..08b977a62dea 100644 --- a/app/client/src/UITelemetry/generateTraces.ts +++ b/app/client/src/UITelemetry/generateTraces.ts @@ -14,6 +14,9 @@ import { matchBuilderPath, matchViewerPath } from "constants/routes"; const GENERATOR_TRACE = "generator-tracer"; +export type OtlpSpan = Span; +export type SpanAttributes = Attributes; + const getCommonTelemetryAttributes = () => { const pathname = window.location.pathname; const isEditorUrl = matchBuilderPath(pathname); @@ -33,16 +36,13 @@ const getCommonTelemetryAttributes = () => { export function startRootSpan( spanName: string, - spanAttributes: Attributes = {}, + spanAttributes: SpanAttributes = {}, startTime?: TimeInput, ) { const tracer = trace.getTracer(GENERATOR_TRACE); - if (!spanName) { - return; - } const commonAttributes = getCommonTelemetryAttributes(); - return tracer?.startSpan(spanName, { + return tracer.startSpan(spanName, { kind: SpanKind.CLIENT, attributes: { ...commonAttributes, @@ -56,15 +56,10 @@ export const generateContext = (span: Span) => { }; export function startNestedSpan( spanName: string, - parentSpan?: Span, - spanAttributes: Attributes = {}, + parentSpan: Span, + spanAttributes: SpanAttributes = {}, startTime?: TimeInput, ) { - 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); @@ -81,14 +76,14 @@ 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: Attributes) { - if (!span) { - return; - } +export function setAttributesToSpan( + span: Span, + spanAttributes: SpanAttributes, +) { span.setAttributes(spanAttributes); } @@ -96,5 +91,3 @@ 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/UITelemetry/generateWebWorkerTraces.ts b/app/client/src/UITelemetry/generateWebWorkerTraces.ts index 32c8ae312365..a9133cb32fee 100644 --- a/app/client/src/UITelemetry/generateWebWorkerTraces.ts +++ b/app/client/src/UITelemetry/generateWebWorkerTraces.ts @@ -1,8 +1,9 @@ +import type { OtlpSpan, SpanAttributes } from "./generateTraces"; import { startNestedSpan } from "./generateTraces"; -import type { TimeInput, Attributes, Span } from "@opentelemetry/api"; +import type { TimeInput } from "@opentelemetry/api"; export interface WebworkerSpanData { - attributes: Attributes; + attributes: SpanAttributes; spanName: string; startTime: TimeInput; endTime: TimeInput; @@ -13,7 +14,7 @@ export interface WebworkerSpanData { //to regular otlp telemetry data and subsequently exported to our telemetry collector export const newWebWorkerSpanData = ( spanName: string, - attributes: Attributes, + attributes: SpanAttributes, ): WebworkerSpanData => { return { attributes, @@ -29,8 +30,8 @@ const addEndTimeForWebWorkerSpanData = (span: WebworkerSpanData) => { export const profileFn = ( spanName: string, - attributes: Attributes = {}, - allSpans: Record, + attributes: SpanAttributes = {}, + allSpans: Record, fn: (...args: any[]) => any, ) => { const span = newWebWorkerSpanData(spanName, attributes); @@ -42,7 +43,7 @@ export const profileFn = ( //convert webworker spans to OTLP spans export const convertWebworkerSpansToRegularSpans = ( - parentSpan: Span, + parentSpan: OtlpSpan, allSpans: Record = {}, ) => { Object.values(allSpans) @@ -53,3 +54,14 @@ export const convertWebworkerSpansToRegularSpans = ( span?.end(endTime); }); }; + +export const filterSpanData = ( + spanData: Record, +): Record => { + return Object.keys(spanData) + .filter((key) => !key.startsWith("__")) + .reduce>((obj, key) => { + obj[key] = spanData[key] as WebworkerSpanData; + return obj; + }, {}); +}; diff --git a/app/client/src/ce/workers/common/types.ts b/app/client/src/ce/workers/common/types.ts index e931d1c291dc..7fdccaa1335c 100644 --- a/app/client/src/ce/workers/common/types.ts +++ b/app/client/src/ce/workers/common/types.ts @@ -1,4 +1,5 @@ import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; +import type { SpanAttributes } from "UITelemetry/generateTraces"; export enum AppsmithWorkers { LINT_WORKER = "LINT_WORKER", @@ -12,5 +13,5 @@ export enum WorkerErrorTypes { export interface WorkerRequest { method: TActions; data: TData; - webworkerTelemetry: Record; + webworkerTelemetry: Record; } diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index 8da86d8cc328..f474374a598f 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -1662,7 +1662,9 @@ function* handleUpdateActionData( EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA, actionDataPayload, ); - endSpan(parentSpan); + if (parentSpan) { + endSpan(parentSpan); + } } export function* watchPluginActionExecutionSagas() { diff --git a/app/client/src/utils/WorkerUtil.ts b/app/client/src/utils/WorkerUtil.ts index a431b12c518a..b9033f6a2993 100644 --- a/app/client/src/utils/WorkerUtil.ts +++ b/app/client/src/utils/WorkerUtil.ts @@ -5,11 +5,16 @@ import { uniqueId } from "lodash"; import log from "loglevel"; import type { TMessage } from "./MessageUtil"; import { MessageType, sendMessage } from "./MessageUtil"; -import type { OtlpSpan } from "UITelemetry/generateTraces"; -import { endSpan, startRootSpan } from "UITelemetry/generateTraces"; +import type { OtlpSpan, SpanAttributes } from "UITelemetry/generateTraces"; +import { + endSpan, + setAttributesToSpan, + startRootSpan, +} from "UITelemetry/generateTraces"; import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; import { convertWebworkerSpansToRegularSpans, + filterSpanData, newWebWorkerSpanData, } from "UITelemetry/generateWebWorkerTraces"; @@ -156,33 +161,35 @@ export class GracefulWorkerService { startTime, webworkerTelemetry, }: { - webworkerTelemetry: Record; + webworkerTelemetry: + | Record + | undefined; rootSpan: OtlpSpan | undefined; method: string; startTime: number; endTime: number; }) { - const webworkerTelemetryResponse = webworkerTelemetry as Record< - string, - WebworkerSpanData - >; + if (!webworkerTelemetry) { + return; + } - if (webworkerTelemetryResponse) { - const { transferDataToMainThread } = webworkerTelemetryResponse; - if (transferDataToMainThread) { - transferDataToMainThread.endTime = Date.now(); - } - /// Add the completeWebworkerComputation span to the root span - webworkerTelemetryResponse["completeWebworkerComputation"] = { - startTime, - endTime, - attributes: {}, - spanName: "completeWebworkerComputation", - }; + const { transferDataToMainThread } = webworkerTelemetry; + if (transferDataToMainThread) { + transferDataToMainThread.endTime = Date.now(); } + /// Add the completeWebworkerComputation span to the root span + webworkerTelemetry["completeWebworkerComputation"] = { + startTime, + endTime, + attributes: {}, + spanName: "completeWebworkerComputation", + }; //we are attaching the child spans to the root span over here rootSpan && - convertWebworkerSpansToRegularSpans(rootSpan, webworkerTelemetryResponse); + convertWebworkerSpansToRegularSpans( + rootSpan, + filterSpanData(webworkerTelemetry), + ); //genereate separate completeWebworkerComputationRoot root span // this span does not contain any child spans, it just captures the webworker computation alone @@ -218,11 +225,15 @@ export class GracefulWorkerService { let timeTaken; const rootSpan = startRootSpan(method); - const webworkerTelemetryData: Record = { + const webworkerTelemetryData: Record< + string, + WebworkerSpanData | SpanAttributes + > = { transferDataToWorkerThread: newWebWorkerSpanData( "transferDataToWorkerThread", {}, ), + __spanAttributes: {}, }; const body = { @@ -231,6 +242,11 @@ export class GracefulWorkerService { webworkerTelemetry: webworkerTelemetryData, }; + let webworkerTelemetryResponse: Record< + string, + WebworkerSpanData | SpanAttributes + > = {}; + try { sendMessage.call(this._Worker, { messageType: MessageType.REQUEST, @@ -241,9 +257,10 @@ 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); const { data, endTime, startTime } = response; - const { webworkerTelemetry } = data; + webworkerTelemetryResponse = data.webworkerTelemetry; + this.addChildSpansToRootSpan({ - webworkerTelemetry, + webworkerTelemetry: webworkerTelemetryResponse, rootSpan, method, startTime, @@ -268,6 +285,14 @@ export class GracefulWorkerService { log.debug(` Worker ${method} took ${timeTaken}ms`); log.debug(` Transfer ${method} took ${transferTime}ms`); } + + if (webworkerTelemetryResponse) { + setAttributesToSpan( + rootSpan, + webworkerTelemetryResponse.__spanAttributes as SpanAttributes, + ); + } + endSpan(rootSpan); // Cleanup ch.close(); diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index 46160713e9d6..4c49a6c14806 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -34,6 +34,7 @@ import { profileFn, newWebWorkerSpanData, } from "UITelemetry/generateWebWorkerTraces"; +import type { SpanAttributes } from "UITelemetry/generateTraces"; import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; @@ -85,6 +86,9 @@ export function evalTree(request: EvalWorkerSyncRequest) { let isNewTree = false; try { + (webworkerTelemetry.__spanAttributes as SpanAttributes)["firstEvaluation"] = + !dataTreeEvaluator; + if (!dataTreeEvaluator) { isCreateFirstTree = true; replayMap = replayMap || {}; diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index c93e566dca32..e151e520ebe5 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -16,6 +16,7 @@ import type { WorkerRequest } from "@appsmith/workers/common/types"; import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils"; import type { APP_MODE } from "entities/App"; import type { WebworkerSpanData } from "UITelemetry/generateWebWorkerTraces"; +import type { SpanAttributes } from "UITelemetry/generateTraces"; import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils"; export type EvalWorkerSyncRequest = WorkerRequest< @@ -59,6 +60,6 @@ export interface EvalTreeResponseData { isNewWidgetAdded: boolean; undefinedEvalValuesMap: Record; jsVarsCreatedEvent?: { path: string; type: string }[]; - webworkerTelemetry?: Record; + webworkerTelemetry?: Record; updates: string; } diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index fe938bb0d442..e658728d9e07 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -137,6 +137,7 @@ import { profileFn, type WebworkerSpanData, } from "UITelemetry/generateWebWorkerTraces"; +import type { SpanAttributes } from "UITelemetry/generateTraces"; import type { AffectedJSObjects } from "sagas/EvaluationsSagaUtils"; import generateOverrideContext from "@appsmith/workers/Evaluation/generateOverrideContext"; @@ -233,7 +234,7 @@ export default class DataTreeEvaluator { setupFirstTree( unEvalTree: any, configTree: ConfigTree, - webworkerTelemetry: Record = {}, + webworkerTelemetry: Record = {}, ): { jsUpdates: Record; evalOrder: string[]; @@ -489,7 +490,7 @@ export default class DataTreeEvaluator { setupUpdateTree( unEvalTree: any, configTree: ConfigTree, - webworkerTelemetry: Record = {}, + webworkerTelemetry: Record = {}, affectedJSObjects: AffectedJSObjects = { isAllAffected: false, ids: [] }, ): { unEvalUpdates: DataTreeDiff[];