Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ public Task StopAsync(CancellationToken cancellationToken)
public Uri Uri { get; set; }
public AgentStatus Status { get; set; }

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>.
/// </summary>
public string Description => $"Wolverine Cosmos DB durability agent for {Uri} — recovers persisted inbox/outbox messages and runs scheduled jobs against the Cosmos DB message store.";

/// <summary>
/// True once <see cref="StartTimers"/> has wired up the recovery and scheduled-job
/// background loops. Exposed for diagnostic and test inspection so callers can detect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ public async Task StopAsync(CancellationToken cancellationToken)
}

public Uri Uri { get; }

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>.
/// </summary>
public string Description => $"Sticky Postgres queue listener — pinned to the per-tenant database '{_databaseName}' for queue '{_queue}'. Only one node listens to each tenant database to avoid duplicate consumption.";
}
10 changes: 10 additions & 0 deletions src/Persistence/Wolverine.RDBMS/DurabilityAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ public async Task StopAsync(CancellationToken cancellationToken)

public Uri Uri { get; internal set; }

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>. This agent recovers
/// persisted inbox / outbox messages from the relational
/// message store, runs scheduled jobs, and emits persistence
/// metrics. The URI carries the store identity so operators
/// can disambiguate when a service has multiple stores.
/// </summary>
public string Description => $"Wolverine durability agent for {Uri} — recovers persisted inbox/outbox messages, runs scheduled jobs, and emits persistence metrics.";

public static Uri SimplifyUri(Uri uri)
{
return new Uri($"{PersistenceConstants.AgentScheme}://{uri.Host}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ public Task StopAsync(CancellationToken cancellationToken)
public Uri Uri { get; set; }
public AgentStatus Status { get; set; }

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>.
/// </summary>
public string Description => $"Wolverine RavenDB durability agent for {Uri} — recovers persisted inbox/outbox messages and runs scheduled jobs against the RavenDB message store.";

/// <summary>
/// True once <see cref="StartTimers"/> has wired up the recovery and scheduled-job
/// background loops. Exposed for diagnostic and test inspection so callers can detect
Expand Down
87 changes: 87 additions & 0 deletions src/Testing/CoreTests/Runtime/Agents/IAgentDescriptionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using JasperFx;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Shouldly;
using Wolverine.Runtime.Agents;
using Xunit;

namespace CoreTests.Runtime.Agents;

/// <summary>
/// Coverage for <see cref="IAgent.Description"/> — the human-readable
/// "what does this agent do" string surfaced to monitoring tools
/// (e.g. CritterWatch). Verifies the default-interface fallback for
/// implementations that don't override it, and verifies an explicit
/// override wins through.
/// </summary>
public class IAgentDescriptionTests
{
[Fact]
public void default_description_includes_uri_scheme_and_full_uri()
{
// Default-interface members are only accessible through the
// interface, not the concrete type — so cast deliberately.
// The default implementation derives a generic description
// from the URI; implementations are expected to override for
// anything more specific, but the fallback should always
// produce a non-empty string identifying scheme and URI so a
// monitoring tool isn't left with a blank tooltip.
IAgent agent = new BareBonesAgent(new Uri("test-agent://default"));

agent.Description.ShouldNotBeNullOrWhiteSpace();
agent.Description.ShouldContain("test-agent");
agent.Description.ShouldContain(agent.Uri.ToString());
}

[Fact]
public void overridden_description_wins_over_default()
{
IAgent agent = new DescribedAgent(
new Uri("test-agent://configured"),
"Custom description for this agent");

agent.Description.ShouldBe("Custom description for this agent");
agent.Description.ShouldNotContain("test-agent://configured");
}

/// <summary>
/// Minimal IAgent that doesn't override Description. The default
/// interface member should provide the fallback — covers the
/// "existing IAgent implementations stay source-compatible"
/// guarantee.
/// </summary>
private sealed class BareBonesAgent : IAgent
{
public BareBonesAgent(Uri uri)
{
Uri = uri;
}

public Uri Uri { get; }
public AgentStatus Status => AgentStatus.Stopped;
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

/// <summary>
/// Agent that explicitly overrides the description — exercises
/// the implementation-side override path that built-in agents
/// (DurabilityAgent, ExclusiveListenerAgent, etc.) use to give
/// CritterWatch operators agent-specific tooltip text.
/// </summary>
private sealed class DescribedAgent : IAgent
{
private readonly string _description;

public DescribedAgent(Uri uri, string description)
{
Uri = uri;
_description = description;
}

public Uri Uri { get; }
public AgentStatus Status => AgentStatus.Stopped;
public string Description => _description;
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
6 changes: 6 additions & 0 deletions src/Wolverine/Runtime/Agents/ExclusiveListenerFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public async Task StopAsync(CancellationToken cancellationToken)

public AgentStatus Status { get; set; } = AgentStatus.Running;

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>.
/// </summary>
public string Description => $"Exclusive listener for {_endpoint.Uri} — only one node at a time holds the listening role for this endpoint, so the cluster avoids competing consumers.";

public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
CancellationToken cancellationToken = default)
{
Expand Down
12 changes: 12 additions & 0 deletions src/Wolverine/Runtime/Agents/IAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public interface IAgent : IHostedService, IHealthCheck
/// </summary>
AgentStatus Status { get; }

/// <summary>
/// Human-readable description of what this agent does on a given
/// node. Surfaced in monitoring tools (e.g. CritterWatch) so
/// operators don't have to recognise an agent purely by its URI
/// scheme. The default implementation returns a generic
/// "{scheme} agent: {Uri}" string; override in concrete agent
/// types to provide more specific text. Kept as a default
/// interface member so existing <see cref="IAgent"/>
/// implementations stay source-compatible.
/// </summary>
string Description => $"{Uri.Scheme} agent: {Uri}";

/// <summary>
/// Default health check implementation based on agent status.
/// Override in implementations for more specific health reporting.
Expand Down
6 changes: 6 additions & 0 deletions src/Wolverine/Runtime/Agents/LeaderPinnedAgentFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public async Task StopAsync(CancellationToken cancellationToken)

public AgentStatus Status { get; set; } = AgentStatus.Running;

/// <summary>
/// Human-readable description for monitoring tools — see
/// <see cref="IAgent.Description"/>.
/// </summary>
public string Description => $"Leader-pinned listener for {_endpoint.Uri} — runs only on the cluster's elected leader node, so the listening role moves with leader elections.";

public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
CancellationToken cancellationToken = default)
{
Expand Down
Loading