Skip to content

Commit 99a8798

Browse files
committed
benchmarks: add governance lifecycle suite
1 parent 41a9a6e commit 99a8798

6 files changed

Lines changed: 255 additions & 0 deletions
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
4+
5+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle;
6+
7+
/// <summary>
8+
/// Benchmarks expiration sweeps for governed requests.
9+
/// </summary>
10+
[BenchmarkCategory("Governance")]
11+
[MemoryDiagnoser]
12+
[InProcess]
13+
public class RequestLifecycleExpirationBenchmarks : RequestLifecycleBenchmarkBase
14+
{
15+
/// <summary>
16+
/// Measures expiration of due requests in the pending lifecycle.
17+
/// </summary>
18+
[Benchmark(Baseline = true)]
19+
public async Task<IReadOnlyList<MutationRequest>> ExpireDueRequests_Sweep()
20+
{
21+
var manager = CreateExpirationWorkflow();
22+
23+
return await manager.ExpireDueRequests(
24+
DateTimeOffset.UtcNow,
25+
RequestLifecycleBenchmarkSupport.CreateSweepContext())
26+
.ConfigureAwait(false);
27+
}
28+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Abstractions.Context;
3+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
4+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
5+
using ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
6+
7+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle;
8+
9+
/// <summary>
10+
/// Benchmarks lifecycle transitions on governed requests.
11+
/// </summary>
12+
[BenchmarkCategory("Governance")]
13+
[MemoryDiagnoser]
14+
[InProcess]
15+
public class RequestLifecycleTransitionBenchmarks : RequestLifecycleBenchmarkBase
16+
{
17+
/// <summary>
18+
/// Measures a pending request being approved through the lifecycle manager.
19+
/// </summary>
20+
[Benchmark(Baseline = true)]
21+
public async Task<MutationRequest> Approve_PendingRequest()
22+
{
23+
var (manager, request) = CreatePendingWorkflow();
24+
25+
return await manager.Approve(
26+
request.RequestId,
27+
RequestLifecycleBenchmarkSupport.CreateDecisionContext("approver", "Approver", "Approve lifecycle request"))
28+
.ConfigureAwait(false);
29+
}
30+
31+
/// <summary>
32+
/// Measures a pending request being rejected through the lifecycle manager.
33+
/// </summary>
34+
[Benchmark]
35+
public async Task<MutationRequest> Reject_PendingRequest()
36+
{
37+
var (manager, request) = CreatePendingWorkflow();
38+
39+
return await manager.Reject(
40+
request.RequestId,
41+
RequestLifecycleBenchmarkSupport.CreateDecisionContext("reviewer", "Reviewer", "Reject lifecycle request"))
42+
.ConfigureAwait(false);
43+
}
44+
45+
/// <summary>
46+
/// Measures a pending request being cancelled through the lifecycle manager.
47+
/// </summary>
48+
[Benchmark]
49+
public async Task<MutationRequest> Cancel_PendingRequest()
50+
{
51+
var (manager, request) = CreatePendingWorkflow();
52+
53+
return await manager.Cancel(
54+
request.RequestId,
55+
RequestLifecycleBenchmarkSupport.CreateDecisionContext("operator", "Operator", "Cancel lifecycle request"))
56+
.ConfigureAwait(false);
57+
}
58+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
4+
5+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle;
6+
7+
/// <summary>
8+
/// Benchmarks submission of governed requests into the pending lifecycle.
9+
/// </summary>
10+
[BenchmarkCategory("Governance")]
11+
[MemoryDiagnoser]
12+
[InProcess]
13+
public class RequestSubmissionBenchmarks : RequestLifecycleBenchmarkBase
14+
{
15+
/// <summary>
16+
/// Measures a pending governed request being submitted and stored.
17+
/// </summary>
18+
[Benchmark(Baseline = true)]
19+
public async Task<MutationRequest> Submit_PendingRequest()
20+
{
21+
var (manager, request) = CreateSubmissionWorkflow();
22+
23+
return await manager.Submit(request).ConfigureAwait(false);
24+
}
25+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Contracts;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Governance.Runtime.Lifecycle.Execution;
4+
using ModularityKit.Mutator.Governance.Runtime.Storage;
5+
6+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
7+
8+
/// <summary>
9+
/// Shared setup helpers for governance request lifecycle benchmarks.
10+
/// </summary>
11+
public abstract class RequestLifecycleBenchmarkBase
12+
{
13+
protected const string RequestId = "governance-lifecycle-request";
14+
15+
protected static (IMutationRequestLifecycleManager Manager, MutationRequest Request) CreateSubmissionWorkflow()
16+
{
17+
var store = new InMemoryMutationRequestStore();
18+
var manager = new MutationRequestLifecycleManager(store);
19+
var request = RequestLifecycleBenchmarkSupport.CreatePendingRequest(RequestId);
20+
21+
return (manager, request);
22+
}
23+
24+
protected static (IMutationRequestLifecycleManager Manager, MutationRequest Request) CreatePendingWorkflow()
25+
{
26+
var store = new InMemoryMutationRequestStore();
27+
var manager = new MutationRequestLifecycleManager(store);
28+
var request = store.Create(RequestLifecycleBenchmarkSupport.CreatePendingRequest(RequestId))
29+
.GetAwaiter()
30+
.GetResult();
31+
32+
return (manager, request);
33+
}
34+
35+
protected static (IMutationRequestLifecycleManager Manager, MutationRequest Request) CreateApprovedWorkflow()
36+
{
37+
var store = new InMemoryMutationRequestStore();
38+
var manager = new MutationRequestLifecycleManager(store);
39+
var request = store.Create(RequestLifecycleBenchmarkSupport.CreateApprovedRequest(RequestId))
40+
.GetAwaiter()
41+
.GetResult();
42+
43+
return (manager, request);
44+
}
45+
46+
protected static IMutationRequestLifecycleManager CreateExpirationWorkflow()
47+
{
48+
var store = new InMemoryMutationRequestStore();
49+
var manager = new MutationRequestLifecycleManager(store);
50+
51+
store.Create(RequestLifecycleBenchmarkSupport.CreateExpiredRequest($"{RequestId}-expired"))
52+
.GetAwaiter()
53+
.GetResult();
54+
store.Create(RequestLifecycleBenchmarkSupport.CreatePendingRequest($"{RequestId}-active"))
55+
.GetAwaiter()
56+
.GetResult();
57+
58+
return manager;
59+
}
60+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using ModularityKit.Mutator.Abstractions.Context;
2+
using ModularityKit.Mutator.Abstractions.Intent;
3+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
4+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory;
5+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
6+
7+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
8+
9+
/// <summary>
10+
/// Builds repeatable request lifecycle benchmark fixtures.
11+
/// </summary>
12+
internal static class RequestLifecycleBenchmarkSupport
13+
{
14+
/// <summary>
15+
/// Creates a pending request used for lifecycle submission and terminal transition benchmarks.
16+
/// </summary>
17+
public static MutationRequest CreatePendingRequest(
18+
string requestId,
19+
DateTimeOffset? expiresAt = null)
20+
=> MutationRequestFactory.Pending(
21+
stateId: "governance-benchmark:lifecycle",
22+
stateType: "GovernanceState",
23+
mutationType: "ManageGovernanceMutation",
24+
intent: CreateIntent(),
25+
context: MutationContext.User("requester", "Requester", "Need lifecycle processing"),
26+
pendingReason: PendingMutationReason.Approval,
27+
expectedStateVersion: "v12",
28+
expiresAt: expiresAt)
29+
with
30+
{
31+
RequestId = requestId
32+
};
33+
34+
/// <summary>
35+
/// Creates a pending request that is already due for expiration sweeps.
36+
/// </summary>
37+
public static MutationRequest CreateExpiredRequest(string requestId)
38+
=> CreatePendingRequest(
39+
requestId,
40+
DateTimeOffset.UtcNow.AddMinutes(-5));
41+
42+
/// <summary>
43+
/// Creates an approved request used to exercise the lifecycle manager approval path.
44+
/// </summary>
45+
public static MutationRequest CreateApprovedRequest(string requestId)
46+
=> MutationRequestFactory.Approved(
47+
stateId: "governance-benchmark:lifecycle",
48+
stateType: "GovernanceState",
49+
mutationType: "ManageGovernanceMutation",
50+
intent: CreateIntent(),
51+
context: MutationContext.User("requester", "Requester", "Need lifecycle processing"),
52+
expectedStateVersion: "v12")
53+
with
54+
{
55+
RequestId = requestId
56+
};
57+
58+
/// <summary>
59+
/// Creates the intent used by request lifecycle benchmark scenarios.
60+
/// </summary>
61+
public static MutationIntent CreateIntent()
62+
=> new()
63+
{
64+
OperationName = "ManageGovernanceRequest",
65+
Category = "Governance",
66+
Description = "Manage governance request lifecycle in benchmark"
67+
};
68+
69+
/// <summary>
70+
/// Creates a decision context used for lifecycle transitions.
71+
/// </summary>
72+
public static MutationContext CreateDecisionContext(
73+
string actorId,
74+
string actorName,
75+
string reason)
76+
=> MutationContext.User(actorId, actorName, reason);
77+
78+
/// <summary>
79+
/// Creates a sweep context used for expiration benchmarks.
80+
/// </summary>
81+
public static MutationContext CreateSweepContext()
82+
=> MutationContext.Service("lifecycle-sweeper", "Expire pending governance requests");
83+
}

Benchmarks/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This folder contains BenchmarkDotNet measurements for `ModularityKit.Mutator`.
1515
- parallel execution, state gate contention, and concurrent batch scheduling pressure in the core runtime
1616
- mutation result creation and history/audit output materialization in the core runtime
1717
- governance approval workflow overhead in the governance runtime
18+
- governance request lifecycle overhead in the governance runtime
1819

1920
The throughput benchmarks use cloned array backed state so state size effects remain visible in the
2021
actual mutation path rather than being hidden behind an artificial inner loop.

0 commit comments

Comments
 (0)