Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring MiniProfilerBaseOptionsExtensions.ExpireAndGetUnviewedAsync #617

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions samples/Misc/RemoteAppendExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public Task<IEnumerable<Guid>> ListAsync(int maxResults, DateTime? start = null,
public Task SaveAsync(MiniProfiler profiler) => Wrapped.SaveAsync(profiler);
public Task SetUnviewedAsync(string user, Guid id) => Wrapped.SetUnviewedAsync(user, id);
public Task SetViewedAsync(string user, Guid id) => Wrapped.SetViewedAsync(user, id);
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => Wrapped.SetViewedAsync(user, ids);
public Task<List<Guid>> GetUnviewedIdsAsync(string user) => Wrapped.GetUnviewedIdsAsync(user);

/// <summary>
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.AspNetCore/Storage/MemoryCacheStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,18 @@ public Task SetViewedAsync(string user, Guid id)
SetViewed(user, id);
return Task.CompletedTask;
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}
}
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.MongoDB/MongoDbStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ public async Task SetViewedAsync(string user, Guid id)
var set = Builders<MiniProfiler>.Update.Set(profiler => profiler.HasUserViewed, true);
await _collection.UpdateOneAsync(p => p.Id == id, set).ConfigureAwait(false);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Returns the underlying client.
Expand Down
24 changes: 18 additions & 6 deletions src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,28 +268,40 @@ public override async Task<MiniProfiler> LoadAsync(Guid id)
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="id">The profiler ID to set viewed.</param>
public override Task SetViewedAsync(string user, Guid id) => ToggleViewedAsync(user, id, true);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public override Task SetViewedAsync(string user, IEnumerable<Guid> ids) => ToggleViewedAsync(user, ids, true);

private string _toggleViewedSql;

private string ToggleViewedSql => _toggleViewedSql ??= $@"
Update {MiniProfilersTable}
Set HasUserViewed = @hasUserVeiwed
Where Id = @id
Set HasUserViewed = @hasUserViewed
Where Id = ANY(@ids)
And ""User"" = @user";

private void ToggleViewed(string user, Guid id, bool hasUserVeiwed)
private void ToggleViewed(string user, Guid id, bool hasUserViewed)
{
using (var conn = GetConnection())
{
conn.Execute(ToggleViewedSql, new { id, user, hasUserVeiwed });
conn.Execute(ToggleViewedSql, new { ids = new [] { id }, user, hasUserViewed });
}
}

private async Task ToggleViewedAsync(string user, Guid id, bool hasUserVeiwed)
private Task ToggleViewedAsync(string user, Guid id, bool hasUserViewed)
{
return ToggleViewedAsync(user, new [] { id }, hasUserViewed);
}

private async Task ToggleViewedAsync(string user, IEnumerable<Guid> ids, bool hasUserViewed)
{
using (var conn = GetConnection())
{
await conn.ExecuteAsync(ToggleViewedSql, new { id, user, hasUserVeiwed }).ConfigureAwait(false);
await conn.ExecuteAsync(ToggleViewedSql, new { ids = ids.ToArray(), user, hasUserViewed }).ConfigureAwait(false);
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.RavenDB/RavenDbStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,19 @@ public async Task SetViewedAsync(string user, Guid id)
profile.HasUserViewed = true;
await session.SaveChangesAsync().ConfigureAwait(false);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.Redis/RedisStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,19 @@ public Task SetViewedAsync(string user, Guid id)
RedisValue value = id.ToString();
return _database.SetRemoveAsync(key, value);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace StackExchange.Profiling.Internal
Expand All @@ -23,7 +24,7 @@ public static List<Guid> ExpireAndGetUnviewed(this MiniProfilerBaseOptions optio
{
for (var i = 0; i < ids.Count - options.MaxUnviewedProfiles; i++)
{
options.Storage.SetViewedAsync(user, ids[i]);
options.Storage.SetViewed(user, ids[i]);
}
}
return ids;
Expand All @@ -45,10 +46,8 @@ public static async Task<List<Guid>> ExpireAndGetUnviewedAsync(this MiniProfiler
var ids = await options.Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
if (ids?.Count > options.MaxUnviewedProfiles)
{
for (var i = 0; i < ids.Count - options.MaxUnviewedProfiles; i++)
{
await options.Storage.SetViewedAsync(user, ids[i]).ConfigureAwait(false);
}
var idsToSetViewed = ids.Take(ids.Count - options.MaxUnviewedProfiles);
await options.Storage.SetViewedAsync(user, idsToSetViewed).ConfigureAwait(false);
}
return ids;
}
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ protected DatabaseStorageBase(string connectionString, string profilersTable, st
/// <param name="id">The profiler ID to set viewed.</param>
public abstract Task SetViewedAsync(string user, Guid id);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public virtual async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/MiniProfiler.Shared/Storage/IAsyncStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ Task<IEnumerable<Guid>> ListAsync(
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="id">The profiler ID to set viewed.</param>
Task SetViewedAsync(string user, Guid id);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
Task SetViewedAsync(string user, IEnumerable<Guid> ids);

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
14 changes: 14 additions & 0 deletions src/MiniProfiler.Shared/Storage/MultiStorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,20 @@ public Task SetViewedAsync(string user, Guid id)

return Task.WhenAll(Stores.Select(s => s.SetViewedAsync(user, id)));
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
if (Stores == null) return Task.CompletedTask;

var idsArray = ids.ToArray(); // Prevent multiple enumerations of ids.

return Task.WhenAll(Stores.Select(s => s.SetViewedAsync(user, idsArray)));
}

/// <summary>
/// Runs <see cref="IAsyncStorage.GetUnviewedIds"/> on each <see cref="IAsyncStorage"/> object in <see cref="Stores"/> and returns the Union of results.
Expand Down
7 changes: 7 additions & 0 deletions src/MiniProfiler.Shared/Storage/NullStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ public void SetViewed(string user, Guid id) { /* no-op */ }
/// <param name="id">No one cares.</param>
public Task SetViewedAsync(string user, Guid id) => Task.CompletedTask;

/// <summary>
/// Sets nothing.
/// </summary>
/// <param name="user">No one cares.</param>
/// <param name="ids">No one cares.</param>
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => Task.CompletedTask;

/// <summary>
/// Gets nothing.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler/Storage/MemoryCacheStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,18 @@ public Task SetViewedAsync(string user, Guid id)
SetViewed(user, id);
return Task.CompletedTask;
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}
}
1 change: 1 addition & 0 deletions tests/MiniProfiler.Tests/InternalErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class KaboomStorage : IAsyncStorage
public Task SetUnviewedAsync(string user, Guid id) => throw new BoomBoom();
public void SetViewed(string user, Guid id) => throw new BoomBoom();
public Task SetViewedAsync(string user, Guid id) => throw new BoomBoom();
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => throw new BoomBoom();

public class BoomBoom : Exception { }
}
Expand Down
32 changes: 32 additions & 0 deletions tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Dapper;
using StackExchange.Profiling.Internal;
using StackExchange.Profiling.Storage;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -146,6 +149,35 @@ public async Task SetViewedAsync()
var unviewedIds2 = await Storage.GetUnviewedIdsAsync(mp.User).ConfigureAwait(false);
Assert.DoesNotContain(mp.Id, unviewedIds2);
}

[Fact]
public async Task ExpireAndGetUnviewedAsync()
{
Options.Storage = Storage;
var user = "TestUser";
var mps = Enumerable.Range(0, 500)
.Select(i => GetMiniProfiler(user: user))
.ToList();

foreach (var mp in mps)
{
Assert.False(mp.HasUserViewed);
await Storage.SaveAsync(mp).ConfigureAwait(false);
Assert.False(mp.HasUserViewed);
}

var unviewedIds = await Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
Assert.All(mps, mp => Assert.Contains(mp.Id, unviewedIds));

var sw = Stopwatch.StartNew();
await Options.ExpireAndGetUnviewedAsync(user);
sw.Stop();
Output.WriteLine($"{nameof(MiniProfilerBaseOptionsExtensions.ExpireAndGetUnviewedAsync)} completed in {sw.ElapsedMilliseconds}ms");

var unviewedIds2 = await Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
Assert.InRange(unviewedIds2.Count, 0, Options.MaxUnviewedProfiles);
Assert.Subset(new HashSet<Guid>(unviewedIds), new HashSet<Guid>(unviewedIds2));
}

[Fact]
public void SetUnviewed()
Expand Down