Skip to content
Closed
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
26 changes: 19 additions & 7 deletions src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace StackExchange.Profiling.Storage
/// <summary>
/// Understands how to store a <see cref="MiniProfiler"/> to a PostgreSQL Server database.
/// </summary>
public class PostgreSqlStorage : DatabaseStorageBase
public class PostgreSqlStorage : DatabaseStorageBase, IAdvancedAsyncStorage
{
/// <summary>
/// Initializes a new instance of the <see cref="PostgreSqlStorage"/> class with the specified connection string.
Expand Down 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 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Profiling.Storage;

namespace StackExchange.Profiling.Internal
{
Expand All @@ -23,7 +25,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 @@ -42,12 +44,23 @@ public static async Task<List<Guid>> ExpireAndGetUnviewedAsync(this MiniProfiler
{
return null;
}

var ids = await options.Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);

if (ids?.Count > options.MaxUnviewedProfiles)
{
for (var i = 0; i < ids.Count - options.MaxUnviewedProfiles; i++)
var idsToSetViewed = ids.Take(ids.Count - options.MaxUnviewedProfiles);

if (options.Storage is IAdvancedAsyncStorage storage)
{
await storage.SetViewedAsync(user, idsToSetViewed).ConfigureAwait(false);
}
else
{
await options.Storage.SetViewedAsync(user, ids[i]).ConfigureAwait(false);
foreach (var id in idsToSetViewed)
{
await options.Storage.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}
return ids;
Expand Down
19 changes: 19 additions & 0 deletions src/MiniProfiler.Shared/Storage/IAdvancedAsyncStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace StackExchange.Profiling.Storage
{
/// <summary>
/// Provides saving and loading <see cref="MiniProfiler"/>s to a storage medium with some advanced operations.
/// </summary>
public interface IAdvancedAsyncStorage : IAsyncStorage
{
/// <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);
}
}
27 changes: 26 additions & 1 deletion src/MiniProfiler.Shared/Storage/MultiStorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace StackExchange.Profiling.Storage
/// When saving, will save in all Stores.
/// </summary>
/// <example>Ideal usage scenario - you want to store requests in Cache and Sql Server, but only want to retrieve from Cache if it is available</example>
public class MultiStorageProvider : IAsyncStorage
public class MultiStorageProvider : IAdvancedAsyncStorage
{
/// <summary>
/// The stores that are exposed by this <see cref="MultiStorageProvider"/>
Expand Down Expand Up @@ -244,6 +244,31 @@ 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;

return Task.WhenAll(Stores.Select(async s =>
{
if (s is IAdvancedAsyncStorage storage)
{
await storage.SetViewedAsync(user, ids).ConfigureAwait(false);
}
else
{
foreach (var id in ids)
{
await s.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}));
}

/// <summary>
/// Runs <see cref="IAsyncStorage.GetUnviewedIds"/> on each <see cref="IAsyncStorage"/> object in <see cref="Stores"/> and returns the Union of results.
/// Will run on multiple stores in parallel if <see cref="AllowParallelOps"/> = true.
Expand Down
9 changes: 8 additions & 1 deletion src/MiniProfiler.Shared/Storage/NullStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace StackExchange.Profiling.Storage
/// <summary>
/// Empty storage no-nothing provider for doing nothing at all. Super efficient.
/// </summary>
public class NullStorage : IAsyncStorage
public class NullStorage : IAdvancedAsyncStorage
{
/// <summary>
/// Returns no profilers.
Expand Down 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
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