Skip to content

Commit

Permalink
feat: or-1834-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
QuintenGreenstack authored and koenmetsu committed Dec 18, 2023
1 parent 1708f76 commit 5e997c1
Show file tree
Hide file tree
Showing 20 changed files with 465 additions and 227 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ wwwroot/
otel-collector-config.yaml
src/*/Internal

otel-collector-config.yaml

.vs/
.vscode/
.fake/
Expand Down
2 changes: 1 addition & 1 deletion AssociationRegistry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solutio
definition_of_done.md = definition_of_done.md
readme.md = readme.md
CONTRIBUTING.md = CONTRIBUTING.md
otel-collector-config.yaml = otel-collector-config.yaml
regen-marten.sh = regen-marten.sh
otel-collector-config.yaml = otel-collector-config.yaml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C812C887-6495-405A-8B99-4F686D243126}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public static class StoreOptionsExtensions
public static void AddPostgresProjections(this StoreOptions source, IServiceProvider serviceProvider)
{
source.Projections.Add(new VerenigingenPerInszProjection(), ProjectionLifecycle.Async);
source.Projections.AsyncListeners.Add(new HighWatermarkListener(serviceProvider.GetRequiredService<Instrumentation>()));
source.Projections.AsyncListeners.Add(new ProjectionStateListener(serviceProvider.GetRequiredService<AcmInstrumentation>()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@ namespace AssociationRegistry.Acm.Api.Infrastructure.Metrics;
/// ActivitySource and Instruments. This avoids possible type collisions
/// with other components in the DI container.
/// </summary>
public class Instrumentation : IInstrumentation, IDisposable
public class AcmInstrumentation : IInstrumentation, IDisposable
{
public string ActivitySourceName => "AssociationRegistry";
public string MeterName => "AcmProjections";
private readonly Meter meter;

public Instrumentation()
public AcmInstrumentation()
{
var version = typeof(Instrumentation).Assembly.GetName().Version?.ToString();
var version = typeof(AcmInstrumentation).Assembly.GetName().Version?.ToString();
ActivitySource = new ActivitySource(ActivitySourceName, version);
meter = new Meter(MeterName, version);

VerenigingPerInszHistogram =
meter.CreateHistogram<long>(name: "ar.p.verenigingPerInsz.h", unit: "events", description: "vereniging per insz projection");
_verenigingPerInszGauge =
meter.CreateObservableGauge(name: "ar.acm.p.verenigingPerInsz.g", unit: "events",
description: "vereniging per insz projection",
observeValue: () => VerenigingPerInszEventValue);
}

public ActivitySource ActivitySource { get; }
public Histogram<long> VerenigingPerInszHistogram { get; }
private ObservableGauge<long> _verenigingPerInszGauge;

public long VerenigingPerInszEventValue = 0;

public void Dispose()
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace AssociationRegistry.Acm.Api.Infrastructure.Metrics;

using Marten;
using Marten.Events.Daemon;
using Marten.Internal.Operations;
using Marten.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Projections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

public class ProjectionStateListener : DocumentSessionListenerBase
{
private readonly AcmInstrumentation _acmInstrumentation;

public ProjectionStateListener(AcmInstrumentation acmInstrumentation)
{
_acmInstrumentation = acmInstrumentation;
}

public override Task AfterCommitAsync(IDocumentSession session, IChangeSet commit, CancellationToken token)
{
if (commit is not IUnitOfWork uow) return Task.CompletedTask;
var operations = uow.OperationsFor<Marten.Events.IEvent>();

foreach (var operation in operations)
{
var range = GetEventRange(operation);

if (range is null) continue;

if (range.ShardName.ProjectionName ==
typeof(VerenigingenPerInszProjection).FullName)
_acmInstrumentation.VerenigingPerInszEventValue =range.SequenceCeiling;
}

return Task.CompletedTask;
}

private static EventRange? GetEventRange(IStorageOperation opperation)
{
return opperation.GetType().Name switch
{
"InsertProjectionProgress" => opperation.GetType().GetField("_progress", BindingFlags.NonPublic|BindingFlags.Instance).GetValue(opperation) as EventRange,

Check warning on line 50 in src/AssociationRegistry.Acm.Api/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build ACM Api / build-image

Dereference of a possibly null reference.

Check warning on line 50 in src/AssociationRegistry.Acm.Api/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build ACM Api / build-image

Dereference of a possibly null reference.

Check warning on line 50 in src/AssociationRegistry.Acm.Api/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Public Api / build-image

Dereference of a possibly null reference.
"UpdateProjectionProgress" => opperation.GetType().GetProperty("Range").GetValue(opperation) as EventRange,

Check warning on line 51 in src/AssociationRegistry.Acm.Api/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build ACM Api / build-image

Dereference of a possibly null reference.

Check warning on line 51 in src/AssociationRegistry.Acm.Api/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Public Api / build-image

Dereference of a possibly null reference.
_ => null
};
}
}

public class AllFieldsContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();

props.ForEach(p =>
{
p.Writable = true;
p.Readable = true;
});

return props;
}
}
2 changes: 1 addition & 1 deletion src/AssociationRegistry.Acm.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ private static void ConfigureServices(WebApplicationBuilder builder)
builder.Services
.AddSingleton(appSettings)
.AddMarten(postgreSqlOptionsSection, builder.Configuration)
.AddOpenTelemetry(new Instrumentation())
.AddOpenTelemetry(new AcmInstrumentation())
.AddHttpContextAccessor()
.AddControllers();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace AssociationRegistry.Admin.Api.Infrastructure.Metrics;
using System.Threading;
using System.Threading.Tasks;

public class HighWatermarkListener : IDocumentSessionListener
public class HighWatermarkListener : DocumentSessionListenerBase
{
private readonly Instrumentation _instrumentation;

Expand All @@ -15,29 +15,14 @@ public HighWatermarkListener(Instrumentation instrumentation)
_instrumentation = instrumentation;
}

public void BeforeSaveChanges(IDocumentSession session)
public override async Task AfterCommitAsync(IDocumentSession session, IChangeSet commit, CancellationToken token)

Check warning on line 18 in src/AssociationRegistry.Admin.Api/Infrastructure/Metrics/HighWatermarkListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Api / build-image

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
}

public async Task BeforeSaveChangesAsync(IDocumentSession session, CancellationToken token)
{
}

public void AfterCommit(IDocumentSession session, IChangeSet commit)
{
}
var events = commit.GetEvents().ToArray();

public void DocumentLoaded(object id, object document)
{
}

public void DocumentAddedForStorage(object id, object document)
{
}

public async Task AfterCommitAsync(IDocumentSession session, IChangeSet commit, CancellationToken token)
{
var highWatermark = commit.GetEvents().Max(x => x.Sequence);
_instrumentation.HighWaterMarkHistogram.Record(highWatermark);
if (events.Any())
{
var highWatermark = events.Max(x => x.Sequence);
_instrumentation.HighWatermarkEventValue = highWatermark;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ public Instrumentation()
ActivitySource = new ActivitySource(ActivitySourceName, version);
meter = new Meter(MeterName, version);

HighWaterMarkHistogram = meter.CreateHistogram<long>(name: "ar.highWatermark.h", unit: "events", description: "high watermark");
_highWatermarkGauge = meter.CreateObservableGauge<long>(name: "ar.beheer.p.highWatermark.g", unit: "events",
description: "high watermark", observeValue: () => HighWatermarkEventValue);
}

public ActivitySource ActivitySource { get; }
public Histogram<long> HighWaterMarkHistogram { get; }
private ObservableGauge<long> _highWatermarkGauge;
public long HighWatermarkEventValue = 0;

public void Dispose()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace AssociationRegistry.Admin.ProjectionHost.Infrastructure.Metrics;

using AssociationRegistry.OpenTelemetry;
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;

/// <summary>
/// It is recommended to use a custom type to hold references for
/// ActivitySource and Instruments. This avoids possible type collisions
/// with other components in the DI container.
/// </summary>
public class AdminInstrumentation : IInstrumentation, IDisposable
{
public string ActivitySourceName => "AssociationRegistry";
public string MeterName => "AdminProjections";
private readonly Meter meter;

public AdminInstrumentation()
{
var version = typeof(AdminInstrumentation).Assembly.GetName().Version?.ToString();
ActivitySource = new ActivitySource(ActivitySourceName, version);
meter = new Meter(MeterName, version);

_verenigingZoeken = meter.CreateObservableGauge(name: "ar.beheer.p.zoeken.g", unit: "events", description: "Beheer zoeken projection", observeValue:() => VerenigingZoekenEventValue);
_verenigingDetail = meter.CreateObservableGauge(name: "ar.beheer.p.detail.g", unit: "events", description: "Beheer detail projection", observeValue:() => VerenigingDetailEventValue);
_historiek = meter.CreateObservableGauge(name: "ar.beheer.p.historiek.g", unit: "events", description: "Beheer detail projection", observeValue:() => VerenigingHistoriekEventValue);
}
public ActivitySource ActivitySource { get; }

private ObservableGauge<long> _verenigingZoeken;
public long VerenigingZoekenEventValue { get; set; }
private ObservableGauge<long> _verenigingDetail;
public long VerenigingDetailEventValue { get; set; }

private ObservableGauge<long> _historiek;
public long VerenigingHistoriekEventValue { get; set; }


public void Dispose()
{
ActivitySource.Dispose();
meter.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace AssociationRegistry.Admin.ProjectionHost.Infrastructure.Metrics;

using Marten;
using Marten.Events.Daemon;
using Marten.Internal.Operations;
using Marten.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Projections.Detail;
using Projections.Historiek;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

public class ProjectionStateListener : DocumentSessionListenerBase
{
private readonly AdminInstrumentation _adminInstrumentation;

public ProjectionStateListener(AdminInstrumentation adminInstrumentation)
{
_adminInstrumentation = adminInstrumentation;
}

public override Task AfterCommitAsync(IDocumentSession session, IChangeSet commit, CancellationToken token)
{
if (commit is not IUnitOfWork uow) return Task.CompletedTask;
var operations = uow.OperationsFor<Marten.Events.IEvent>();

foreach (var operation in operations)
{
var range = GetEventRange(operation);

if (range is null) continue;

if (range.ShardName.ProjectionName == "BeheerVerenigingZoekenDocument")
_adminInstrumentation.VerenigingZoekenEventValue = range.SequenceCeiling;

if (range.ShardName.ProjectionName == typeof(BeheerVerenigingDetailProjection).FullName)
_adminInstrumentation.VerenigingDetailEventValue = range.SequenceCeiling;

if (range.ShardName.ProjectionName == typeof(BeheerVerenigingHistoriekProjection).FullName)
_adminInstrumentation.VerenigingHistoriekEventValue = range.SequenceCeiling;
}

return Task.CompletedTask;
}

private static EventRange? GetEventRange(IStorageOperation opperation)
{
return opperation.GetType().Name switch
{
"InsertProjectionProgress" => opperation.GetType().GetField("_progress", BindingFlags.NonPublic | BindingFlags.Instance)

Check warning on line 55 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.

Check warning on line 55 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.

Check warning on line 55 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.
.GetValue(opperation) as EventRange,
"UpdateProjectionProgress" => opperation.GetType().GetProperty("Range").GetValue(opperation) as EventRange,

Check warning on line 57 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.

Check warning on line 57 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.

Check warning on line 57 in src/AssociationRegistry.Admin.ProjectionHost/Infrastructure/Metrics/ProjectionStateListener.cs

View workflow job for this annotation

GitHub Actions / Build Admin Projections / build-image

Dereference of a possibly null reference.
_ => null
};
}
}

public class AllFieldsContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();

props.ForEach(p =>
{
p.Writable = true;
p.Readable = true;
});

return props;
}
}
Loading

0 comments on commit 5e997c1

Please sign in to comment.