Table of Contents

ActivityService (Metrics)

Overview

The ActivityService provides functionality to collect critical Linq To DB telemetry data, that can be used to monitor, analyze, and optimize your application. The ActivityService is compatible with the OpenTelemetry specification and System.Diagnostics.DiagnosticSource package.

IActivity interface

The IActivity represents a single activity that can be measured. This is an interface that you need to implement to collect Linq To DB telemetry data.

ActivityBase class

The ActivityBase class provides a basic implementation of the IActivity interface. You do not have to use this class. However, it can help you to avoid incompatibility issues in the future if the IActivity interface is extended.

ActivityService class

The ActivityService class provides a simple API to register factory methods that create IActivity instances or null for provided ActivityID event. You can register multiple factory methods.

ActivityID

The ActivityID is a unique identifier of the LinqToDB activity. It is used to identify the activity in the metrics data.

Linq To DB contains a large set of telemetry collection points that can be used to collect data. Each collection point has a unique ActivityID identifier.

Configuration.TraceMaterializationActivity

The Configuration.TraceMaterializationActivity property can be used to enable or disable tracing of object materialization activity. It can significantly break performance if tracing consumer performs slow, so it is disabled by default.

Example

The following example shows how to use the ActivityService and OpenTelemetry to collect Linq To DB telemetry data.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

using LinqToDB;
using LinqToDB.Data;
using LinqToDB.Mapping;
using LinqToDB.Tools;

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OpenTelemetryExample
{
    static class Program
    {
        static async Task Main()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.LinqToDB")
                .AddConsoleExporter()
                .Build();

            ActivitySource.AddActivityListener(_activityListener);

            // Register the factory method that creates LinqToDBActivity instances.
            //
            ActivityService.AddFactory(LinqToDBActivity.Create);

            {
                await using var db = new DataConnection(new DataOptions().UseSQLiteMicrosoft("Data Source=Northwind.MS.sqlite"));

                await db.CreateTableAsync<Customer>(tableOptions:TableOptions.CheckExistence);

                var count = await db.GetTable<Customer>().CountAsync();

                Console.WriteLine($"Count is {count}");
            }
        }

        static readonly ActivitySource   _activitySource   = new("Sample.LinqToDB");
        static readonly ActivityListener _activityListener = new()
        {
            ShouldListenTo      = _ => true,
            SampleUsingParentId = (ref ActivityCreationOptions<string>          _) => ActivitySamplingResult.AllData,
            Sample              = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData
        };

        // This class is used to collect LinqToDB telemetry data.
        //
        sealed class LinqToDBActivity : IActivity
        {
            readonly Activity _activity;

            LinqToDBActivity(Activity activity)
            {
                _activity = activity;
            }

            public void Dispose()
            {
                _activity.Dispose();
            }

            public ValueTask DisposeAsync()
            {
                Dispose();
                return default;
            }

            // This method is called by the ActivityService to create an instance of the LinqToDBActivity class.
            //
            public static IActivity? Create(ActivityID id)
            {
                var a = _activitySource.StartActivity(id.ToString());
                return a == null ? null : new LinqToDBActivity(a);
            }
        }

        [Table(Name="Customers")]
        public sealed class Customer
        {
            [PrimaryKey]      public string CustomerID  = null!;
            [Column, NotNull] public string CompanyName = null!;
        }
    }
}

Output:

Activity.TraceId:            4ee29f995cd25bba583def846a2aa220
Activity.SpanId:             cd0647218f959924
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       268635637f43fbd1
Activity.ActivitySourceName: Sample.LinqToDB
Activity.DisplayName:        FinalizeQuery
Activity.Kind:               Internal
Activity.StartTime:          2023-12-28T07:14:45.0200111Z
Activity.Duration:           00:00:00.0644992
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 61b68727-d6bd-43a1-a426-c206851b6bdb
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.6.0

Activity.TraceId:            4ee29f995cd25bba583def846a2aa220
Activity.SpanId:             8f5842b0c199c668
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       93fb3a253e06df95
Activity.ActivitySourceName: Sample.LinqToDB
Activity.DisplayName:        BuildSql
Activity.Kind:               Internal
Activity.StartTime:          2023-12-28T07:14:45.4280856Z
Activity.Duration:           00:00:00.0101335
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 61b68727-d6bd-43a1-a426-c206851b6bdb
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.6.0


...


Activity.TraceId:            45a597dbc0d5b354e18d371b02a101c6
Activity.SpanId:             38cfd4c41f55583b
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: Sample.LinqToDB
Activity.DisplayName:        ExecuteElementAsync
Activity.Kind:               Internal
Activity.StartTime:          2023-12-28T07:14:46.2702044Z
Activity.Duration:           00:00:00.1555957
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 61b68727-d6bd-43a1-a426-c206851b6bdb
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.6.0

Count is 91

Tools

The LinqToDB.Tools package contains two activity factories ActivityHierarchy and ActivityStatistics that can be used to generate Linq To DB activity reports. An exsample of how to use these factories can be found here.

The following is an output of this example:

Count is 91
LinqToDB call hierarchy:

CreateTable
  FinalizeQuery
  Execute NonQuery
    BuildSql
    Connection Open
    Command ExecuteNonQuery

IQueryProvider.Execute<T>
  GetQuery
    Find
      Expose
      Find
    Create
      Build
        BuildSequence
          CanBuild (22)
          Build
            BuildSequence
              CanBuild
              Build
        ReorderBuilders
        BuildQuery
          FinalizeQuery
  Execute Element
    BuildSql
    Command ExecuteReader
    Materialization

Connection Dispose


LinqToDB statistics:

Count : 77
+----------------------------------------+------------------+-----------+------------------+---------+
| Name                                   | Elapsed          | CallCount | TimePerCall      | Percent |
+----------------------------------------+------------------+-----------+------------------+---------+
| IQueryProvider.Execute<T>              | 00:00:00.1279609 |         1 | 00:00:00.1279609 |  35.85% |
| IQueryProvider.Execute                 | 00:00:00         |         0 | 00:00:00         |         |
| IQueryProvider.GetEnumerator<T>        | 00:00:00         |         0 | 00:00:00         |         |
| IQueryProvider.GetEnumerator           | 00:00:00         |         0 | 00:00:00         |         |
|   GetQuery                             | 00:00:00.0748878 |         1 | 00:00:00.0748878 |  20.98% |
|     Find                               | 00:00:00.0084079 |         1 | 00:00:00.0084079 |   2.36% |
|       Expose                           | 00:00:00.0081031 |         1 | 00:00:00.0081031 |   2.27% |
|       Find                             | 00:00:00.0002990 |         1 | 00:00:00.0002990 |   0.08% |
|     Create                             | 00:00:00.0664771 |         1 | 00:00:00.0664771 |  18.63% |
|       Build                            | 00:00:00.0450327 |         1 | 00:00:00.0450327 |  12.62% |
|         BuildSequence                  | 00:00:00.0083066 |         2 | 00:00:00.0041533 |   2.33% |
|           CanBuild                     | 00:00:00.0000421 |        23 | 00:00:00.0000018 |   0.01% |
|           Build                        | 00:00:00.0080319 |         2 | 00:00:00.0040159 |   2.25% |
|         ReorderBuilders                | 00:00:00.0004451 |         1 | 00:00:00.0004451 |   0.12% |
|         BuildQuery                     | 00:00:00.0382906 |         1 | 00:00:00.0382906 |  10.73% |
|           FinalizeQuery                | 00:00:00.0590411 |         2 | 00:00:00.0295205 |  16.54% |
|   GetIEnumerable                       | 00:00:00         |         0 | 00:00:00         |         |
| Execute                                | 00:00:00.2289324 |         3 | 00:00:00.0763108 |  64.15% |
|   Execute Query                        | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Query Async                  | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Element                      | 00:00:00.0513571 |         1 | 00:00:00.0513571 |  14.39% |
|   Execute Element Async                | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Scalar                       | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Scalar Async                 | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Scalar 2                     | 00:00:00         |         0 | 00:00:00         |         |
|   Execute Scalar 2 Async               | 00:00:00         |         0 | 00:00:00         |         |
|   Execute NonQuery                     | 00:00:00.0511693 |         1 | 00:00:00.0511693 |  14.34% |
|   Execute NonQuery Async               | 00:00:00         |         0 | 00:00:00         |         |
|   Execute NonQuery 2                   | 00:00:00         |         0 | 00:00:00         |         |
|   Execute NonQuery 2 Async             | 00:00:00         |         0 | 00:00:00         |         |
|   CreateTable                          | 00:00:00.1264060 |         1 | 00:00:00.1264060 |  35.42% |
|   CreateTable Async                    | 00:00:00         |         0 | 00:00:00         |         |
|   DropTable                            | 00:00:00         |         0 | 00:00:00         |         |
|   DropTable Async                      | 00:00:00         |         0 | 00:00:00         |         |
|   Delete Object                        | 00:00:00         |         0 | 00:00:00         |         |
|   Delete Object Async                  | 00:00:00         |         0 | 00:00:00         |         |
|   Insert Object                        | 00:00:00         |         0 | 00:00:00         |         |
|   Insert Object Async                  | 00:00:00         |         0 | 00:00:00         |         |
|   InsertOrReplace Object               | 00:00:00         |         0 | 00:00:00         |         |
|   InsertOrReplace Object Async         | 00:00:00         |         0 | 00:00:00         |         |
|   InsertWithIdentity Object            | 00:00:00         |         0 | 00:00:00         |         |
|   InsertWithIdentity Object Async      | 00:00:00         |         0 | 00:00:00         |         |
|   Update Object                        | 00:00:00         |         0 | 00:00:00         |         |
|   Update Object Async                  | 00:00:00         |         0 | 00:00:00         |         |
|   BulkCopy                             | 00:00:00         |         0 | 00:00:00         |         |
|   BulkCopy Async                       | 00:00:00         |         0 | 00:00:00         |         |
|     BuildSql                           | 00:00:00.0383149 |         2 | 00:00:00.0191574 |  10.74% |
|   SQL Execute                          | 00:00:00         |         0 | 00:00:00         |         |
|   SQL Execute<T>                       | 00:00:00         |         0 | 00:00:00         |         |
|   SQL ExecuteCustom                    | 00:00:00         |         0 | 00:00:00         |         |
|   SQL ExecuteAsync                     | 00:00:00         |         0 | 00:00:00         |         |
|   SQL ExecuteAsync<T>                  | 00:00:00         |         0 | 00:00:00         |         |
|     ADO.NET                            | 00:00:00.0100436 |         4 | 00:00:00.0025109 |   2.81% |
|       Connection Open                  | 00:00:00.0041363 |         1 | 00:00:00.0041363 |   1.16% |
|       Connection OpenAsync             | 00:00:00         |         0 | 00:00:00         |         |
|       Connection Close                 | 00:00:00         |         0 | 00:00:00         |         |
|       Connection CloseAsync            | 00:00:00         |         0 | 00:00:00         |         |
|       Connection Dispose               | 00:00:00.0009168 |         1 | 00:00:00.0009168 |   0.26% |
|       Connection DisposeAsync          | 00:00:00         |         0 | 00:00:00         |         |
|       Connection BeginTransaction      | 00:00:00         |         0 | 00:00:00         |         |
|       Connection BeginTransactionAsync | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction Commit               | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction CommitAsync          | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction Rollback             | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction RollbackAsync        | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction Dispose              | 00:00:00         |         0 | 00:00:00         |         |
|       Transaction DisposeAsync         | 00:00:00         |         0 | 00:00:00         |         |
|       Command ExecuteScalar            | 00:00:00         |         0 | 00:00:00         |         |
|       Command ExecuteScalarAsync       | 00:00:00         |         0 | 00:00:00         |         |
|       Command ExecuteReader            | 00:00:00.0003941 |         1 | 00:00:00.0003941 |   0.11% |
|       Command ExecuteReaderAsync       | 00:00:00         |         0 | 00:00:00         |         |
|       Command ExecuteNonQuery          | 00:00:00.0045964 |         1 | 00:00:00.0045964 |   1.29% |
|       Command ExecuteNonQueryAsync     | 00:00:00         |         0 | 00:00:00         |         |
|     OnTraceInternal                    | 00:00:00         |         0 | 00:00:00         |         |
|     Materialization                    | 00:00:00.0005660 |         1 | 00:00:00.0005660 |   0.16% |
|   GetSqlText                           | 00:00:00         |         0 | 00:00:00         |         |
| Total                                  | 00:00:00.3568933 |         4 | 00:00:00.0892233 | 100.00% |
+----------------------------------------+------------------+-----------+------------------+---------+