Diagnostics API

The presentation manager provides a way to gather diagnostics (performance metrics, logs, etc.) on a per-request basis. The APIs that should be used to get the diagnostics depend on the requirements and whether diagnostics are needed on the backend or the frontend.

In all cases getting diagnostics consists of two pieces:

  • Options - what kind of diagnostics are requested.
  • Handler - a function that accepts the diagnostics after the request is fulfilled.

Getting request diagnostics on the frontend

On the frontend side diagnostics can be requested on a per request basis, by supplying diagnostics options through diagnostics attribute to PresentationManager requests. Resulting diagnostics are then passed to the given handler.

await Presentation.presentation.getElementProperties({
  imodel,
  elementId,
  diagnostics: {
    // request version of the backend that handles this request
    backendVersion: true,
    // supply a callback that'll receive the diagnostics
    handler: (diagnostics: ClientDiagnostics) => {
      // log the backend version
      log(`Backend version: ${diagnostics.backendVersion}`);
    },
  },
});

Getting request diagnostics on the backend

There are two ways to retrieve diagnostics on the backend - through request parameters or through PresentationManager.

Getting diagnostics on a per-request basis

To get diagnostics on a per request basis, diagnostics options can be supplied through diagnostics attribute to PresentationManager requests. Resulting diagnostics are then passed to the given handler.

await Presentation.getManager().getElementProperties({
  imodel,
  elementId,
  diagnostics: {
    // request performance metrics
    perf: true,
    // supply a callback that'll receive the diagnostics
    handler: (diagnostics: Diagnostics) => {
      // log duration of each diagnostics scope
      diagnostics.logs && diagnostics.logs.forEach((entry) => {
        log(`${entry.scope}: ${entry.duration}`);
      });
    },
  },
});

Getting diagnostics for all requests

It's also possible to set up PresentationManager to retrieve diagnostics of every request made through it. This can be done by supplying diagnostics options, including the handler, when calling Presentation.initialize.

Presentation.initialize({
  diagnostics: {
    // request performance metrics
    perf: true,
    // supply a method to capture current request context
    requestContextSupplier: getCurrentActivityId,
    // supply a callback that'll receive the diagnostics and request context supplied by `requestContextSupplier`
    handler: (diagnostics: Diagnostics, currentActivityId?: string) => {
      // log duration of each diagnostics scope
      diagnostics.logs && diagnostics.logs.forEach((entry) => {
        log(`[${currentActivityId}] ${entry.scope}: ${entry.duration}`);
      });
    },
  },
});

// diagnostics of the following requests are captured by the handler supplied to `Presentation.initialize` call
await Presentation.getManager().getElementProperties({ imodel, elementId: id1 });
await Presentation.getManager().getElementProperties({ imodel, elementId: id2 });

This approach also allows the backend to use request diagnostics for telemetry and logging, e.g. in combination with OpenTelemetry. See the Diagnostics and OpenTelemetry section for more details.

Diagnostics and OpenTelemetry

OpenTelemetry is a vendor-neutral standard to collect telemetry data - metrics, logs and traces. The @itwin/presentation-opentelemetry package provides APIs to easily export presentation diagnostics as OpenTelemetry data, which makes collecting Presentation-related telemetry much easier.

The first part is to set up OpenTelemetry tracing, which could look like this:

import { Resource } from "@opentelemetry/resources";
import * as opentelemetry from "@opentelemetry/sdk-node";
import { ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

// configure the OpenTelemetry data exporting to the console
const telemetry = new opentelemetry.NodeSDK({
  traceExporter: new ConsoleSpanExporter(),
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: "presentation-test-app",
  }),
});
telemetry.start();
process.on("SIGTERM", () => {
  telemetry.shutdown().finally(() => process.exit(0));
});

See OpenTelemetry for more details on this part.

The second part is to set up presentation diagnostics data exporting as OpenTelemetry traces:

import { exportDiagnostics } from "@itwin/presentation-opentelemetry";
import { context } from "@opentelemetry/api";

const presentationBackendProps: PresentationProps = {};
presentationBackendProps.diagnostics = {
  // requesting performance metrics
  perf: {
    // only capture spans that take more than 50 ms
    minimumDuration: 50,
  },
  // a function to capture current context - it's passed to the `handler` function as the second argument
  requestContextSupplier: () => context.active(),
  // the handler function is called after every request made through the `Presentation` APIs
  handler: (diagnostics, ctx) => {
    // call `exportDiagnostics` from the `@itwin/presentation-opentelemetry` package to parse diagnostics
    // data and export it through OpenTelemetry
    exportDiagnostics(diagnostics, ctx);
  },
};
Presentation.initialize(presentationBackendProps);

Last Updated: 17 April, 2023