diff --git a/packages/core/src/measurements/context.ts b/packages/core/src/measurements/context.ts index 5bcb4af6ca..ac95bf4c42 100644 --- a/packages/core/src/measurements/context.ts +++ b/packages/core/src/measurements/context.ts @@ -1,7 +1,16 @@ // Basic performance metrics suite. +import { generateId } from '../utils' import { childMetrics, measure, newMetrics } from './metrics' -import { FullParamsType, MeasureContext, MeasureLogger, Metrics, ParamsType } from './types' +import { + FullParamsType, + MeasureContext, + MeasureLogger, + Metrics, + ParamsType, + type OperationLog, + type OperationLogEntry +} from './types' /** * @public @@ -98,21 +107,29 @@ export class MeasureMetricsContext implements MeasureContext { ) } - async with( + with( name: string, params: ParamsType, op: (ctx: MeasureContext) => T | Promise, fullParams?: ParamsType | (() => FullParamsType) ): Promise { const c = this.newChild(name, params, fullParams, this.logger) + let needFinally = true try { - let value = op(c) - if (value instanceof Promise) { - value = await value + const value = op(c) + if (value != null && value instanceof Promise) { + needFinally = false + void value.finally(() => { + c.end() + }) + return value + } else { + return Promise.resolve(value) } - return value } finally { - c.end() + if (needFinally) { + c.end() + } } } @@ -176,3 +193,96 @@ export function withContext (name: string, params: ParamsType = {}): any { return descriptor } } + +let operationProfiling = false + +export function setOperationLogProfiling (value: boolean): void { + operationProfiling = value +} + +export function registerOperationLog (ctx: MeasureContext): { opLogMetrics?: Metrics, op?: OperationLog } { + if (!operationProfiling) { + return {} + } + const op: OperationLog = { start: Date.now(), ops: [], end: -1 } + let opLogMetrics: Metrics | undefined + ctx.id = generateId() + if (ctx.metrics !== undefined) { + if (ctx.metrics.opLog === undefined) { + ctx.metrics.opLog = {} + } + ctx.metrics.opLog[ctx.id] = op + opLogMetrics = ctx.metrics + } + return { opLogMetrics, op } +} + +export function updateOperationLog (opLogMetrics: Metrics | undefined, op: OperationLog | undefined): void { + if (!operationProfiling) { + return + } + if (op !== undefined) { + op.end = Date.now() + } + // We should keep only longest one entry + if (opLogMetrics?.opLog !== undefined) { + const entries = Object.entries(opLogMetrics.opLog) + + const incomplete = entries.filter((it) => it[1].end === -1) + const complete = entries.filter((it) => it[1].end !== -1) + complete.sort((a, b) => a[1].start - b[1].start) + if (complete.length > 30) { + complete.splice(0, complete.length - 30) + } + + opLogMetrics.opLog = Object.fromEntries(incomplete.concat(complete)) + } +} + +export function addOperation ( + ctx: MeasureContext, + name: string, + params: ParamsType, + op: (ctx: MeasureContext) => Promise, + fullParams?: FullParamsType +): Promise { + if (!operationProfiling) { + return op(ctx) + } + let opEntry: OperationLogEntry | undefined + + let p: MeasureContext | undefined = ctx + let opLogMetrics: Metrics | undefined + let id: string | undefined + + while (p !== undefined) { + if (p.metrics?.opLog !== undefined) { + opLogMetrics = p.metrics + } + if (id === undefined && p.id !== undefined) { + id = p.id + } + p = p.parent + } + const opLog = id !== undefined ? opLogMetrics?.opLog?.[id] : undefined + + if (opLog !== undefined) { + opEntry = { + op: name, + start: Date.now(), + params: {}, + end: -1 + } + } + const result = op(ctx) + if (opEntry !== undefined && opLog !== undefined) { + void result.finally(() => { + if (opEntry !== undefined && opLog !== undefined) { + opEntry.end = Date.now() + opEntry.params = { ...params, ...(typeof fullParams === 'function' ? fullParams() : fullParams) } + opLog.ops.push(opEntry) + } + }) + } + return result +} diff --git a/packages/core/src/measurements/metrics.ts b/packages/core/src/measurements/metrics.ts index d3a9ae014d..e60cc2e214 100644 --- a/packages/core/src/measurements/metrics.ts +++ b/packages/core/src/measurements/metrics.ts @@ -161,7 +161,8 @@ export function metricsAggregate (m: Metrics, limit: number = -1): Metrics { params: m.params, value: sumVal, topResult: m.topResult, - namedParams: m.namedParams + namedParams: m.namedParams, + opLog: m.opLog } } diff --git a/packages/core/src/measurements/types.ts b/packages/core/src/measurements/types.ts index e056ba1009..7c6940a96d 100644 --- a/packages/core/src/measurements/types.ts +++ b/packages/core/src/measurements/types.ts @@ -25,6 +25,18 @@ export interface MetricsData { }[] } +export interface OperationLogEntry { + op: string + params: ParamsType + start: number + end: number +} +export interface OperationLog { + ops: OperationLogEntry[] + start: number + end: number +} + /** * @public */ @@ -32,6 +44,8 @@ export interface Metrics extends MetricsData { namedParams: ParamsType params: Record> measurements: Record + + opLog?: Record } /** @@ -53,9 +67,12 @@ export interface MeasureLogger { * @public */ export interface MeasureContext { + id?: string // Create a child metrics context newChild: (name: string, params: ParamsType, fullParams?: FullParamsType, logger?: MeasureLogger) => MeasureContext + metrics?: Metrics + with: ( name: string, params: ParamsType, diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index feea3392fb..79f8885297 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -48,6 +48,9 @@ export interface SessionOperationContext { op: (ctx: SessionOperationContext) => T | Promise, fullParams?: FullParamsType ) => Promise + + contextCache: Map + removedMap: Map, Doc> } /** diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts index 5232215221..9291058254 100644 --- a/packages/query/src/index.ts +++ b/packages/query/src/index.ts @@ -312,6 +312,12 @@ export class LiveQuery implements WithTx, Client { return this.clone(q.result)[0] as WithLookup } + private optionsCompare (opt1?: FindOptions, opt2?: FindOptions): boolean { + const { ctx: _1, ..._opt1 } = (opt1 ?? {}) as any + const { ctx: _2, ..._opt2 } = (opt2 ?? {}) as any + return deepEqual(_opt1, _opt2) + } + private findQuery( _class: Ref>, query: DocumentQuery, @@ -319,8 +325,9 @@ export class LiveQuery implements WithTx, Client { ): Query | undefined { const queries = this.queries.get(_class) if (queries === undefined) return + for (const q of queries) { - if (!deepEqual(query, q.query) || !deepEqual(options, q.options)) continue + if (!deepEqual(query, q.query) || !this.optionsCompare(options, q.options)) continue return q } } diff --git a/plugins/workbench-resources/src/components/statistics/MetricsInfo.svelte b/plugins/workbench-resources/src/components/statistics/MetricsInfo.svelte index 379cdc5c2c..714b0fe849 100644 --- a/plugins/workbench-resources/src/components/statistics/MetricsInfo.svelte +++ b/plugins/workbench-resources/src/components/statistics/MetricsInfo.svelte @@ -40,7 +40,7 @@