Skip to content

Commit

Permalink
Add Misc examples project and first example for remote appends
Browse files Browse the repository at this point in the history
  • Loading branch information
NickCraver committed Nov 22, 2018
1 parent 320a6b5 commit 2fec211
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 1 deletion.
9 changes: 8 additions & 1 deletion MiniProfiler.sln
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Mvc5.EFCore", "samp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniProfiler.Providers.MongoDB", "src\MiniProfiler.Providers.MongoDB\MiniProfiler.Providers.MongoDB.csproj", "{001EFFB2-E56C-4A63-9954-79D7AEB052DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misc", "samples\Misc\Misc.csproj", "{44075C61-13E9-4C78-B340-6045EA115433}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -176,6 +178,10 @@ Global
{001EFFB2-E56C-4A63-9954-79D7AEB052DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{001EFFB2-E56C-4A63-9954-79D7AEB052DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{001EFFB2-E56C-4A63-9954-79D7AEB052DB}.Release|Any CPU.Build.0 = Release|Any CPU
{44075C61-13E9-4C78-B340-6045EA115433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44075C61-13E9-4C78-B340-6045EA115433}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44075C61-13E9-4C78-B340-6045EA115433}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44075C61-13E9-4C78-B340-6045EA115433}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -204,10 +210,11 @@ Global
{CC4AD661-0F2D-4E27-934D-F321B2214E4B} = {33644D67-D9BB-4989-9B7A-98D51967059B}
{44F5C82B-412E-485D-B349-BA9A008BD5AC} = {E0DA4035-4D64-4BB8-8EA1-42197DE62519}
{001EFFB2-E56C-4A63-9954-79D7AEB052DB} = {6A510DBF-E85F-4D2C-B8F7-006DA31B3418}
{44075C61-13E9-4C78-B340-6045EA115433} = {E0DA4035-4D64-4BB8-8EA1-42197DE62519}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
LessCompiler = 6a2b5b70-1c32-482f-b5c6-0597e2d4e376
SolutionGuid = {9373F37A-A996-4545-A251-1902C8886E3F}
LessCompiler = 6a2b5b70-1c32-482f-b5c6-0597e2d4e376
EndGlobalSection
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = StackExchange.Profiling.vsmdi
Expand Down
9 changes: 9 additions & 0 deletions samples/Misc/Misc.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\MiniProfiler.Shared\MiniProfiler.Shared.csproj" />
</ItemGroup>
</Project>
146 changes: 146 additions & 0 deletions samples/Misc/RemoteAppendExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Profiling;
using StackExchange.Profiling.Storage;

namespace Misc
{
/// <summary>
/// <para>
/// This is an example of how you can use a shared storage (e.g. Redis) to get a complete MiniProfiler call
/// across applications. For example, if App1 makes an HTTP call to App2 and you want the profiler from App2
/// to show as a child in that tree when you're viewing it in App1, we can fetch and append the remote profiling
/// session recorded from App1 at fetch time from App1.
/// </para>
/// <param>
/// This requires shared storage! Or else you need to switch up how the .Load/.LoadAsync methods work below.
/// </param>
/// <para>
/// If the source is this:
/// Root
/// - HTTP Call timing
/// And the remote side is:
/// Root
/// - Some Work
/// - Some Work 2
/// </para>
/// <para>
/// The result looks like this:
/// Root
/// - HTTP Call timing
/// - Some Work
/// - Some Work 2
/// </para>
/// </summary>
public class RemoteAppendExample : IAsyncStorage
{
private IAsyncStorage Wrapped { get; }

/// <summary>
/// We're just wrapping the base storage here, so that we can intercept the .Load(Async)() methods
/// and append a remote profiler if needed.
/// </summary>
public RemoteAppendExample(IAsyncStorage storage) => Wrapped = storage ?? throw new ArgumentNullException(nameof(storage));

public IEnumerable<Guid> List(int maxResults, DateTime? start = null, DateTime? finish = null, ListResultsOrder orderBy = ListResultsOrder.Descending) =>
Wrapped.List(maxResults, start, finish, orderBy);
public void Save(MiniProfiler profiler) => Wrapped.Save(profiler);
public void SetUnviewed(string user, Guid id) => Wrapped.SetUnviewed(user, id);
public void SetViewed(string user, Guid id) => Wrapped.SetViewed(user, id);
public List<Guid> GetUnviewedIds(string user) => Wrapped.GetUnviewedIds(user);

public Task<IEnumerable<Guid>> ListAsync(int maxResults, DateTime? start = null, DateTime? finish = null, ListResultsOrder orderBy = ListResultsOrder.Descending) =>
Wrapped.ListAsync(maxResults, start, finish, orderBy);
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<List<Guid>> GetUnviewedIdsAsync(string user) => Wrapped.GetUnviewedIdsAsync(user);

/// <summary>
/// This is a timing name prefix we check to see if we should even be trying to load a remote profiler
/// ...but this signal could be anything you want.
/// </summary>
public const string RemotePrefix = "Remote: ";

/// <summary>
/// Loads a profiler and appends any remote ones found.
/// </summary>
public MiniProfiler Load(Guid id)
{
var result = Wrapped.Load(id);
if (result == null) return null;

// TODO: You may want to filter here to only run this search for routes you expect to have a child profiler

// Gets the timing hierarchy of the whole tree in a flat list form for iteration.
foreach (var t in result.GetTimingHierarchy())
{
// We should only expect it once. Hop out as soon as we find it.
// Check if this looks like a remote profile, and if so, attempt to load it:
if (t.Name?.StartsWith(RemotePrefix) == true)
{
// Note: if you wanted to recursively do this, call Load() instead of Wrapped.Load() here.
var remote = Wrapped.Load(t.Id);
if (remote != null)
{
// Found it!
// Let's pretty up the result and indicate which server we hit.
remote.Root.Name = $"Remote: {remote.Name} ({remote.MachineName})";
// In case you're using in-memory caching, let's protect against a double append on dupe runs.
if (!t.Children.Any(c => c.Id == remote.Root.Id))
{
// Add the remote profiler!
t.AddChild(remote.Root);
}
break;
}
}
}
return result;
}

/// <summary>
/// This is just an async version of above.
/// </summary>
public async Task<MiniProfiler> LoadAsync(Guid id)
{
var result = await Wrapped.LoadAsync(id).ConfigureAwait(false);
if (result == null) return null;

// TODO: You may want to filter here to only run this search for routes you expect to have a child profiler

// Gets the timing hierarchy of the whole tree in a flat list form for iteration.
foreach (var t in result.GetTimingHierarchy())
{
// We should only expect it once. Hop out as soon as we find it.
// Check if this looks like a remote profile, and if so, attempt to load it:
if (t.Name?.StartsWith(RemotePrefix) == true)
{
// Note: if you wanted to recursively do this, call Load() instead of Wrapped.Load() here.
var remote = await Wrapped.LoadAsync(t.Id).ConfigureAwait(false);
if (remote != null)
{
// Found it!
// Let's pretty up the result and indicate which server we hit.
remote.Root.Name = $"Remote: {remote.Name} ({remote.MachineName})";
// In case you're using in-memory caching, let's protect against a double append on dupe runs.
if (!t.Children.Any(c => c.Id == remote.Root.Id))
{
// Add the remote profiler!
t.AddChild(remote.Root);
}
break;
}
}
}
return result;
}
}

public class RemoteAppendExampleUsage
{
// TODO: Add HttpClient example usage
}
}

0 comments on commit 2fec211

Please sign in to comment.