Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
12 changes: 7 additions & 5 deletions src/MiniProfiler.Providers.SqlServer/SqlServerStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ public override void Save(MiniProfiler profiler)
}

SaveTimings(timings, conn);
if (profiler.ClientTimings != null && profiler.ClientTimings.Timings != null && profiler.ClientTimings.Timings.Any())

var clientTimings = profiler.ClientTimings?.Timings;
if (clientTimings != null && clientTimings.Any())
{
// set the profilerId (isn't needed unless we are storing it)
profiler.ClientTimings.Timings.ForEach(x =>
foreach(var x in clientTimings)
{
x.MiniProfilerId = profiler.Id;
x.Id = Guid.NewGuid();
});
SaveClientTimings(profiler.ClientTimings.Timings, conn);
}
SaveClientTimings(clientTimings, conn);
}
}
}
Expand Down Expand Up @@ -139,7 +141,7 @@ INSERT INTO MiniProfilerTimings
}
}

private void SaveClientTimings(List<ClientTiming> timings, DbConnection conn)
private void SaveClientTimings(IReadOnlyList<ClientTiming> timings, DbConnection conn)
{
const string sql = @"
INSERT INTO MiniProfilerClientTimings
Expand Down
11 changes: 6 additions & 5 deletions src/MiniProfiler.Providers.SqlServerCe/SqlServerCeStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,16 @@ public override void Save(MiniProfiler profiler)
}

SaveTimings(timings, conn);
if (profiler.ClientTimings != null && profiler.ClientTimings.Timings != null && profiler.ClientTimings.Timings.Any())
var clientTimings = profiler.ClientTimings?.Timings;
if (clientTimings != null && clientTimings.Any())
{
// set the profilerId (isn't needed unless we are storing it)
profiler.ClientTimings.Timings.ForEach(x =>
foreach (var x in clientTimings)
{
x.MiniProfilerId = profiler.Id;
x.Id = Guid.NewGuid();
});
SaveClientTimings(profiler.ClientTimings.Timings, conn);
}
SaveClientTimings(clientTimings, conn);
}
}
}
Expand Down Expand Up @@ -140,7 +141,7 @@ INSERT INTO MiniProfilerTimings
}
}

private void SaveClientTimings(List<ClientTiming> timings, DbConnection conn)
private void SaveClientTimings(IReadOnlyList<ClientTiming> timings, DbConnection conn)
{
const string sql = @"
INSERT INTO MiniProfilerClientTimings
Expand Down
16 changes: 15 additions & 1 deletion src/MiniProfiler.Shared/ClientTimings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@ public class ClientTimings
{
private const string TimingPrefix = "clientPerformance[timing][";
private const string ProbesPrefix = "clientProbes[";
private readonly object _lockObject = new object();
private List<ClientTiming> _timings;

/// <summary>
/// Gets or sets the list of client side timings
/// </summary>
[DataMember(Order = 2)]
public List<ClientTiming> Timings { get; set; }
public List<ClientTiming> Timings
{
get
{
lock(_lockObject)
return _timings == null ? null : new List<ClientTiming>(_timings);
}
set
{
lock (_lockObject)
_timings = value == null ? null : new List<ClientTiming>(value);
}
}

/// <summary>
/// Gets or sets the redirect count.
Expand Down
78 changes: 78 additions & 0 deletions src/MiniProfiler.Shared/Helpers/Net45/AsyncLocal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#if NET45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;

namespace StackExchange.Profiling.Helpers.Net45
{
/// <summary>
/// Implements interface similar to AsyncLocal which is not available until .Net 4.6.
///
/// The implementation is inspired by Ambient Context Logic from here:
/// https://github.com/mehdime/DbContextScope/blob/master/Mehdime.Entity/Implementations/DbContextScope.cs
/// Unlike DbContextScope's implementation this implementation doesn't support serialization/deserialization
/// ths not allowing to cross app domain barrier.
/// </summary>
internal class AsyncLocal<T>
Copy link
Owner Author

@alexsku alexsku Dec 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a polyfill for AsyncLocal which is not available before .Net 4.6

where T : class
{
public AsyncLocal()
{
}

public AsyncLocal(
T obj
)
{
Value = obj;
}

/// <summary>
/// Gets or Sets the value.
/// </summary>
public T Value
{
get
{
return (CallContext.LogicalGetData(_id) as ObjectRef)?.Ref;
}

set
{
CallContext.LogicalSetData(_id, new ObjectRef { Ref = value });
}
}

/// <summary>
/// Identifies this instance of <see cref="AsyncLocal"/> in CallContext.
/// </summary>
private readonly string _id = Guid.NewGuid().ToString();

[Serializable]
private class ObjectRef : MarshalByRefObject, ISerializable
{
// The special constructor is used to deserialize values.
public ObjectRef()
{
}

// The special constructor is used to deserialize values.
public ObjectRef(
SerializationInfo info,
StreamingContext context
)
{
}

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
}

public T Ref { get; set; }
}
}
}
#endif
3 changes: 2 additions & 1 deletion src/MiniProfiler.Shared/MiniProfiler.Shared.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net45;netstandard1.5</TargetFrameworks>
<AssemblyName>MiniProfiler.Shared</AssemblyName>
Expand All @@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NETStandard.Library" Version="1.6.1" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.5'">
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
Expand Down
23 changes: 17 additions & 6 deletions src/MiniProfiler.Shared/MiniProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,9 @@ public Timing Root
{
var timing = timings.Pop();

if (timing.HasChildren)
var children = timing.Children;
if (children?.Count > 0)
{
var children = timing.Children;

for (int i = children.Count - 1; i >= 0; i--)
{
children[i].ParentTiming = timing;
Expand Down Expand Up @@ -189,7 +188,19 @@ public Timing Root
/// <summary>
/// Gets or sets points to the currently executing Timing.
/// </summary>
public Timing Head { get; set; }
public Timing Head
{
get
{
Settings.EnsureProfilerProvider();
return Settings.ProfilerProvider.CurrentHead;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible that the current thread spawns more than one task on separate threads and wait for them. As a result technically it is possible that there would be more than one separate head for a miniprofiler instance (one for each spawned thread). This code offloads managing heads to the profiler provider, so if the provider handles spawning threads (i.e. it relies on AsyncLocal or CallContext) then it should be able to manage head properly.

}
set
{
Settings.EnsureProfilerProvider();
Settings.ProfilerProvider.CurrentHead = value;
}
}

/// <summary>
/// Gets the ticks since this MiniProfiler was started.
Expand Down Expand Up @@ -329,9 +340,9 @@ public IEnumerable<Timing> GetTimingHierarchy()

yield return timing;

if (timing.HasChildren)
var children = timing.Children;
if (children?.Count > 0)
{
var children = timing.Children;
for (int i = children.Count - 1; i >= 0; i--) timings.Push(children[i]);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public abstract class BaseProfilerProvider : IProfilerProvider
/// </summary>
public abstract MiniProfiler GetCurrentProfiler();

/// <summary>
/// GEts or sets the current head timing. This is used by <see cref="MiniProfiler.Head"/>.
/// </summary>
public abstract Timing CurrentHead { get; set; }

/// <summary>
/// Sets <paramref name="profiler"/> to be active (read to start profiling)
/// This should be called once a new MiniProfiler has been created.
Expand Down
43 changes: 12 additions & 31 deletions src/MiniProfiler.Shared/ProfileProviders/DefaultProfilerProvider.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#if NET45
using System;
using System.Runtime.Remoting.Messaging;
using System.Web;
using System.Web;
using StackExchange.Profiling.Helpers.Net45;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is AsyncLocal polyfill we can keep the implementation of the provider the same - it will be either using the polyfill or .net version of AsyncLocal

#else
using System.Threading;
#endif
Expand All @@ -13,43 +14,23 @@ namespace StackExchange.Profiling
/// </summary>
public class DefaultProfilerProvider : BaseProfilerProvider
{
#if NET45
const string ContextKey = ":miniprofiler:";
private readonly AsyncLocal<MiniProfiler> _profiler = new AsyncLocal<MiniProfiler>();
private readonly AsyncLocal<Timing> _currentTiming = new AsyncLocal<Timing>();

private MiniProfiler Profiler
{
get
{
if (HttpContext.Current != null)
{
return HttpContext.Current?.Items[ContextKey] as MiniProfiler;
}
else
{
return CallContext.LogicalGetData(ContextKey) as MiniProfiler;
}
}
set
{
if (HttpContext.Current != null)
{
HttpContext.Current.Items[ContextKey] = value;
}
else
{
CallContext.LogicalSetData(ContextKey, value);
}
}
get { return _profiler.Value; }
set { _profiler.Value = value; }
}
#else
private AsyncLocal<MiniProfiler> _profiler = new AsyncLocal<MiniProfiler>();

private MiniProfiler Profiler
/// <summary>
/// Current head timing.
/// </summary>
public override Timing CurrentHead
{
get { return _profiler.Value; }
set { _profiler.Value = value; }
get { return _currentTiming.Value; }
set { _currentTiming.Value = value; }
}
#endif

/// <summary>
/// The name says it all.
Expand Down
5 changes: 5 additions & 0 deletions src/MiniProfiler.Shared/ProfileProviders/IProfilerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ public interface IProfilerProvider
/// Returns the current MiniProfiler. This is used by <see cref="MiniProfiler.Current"/>.
/// </summary>
MiniProfiler GetCurrentProfiler();

/// <summary>
/// Current head <see cref="Timing"/>. Used by <see cref="MiniProfiler.Head"/>
/// </summary>
Timing CurrentHead { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ namespace StackExchange.Profiling
public class SingletonProfilerProvider : IProfilerProvider
{
private MiniProfiler _profiler;
private Timing _timing;

/// <summary>
/// The name says it all
/// </summary>
public MiniProfiler GetCurrentProfiler() => _profiler;

/// <summary>
/// Current head timing.
/// </summary>
public Timing CurrentHead
{
get { return _timing; }
set { _timing = value; }
}

/// <summary>
/// Starts a new profiling session.
/// </summary>
Expand Down
8 changes: 6 additions & 2 deletions src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,13 @@ private void PopulateChildTimings(Timing parent, ILookup<Guid, Timing> timingsLo
protected void FlattenTimings(Timing timing, List<Timing> timingsCollection)
{
timingsCollection.Add(timing);
if (timing.HasChildren)
var children = timing.Children;
if (children?.Count > 0)
{
timing.Children.ForEach(x => FlattenTimings(x, timingsCollection));
foreach (var child in children)
{
FlattenTimings(child, timingsCollection);
}
}
}
}
Expand Down
Loading