Merged PR 311207: GitHub Forward Integration - cd92be8a7a

GitHub forward integration Pull Request for commit cd92be8a7a
This commit is contained in:
Project Collection Build Service (office) 2019-08-07 15:40:19 +00:00 коммит произвёл Magdalena Stachon
Родитель f7ecb2e30f 24108f776c
Коммит 530ddd23ae
77 изменённых файлов: 7101 добавлений и 116 удалений

Просмотреть файл

@ -43,5 +43,8 @@
<MicrosoftAspNetCoreHttpVersionCore>2.2.0</MicrosoftAspNetCoreHttpVersionCore>
<MicrosoftAzureCosmosDBBulkExecutorVersionCore>2.2.0-preview2</MicrosoftAzureCosmosDBBulkExecutorVersionCore>
<WindowsAzureStorageVersionCore>9.3.3</WindowsAzureStorageVersionCore>
<SystemThreadingTasks>4.0.11</SystemThreadingTasks>
<MSTestAdapterVersion>1.4.0</MSTestAdapterVersion>
<MSTestFrameworkVersion>1.4.0</MSTestFrameworkVersion>
</PropertyGroup>
</Project>

Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28729.10
# Visual Studio 15
VisualStudioVersion = 15.0.28307.757
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System", "src\System\Microsoft.Omex.System.csproj", "{6E08AF9B-BACC-47DB-BC47-5B818C4FB262}"
EndProject
@ -42,6 +42,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.Gating.UnitT
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.Gating.UnitTests", "tests\Gating.UnitTests\Microsoft.Omex.Gating.UnitTests.csproj", "{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System.AspNetCore", "src\System.AspNetCore\Microsoft.Omex.System.AspNetCore.csproj", "{07E79934-29BD-4E03-8533-C086CD1DC50F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System.AspNetCore.UnitTests", "tests\System.AspNetCore.UnitTests\Microsoft.Omex.System.AspNetCore.UnitTests.csproj", "{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeGenerators", "CodeGenerators", "{C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.CodeGenerators.GateGen", "src\CodeGenerators\gategen\Microsoft.Omex.CodeGenerators.GateGen.csproj", "{D3B05497-4540-4D95-A829-E6CD42EE09D3}"
@ -118,6 +122,22 @@ Global
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|Any CPU.Build.0 = Release|Any CPU
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|x64.ActiveCfg = Release|Any CPU
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|x64.Build.0 = Release|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|x64.ActiveCfg = Debug|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|x64.Build.0 = Debug|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|Any CPU.Build.0 = Release|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|x64.ActiveCfg = Release|Any CPU
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|x64.Build.0 = Release|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|x64.ActiveCfg = Debug|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|x64.Build.0 = Debug|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|Any CPU.Build.0 = Release|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|x64.ActiveCfg = Release|Any CPU
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|x64.Build.0 = Release|Any CPU
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -139,6 +159,8 @@ Global
{C79F8F76-F62C-4F46-8D54-4B0E44BD53A6} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
{D4799136-2642-4268-86DE-4909F84FB29E} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8} = {551C93F8-6E89-4954-8905-7F5AC7173285}
{07E79934-29BD-4E03-8533-C086CD1DC50F} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A} = {551C93F8-6E89-4954-8905-7F5AC7173285}
{C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
{D3B05497-4540-4D95-A829-E6CD42EE09D3} = {C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8}
EndGlobalSection

Просмотреть файл

@ -1,14 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace Microsoft.Omex.Gating.UnitTests.Shared
{
/// <summary>

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Threading;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.TimedScopes;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Call context manager class
/// </summary>
public class CallContextManager : ICallContextManager
{
/// <summary>
/// An overridable call context for unit test purposes
/// </summary>
public ICallContext CallContextOverride { get; set; }
/// <summary>
/// The call context
/// Depending on the 'UseLogicalCallContext' app setting it will either use classic - OperationCallContext -> ThreadCallContext chain
/// or the new LogicalCallContext as backup
/// </summary>
private Lazy<ICallContext> s_callContext(IMachineInformation machineInformation)
{
return new Lazy<ICallContext>(() => new HttpCallContext(useLogicalCallContext: false, machineInformation: machineInformation), LazyThreadSafetyMode.PublicationOnly);
}
/// <summary>
/// Current call context
/// </summary>
public ICallContext CallContextHandler(IMachineInformation machineInformation)
{
return CallContextOverride ?? new HttpCallContext(useLogicalCallContext: false, machineInformation: machineInformation);
}
}
}

Просмотреть файл

@ -0,0 +1,365 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Context data stored on HttpContext
/// </summary>
public class HttpCallContext : ICallContext
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="useLogicalCallContext">Should LogicalCallContext be used as backup context</param>
/// <param name="machineInformation"></param>
public HttpCallContext(bool useLogicalCallContext = true, IMachineInformation machineInformation = null)
{
m_backupCallContext = useLogicalCallContext ? (ICallContext)new LogicalCallContext(machineInformation) : new OperationCallContext();
}
/// <summary>
/// Constructor
/// </summary>
public HttpCallContext() : this(true)
{
}
/// <summary>
/// Used when there is no http call context
/// </summary>
private readonly ICallContext m_backupCallContext;
/// <summary>
/// Dictionary of data stored on the call context
/// </summary>
public IDictionary<string, object> Data
{
get
{
HttpContext current = HttpContextWrapper.Current;
if (current != null)
{
if (current.Items.ContainsKey(UseBackupKey))
{
return m_backupCallContext.Data;
}
if (current.Items.ContainsKey(DictionaryKey))
{
if (!(current.Items[DictionaryKey] is Dictionary<string, object> dictionary))
{
dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
current.Items[DictionaryKey] = dictionary;
}
return dictionary;
}
else
{
throw new InvalidOperationException("Attempting to access data for a HttpCallContext that has not been started");
}
}
return m_backupCallContext.Data;
}
}
/// <summary>
/// Dictionary of data stored on the call context and shared between derived call contexts
/// </summary>
public ConcurrentDictionary<string, object> SharedData
{
get
{
HttpContext current = HttpContextWrapper.Current;
if (current != null)
{
if (current.Items.ContainsKey(UseBackupKey))
{
return m_backupCallContext.SharedData;
}
return current.Items[DataKey] as ConcurrentDictionary<string, object>;
}
return m_backupCallContext.SharedData;
}
}
/// <summary>
/// Set data to http context
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
public void AddContextValue(string key, object value)
{
if (string.IsNullOrWhiteSpace(key))
{
return;
}
HttpContext current = HttpContextWrapper.Current;
if (current != null && !current.Items.ContainsKey(UseBackupKey))
{
current.Items[key.Trim()] = value;
}
else
{
m_backupCallContext.AddContextValue(key, value);
}
}
/// <summary>
/// Start the call context
/// </summary>
/// <returns>id of the context node</returns>
public Guid? StartCallContext()
{
HttpContext current = HttpContextWrapper.Current;
if (current != null)
{
if (current.Items.ContainsKey(UseBackupKey))
{
return m_backupCallContext.StartCallContext();
}
if (!current.Items.ContainsKey(DataKey))
{
current.Items[DataKey] = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
}
if (current.Items.ContainsKey(DictionaryKey))
{
Stack<Dictionary<string, object>> dataStack = null;
if (current.Items.ContainsKey(ContextStack))
{
dataStack = current.Items[ContextStack] as Stack<Dictionary<string, object>>;
}
else
{
dataStack = new Stack<Dictionary<string, object>>();
current.Items[ContextStack] = dataStack;
}
dataStack.Push(current.Items[DictionaryKey] as Dictionary<string, object>);
}
current.Items[DictionaryKey] = null;
}
else
{
return m_backupCallContext.StartCallContext();
}
return null;
}
/// <summary>
/// End the call context
/// </summary>
/// <param name="threadId">id of the thread on which to end the context</param>
/// <param name="nodeId">id of the context node</param>
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
{
HttpContext current = HttpContextWrapper.Current;
if (current != null)
{
if (current.Items.ContainsKey(UseBackupKey))
{
m_backupCallContext.EndCallContext(threadId, nodeId);
return;
}
if (current.Items.ContainsKey(DictionaryKey))
{
current.Items.Remove(DictionaryKey);
if (current.Items.ContainsKey(ContextStack))
{
if (current.Items[ContextStack] is Stack<Dictionary<string, object>> dataStack && dataStack.Count > 0)
{
current.Items[DictionaryKey] = dataStack.Pop();
}
}
}
#if DEBUG
else
{
throw new InvalidOperationException("Attempting to end a HttpCallContext that has not been started.");
}
#endif
}
else
{
m_backupCallContext.EndCallContext(threadId, nodeId);
}
}
/// <summary>
/// Get the existing call context if there is one
/// </summary>
/// <param name="threadId">id of the thread from which to take the context</param>
/// <returns>the call context that is being used</returns>
public ICallContext ExistingCallContext(int? threadId = null)
{
HttpContext current = HttpContextWrapper.Current;
if (current != null)
{
if (current.Items.ContainsKey(UseBackupKey))
{
return m_backupCallContext.ExistingCallContext(threadId);
}
if (current.Items.ContainsKey(DictionaryKey))
{
return this;
}
return null;
}
return m_backupCallContext.ExistingCallContext(threadId);
}
/// <summary>
/// Move data to backup context
/// </summary>
public void MoveCurrentContextToBackup()
{
HttpContext current = HttpContextWrapper.Current;
if (current != null && m_backupCallContext is LogicalCallContext)
{
IDictionary<string, object> dictionary = GetData(current);
ConcurrentDictionary<string, object> sharedDictionary = GetSharedData(current);
// Start call context on the backup context
int threadId = Thread.CurrentThread.ManagedThreadId;
Guid? nodeId = m_backupCallContext.StartCallContext();
foreach (KeyValuePair<string, object> item in dictionary)
{
m_backupCallContext.Data.Add(item);
}
foreach (KeyValuePair<string, object> item in sharedDictionary)
{
m_backupCallContext.SharedData.AddOrUpdate(item.Key, _ => item.Value, (_, __) => item.Value);
}
// Mark this context as copied over to backup
// This will result in a fallthrough to backup context
current.Items[UseBackupKey] = Tuple.Create(threadId, nodeId);
}
}
/// <summary>
/// Retrieve data from backup context
/// </summary>
public void RetrieveCurrentContextFromBackup()
{
HttpContext current = HttpContextWrapper.Current;
if (current != null && m_backupCallContext is LogicalCallContext)
{
// Do not use Data or SharedData to avoid falling through to BackupContext
IDictionary<string, object> dictionary = GetData(current);
ConcurrentDictionary<string, object> sharedDictionary = GetSharedData(current);
foreach (KeyValuePair<string, object> item in m_backupCallContext.Data)
{
dictionary[item.Key] = item.Value;
}
foreach (KeyValuePair<string, object> item in m_backupCallContext.SharedData)
{
sharedDictionary.AddOrUpdate(item.Key, _ => item.Value, (_, __) => item.Value);
}
// End call context on the backup context
Tuple<int, Guid?> contextKey = current.Items[UseBackupKey] as Tuple<int, Guid?>;
m_backupCallContext.EndCallContext(contextKey?.Item1, contextKey?.Item2);
// Unmark this context
current.Items.Remove(UseBackupKey);
}
}
private static IDictionary<string, object> GetData(HttpContext currentContext)
{
Dictionary<string, object> dictionary = null;
if (currentContext.Items.ContainsKey(DictionaryKey))
{
dictionary = currentContext.Items[DictionaryKey] as Dictionary<string, object>;
}
if (dictionary == null)
{
dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
currentContext.Items[DictionaryKey] = dictionary;
}
return dictionary;
}
private static ConcurrentDictionary<string, object> GetSharedData(HttpContext currentContext)
{
ConcurrentDictionary<string, object> sharedDictionary = null;
if (currentContext.Items.ContainsKey(DataKey))
{
sharedDictionary = currentContext.Items[DataKey] as ConcurrentDictionary<string, object>;
}
if (sharedDictionary == null)
{
sharedDictionary = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
currentContext.Items[DataKey] = sharedDictionary;
}
return sharedDictionary;
}
/// <summary>
/// Key used to signify that the context should fall through and use backup context instead
/// </summary>
private const string UseBackupKey = "ThreadCallContext.UseBackup";
/// <summary>
/// Key used to store data shared between multiple related contexts
/// </summary>
private const string DataKey = "ThreadCallContext.Data";
/// <summary>
/// Key used to store data on the underlying context
/// </summary>
private const string DictionaryKey = "ThreadCallContext.Dictionary";
/// <summary>
/// Key used to store nested call contexts
/// </summary>
private const string ContextStack = "ThreadCallContext.Stack";
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.AspNetCore.Http;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Http context wrapper
/// </summary>
public class HttpContextWrapper
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="contextAccessor">Http context accessor</param>
public static void Configure(IHttpContextAccessor contextAccessor)
{
s_contextAccessor = Code.ExpectsArgument(contextAccessor, nameof(contextAccessor), TaggingUtilities.ReserveTag(0x23817743 /* tag_96x3d */));
}
/// <summary>
/// The current http context
/// </summary>
public static HttpContext Current => s_contextAccessor?.HttpContext;
/// <summary>
/// Http context accessor
/// </summary>
private static IHttpContextAccessor s_contextAccessor;
}
}

Просмотреть файл

@ -0,0 +1,381 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.Logging;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Context data stored on LogicalCallContext
/// </summary>
public class LogicalCallContext : ICallContext
{
private static readonly AsyncLocal<ConcurrentDictionary<string, object>> s_dataAsyncLocal = new AsyncLocal<ConcurrentDictionary<string, object>>();
private static readonly AsyncLocal<IImmutableDictionary<string, object>> s_dictionaryAsyncLocal = new AsyncLocal<IImmutableDictionary<string, object>>();
private static readonly AsyncLocal<IImmutableStack<IImmutableDictionary<string, object>>> s_stackAsyncLocal = new AsyncLocal<IImmutableStack<IImmutableDictionary<string, object>>>();
public LogicalCallContext(IMachineInformation machineInformation)
{
MachineInformation = machineInformation;
}
/// <summary> Gets or sets the context data </summary>
protected ConcurrentDictionary<string, object> ContextData
{
get => s_dataAsyncLocal.Value;
set => s_dataAsyncLocal.Value = value;
}
/// <summary> Gets or sets the context dictionary </summary>
protected IImmutableDictionary<string, object> ContextDictionary
{
get => s_dictionaryAsyncLocal.Value;
set => s_dictionaryAsyncLocal.Value = value;
}
/// <summary> Gets or sets the context stack </summary>
protected IImmutableStack<IImmutableDictionary<string, object>> ContextStack
{
get => s_stackAsyncLocal.Value;
set => s_stackAsyncLocal.Value = value;
}
/// <summary>
/// Dictionary of data stored on the call context
/// </summary>
public IDictionary<string, object> Data
{
get
{
if (ContextData == null)
{
throw new InvalidOperationException("Attempting to access data for a LogicalCallContext that has not been started");
}
IImmutableDictionary<string, object> dictionary = ContextDictionary;
if (dictionary == null)
{
dictionary = new SerializableImmutableDictionary<string, object>(ImmutableDictionary<string, object>.Empty.WithComparers(StringComparer.Ordinal));
ContextDictionary = dictionary;
}
return new LogicalCallContextDictionary(this, dictionary);
}
}
/// <summary>
/// Dictionary of data stored on the call context and shared between derived call contexts
/// </summary>
public ConcurrentDictionary<string, object> SharedData
{
get
{
if (ContextData == null)
{
throw new InvalidOperationException("Attempting to access shared data for a LogicalCallContext that has not been started");
}
return ContextData;
}
}
/// <summary>
/// Set data to http context
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
public void AddContextValue(string key, object value)
{
}
/// <summary>
/// Start the call context
/// </summary>
/// <returns>id of the context node</returns>
public Guid? StartCallContext()
{
if (ContextData == null)
{
ContextData = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
}
if (ContextDictionary != null)
{
IImmutableStack<IImmutableDictionary<string, object>> dataStack = null;
if (ContextStack != null)
{
dataStack = ContextStack;
}
else
{
dataStack = new SerializableImmutableStack<IImmutableDictionary<string, object>>(ImmutableStack<IImmutableDictionary<string, object>>.Empty);
ContextStack = dataStack;
}
ContextStack = dataStack.Push(ContextDictionary);
}
ContextDictionary = new SerializableImmutableDictionary<string, object>(ImmutableDictionary<string, object>.Empty.WithComparers(StringComparer.Ordinal));
return null;
}
/// <summary>
/// End the call context
/// </summary>
/// <param name="threadId">id of the thread on which to end the context</param>
/// <param name="nodeId">id of the context node</param>
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
{
if (ContextDictionary != null)
{
ContextDictionary = null;
if (ContextStack != null)
{
IImmutableStack<IImmutableDictionary<string, object>> dataStack = ContextStack;
if (dataStack != null && !dataStack.IsEmpty)
{
ContextDictionary = dataStack.Peek();
ContextStack = dataStack.Pop();
}
}
}
else if (!s_testEnvironment.Value)
{
ULSLogging.LogTraceTag(0x23817742 /* tag_96x3c */, Categories.Infrastructure, Levels.Warning,
"Attempting to end a LogicalCallContext that has not been started.");
}
else
{
throw new InvalidOperationException("Attempting to end a LogicalCallContext that has not been started.");
}
}
public static IMachineInformation MachineInformation { get; set; }
/// <summary>
/// Current environment
/// </summary>
private static readonly Lazy<bool> s_testEnvironment = new Lazy<bool>(() =>
MachineInformation.IsPrivateDeployment,
LazyThreadSafetyMode.PublicationOnly);
/// <summary>
/// Get the existing call context if there is one
/// </summary>
/// <param name="threadId">id of the thread from which to take the context</param>
/// <returns>the call context that is being used</returns>
public ICallContext ExistingCallContext(int? threadId = null)
{
if (ContextDictionary != null)
{
return this;
}
return null;
}
/// <summary>
/// Mutable dictionary which ensures that all changes are persisted correctly
/// </summary>
private class LogicalCallContextDictionary : IDictionary<string, object>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="context">Instance of logical context to set dictionary</param>
/// <param name="innerDictionary">Items storage</param>
public LogicalCallContextDictionary(LogicalCallContext context, IImmutableDictionary<string, object> innerDictionary)
{
m_context = context;
m_innerDictionary = innerDictionary;
}
/// <summary>
/// Items storage
/// </summary>
private IImmutableDictionary<string, object> m_innerDictionary;
/// <summary>
/// Returns an enumerator that iterates through the immutable dictionary.
/// </summary>
/// <returns></returns>
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => m_innerDictionary.GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through the immutable dictionary.
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Adds an element with the provided key and value to the immutable dictionary.
/// </summary>
/// <param name="item"></param>
public void Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
/// <summary>
/// Clears this instance.
/// </summary>
public void Clear()
{
IImmutableDictionary<string, object> dict = m_innerDictionary = m_innerDictionary.Clear();
m_context.ContextDictionary = dict;
}
/// <summary>
/// Determines whether this immutable dictionary contains the specified key/value pair.
/// </summary>
/// <param name="item"></param>
public bool Contains(KeyValuePair<string, object> item) => m_innerDictionary.Contains(item);
/// <summary>
/// Copies the elements of the dictionary to an array, starting at a particular array index.
/// </summary>
/// <param name="array">Array</param>
/// <param name="arrayIndex">Array index</param>
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
((IDictionary<string, object>)m_innerDictionary).CopyTo(array, arrayIndex);
}
/// <summary>
/// Removes an item from the dictionary
/// </summary>
/// <param name="item">Item to remove</param>
/// <returns>True if items was removed from the dictionary</returns>
public bool Remove(KeyValuePair<string, object> item) => Remove(item.Key);
/// <summary>
/// Gets the number of elements contained in the dictionary
/// </summary>
public int Count => m_innerDictionary.Count;
/// <summary>
/// Gets a value indicating whether the dictionary is read only
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Determines whether the dictinoary contains an element with the specified key.
/// </summary>
/// <param name="key">Key</param>
/// <returns>True if dictionary contains specified key</returns>
public bool ContainsKey(string key) => m_innerDictionary.ContainsKey(key);
/// <summary>
/// Adds an element with the provided key and value to the Dictionary
/// </summary>
/// <param name="key">Key</param>
/// <param name="value">Value</param>
public void Add(string key, object value)
{
m_innerDictionary = m_innerDictionary.SetItem(key, value);
m_context.ContextDictionary = m_innerDictionary;
}
/// <summary>
/// Removes the element with the specified key from the dictionary
/// </summary>
/// <param name="key">Key</param>
/// <returns>True if item was removed</returns>
public bool Remove(string key)
{
bool result = ContainsKey(key);
if (!result)
{
return false;
}
m_innerDictionary = m_innerDictionary.Remove(key);
m_context.ContextDictionary = m_innerDictionary;
return true;
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">Key</param>
/// <param name="value">Value</param>
/// <returns>True if key exisited in the dictionary</returns>
public bool TryGetValue(string key, out object value) => m_innerDictionary.TryGetValue(key, out value);
/// <summary>
/// Gets or sets the element with the specified key.
/// </summary>
/// <param name="key">Key to set or get</param>
/// <returns>Value associated with the key</returns>
public object this[string key]
{
get
{
return m_innerDictionary[key];
}
set
{
Add(key, value);
}
}
/// <summary>
/// Collection containing keys of the dictionary
/// </summary>
public ICollection<string> Keys => m_innerDictionary.Keys.ToList();
/// <summary>
/// Collection containing values of the dictionary
/// </summary>
public ICollection<object> Values => m_innerDictionary.Values.ToList();
private readonly LogicalCallContext m_context;
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
<Description>Contains common classes for Omex libraries in AspNetCore.</Description>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.ServiceModel" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpVersionCore)" />
<PackageReference Include="System.Threading.Tasks" Version="$(SystemThreadingTasks)"/>
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
<PackageReference Include="System.ServiceModel.Primitives" Version="$(SystemServiceModelPrimitivesVersionCore)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,186 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ServiceModel;
using Microsoft.Omex.System.Context;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Context data specific to the current call connected with an OperationContext (WCF)
/// </summary>
public class OperationCallContext : ICallContext
{
/// <summary>
/// Used when there is no operation call context
/// </summary>
private readonly ICallContext m_backupCallContext = new ThreadCallContext();
/// <summary>
/// Dictionary of data stored on the call context
/// </summary>
public IDictionary<string, object> Data
{
get
{
OperationContext context = OperationContext.Current;
if (context != null)
{
Stack<Dictionary<string, object>> dataStack;
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
{
Dictionary<string, object> data = dataStack.Peek();
if (data == null)
{
data = new Dictionary<string, object>();
dataStack.Pop();
dataStack.Push(data);
}
return data;
}
else
{
throw new InvalidOperationException("Attempting to access data for an OperationCallContext that has not been started");
}
}
return m_backupCallContext.Data;
}
}
/// <summary>
/// Dictionary of data stored on the call context and shared between derived call contexts
/// </summary>
public ConcurrentDictionary<string, object> SharedData
{
get
{
OperationContext context = OperationContext.Current;
if (context != null)
{
return s_sharedContextData.GetOrAdd(context, _ => new ConcurrentDictionary<string, object>(StringComparer.Ordinal));
}
return m_backupCallContext.SharedData;
}
}
/// <summary>
/// Add data to context
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
public void AddContextValue(string key, object value)
{
}
/// <summary>
/// Start the call context
/// </summary>
/// <returns>id of the context node</returns>
public Guid? StartCallContext()
{
OperationContext context = OperationContext.Current;
if (context != null)
{
Stack<Dictionary<string, object>> dataStack = s_contextData.GetOrAdd(context, (operationContext) => new Stack<Dictionary<string, object>>());
dataStack.Push(null);
}
else
{
return m_backupCallContext.StartCallContext();
}
return null;
}
/// <summary>
/// End the call context
/// </summary>
/// <param name="threadId">id of the thread on which to end the context</param>
/// <param name="nodeId">id of the context node</param>
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
{
OperationContext context = OperationContext.Current;
if (context != null)
{
Stack<Dictionary<string, object>> dataStack;
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
{
dataStack.Pop();
if (dataStack.Count == 0)
{
s_contextData.TryRemove(context, out dataStack);
ConcurrentDictionary<string, object> _;
s_sharedContextData.TryRemove(context, out _);
}
return;
}
else
{
throw new InvalidOperationException("Attempting to end an OperationCallContext that has not been started");
}
}
m_backupCallContext.EndCallContext(threadId, nodeId);
}
/// <summary>
/// Get the existing call context if there is one
/// </summary>
/// <param name="threadId">id of the thread from which to take the context</param>
/// <returns>the call context that is being used</returns>
public ICallContext ExistingCallContext(int? threadId = null)
{
OperationContext context = OperationContext.Current;
if (context != null)
{
Stack<Dictionary<string, object>> dataStack;
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
{
return this;
}
return null;
}
return m_backupCallContext.ExistingCallContext(threadId);
}
/// <summary>
/// Data stored per operation context
/// </summary>
/// <remarks>Concurrency level is set to 100 instead of the default 4*number of processors. This gives a more
/// granular lock for processors using high contention. The initial capacity is set to avoid capacity to grow
/// which is expensive.
/// </remarks>
private static readonly ConcurrentDictionary<OperationContext, Stack<Dictionary<string, object>>> s_contextData =
new ConcurrentDictionary<OperationContext, Stack<Dictionary<string, object>>>(100, 1000);
/// <summary>
/// Data stored per operation context and shared between all child contexts
/// </summary>
/// <remarks>Concurrency level is set to 100 instead of the default 4*number of processors. This gives a more
/// granular lock for processors using high contention. The initial capacity is set to avoid capacity to grow
/// which is expensive.
/// </remarks>
private static readonly ConcurrentDictionary<OperationContext, ConcurrentDictionary<string, object>> s_sharedContextData =
new ConcurrentDictionary<OperationContext, ConcurrentDictionary<string, object>>(100, 1000);
}
}

Просмотреть файл

@ -0,0 +1,259 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Serializable implementation of the ImmutableDictionary
/// </summary>
/// <typeparam name="TKey">Key type</typeparam>
/// <typeparam name="TValue">Value type</typeparam>
[Serializable]
public class SerializableImmutableDictionary<TKey, TValue> : MarshalByRefObject, IImmutableDictionary<TKey, TValue>, ISerializable, IDeserializationCallback
{
[XmlIgnore]
[NonSerialized]
private ImmutableDictionary<TKey, TValue> m_store;
/// <summary>
/// Stores serilization info received in the constructor until
/// the deserilization callback is called and the object can be deserilized
/// </summary>
[XmlIgnore]
[NonSerialized]
private SerializationInfo m_serializationInfo;
private ImmutableDictionary<TKey, TValue> Store => m_store;
/// <summary>
/// Constructor
/// </summary>
/// <param name="store">Data store</param>
public SerializableImmutableDictionary(ImmutableDictionary<TKey, TValue> store)
{
Code.ExpectsArgument(store, nameof(store), TaggingUtilities.ReserveTag(0x23817723 /* tag_96x29 */));
m_store = store;
}
/// <summary>
/// Constructor
/// </summary>
public SerializableImmutableDictionary()
{
m_store = ImmutableDictionary<TKey, TValue>.Empty;
}
/// <summary>
/// Constructor used for deserialization.
/// </summary>
/// <param name="info">Serialization info</param>
/// <param name="context">Serialization context - unused</param>
/// <remarks>Stores serialization info in a field to allow deserialization callback to complete deserialization</remarks>
protected SerializableImmutableDictionary(SerializationInfo info, StreamingContext context)
{
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817740 /* tag_96x3a */));
m_serializationInfo = info;
}
/// <summary>
/// Returns an enumerator that iterates through the immutable dictionary.
/// </summary>
/// <returns>Enumerator</returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => Store.GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through the immutable dictionary.
/// </summary>
/// <returns>Enumerator</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets the number of key/value pairs in the immutable dictionary.
/// </summary>
/// <returns>Number of key/value pairs in the immutable dictionary</returns>
public int Count => Store.Count;
/// <summary>
/// Determines whether this immutable dictionary contains the specified key/value pair.
/// </summary>
/// <param name="key">Key</param>
/// <returns>True if dictionary contains the key</returns>
public bool ContainsKey(TKey key) => Store.ContainsKey(key);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">Key</param>
/// <param name="value">Value</param>
/// <returns>The value associated with the specified key</returns>
public bool TryGetValue(TKey key, out TValue value) => Store.TryGetValue(key, out value);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">Key</param>
/// <returns>Value associated with the specified key</returns>
public TValue this[TKey key] => Store[key];
/// <summary>
/// Gets the keys in the immutable dictionary.
/// </summary>
public IEnumerable<TKey> Keys => Store.Keys;
/// <summary>
/// Gets the values in the immutable dictionary.
/// </summary>
public IEnumerable<TValue> Values => Store.Values;
/// <summary>
/// Retrieves an empty immutable dictionary that has the same ordering and key/value comparison rules as this dictionary instance.
/// </summary>
/// <returns></returns>
public IImmutableDictionary<TKey, TValue> Clear()
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Clear());
/// <summary>
/// Adds an element with the specified key and value to the immutable dictionary.
/// </summary>
/// <param name="key">Key</param>
/// <param name="value">Value</param>
/// <returns>Immutable dictionary with the added value</returns>
public IImmutableDictionary<TKey, TValue> Add(TKey key, TValue value)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Add(key, value));
/// <summary>
/// Adds the specified key/value pairs to the immutable dictionary.
/// </summary>
/// <param name="pairs">Key/Value pairs</param>
/// <returns>Immutable dictionary with the pairs added</returns>
public IImmutableDictionary<TKey, TValue> AddRange(IEnumerable<KeyValuePair<TKey, TValue>> pairs)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.AddRange(pairs));
/// <summary>
/// Sets the specified key and value in the immutable dictionary, possibly overwriting an existing value for the key.
/// </summary>
/// <param name="key">Key</param>
/// <param name="value">Value</param>
/// <returns>Immutable dictionary with the item set</returns>
public IImmutableDictionary<TKey, TValue> SetItem(TKey key, TValue value)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.SetItem(key, value));
/// <summary>
/// Sets the specified key/value pairs in the immutable dictionary, possibly overwriting existing values for the keys.
/// </summary>
/// <param name="items">Items to set</param>
/// <returns>Immutable dictionary with those items set</returns>
public IImmutableDictionary<TKey, TValue> SetItems(IEnumerable<KeyValuePair<TKey, TValue>> items)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.SetItems(items));
/// <summary>
/// Removes the elements with the specified keys from the immutable dictionary.
/// </summary>
/// <param name="keys">Keys</param>
/// <returns>Immutable dictionary with the keys removed</returns>
public IImmutableDictionary<TKey, TValue> RemoveRange(IEnumerable<TKey> keys)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.RemoveRange(keys));
/// <summary>
/// Removes the element with the specified key from the immutable dictionary.
/// </summary>
/// <param name="key">Key</param>
/// <returns>Immutable dictionary with the key removed</returns>
public IImmutableDictionary<TKey, TValue> Remove(TKey key)
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Remove(key));
/// <summary>
/// Determines whether this immutable dictionary contains the specified key/value pair.
/// </summary>
/// <param name="pair">Pair to check</param>
/// <returns>True if dictionary contains the pair</returns>
public bool Contains(KeyValuePair<TKey, TValue> pair) => Store.Contains(pair);
/// <summary>
/// Determines whether this dictionary contains a specified key.
/// </summary>
/// <param name="equalKey">Key</param>
/// <param name="actualKey">Value</param>
/// <returns>True if the key is in the dictionary</returns>
public bool TryGetKey(TKey equalKey, out TKey actualKey) => Store.TryGetKey(equalKey, out actualKey);
/// <summary>
/// Populates a SerializationInfo with the data needed to serialize the target object.
/// </summary>
/// <param name="info">Serialization info</param>
/// <param name="context">Serialization context - unused</param>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817741 /* tag_96x3b */));
info.AddValue(SerializedFieldName, m_store.ToArray(), typeof(KeyValuePair<TKey, TValue>[]));
info.AddValue(KeyComparerFieldName, m_store.KeyComparer, typeof(IEqualityComparer<TKey>));
info.AddValue(ValueComparerFieldName, m_store.ValueComparer, typeof(IEqualityComparer<TValue>));
}
/// <summary>
/// Runs when the entire object graph has been deserialized.
/// </summary>
/// <param name="sender">Unused</param>
public void OnDeserialization(object sender)
{
KeyValuePair<TKey, TValue>[] deserializedValues =
(KeyValuePair<TKey, TValue>[])m_serializationInfo.GetValue(SerializedFieldName, typeof(KeyValuePair<TKey, TValue>[]));
IEqualityComparer<TKey> keyComparer =
(IEqualityComparer<TKey>)m_serializationInfo.GetValue(KeyComparerFieldName, typeof(IEqualityComparer<TKey>));
IEqualityComparer<TValue> valueComparer =
(IEqualityComparer<TValue>)m_serializationInfo.GetValue(ValueComparerFieldName, typeof(IEqualityComparer<TValue>));
m_store = deserializedValues.ToImmutableDictionary().WithComparers(keyComparer, valueComparer);
m_serializationInfo = null;
}
private const string SerializedFieldName = "Store";
private const string KeyComparerFieldName = "KeyComparer";
private const string ValueComparerFieldName = "ValueComparer";
}
}

Просмотреть файл

@ -0,0 +1,153 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.AspNetCore
{
/// <summary>
/// Serializable implementation of the ImmutableStack
/// </summary>
/// <typeparam name="TValue">Value type</typeparam>
[Serializable]
public class SerializableImmutableStack<TValue> : MarshalByRefObject, IImmutableStack<TValue>, ISerializable, IDeserializationCallback
{
[XmlIgnore]
[NonSerialized]
private IImmutableStack<TValue> m_store;
/// <summary>
/// Stores serilization info received in the constructor until
/// the deserilization callback is called and the object can be deserilized
/// </summary>
[XmlIgnore]
[NonSerialized]
private SerializationInfo m_serializationInfo;
private IImmutableStack<TValue> Store => m_store;
/// <summary>
/// Constructor
/// </summary>
/// <param name="store">Data store</param>
public SerializableImmutableStack(IImmutableStack<TValue> store)
{
Code.ExpectsArgument(store, nameof(store), TaggingUtilities.ReserveTag(0x23817720 /* tag_96x26 */));
m_store = store;
}
/// <summary>
/// Constructor
/// </summary>
public SerializableImmutableStack()
{
m_store = ImmutableStack<TValue>.Empty;
}
/// <summary>
/// Constructor used for deserialization.
/// </summary>
/// <remarks>Stores serialization info in a field to allow deserialization callback to complete deserialization</remarks>
protected SerializableImmutableStack(SerializationInfo info, StreamingContext context)
{
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817721 /* tag_96x27 */));
m_serializationInfo = info;
}
/// <summary>
/// Returns an enumerator that iterates through the immutable stack.
/// </summary>
/// <returns>Enumerator</returns>
public IEnumerator<TValue> GetEnumerator() => Store.GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through the immutable stack.
/// </summary>
/// <returns>Enumerator</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Removes all objects from the immutable stack.
/// </summary>
/// <returns>Empty stack</returns>
public IImmutableStack<TValue> Clear()
=> new SerializableImmutableStack<TValue>(Store.Clear());
/// <summary>
/// Inserts an object at the top of the immutable stack and returns the new stack.
/// </summary>
/// <param name="value">Value to push</param>
/// <returns>Immutable stack with the value at the top</returns>
public IImmutableStack<TValue> Push(TValue value)
=> new SerializableImmutableStack<TValue>(Store.Push(value));
/// <summary>
/// Removes the specified element from the immutable stack and returns the stack after the removal.
/// </summary>
/// <returns>Immutable stack with the top element removed</returns>
public IImmutableStack<TValue> Pop()
=> new SerializableImmutableStack<TValue>(Store.Pop());
/// <summary>
/// Returns the object at the top of the stack without removing it.
/// </summary>
/// <returns>Object at the top of the stack</returns>
public TValue Peek() => Store.Peek();
/// <summary>
/// Gets a value that indicates whether this instance of the immutable stack is empty.
/// </summary>
public bool IsEmpty => Store.IsEmpty;
/// <summary>
/// Populates a SerializationInfo with the data needed to serialize the target object.
/// </summary>
/// <param name="info">Serialization info</param>
/// <param name="context">Serialization context - unused</param>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817722 /* tag_96x28 */));
info.AddValue(SerializedFieldName, new Stack<TValue>(m_store), typeof(Stack<TValue>));
}
/// <summary>
/// Runs when the entire object graph has been deserialized.
/// </summary>
/// <param name="sender">Unused</param>
public void OnDeserialization(object sender)
{
Stack<TValue> deserializedValues =
(Stack<TValue>)m_serializationInfo.GetValue(SerializedFieldName, typeof(Stack<TValue>));
m_store = ImmutableStack.CreateRange(deserializedValues);
m_serializationInfo = null;
}
private const string SerializedFieldName = "Store";
}
}

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System.Collections.Generic;
using System.Text;
using Microsoft.Omex.System.Configuration.DataSets;
using Microsoft.Omex.System.Data;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration.DataSets
{
/// <summary>

Просмотреть файл

@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Omex.System.Configuration.DataSets;
using Microsoft.Omex.System.Data;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration.DataSets
{
/// <summary>

Просмотреть файл

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using System.IO;
@ -12,8 +10,6 @@ using Microsoft.Omex.System.Data.FileSystem;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration
{
/// <summary>

Просмотреть файл

@ -1,13 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using Microsoft.Omex.System.Data.FileSystem;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem
{
/// <summary>

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using Microsoft.Omex.System.Data;
using Microsoft.Omex.System.UnitTests.Shared.Configuration;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Data
{
/// <summary>

Просмотреть файл

@ -1,13 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System.IO;
using System.Text;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem
{
/// <summary>

Просмотреть файл

@ -8,12 +8,10 @@
<ItemGroup Condition="'$(TargetFramework)' != '$(NetStandardVersion)'">
<PackageReference Include="xunit.assert" Version="$(XunitAssertVersion)" />
<PackageReference Include="xunit.core" Version="$(XunitCoreVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetStandardVersion)'">
<PackageReference Include="xunit.assert" Version="$(XunitAssertVersionCore)" />
<PackageReference Include="xunit.core" Version="$(XunitCoreVersionCore)" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\System\Microsoft.Omex.System.csproj" />

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
{
/// <summary>
/// Bridge for tests accessing internal classes.
/// </summary>
public static class TestHooks
{
/// <summary>
/// Default scope name.
/// </summary>
public static string DefaultTimedScopeName = TimedScopes.DefaultScope.Name;
/// <summary>
/// Creates a default timed scope.
/// </summary>
/// <param name="scopeLogger"></param>
/// <param name="replayEventConfigurator"></param>
/// <param name="initialResult">Initial scope result.</param>
/// <param name="startScope">Start scope implicitly.</param>
/// <returns>The created scope.</returns>
public static TimedScope CreateDefaultTimedScope(
ITimedScopeLogger scopeLogger = null,
IReplayEventConfigurator replayEventConfigurator = null,
bool? initialResult = null,
bool startScope = true)
{
CorrelationData data = new CorrelationData();
return UnitTestTimedScopes.DefaultScope.Create(data, new UnitTestMachineInformation(), initialResult, startScope, scopeLogger, replayEventConfigurator);
}
/// <summary>
/// Creates an instance of the test counters timed scope.
/// </summary>
/// <param name="initialResult">Initial scope result.</param>
/// <param name="startScope">Start scope implicitly.</param>
/// <param name="scopeLogger"></param>
/// <param name="replayEventConfigurator"></param>
/// <returns>The created scope.</returns>
public static TimedScope CreateTestCountersUnitTestTimedScope(
bool? initialResult = null,
bool startScope = true,
ITimedScopeLogger scopeLogger = null,
IReplayEventConfigurator replayEventConfigurator = null)
{
CorrelationData data = new CorrelationData();
return UnitTestTimedScopes.TestCounters.UnitTest.Create(data, new UnitTestMachineInformation(), initialResult, startScope, scopeLogger, replayEventConfigurator);
}
}
}

Просмотреть файл

@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Omex.System.TimedScopes;
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
{
/// <summary>
/// Represents TimedScope event for easier validating TimedScopes in UTs
/// </summary>
public class TimedScopeLogEvent
{
/// <summary>
/// Scope name
/// </summary>
public string Name { get; }
/// <summary>
/// Scope subtype
/// </summary>
public string SubType { get; }
/// <summary>
/// Scope metadata
/// </summary>
public string MetaData { get; }
/// <summary>
/// User hash
/// </summary>
public string UserHash { get; }
/// <summary>
/// Duration
/// </summary>
public TimeSpan Duration { get; }
/// <summary>
/// Scope result
/// </summary>
public TimedScopeResult Result { get; }
/// <summary>
/// Failure Description (strongly typed)
/// </summary>
public Enum FailureDescriptionEnum { get; }
/// <summary>
/// Failure Description
/// </summary>
public string FailureDescription => FailureDescriptionEnum?.ToString();
/// <summary>
/// Constructor
/// </summary>
/// <param name="name">Scope name</param>
/// <param name="subtype">Subtype</param>
/// <param name="metadata">Metadata</param>
/// <param name="result">Scope result</param>
/// <param name="failureDescription">Failure description</param>
/// <param name="userHash">User hash</param>
/// <param name="duration">Duration</param>
public TimedScopeLogEvent(string name, string subtype, string metadata,
TimedScopeResult result, Enum failureDescription, string userHash,
TimeSpan duration)
{
Name = name;
SubType = subtype;
Result = result;
MetaData = metadata;
FailureDescriptionEnum = failureDescription;
UserHash = userHash;
Duration = duration;
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System.CodeDom.Compiler;
using Microsoft.Omex.System.TimedScopes;
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
{
internal static class UnitTestTimedScopes
{
/// <summary>
/// TryGetHash Timed Scope
/// </summary>
public static TimedScopeDefinition DefaultScope => new TimedScopeDefinition("OmexDefaultTimedScope",
"Omex default timed scope");
/// <summary>
/// Test counters
/// </summary>
public static class TestCounters
{
/// <summary>
/// Unit test timed scope
/// </summary>
public static TimedScopeDefinition UnitTest => new TimedScopeDefinition("UnitTest",
"Omex unit test timed scope");
}
}
/// <summary>
/// Timed scopes
/// </summary>
internal static class TimedScopes
{
/// <summary>
/// TryGetHash Timed Scope
/// </summary>
public static TimedScopeDefinition DefaultScope => new TimedScopeDefinition("OmexDefaultTimedScope",
"Tries to retrieve a dictionary of values from Omex Redis Cache.");
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Omex.System.TimedScopes;
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
{
/// <summary>
/// Unit test implementation of ITimedScopeLogger
/// </summary>
public class UnitTestTimedScopeLogger : ITimedScopeLogger
{
private readonly ConcurrentQueue<TimedScopeLogEvent> m_events = new ConcurrentQueue<TimedScopeLogEvent>();
/// <summary>
/// Logs the scope start
/// </summary>
/// <param name="scope">Scope to log</param>
public void LogScopeStart(TimedScope scope)
{
}
/// <summary>
/// Logs the scope end
/// </summary>
/// <param name="scope">Scope to log</param>
/// <param name="data">Correlation data</param>
public void LogScopeEnd(TimedScope scope, CorrelationData data)
{
if (scope == null)
{
return;
}
TimedScopeLogEvent evt = new TimedScopeLogEvent(scope.Name, scope.SubType,
scope.MetaData, scope.Result, scope.FailureDescription,
data.Data(TimedScopeDataKeys.InternalOnly.UserHash),
scope.Duration ?? TimeSpan.Zero);
m_events.Enqueue(evt);
}
/// <summary>
/// Gets the last scope event of given name logged
/// </summary>
/// <param name="scopeName">Scope name</param>
/// <returns>The last timed scope log event, null if there is no timed scope event</returns>
public TimedScopeLogEvent LastTimedScopeEvent(string scopeName)
{
return m_events.LastOrDefault(evt => string.Equals(evt.Name, scopeName, StringComparison.Ordinal));
}
/// <summary>
/// Gets the single scope event of given name logged
/// </summary>
/// <param name="scopeName">Scope name</param>
/// <returns>The single timed scope log event, null if there is no or more than one scope event of given name</returns>
public TimedScopeLogEvent SingleTimedScopeEvent(string scopeName)
{
return m_events.SingleOrDefault(evt => string.Equals(evt.Name, scopeName, StringComparison.Ordinal));
}
/// <summary>
/// Logged events
/// </summary>
public IEnumerable<TimedScopeLogEvent> Events => m_events;
}
}

Просмотреть файл

@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
#endregion
namespace Microsoft.Omex.System.Caching
{
/// <summary>

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Concurrent;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
#endregion
namespace Microsoft.Omex.System.Caching
{
/// <summary>

Просмотреть файл

@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using Microsoft.Omex.System.Data;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
#endregion
namespace Microsoft.Omex.System.Configuration.DataSets
{
/// <summary>

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Globalization;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
#endregion
namespace Microsoft.Omex.System.Configuration.DataSets
{
/// <summary>

Просмотреть файл

@ -1,14 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using Microsoft.Omex.System.Data;
#endregion
namespace Microsoft.Omex.System.Configuration.DataSets
{
/// <summary>

Просмотреть файл

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using Directives
using System;
using System.Collections;
using System.Collections.Concurrent;
@ -11,8 +9,6 @@ using System.Collections.Specialized;
using System.Linq;
using System.Threading;
#endregion
namespace Microsoft.Omex.System.Context
{
/// <summary>

Просмотреть файл

@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
#endregion
namespace Microsoft.Omex.System.Data.FileSystem
{
/// <summary>

Просмотреть файл

@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
#endregion
namespace Microsoft.Omex.System.Data
{
/// <summary>

Просмотреть файл

@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
#endregion
namespace Microsoft.Omex.System.Data
{
/// <summary>

Просмотреть файл

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Globalization;
using System.Security.Cryptography;
@ -10,8 +8,6 @@ using Microsoft.Omex.System.Extensions;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
#endregion
namespace Microsoft.Omex.System.Data
{
/// <summary>

Просмотреть файл

@ -5,6 +5,10 @@
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">$(NetStandardVersion)</TargetFrameworks>
<Description>Contains common classes for Omex libraries.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bond.Core.CSharp" Version="$(BondCoreCSharpVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
</ItemGroup>
<PropertyGroup Label="NuGet Properties">
<Title>Microsoft.Omex.System</Title>
<DevelopmentDependency>True</DevelopmentDependency>

Просмотреть файл

@ -1,14 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using Directives
using System;
using System.Runtime.Serialization;
using Microsoft.Omex.System.Logging;
#endregion
namespace Microsoft.Omex.System.Model.Types
{
/// <summary>

Просмотреть файл

@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
#endregion
namespace Microsoft.Omex.System.Monads
{
/// <summary>

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Threading;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
#endregion
namespace Microsoft.Omex.System.Monads
{
/// <summary>

Просмотреть файл

@ -0,0 +1,310 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Correlation class, handle in-memory correlation data for logging purposes.
/// </summary>
/// <remarks>
/// Allows correlation data to be set and retrieved on a calling context. ULS logging (when hooked up)
/// only allows for the correlation id to be retrieved, this allows for additional data to be retrieved.
/// Correlation data by default only applies to the current thread and is lost when a thread hop
/// occurs (be it ASP.NET underlying thread hop or an explicit thread change). By calling
/// CorrelationStart with cloned CorrelationData, the correlation can be transferred to the new
/// thread.
/// </remarks>
public sealed class Correlation
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="correlationHandler">correlation handler, used to store correlation data in underlying logging system</param>
/// <param name="callContextManager">call context manager, to handle context operations</param>
/// <param name="machineInformation">machine information</param>
public Correlation(ICorrelationStorage correlationHandler, ICallContextManager callContextManager, IMachineInformation machineInformation)
{
Code.ExpectsArgument(correlationHandler, nameof(correlationHandler), TaggingUtilities.ReserveTag(0x2381771b /* tag_96x21 */));
Code.ExpectsArgument(callContextManager, nameof(callContextManager), TaggingUtilities.ReserveTag(0x2381771c /* tag_96x22 */));
Code.ExpectsArgument(machineInformation, nameof(machineInformation), TaggingUtilities.ReserveTag(0x2381771d /* tag_96x23 */));
CorrelationHandler = correlationHandler;
CallContextManager = callContextManager;
MachineInformation = machineInformation;
}
/// <summary>
/// Current call context
/// </summary>
private ICallContext CallContext => CallContextManager.CallContextHandler(MachineInformation);
/// <summary>
/// Event raised when the correlation has started
/// </summary>
public event EventHandler<CorrelationEventArgs> CorrelationStarted;
/// <summary>
/// Event raised when correlation data has been added
/// </summary>
public event EventHandler<CorrelationEventArgs> CorrelationDataAdded;
/// <summary>
/// Event raised when the correlation has ended
/// </summary>
public event EventHandler<CorrelationEventArgs> CorrelationEnded;
/// <summary>
/// Key used to store the correlation context in the current HttpContext
/// </summary>
private const string CorrelationContextKey = "ULSLogging.correlations";
/// <summary>
/// Key used to store if direct logging should be used
/// </summary>
private const string LoggingContextKey = "ULSLogging.directLogging";
/// <summary>
/// Get the current correlation if set
/// </summary>
/// <exception cref="InvalidCastException">Data exists on the CallContext
/// but it isn't a valid CorrelationData object.</exception>
public CorrelationData CurrentCorrelation
{
get
{
return GetCorrelationData();
}
private set
{
ICallContext existingContext = CallContext.ExistingCallContext();
if (existingContext != null)
{
if (value != null)
{
existingContext.Data[CorrelationContextKey] = value;
}
else
{
existingContext.Data.Remove(CorrelationContextKey);
}
}
}
}
/// <summary>
/// Should log directly to the underlying log handler
/// </summary>
public bool ShouldLogDirectly
{
get
{
CorrelationData correlation = CurrentCorrelation;
if (correlation?.ShouldLogDirectly == true)
{
return true;
}
ICallContext existingContext = CallContext.ExistingCallContext();
if (existingContext != null)
{
object data;
return existingContext.Data.TryGetValue(LoggingContextKey, out data) && data != null;
}
return false;
}
set
{
CorrelationData correlation = CurrentCorrelation;
if (correlation != null)
{
correlation.ShouldLogDirectly = value;
}
else
{
ICallContext existingContext = CallContext.ExistingCallContext();
if (existingContext != null)
{
if (value)
{
existingContext.Data[LoggingContextKey] = new object();
}
else
{
existingContext.Data.Remove(LoggingContextKey);
}
}
}
}
}
/// <summary>
/// The correlation handler used to store the correlation
/// </summary>
/// <remarks>
/// Calling methods on this interface bypasses the
/// event handlers for correlation and commits the correlation
/// directly to the correlation handler.
/// </remarks>
public ICorrelationStorage CorrelationHandler { get; }
/// <summary>
/// The correlation handler used to store the correlation
/// </summary>
/// <remarks>
/// Calling methods on this interface bypasses the
/// event handlers for correlation and commits the correlation
/// directly to the correlation handler.
/// </remarks>
public ICallContextManager CallContextManager { get; }
/// <summary>
/// The correlation handler used to store the correlation
/// </summary>
/// <remarks>
/// Calling methods on this interface bypasses the
/// event handlers for correlation and commits the correlation
/// directly to the correlation handler.
/// </remarks>
public IMachineInformation MachineInformation { get; }
/// <summary>
/// Start a correlation
/// </summary>
/// <param name="data">correlation to set on the thread, null for default (new) correlation</param>
public void CorrelationStart(CorrelationData data)
{
data = CorrelationHandler.CorrelationStart(data);
if (data != null)
{
data.ParentCorrelation = CurrentCorrelation;
if (data.ParentCorrelation == null)
{
data.ShouldLogDirectly = ShouldLogDirectly;
}
CurrentCorrelation = data;
// Note: Creating a copy of the event handler to avoid multi-threaded race conditions
// Not creating extension methods to avoid unnecessary creating of arguments if not set
EventHandler<CorrelationEventArgs> correlationStarted = CorrelationStarted;
if (correlationStarted != null)
{
correlationStarted(this, new CorrelationEventArgs(data));
}
}
}
/// <summary>
/// Add data to the current correlation
/// </summary>
/// <remarks>If there is no current correlation, starts a new correlation</remarks>
/// <param name="key">key (name) of the correlation</param>
/// <param name="value">value of the added correlation data</param>
public void CorrelationAdd(string key, string value)
{
Code.ExpectsNotNullOrWhiteSpaceArgument(key, nameof(key), TaggingUtilities.ReserveTag(0x2381771e /* tag_96x24 */));
Code.ExpectsNotNullOrWhiteSpaceArgument(value, nameof(value), TaggingUtilities.ReserveTag(0x2381771f /* tag_96x25 */));
CorrelationData data = CurrentCorrelation;
if (data == null)
{
CorrelationStart(null);
data = CurrentCorrelation;
}
if (data != null)
{
string oldData = data.Data(key);
CorrelationHandler.CorrelationAdd(key, value, data);
EventHandler<CorrelationEventArgs> correlationDataAdded = CorrelationDataAdded;
if (correlationDataAdded != null)
{
correlationDataAdded(this, new CorrelationEventArgs(data, key, oldData));
}
}
}
/// <summary>
/// Get correlation data from a thread
/// </summary>
/// <param name="threadId">Id of the thread</param>
/// <returns>Correlation data</returns>
private CorrelationData GetCorrelationData(int? threadId = null)
{
ICallContext existingContext = CallContext.ExistingCallContext(threadId);
if (existingContext != null)
{
object data;
if (existingContext.Data.TryGetValue(CorrelationContextKey, out data))
{
return data as CorrelationData;
}
}
return null;
}
/// <summary>
/// End the correlation
/// </summary>
/// <param name="id">Id of the thread</param>
/// <param name="invokeEventHandler">Should we invoke the correlation ended event handler</param>
public void CorrelationEnd(int? id = null, bool invokeEventHandler = true)
{
CorrelationData correlationData = GetCorrelationData(id);
if (correlationData != null)
{
CurrentCorrelation = correlationData.ParentCorrelation;
CorrelationHandler.CorrelationEnd(correlationData);
if (invokeEventHandler)
{
EventHandler<CorrelationEventArgs> correlationEnded = CorrelationEnded;
if (correlationEnded != null)
{
correlationEnded(this, new CorrelationEventArgs(correlationData));
}
}
}
}
/// <summary>
/// Clear all correlations from the current thread
/// </summary>
public void CorrelationClear()
{
while (CurrentCorrelation != null)
{
CorrelationEnd();
}
}
}
}

Просмотреть файл

@ -0,0 +1,562 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Specialized;
using System.Threading;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Correlation data used for logging purposes
/// </summary>
public sealed class CorrelationData
{
/// <summary>
/// Dictionary storing additional correlation data, only used when
/// non-standard correlation data (AddData) is used.
/// </summary>
/// <remarks>ListDictionary choosen since the correlation data
/// should be small (less than 10 items).</remarks>
private ListDictionary m_additionalData;
/// <summary>
/// Parent correlation for nested correlations.
/// </summary>
private CorrelationData m_parentCorrelation;
/// <summary>
/// Should log directly to the underlying log handler
/// </summary>
/// <remarks>
/// This forces any logging with this correlation data to bypass
/// any logging handlers.
/// </remarks>
private bool m_shouldLogDirectly;
/// <summary>
/// Is this correlation running in ULS replay mode
/// </summary>
/// <remarks>
/// When first set, all previous ULS traces are replayed under the tag_repl event Id, then
/// subsequent events are also replayed until the end of the correlation; all events are written at Info level
/// </remarks>
private bool m_ulsReplay;
/// <summary>
/// Information if previously cached ULS events for this correlation already been replayed
/// </summary>
private int m_cachedUlsEventsReplayed;
/// <summary>
/// Event sequence number for guarenteed logging order within a correlation
/// </summary>
private long m_eventSequenceNumber;
/// <summary>
/// Log event cache reference for triggering replay events.
/// </summary>
private readonly ILogEventCache m_logEventCache;
/// <summary>
/// Root scopes have this sequence number (with high probability),
/// non-root scopes have always a larger sequence number.
/// </summary>
public const int RootScopeBoundary = 1000;
/// <summary>
/// Transaction Id key
/// </summary>
public const string TransactionIdKey = "TransactionId";
/// <summary>
/// The ID of the correlation
/// </summary>
/// <remarks>Seting this value only modifies the in memory representation
/// of the correlation data, to modify the actual correlation data, use
/// Correlation.CorrelationStart(CorrelationData correlation).</remarks>
public Guid VisibleId { get; set; }
/// <summary>
/// Transaction id for the correlation
/// </summary>
/// <remarks>If non-zero, indicates that the correlation data
/// is used as part of a specific monitored transaction</remarks>
public uint TransactionId { get; set; }
/// <summary>
/// Id of a registered ITransactionContext to apply
/// </summary>
public uint TransactionContextId { get; set; }
/// <summary>
/// Step number for a multi-step Transaction
/// </summary>
public uint TransactionStep { get; set; }
/// <summary>
/// Is this a fallback call
/// </summary>
public bool IsFallbackCall { get; set; }
/// <summary>
/// Tracks the depth of the correlation through proxying layers
/// Original caller is Depth 0, subsequent hops are +1 each time
/// </summary>
public uint CallDepth { get; set; }
/// <summary>
/// Gets or sets the user hash for the current correlation. Only call the setter directly
/// if you already have a hashed value, use ComputeUserHash to compute the hash
/// of a user string and set it.
/// This is set by:
/// 1. External web service calls where we have direct access to information
/// such as user PUID (e.g. POSA provisioning calls)
/// 2. Microsoft.Office.Web.OfficeMarketplace.WebSite.RequestProcessHandler.StartRequest.
/// 3. Microsoft.Office.Web.OfficeMarketplace.WebService.RequestProcessHandler.StartRequest.
/// Its value is read and logged by <see cref="TimedScope.EndScope"/>.
/// </summary>
/// <remarks>Used to count timed scope hits by user.</remarks>
public string UserHash { get; set; }
/// <summary>
/// Current event sequence number for this correlation
/// </summary>
public long EventSequenceNumber
{
get
{
return m_eventSequenceNumber;
}
set
{
m_eventSequenceNumber = value;
}
}
/// <summary>
/// All correlation data keys
/// </summary>
/// <remarks>Null if there are no correlation data keys</remarks>
public ICollection Keys
{
get
{
if (m_additionalData == null)
{
return null;
}
else
{
return m_additionalData.Keys;
}
}
}
/// <summary>
/// Does the correlation have any correlation data
/// </summary>
public bool HasData
{
get
{
return m_additionalData == null ? false : m_additionalData.Count > 0;
}
}
/// <summary>
/// Parent correlation for nested correlation
/// </summary>
/// <exception cref="InvalidOperationException">Attempting to set
/// the parent correlation when it is already set, or setting
/// the parent correlation causes circular reference</exception>
/// <remarks>Implemented as a parent node rather than stack
/// as most correlation will never have a parent correlation.</remarks>
public CorrelationData ParentCorrelation
{
get
{
return m_parentCorrelation;
}
set
{
if (this.ParentCorrelation != null)
{
throw new InvalidOperationException("Parent correlation is already set.");
}
CorrelationData temp = value;
while (temp != null)
{
if (temp == this)
{
throw new InvalidOperationException("Cannot set the parent correlation as it will cause circular reference.");
}
temp = temp.ParentCorrelation;
}
m_parentCorrelation = value;
}
}
/// <summary>
/// Allocates the next sequence number to be used for logging purposes
/// </summary>
/// <returns>Incremented sequence number unique within the correlation</returns>
public long NextEventSequenceNumber()
{
return Interlocked.Increment(ref m_eventSequenceNumber);
}
/// <summary>
/// Allocates the next sequence number to be used for logging the
/// start of a new scope.
/// </summary>
/// <returns>Incremented sequence number unique within the correlation</returns>
public long NextScopeStartSequenceNumber()
{
// Make the root scope of a correlation identifiable
// by giving it a sequence of at least 'RootScopeBoundary'.
// Note that this is only a heuristic, but avoids excessive code changes.
if (m_eventSequenceNumber < RootScopeBoundary)
{
m_eventSequenceNumber = RootScopeBoundary;
return m_eventSequenceNumber;
}
return Interlocked.Increment(ref m_eventSequenceNumber);
}
/// <summary>
/// Should log directly to the underlying log handler
/// </summary>
public bool ShouldLogDirectly
{
get
{
return m_shouldLogDirectly || (ParentCorrelation != null && ParentCorrelation.ShouldLogDirectly);
}
set
{
m_shouldLogDirectly = value;
}
}
/// <summary>
/// Determines if ULS events should be replayed for the current correlation.
/// Setting this to true will only affect events logged afterwards, see ReplayPreviouslyCachedUlsEvents below.
/// One-way switch, cannot be set back to false.
/// </summary>
public bool ShouldReplayUls
{
get
{
return m_ulsReplay;
}
set
{
// this flag is a one way switch
if (value == false)
{
return;
}
m_ulsReplay = true;
}
}
/// <summary>
/// Have previously cached ULS events for this correlation already been replayed?
/// </summary>
public bool CachedUlsEventsReplayed
{
get
{
return m_cachedUlsEventsReplayed == 1;
}
}
/// <summary>
/// Replay previously cached ULS events for this correlation now.
/// This should be called along with ShouldReplayUls = true to replay previous events in the correlation.
/// Will only run once per correlation.
/// </summary>
public void ReplayPreviouslyCachedUlsEvents()
{
int originalValue = Interlocked.CompareExchange(ref m_cachedUlsEventsReplayed, 1, 0);
if (originalValue == 0)
{
m_logEventCache.ReplayCorrelation(VisibleId);
}
}
/// <summary>
/// Get the correlation data for a key
/// </summary>
/// <param name="key">key to get data for</param>
/// <returns>string containing the data, null if it doesn't exist</returns>
public string Data(string key)
{
if (m_additionalData == null || key == null)
{
return null;
}
return (string)m_additionalData[key];
}
/// <summary>
/// Add correlation data
/// </summary>
/// <param name="key">key to get data for</param>
/// <param name="value">value of data</param>
/// <remarks>Calling this method only modifies the in memory representation
/// of the correlation data, to modify the actual correlation data, use
/// Correlation.CorrelationAdd(string key, string value).</remarks>
/// <exception cref="ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null</exception>
public void AddData(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (string.Equals(key, TransactionIdKey, StringComparison.OrdinalIgnoreCase))
{
// Caching the TransactionId specifically as it is likely to be queried for
// repeatedly
uint transactionId;
if (uint.TryParse(value, out transactionId))
{
TransactionId = transactionId;
}
else
{
TransactionId = 0;
}
}
if (m_additionalData == null)
{
m_additionalData = new ListDictionary(StringComparer.OrdinalIgnoreCase);
}
m_additionalData[key] = value;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="cachedUlsEventsReplayed">Have previously cached ULS events for this correlation already been replayed?</param>
public CorrelationData(bool cachedUlsEventsReplayed = false)
{
m_cachedUlsEventsReplayed = cachedUlsEventsReplayed ? 1 : 0;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="logEventCache">Log event cache</param>
/// <param name="cachedUlsEventsReplayed">Have previously cached ULS events for this correlation already been replayed?</param>
public CorrelationData(ILogEventCache logEventCache, bool cachedUlsEventsReplayed = false)
{
Code.ExpectsArgument(logEventCache, nameof(logEventCache), TaggingUtilities.ReserveTag(0x2381771a /* tag_96x20 */));
m_logEventCache = logEventCache;
m_cachedUlsEventsReplayed = cachedUlsEventsReplayed ? 1 : 0;
}
}
/// <summary>
/// Correlation data extension methods
/// </summary>
public static class CorrelationDataExtensionMethods
{
/// <summary>
/// Clone the correlation
/// </summary>
/// <param name="existingCorrelation">existing correlation</param>
/// <returns>cloned correlation, null if existing correlation is null</returns>
/// <remarks>Added as a extension method instead of a method on the
/// object to allow for cloning of the data when it is null, i.e.
/// CorrelationData data = null; data.Clone(); will not throw exception.</remarks>
public static CorrelationData Clone(this CorrelationData existingCorrelation)
{
if (existingCorrelation == null)
{
return null;
}
CorrelationData newCorrelation = new CorrelationData(existingCorrelation.CachedUlsEventsReplayed);
newCorrelation.ShouldLogDirectly = existingCorrelation.ShouldLogDirectly;
newCorrelation.ShouldReplayUls = existingCorrelation.ShouldReplayUls;
newCorrelation.VisibleId = existingCorrelation.VisibleId;
newCorrelation.CallDepth = existingCorrelation.CallDepth;
newCorrelation.UserHash = existingCorrelation.UserHash;
newCorrelation.EventSequenceNumber = existingCorrelation.EventSequenceNumber;
newCorrelation.IsFallbackCall = existingCorrelation.IsFallbackCall;
newCorrelation.TransactionContextId = existingCorrelation.TransactionContextId;
newCorrelation.TransactionId = existingCorrelation.TransactionId;
newCorrelation.TransactionStep = existingCorrelation.TransactionStep;
if (existingCorrelation.HasData)
{
bool copiedSuccessfully = SpinWait.SpinUntil(() =>
{
bool success = false;
try
{
foreach (object key in existingCorrelation.Keys)
{
string keystring = (string)key;
newCorrelation.AddData(keystring, existingCorrelation.Data(keystring));
}
success = true;
}
catch (InvalidOperationException)
{
}
return success;
}, 10);
if (!copiedSuccessfully)
{
// Add a marker to the correlation data indicating it is not complete
newCorrelation.AddData("Error", "Failed to clone correlation data.");
}
}
return newCorrelation;
}
/// <summary>
/// Creates a new TransactionData object from the current CorrelationData object.
/// </summary>
/// <param name="correlationData">The CorrelationData object to copy when creating the TransactionData object.</param>
/// <returns>A new TransactionData copy of the supplied CorrelationData or null if the current CorrelationData is null.</returns>
public static TransactionData ToTransactionData(this CorrelationData correlationData)
{
if (correlationData == null)
{
return null;
}
return new TransactionData()
{
CallDepth = correlationData.CallDepth,
CorrelationId = correlationData.VisibleId,
EventSequenceNumber = correlationData.EventSequenceNumber,
TransactionContextId = correlationData.TransactionContextId,
TransactionId = correlationData.TransactionId,
TransactionStep = correlationData.TransactionStep,
UserHash = correlationData.UserHash,
IsFallbackCall = correlationData.IsFallbackCall
};
}
/// <summary>
/// Clear the test type resolver
/// </summary>
/// <param name="correlationData">correlation data</param>
/// <returns>test type resolver in use</returns>
public static IStructuralTypeResolver ClearTestTypeResolver(this CorrelationData correlationData)
{
if (correlationData == null)
{
return null;
}
string uniqueId = correlationData.Data(TypeResolverKey);
if (!string.IsNullOrWhiteSpace(uniqueId))
{
IStructuralTypeResolver testResolver;
if (s_testResolvers.TryRemove(uniqueId, out testResolver))
{
return testResolver;
}
}
return null;
}
/// <summary>
/// Get the current test type resolver
/// </summary>
/// <param name="correlationData">correlation data</param>
/// <returns>test type resolver in use</returns>
public static IStructuralTypeResolver GetTestTypeResolver(this CorrelationData correlationData)
{
if (correlationData == null)
{
return null;
}
string uniqueId = correlationData.Data(TypeResolverKey);
if (!string.IsNullOrWhiteSpace(uniqueId))
{
IStructuralTypeResolver testResolver;
if (s_testResolvers.TryGetValue(uniqueId, out testResolver))
{
return testResolver;
}
}
return null;
}
private const string TypeResolverKey = "Test.TypeResolver";
private static readonly ConcurrentDictionary<string, IStructuralTypeResolver> s_testResolvers = new ConcurrentDictionary<string, IStructuralTypeResolver>();
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Event arguments used with correlation events
/// </summary>
public class CorrelationEventArgs : EventArgs
{
/// <summary>
/// Correlation data
/// </summary>
public CorrelationData Correlation { get; private set; }
/// <summary>
/// If the correlation data has changed, the key of the data that changed
/// </summary>
public string ChangedKey { get; private set; }
/// <summary>
/// If the correlation data has changed, the old value of the data
/// </summary>
public string OldData { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="data">correlation data</param>
/// <param name="key">changed key</param>
/// <param name="oldData">the previous value of the data</param>
public CorrelationEventArgs(CorrelationData data, string key, string oldData)
{
Correlation = data;
ChangedKey = key;
OldData = oldData;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="data">correlation data</param>
public CorrelationEventArgs(CorrelationData data)
{
Correlation = data;
ChangedKey = null;
OldData = null;
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface for handling call context
/// </summary>
public interface ICallContextManager
{
/// <summary>
/// Current call context
/// </summary>
ICallContext CallContextHandler(IMachineInformation machineInformation);
/// <summary>
/// An overridable call context for unit test purposes
/// </summary>
ICallContext CallContextOverride { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface for handling correlation
/// </summary>
public interface ICorrelationStorage
{
/// <summary>
/// Start correlation
/// </summary>
/// <param name="data">existing correlation data</param>
/// <returns>correlation data</returns>
CorrelationData CorrelationStart(CorrelationData data);
/// <summary>
/// Add correlation data
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
/// <param name="data">existing correlation data</param>
void CorrelationAdd(string key, string value, CorrelationData data);
/// <summary>
/// End correlation
/// </summary>
/// <param name="data">correlation data</param>
void CorrelationEnd(CorrelationData data);
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.Omex.System.Logging;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface for a log event cache with log replay logic.
/// </summary>
public interface ILogEventCache
{
/// <summary>
/// Next process-wide sequence number for event caching
/// </summary>
long NextSequenceNumber { get; }
/// <summary>
/// Add a log event to the cache and replay if required.
/// </summary>
/// <param name="logEventArgs">Log event</param>
void AddLogEvent(LogEventArgs logEventArgs);
/// <summary>
/// Query the events for a specified correlation
/// </summary>
/// <param name="correlation">Target correlation Id</param>
/// <returns>list of trace events</returns>
IReadOnlyList<LogEventArgs> GetEventsForCorrelation(Guid correlation);
/// <summary>
/// Query the trace for a specified correlation
/// </summary>
/// <param name="correlation">Target correlation Id</param>
/// <returns>list of trace events</returns>
IReadOnlyList<LogEntry> GetTraceForCorrelation(Guid correlation);
/// <summary>
/// Query the trace for entries starting at a specified sequence number
/// </summary>
/// <param name="sequenceNumber">Start number</param>
/// <returns>enumeration of trace events</returns>
IEnumerable<LogEntry> GetTraceFromSequenceNumber(long sequenceNumber);
/// <summary>
/// Replay a specified correlation
/// </summary>
/// <param name="correlation">Target correlation Id</param>
void ReplayCorrelation(Guid correlation);
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface to resolve a type to an instance, with parent inheritance
/// </summary>
public interface IStructuralTypeResolver : ITypeResolver
{
/// <summary>
/// Parent resolver
/// </summary>
IStructuralTypeResolver Parent { get; set; }
/// <summary>
/// Resolve type 'TFrom' to an instance of type 'TFrom' in context of the current call
/// </summary>
/// <typeparam name="TFrom">type to resolve</typeparam>
/// <returns>instance of type 'TFrom'</returns>
TFrom ResolveInCallContext<TFrom>()
where TFrom : class;
/// <summary>
/// Is the type 'TFrom' known to the type resolver in context of the current call
/// </summary>
/// <typeparam name="TFrom">type to resolve</typeparam>
/// <param name="instance">the stored value, if one exists</param>
/// <returns>true if a type is known to the resover, false otherwise</returns>
bool DoesTypeExistInCallContext<TFrom>(out TFrom instance)
where TFrom : class;
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface for logging timed scopes
/// </summary>
public interface ITimedScopeLogger
{
/// <summary>
/// Logs the scope start
/// </summary>
/// <param name="scope">Scope to log</param>
void LogScopeStart(TimedScope scope);
/// <summary>
/// Logs the scope end
/// </summary>
/// <param name="scope">Scope to log</param>
/// <param name="data">Correlation data</param>
void LogScopeEnd(TimedScope scope, CorrelationData data);
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright(c) Microsoft Corporation.All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Interface to resolve a type to an instance
/// </summary>
public interface ITypeResolver
{
/// <summary>
/// Resolve type 'TFrom' to an instance of type 'TFrom'
/// </summary>
/// <typeparam name="TFrom">type to resolve</typeparam>
/// <returns>instance of type 'TFrom'</returns>
TFrom Resolve<TFrom>()
where TFrom : class;
/// <summary>
/// Is the type 'TFrom' known to the type resolver
/// </summary>
/// <typeparam name="TFrom">type to resolve</typeparam>
/// <param name="instance">the stored value, if one exists</param>
/// <returns>true if a type is known to the resover, false otherwise</returns>
bool DoesTypeExist<TFrom>(out TFrom instance)
where TFrom : class;
}
}

Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Data Contract for a single ULS Log Entry
/// </summary>
[DataContract]
[Serializable]
public sealed class LogEntry
{
/// <summary>
/// Logging category name
/// </summary>
[DataMember]
public string CategoryName { get; set; }
/// <summary>
/// CorrelationId this entry is part of
/// </summary>
[DataMember]
public Guid CorrelationId { get; set; }
/// <summary>
/// ULS Message
/// </summary>
[DataMember]
public string Message { get; set; }
/// <summary>
/// ULS TagId in numeric format
/// </summary>
[DataMember]
public uint NumericTagId { get; set; }
/// <summary>
/// Unique Sequence Number for this event in the context of the host service type
/// </summary>
[DataMember]
public long SequenceNumber { get; set; }
/// <summary>
/// Server Timestamp (Stopwatch.GetTimestamp) when event occurred
/// </summary>
[DataMember]
public long ServerTimestamp { get; set; }
/// <summary>
/// Server UTC Time when event occurred
/// </summary>
[DataMember]
public DateTime ServerTimeUtc { get; set; }
/// <summary>
/// ULS TagId in string format
/// </summary>
[DataMember]
public string TagId { get; set; }
/// <summary>
/// ULS trace level
/// </summary>
[DataMember]
public string TraceLevel { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// In-memory correlation handler
/// </summary>
public class MemoryCorrelationHandler : ICorrelationStorage
{
/// <summary>
/// Start correlation
/// </summary>
/// <param name="data">existing correlation data</param>
/// <returns>correlation data</returns>
public CorrelationData CorrelationStart(CorrelationData data)
{
if (data == null)
{
data = new CorrelationData();
data.VisibleId = Guid.NewGuid();
}
return data;
}
/// <summary>
/// Add correlation data
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
/// <param name="data">existing correlation data</param>
public void CorrelationAdd(string key, string value, CorrelationData data)
{
if (data != null)
{
data.AddData(key, value);
}
}
/// <summary>
/// End correlation
/// </summary>
/// <param name="data">correlation data</param>
public void CorrelationEnd(CorrelationData data)
{
}
}
}

Просмотреть файл

@ -0,0 +1,309 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Omex.System.Logging;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Implements a class that captures Performance Diagnostics.
/// </summary>
public class PerfDiagnostics
{
/// <summary>
/// Status of the last timer run
/// </summary>
public bool LastStatus { get; private set; }
/// <summary>
/// Number of cycles used (updated after Stop is called)
/// </summary>
public long CyclesUsed { get; private set; }
/// <summary>
/// Approximate number of milliseconds spent in User Mode
/// </summary>
public decimal UserModeMilliseconds { get; private set; }
/// <summary>
/// Approximate number of milliseconds spent in Kernel Mode
/// </summary>
public decimal KernelModeMilliseconds { get; private set; }
/// <summary>
/// Backing field for number of http requests
/// </summary>
private int m_httpRequests;
/// <summary>
/// Number of HttpRequests
/// </summary>
public int HttpRequestCount
{
get
{
return m_httpRequests;
}
}
/// <summary>
/// Backing field for number of service calls
/// </summary>
private int m_serviceCalls;
/// <summary>
/// Number of service calls
/// </summary>
public int ServiceCallCount
{
get
{
return m_serviceCalls;
}
}
/// <summary>
/// Psuedo handle to the current thread
/// </summary>
private static readonly IntPtr s_currentThreadHandle = NativeMethods.GetCurrentThread();
/// <summary>
/// Parent diagnostics if available
/// </summary>
private readonly PerfDiagnostics m_parent;
/// <summary>
/// Id of the thread being timed
/// </summary>
private uint m_threadId;
/// <summary>
/// Kernel mode ticks at start of timing
/// </summary>
private long m_startKernel;
/// <summary>
/// User mode ticks at start of timing
/// </summary>
private long m_startUser;
/// <summary>
/// Cpu cycles at start of timing
/// </summary>
private ulong m_startCycles;
/// <summary>
/// Constructor
/// </summary>
/// <param name="parent">Parent diagnostics, or null if not available</param>
public PerfDiagnostics(PerfDiagnostics parent = null)
{
m_parent = parent;
}
/// <summary>
/// Reset the state of the Timer
/// </summary>
public void Reset()
{
CyclesUsed = 0;
UserModeMilliseconds = 0;
KernelModeMilliseconds = 0;
LastStatus = false;
m_threadId = uint.MaxValue;
m_httpRequests = 0;
m_serviceCalls = 0;
}
/// <summary>
/// Start the timer
/// </summary>
/// <returns>Success status</returns>
public bool Start()
{
m_threadId = uint.MaxValue;
long creationTime, exitTime;
if (!NativeMethods.GetThreadTimes(s_currentThreadHandle, out creationTime, out exitTime, out m_startKernel, out m_startUser))
{
ULSLogging.LogTraceTag(0x23817716 /* tag_96x2w */, Categories.Infrastructure, Levels.Error, "GetThreadTimes system call failed.");
return false;
}
if (!NativeMethods.QueryThreadCycleTime(s_currentThreadHandle, out m_startCycles))
{
ULSLogging.LogTraceTag(0x23817717 /* tag_96x2x */, Categories.Infrastructure, Levels.Error, "QueryThreadCycleTime system call failed.");
return false;
}
m_threadId = NativeMethods.GetCurrentThreadId();
LastStatus = false;
return true;
}
/// <summary>
/// Get timing metrics since Start was called
/// </summary>
/// <returns>Success status</returns>
public bool Stop()
{
LastStatus = false;
if (NativeMethods.GetCurrentThreadId() != m_threadId)
{
// Thread Id mismatch between Start and Stop.
// This is expected in async methods so not logging anything, would be too much spam.
m_threadId = uint.MaxValue;
return false;
}
m_threadId = uint.MaxValue;
long creationTime, exitTime, kernelModeTime, userModeTime;
if (!NativeMethods.GetThreadTimes(s_currentThreadHandle, out creationTime, out exitTime, out kernelModeTime, out userModeTime))
{
ULSLogging.LogTraceTag(0x23817718 /* tag_96x2y */, Categories.Infrastructure, Levels.Error, "GetThreadTimes system call failed.");
return false;
}
ulong cycles;
if (!NativeMethods.QueryThreadCycleTime(s_currentThreadHandle, out cycles))
{
ULSLogging.LogTraceTag(0x23817719 /* tag_96x2z */, Categories.Infrastructure, Levels.Error, "QueryThreadCycleTime system call failed.");
return false;
}
LastStatus = true;
CyclesUsed += m_startCycles >= cycles ? 0 : (long)(cycles - m_startCycles);
KernelModeMilliseconds += m_startKernel >= kernelModeTime ? 0 : (decimal)(kernelModeTime - m_startKernel) / 10000m;
UserModeMilliseconds += m_startUser >= userModeTime ? 0 : (decimal)(userModeTime - m_startUser) / 10000m;
return true;
}
/// <summary>
/// Suspend Cpu timer tree
/// </summary>
public void Suspend()
{
Stop();
if (m_parent != null)
{
m_parent.Stop();
}
}
/// <summary>
/// Resume Cpu timer tree
/// </summary>
public void Resume()
{
Start();
if (m_parent != null)
{
m_parent.Start();
}
}
/// <summary>
/// Increment the number of Service Calls
/// </summary>
public void IncrementServiceCallCount()
{
if (m_parent != null)
{
m_parent.IncrementServiceCallCount();
}
Interlocked.Increment(ref m_serviceCalls);
}
/// <summary>
/// Increment the number of Http Requests
/// </summary>
public void IncrementHttpRequestCount()
{
if (m_parent != null)
{
m_parent.IncrementHttpRequestCount();
}
Interlocked.Increment(ref m_httpRequests);
}
/// <summary>
/// Private Windows API definitions
/// </summary>
private static class NativeMethods
{
/// <summary>
/// Gets the Id of the current native thread
/// </summary>
/// <returns>Thread Id</returns>
[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();
/// <summary>
/// Gets a psuedo handle to the current native thread
/// </summary>
/// <returns>Psuedo handle, doesn't need to be closed</returns>
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentThread();
/// <summary>
/// Gets Cpu times for the specified native thread, accurate to around 15ms
/// </summary>
/// <param name="thread">Target thread</param>
/// <param name="creationTime">Creation time as 100-nanosecond intervals since January 1, 1601 (UTC)</param>
/// <param name="exitTime">Exit time as 100-nanosecond intervals since January 1, 1601 (UTC)</param>
/// <param name="kernelModeTime">Number of 100-nanosecond intervals thread has spent in Kernel mode</param>
/// <param name="userModeTime">Number of 100-nanosecond intervals thread has spent in User mode</param>
/// <returns>Success status</returns>
[DllImport("kernel32.dll")]
public static extern bool GetThreadTimes(IntPtr thread, out long creationTime, out long exitTime, out long kernelModeTime, out long userModeTime);
/// <summary>
/// Query the current Cpu cycle count for a specified thread
/// </summary>
/// <param name="thread">Target thread</param>
/// <param name="cycles">Number of cycles</param>
/// <returns>Success status</returns>
[DllImport("kernel32.dll")]
public static extern bool QueryThreadCycleTime(IntPtr thread, out ulong cycles);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
{
/// <summary>
/// Configures event replaying when a timed scope ends
/// </summary>
public interface IReplayEventConfigurator
{
/// <summary>
/// Configure event replaying when a timed scope ends
/// </summary>
/// <param name="scope"></param>
void ConfigureReplayEventsOnScopeEnd(TimedScope scope);
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
{
/// <summary>
/// Decides if a timed scope is disabled for event replaying
/// </summary>
public interface IReplayEventDisabledTimedScopes
{
/// <summary>
/// Is the timed scope disabled for event replaying
/// </summary>
/// <param name="scopeDefinition">Scope definition</param>
/// <returns><c>true</c> if the scope is disabled, <c>false</c> otherwise.</returns>
bool IsDisabled(TimedScopeDefinition scopeDefinition);
}
}

Просмотреть файл

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
{
/// <summary>
/// Configures event replaying when a timed scope ends
/// </summary>
public class ReplayEventConfigurator : IReplayEventConfigurator
{
private IReplayEventDisabledTimedScopes DisabledTimedScopes { get; }
private Correlation Correlation { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="disabledTimedScopes">Disabled timed scopes</param>
/// <param name="correlation">Correlation</param>
public ReplayEventConfigurator(IReplayEventDisabledTimedScopes disabledTimedScopes, Correlation correlation)
{
Code.ExpectsArgument(disabledTimedScopes, nameof(disabledTimedScopes), TaggingUtilities.ReserveTag(0x23817714 /* tag_96x2u */));
Code.ExpectsArgument(correlation, nameof(correlation), TaggingUtilities.ReserveTag(0x23817715 /* tag_96x2v */));
DisabledTimedScopes = disabledTimedScopes;
Correlation = correlation;
}
/// <summary>
/// Configure event replaying when a timed scope ends
/// </summary>
/// <param name="scope"></param>
public void ConfigureReplayEventsOnScopeEnd(TimedScope scope)
{
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
if (scope.IsSuccessful ?? false)
{
// assumption is that if any lower level scopes fail that should bubble up to the parent scope; if replay is enabled a previous scope has failed so
// log some telemetry to help us understand these mixed scenarios better / identify error handling bugs
if (currentCorrelation != null && currentCorrelation.ShouldReplayUls)
{
// ASSERTTAG_IGNORE_START
ULSLogging.LogTraceTag(0, Categories.TimingGeneral, Levels.Warning,
"Scope '{0}' succeeded even though a previous scope on this correlation failed.", scope.Name);
// ASSERTTAG_IGNORE_FINISH
}
}
else
{
// flip the replay switch on Scope failure for scenarios where its useful to get a verbose ULS trace in production
if (currentCorrelation != null &&
scope.Result.ShouldReplayEvents() &&
!scope.IsTransaction &&
!scope.ScopeDefinition.OnDemand &&
!scope.DisableVerboseUlsCapture &&
!DisabledTimedScopes.IsDisabled(scope.ScopeDefinition))
{
currentCorrelation.ShouldReplayUls = true;
currentCorrelation.ReplayPreviouslyCachedUlsEvents();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,983 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Threading;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Class for managing timed scopes, writing to performance
/// monitor and loggign to ULS if test transactions are running
/// </summary>
public class TimedScope : IDisposable
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeDefinition">Timed scope definition</param>
/// <param name="scopeLogger">Scope metrics logger</param>
/// <param name="replayEventConfigurator">Replay event configurator</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="correlationData">Correlation data</param>
private TimedScope(TimedScopeDefinition scopeDefinition, CorrelationData correlationData, ITimedScopeLogger scopeLogger,
IReplayEventConfigurator replayEventConfigurator, IMachineInformation machineInformation)
{
Code.ExpectsArgument(scopeDefinition, nameof(scopeDefinition), TaggingUtilities.ReserveTag(0x23817708 /* tag_96x2i */));
Code.ExpectsArgument(scopeLogger, nameof(scopeLogger), TaggingUtilities.ReserveTag(0x23817709 /* tag_96x2j */));
Code.ExpectsArgument(replayEventConfigurator, nameof(replayEventConfigurator), TaggingUtilities.ReserveTag(0x2381770a /* tag_96x2k */));
ScopeDefinition = scopeDefinition;
ScopeLogger = scopeLogger;
ReplayEventConfigurator = replayEventConfigurator;
CorrelationData = correlationData;
MachineInformation = machineInformation;
}
/// <summary>
/// Create a timed scope
/// </summary>
/// <param name="scopeDefinition">Timed scope definition</param>
/// <param name="initialResult">The default result for the scope</param>
/// <param name="customLogger">Use a custom logger for the timed scope</param>
/// <param name="replayEventConfigurator">Replay event configurator</param>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <returns>Newly created scope</returns>
public static TimedScope Create(TimedScopeDefinition scopeDefinition, CorrelationData correlationData, IMachineInformation machineInformation,
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
{
return new TimedScope(scopeDefinition, correlationData, customLogger, replayEventConfigurator, machineInformation)
{
TimedScopeData = correlationData,
RunningTransaction = TransactionMonitor.RunningTransaction(correlationData),
Result = initialResult,
};
}
/// <summary>
/// Deprecated - Start a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <param name="customLogger">Use a custom logger for the timed scope</param>
/// <param name="replayEventConfigurator">Replay event configurator</param>
/// <returns>Newly created scope</returns>
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, TimedScopeResult initialResult = default(TimedScopeResult),
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
=> new TimedScopeDefinition(scopeName).Start(correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
/// <summary>
/// Deprecated - Start a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <returns>Newly created scope</returns>
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, bool? initialResult)
=> new TimedScopeDefinition(scopeName).Start(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult));
/// <summary>
/// Deprecated - Start a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation Data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="description">The description of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <param name="customLogger">Use a custom logger for the timed scope</param>
/// <param name="replayEventConfigurator">Replay event configurator</param>
/// <returns>Newly created scope</returns>
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description,
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
=> new TimedScopeDefinition(scopeName, description).Start(correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
/// <summary>
/// Deprecated - Start a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation Data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="description">The description of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <returns>Newly created scope</returns>
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description, bool? initialResult)
=> new TimedScopeDefinition(scopeName, description).Start(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult));
/// <summary>
/// Deprecated - Create a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="description">The description of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <param name="customLogger">Use a custom logger for the timed scope</param>
/// <param name="replayEventConfigurator">Replay event configurator</param>
/// <returns>newly created scope</returns>
public static TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description,
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
=> new TimedScopeDefinition(scopeName, description).Create(correlationData, machineInformation, initialResult, startScope: false, customLogger: customLogger,
replayEventConfigurator: replayEventConfigurator);
/// <summary>
/// Deprecated - Create a timed scope
/// </summary>
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="scopeName">The name of the timed scope</param>
/// <param name="description">The description of the timed scope</param>
/// <param name="initialResult">The default result for the scope</param>
/// <returns>Newly created scope</returns>
public static TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description, bool? initialResult)
=> new TimedScopeDefinition(scopeName, description).Create(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult), startScope: false);
/// <summary>
/// Gets the current active timed scope, or null if there are none active
/// </summary>
public static TimedScope Current => Scopes?.Peek();
/// <summary>
/// Parent Scope if available
/// </summary>
public TimedScope Parent { get; private set; }
/// <summary>
/// Start the timed scope
/// </summary>
public void Start()
{
if (IsDisposed)
{
ULSLogging.LogTraceTag(0x2381770b /* tag_96x2l */, Categories.TimingGeneral, Levels.Error,
"Attempting to start scope '{0}' that has already been disposed.", Name);
return;
}
if (IsScopeActive)
{
ULSLogging.LogTraceTag(0x2381770c /* tag_96x2m */, Categories.TimingGeneral, Levels.Error,
"Attempting to start scope '{0}' that has already been started.", Name);
return;
}
string metaDataCopy = MetaData;
string subTypeCopy = SubType;
CorrelationData currentCorrelation = CorrelationData;
TimedScopeData = currentCorrelation.Clone() ?? new CorrelationData();
RunningTransaction = TransactionMonitor.RunningTransaction(TimedScopeData);
if (!string.IsNullOrWhiteSpace(metaDataCopy) && string.IsNullOrWhiteSpace(MetaData))
{
MetaData = metaDataCopy;
}
if (!string.IsNullOrWhiteSpace(subTypeCopy) && string.IsNullOrWhiteSpace(SubType))
{
SubType = subTypeCopy;
}
// differentiate scope name when running under a test transaction
if (IsTransaction)
{
NameSuffix = string.Concat(NameSuffix, "::Trx", RunningTransaction.ToString(CultureInfo.InvariantCulture));
}
// differentiate special scopes
if (TimedScopeData.IsFallbackCall)
{
NameSuffix = string.Concat(NameSuffix, "::Fallback");
}
// differentiate scope name for inner (proxied) calls
if (TimedScopeData.CallDepth > 0)
{
NameSuffix = string.Concat(NameSuffix, "::Depth", TimedScopeData.CallDepth.ToString(CultureInfo.InvariantCulture));
if (currentCorrelation != null)
{
// reset call depth so any inner scopes are reported as layer 0 again
currentCorrelation.CallDepth = 0;
}
}
Parent = TimedScope.Current;
IsRoot = Parent == null && TimedScopeData.CallDepth == 0;
StartTick = Stopwatch.GetTimestamp();
IsScopeActive = true;
ScopeLogger.LogScopeStart(this);
PerfDiagnostics = new PerfDiagnostics(Parent != null ? Parent.PerfDiagnostics : null);
PerfDiagnostics.Start();
}
/// <summary>
/// End the timed scope
/// </summary>
public void End(IMachineInformation machineInformation)
{
if (IsDisposed)
{
ULSLogging.LogTraceTag(0x2381770d /* tag_96x2n */, Categories.TimingGeneral, Levels.Error,
"Attempting to end scope '{0}' that has already been disposed.", Name);
return;
}
EndScope(machineInformation);
}
/// <summary>
/// Discard the timed scope, aborting timer and avoiding any data being logged
/// </summary>
public void Discard()
{
if (Interlocked.CompareExchange(ref m_isScopeEnded, 1, 0) == 0)
{
IsScopeActive = false;
}
}
/// <summary>
/// Timed Scope Definition
/// </summary>
public TimedScopeDefinition ScopeDefinition { get; }
/// <summary>
/// Timed Scope Definition
/// </summary>
public IMachineInformation MachineInformation { get; }
/// <summary>
/// The name of the timed scope
/// </summary>
public string Name => string.Concat(ScopeDefinition.Name, NameSuffix);
/// <summary>
/// Timed Scope Name suffix
/// </summary>
private string NameSuffix { get; set; } = string.Empty;
/// <summary>
/// Subtype
/// </summary>
public string SubType
{
set { AddLoggingValue(TimedScopeDataKeys.SubType, value, overrideValue: true); }
get { return TimedScopeData.Data(TimedScopeDataKeys.SubType); }
}
/// <summary>
/// Metadata
/// </summary>
public string MetaData
{
set { AddLoggingValue(TimedScopeDataKeys.ObjectMetaData, value, overrideValue: true); }
get { return TimedScopeData.Data(TimedScopeDataKeys.ObjectMetaData); }
}
/// <summary>
/// Unique Id for this timed scope
/// </summary>
public Guid InstanceId { get; } = Guid.NewGuid();
/// <summary>
/// Description of the timed scope
/// </summary>
public string Description => ScopeDefinition.Description;
/// <summary>
/// Is this the outermost scope in a call stack
/// </summary>
public bool IsRoot { get; private set; }
/// <summary>
/// The Performance diagnostics instance
/// </summary>
public PerfDiagnostics PerfDiagnostics { get; private set; }
/// <summary>
/// Timed Scope metrics logger
/// </summary>
private ITimedScopeLogger ScopeLogger { get; }
/// <summary>
/// Replay event configurator
/// </summary>
private IReplayEventConfigurator ReplayEventConfigurator { get; }
public CorrelationData CorrelationData { get; }
/// <summary>
/// Id of the counterset
/// </summary>
public uint Id
{
get
{
unchecked
{
return (uint)Name.GetHashCode();
}
}
}
/// <summary>
/// Explicit duration property
/// </summary>
public TimeSpan? Duration { get; set; }
/// <summary>
/// Starting tick of the timed scope
/// </summary>
public long StartTick { get; private set; }
/// <summary>
/// Ending tick of the timed scope
/// </summary>
public long EndTick { get; private set; }
/// <summary>
/// Scope duration in milliseconds
/// </summary>
public double DurationInMilliseconds
{
get
{
if (Duration.HasValue)
{
return Duration.Value.TotalMilliseconds;
}
else if (EndTick != 0)
{
return Math.Round((EndTick - StartTick) * 1000d / Frequency, 2);
}
else
{
return Math.Round((Stopwatch.GetTimestamp() - StartTick) * 1000d / Frequency, 2);
}
}
}
/// <summary>
/// The frequency for the timed scope
/// </summary>
public static long Frequency
{
get
{
return Stopwatch.Frequency;
}
}
/// <summary>
/// Failure description
/// </summary>
/// <remarks>
/// This field is only exposed in ULS logs and the MDS timed scope stream.
/// </remarks>
public Enum FailureDescription { get; set; }
/// <summary>
/// Result of the timed scope.
/// </summary>
/// <remarks>
/// You should set this property instead of IsSuccessful property. You shouldn't set both of them as one rewrites the other.
/// Setting this property is strongly prefered over authoring new heuristic rules.
/// </remarks>
public TimedScopeResult Result { get; set; }
/// <summary>
/// Is the timed scope successful
/// </summary>
/// <remarks>
/// Setting this property is obsoleted and shouldn't be used in new code. You should set Result property directly. This property internally
/// sets the Result property anyway. We do not remove the property setter because of the backward compatibility.
/// </remarks>
public bool? IsSuccessful
{
get
{
return Result.IsSuccessful();
}
set
{
Result = ConvertBoolResultToTimedScopeResult(value);
}
}
/// <summary>
/// Is running transaction
/// </summary>
public bool IsTransaction => RunningTransaction != Transactions.None;
/// <summary>
/// Scope ended flag - 0 not ended, 1 ended
/// </summary>
private int m_isScopeEnded;
/// <summary>
/// Is the timed scope active
/// </summary>
private bool m_isScopeActive;
/// <summary>
/// UserHash override
/// </summary>
private string m_userHashOverride = null;
/// <summary>
/// Sets the user hash override
/// </summary>
/// <param name="userHash">User hash</param>
public void OverrideUserHash(string userHash)
{
if (!Code.ValidateNotNullOrWhiteSpaceArgument(userHash, nameof(userHash), TaggingUtilities.ReserveTag(0x2381770e /* tag_96x2o */)))
{
return;
}
m_userHashOverride = userHash;
}
/// <summary>
/// Is the timed scope active
/// </summary>
public bool IsScopeActive
{
get
{
return m_isScopeActive;
}
private set
{
if (value != m_isScopeActive)
{
ModifyActiveScopes(value);
}
m_isScopeActive = value;
}
}
/// <summary>
/// Gets or sets a flag indicating if Verbose ULS capture should be disabled if this scope fails
/// </summary>
public bool DisableVerboseUlsCapture { get; set; }
/// <summary>
/// Abort the timer
/// </summary>
/// <param name="success">true if action should be considered successful</param>
public void AbortTimer(bool? success = null)
{
IsScopeActive = false;
if (success.HasValue)
{
IsSuccessful = success;
}
}
/// <summary>
/// Adds the given key and value to the context of the timed scope
/// </summary>
/// <param name="key">Key of the item to be added.</param>
/// <param name="value">Value of the item to be added.</param>
/// <param name="overrideValue">Whether the value should be overriden</param>
public void AddLoggingValue(string key, string value, bool overrideValue = false)
{
if (string.IsNullOrWhiteSpace(key))
{
ULSLogging.LogTraceTag(0x2381770f /* tag_96x2p */, Categories.TimingGeneral, Levels.Error,
"Empty or null key detected when attempting to add an entry in the timed scope data dictionary. Key : '{0}'.",
key ?? "<NULL>");
return;
}
string existingValue = TimedScopeData.Data(key);
if (existingValue != null && !overrideValue)
{
ULSLogging.LogTraceTag(0x23817710 /* tag_96x2q */, Categories.TimingGeneral, Levels.Warning,
"Timed scope data dictionary already contains key '{0}' with value '{1}'.", key, existingValue);
}
else
{
TimedScopeData.AddData(key, value ?? string.Empty);
}
}
/// <summary>
/// Set success value from a passed in status code
/// </summary>
/// <param name="statusCode">status code to set success value from</param>
/// <remarks>500 or above is considered failure,
/// all other values considered success. However, if the success value is already set,
/// calling this method does not override the success value.
/// The status code is stored on the scope in any case.</remarks>
public void SetSuccessFromStatusCode(HttpStatusCode statusCode)
{
if (!IsSuccessful.HasValue)
{
IsSuccessful = (uint)statusCode < (uint)HttpStatusCode.InternalServerError;
}
if (string.IsNullOrWhiteSpace(TimedScopeData.Data(TimedScopeDataKeys.InternalOnly.StatusCode)))
{
AddLoggingValue(TimedScopeDataKeys.InternalOnly.StatusCode, statusCode.ToString());
}
}
/// <summary>
/// Is the timed scope disposed
/// </summary>
private bool IsDisposed { get; set; }
/// <summary>
/// End the timed scope explicitly
/// </summary>
private void EndScope(IMachineInformation machineInformation)
{
if (Interlocked.CompareExchange(ref m_isScopeEnded, 1, 0) == 0)
{
EndTick = Stopwatch.GetTimestamp();
PerfDiagnostics?.Stop();
LogEnd(machineInformation);
IsScopeActive = false;
}
}
/// <summary>
/// Logs the scope end to ULS
/// </summary>
private void LogEnd(IMachineInformation machineInformation)
{
if (!IsSuccessful.HasValue)
{
ULSLogging.LogTraceTag(0x23817711 /* tag_96x2r */, Categories.TimingGeneral, Levels.Warning,
"Result not set for scope {0}. Considered as SystemError", Name);
Result = TimedScopeResult.SystemError;
FailureDescription = InternalFailureDescription.UnknownResultAsSystemError;
}
CorrelationData scopeData = ConstructCorrelationDataEntries(machineInformation);
ScopeLogger.LogScopeEnd(this, scopeData);
ReplayEventConfigurator.ConfigureReplayEventsOnScopeEnd(this);
}
/// <summary>
/// Dispose of the timed scope
/// </summary>
public void Dispose()
{
Dispose(true, MachineInformation);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose of the timed scope
/// </summary>
/// <param name="disposing">Should dispose or not</param>
/// <param name="machineInformation">Machine Information</param>
protected virtual void Dispose(bool disposing, IMachineInformation machineInformation)
{
if (disposing && !IsDisposed)
{
IsDisposed = true;
EndScope(machineInformation);
}
}
/// <summary>
/// Timed scope data, added to the log output
/// </summary>
private CorrelationData TimedScopeData { get; set; }
/// <summary>
/// Constructs the timed scope correlation data
/// </summary>
/// <returns>Correlation data</returns>
private CorrelationData ConstructCorrelationDataEntries(IMachineInformation machineInformation)
{
CorrelationData correlationData = TimedScopeData;
CorrelationData scopeData = TimedScopeData.Clone();
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ScopeName, Name);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.InstanceId, InstanceId.ToString());
scopeData.AddData(TimedScopeDataKeys.InternalOnly.IsSuccessful, IsSuccessful.HasValue ? IsSuccessful.Value.ToString() : bool.FalseString);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.IsRoot, IsRoot.ToString());
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ScopeResult, Result.ToString());
bool isFailed = !IsSuccessful ?? false;
if (isFailed && FailureDescription != null)
{
scopeData.AddData(TimedScopeDataKeys.InternalOnly.FailureDescription, FailureDescription.ToString());
}
scopeData.AddData(TimedScopeDataKeys.InternalOnly.Duration, DurationInMilliseconds.ToString(CultureInfo.InvariantCulture));
long sequenceNumber = correlationData == null ? 0 : correlationData.NextEventSequenceNumber();
scopeData.AddData(TimedScopeDataKeys.InternalOnly.SequenceNumber, sequenceNumber.ToString(CultureInfo.InvariantCulture));
scopeData.AddData(TimedScopeDataKeys.InternalOnly.CallDepth, correlationData == null ? "0" : correlationData.CallDepth.ToString(CultureInfo.InvariantCulture));
IMachineInformation machineInfo = machineInformation;
if (machineInfo != null)
{
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineId, machineInfo.MachineId);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineCluster, machineInfo.MachineCluster);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineRole, machineInfo.MachineRole);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.AgentName, machineInfo.AgentName);
}
// if the user hash has been set, add it to the scope data
if (!string.IsNullOrWhiteSpace(m_userHashOverride))
{
ULSLogging.LogTraceTag(0x23817712 /* tag_96x2s */, Categories.TimingGeneral, Levels.Verbose,
"Overriding user hash metadata in the Timed Scope '{0}' with value '{1}'", Name, m_userHashOverride);
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserHash, m_userHashOverride);
}
else if (correlationData != null && !string.IsNullOrWhiteSpace(correlationData.UserHash))
{
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserHash, correlationData.UserHash);
}
// capture performance metrics
if (PerfDiagnostics != null && PerfDiagnostics.LastStatus)
{
scopeData.AddData(TimedScopeDataKeys.InternalOnly.CpuCycles,
PerfDiagnostics.CyclesUsed.ToString(CultureInfo.InvariantCulture));
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserModeDuration,
PerfDiagnostics.UserModeMilliseconds.ToString(CultureInfo.InvariantCulture));
scopeData.AddData(TimedScopeDataKeys.InternalOnly.KernelModeDuration,
PerfDiagnostics.KernelModeMilliseconds.ToString(CultureInfo.InvariantCulture));
scopeData.AddData(TimedScopeDataKeys.InternalOnly.HttpRequestCount,
PerfDiagnostics.HttpRequestCount.ToString(CultureInfo.InvariantCulture));
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ServiceCallCount,
PerfDiagnostics.ServiceCallCount.ToString(CultureInfo.InvariantCulture));
}
return scopeData;
}
/// <summary>
/// Retrieve or create a guid which identifies set of all active timedscopes for this flow
/// </summary>
private static Guid? AllScopesGuid
{
get
{
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
if (callContext != null)
{
Guid scopesId;
object scopesIdObject;
if (callContext.SharedData.TryGetValue(AllActiveScopesDataKey, out scopesIdObject))
{
scopesId = (Guid)scopesIdObject;
}
else
{
scopesId = Guid.NewGuid();
callContext.SharedData[AllActiveScopesDataKey] = scopesId;
}
return scopesId;
}
return null;
}
}
/// <summary>
/// Get a stack of active scopes, creating a new stack if one does not exist
/// </summary>
/// <returns>stack of scopes</returns>
private static TimedScopeStack Scopes
{
get
{
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
TimedScopeStack stack = null;
if (callContext != null)
{
object stackObject = null;
if (callContext.Data.TryGetValue(ActiveScopesDataKey, out stackObject))
{
stack = stackObject as TimedScopeStack;
}
if (stack == null)
{
stack = TimedScopeStack.Root;
callContext.Data[ActiveScopesDataKey] = stack;
}
}
return stack;
}
set
{
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
if (callContext != null)
{
callContext.Data[ActiveScopesDataKey] = value;
}
}
}
/// <summary>
/// Modify the set of active scopes
/// </summary>
/// <param name="addScope">should the scope be added or removed</param>
private void ModifyActiveScopes(bool addScope)
{
TimedScopeStack scopes = Scopes;
if (scopes == null)
{
return;
}
if (addScope)
{
scopes = scopes.Push(this);
}
else
{
Stack<TimedScope> temporaryScopes = new Stack<TimedScope>();
while (!scopes.IsRoot)
{
TimedScope popScope;
scopes = scopes.Pop(out popScope);
if (ReferenceEquals(popScope, this))
{
break;
}
temporaryScopes.Push(popScope);
}
while (temporaryScopes.Count > 0)
{
scopes = scopes.Push(temporaryScopes.Pop());
}
}
Scopes = scopes;
}
/// <summary>
/// Converts bool? scope results (legacy type) to TimedScopeResult type
/// </summary>
/// <param name="scopeResult">Scope result</param>
/// <returns>Scope result of type TimedScopeResult</returns>
public static TimedScopeResult ConvertBoolResultToTimedScopeResult(bool? scopeResult)
{
if (!scopeResult.HasValue)
{
return default(TimedScopeResult);
}
else if (scopeResult.Value)
{
return TimedScopeResult.Success;
}
else
{
return TimedScopeResult.SystemError;
}
}
/// <summary>
/// LinkedStack
/// </summary>
[Serializable]
private class TimedScopeStack
{
/// <summary>
/// Root item for all stacks
/// </summary>
public static TimedScopeStack Root { get; } = new TimedScopeStack();
/// <summary>
/// Root node
/// </summary>
public bool IsRoot => ReferenceEquals(this, Parent);
/// <summary>
/// Adds a new item to the stack
/// </summary>
/// <param name="item">Data item to store</param>
/// <returns>Stack with the new item on it</returns>
public TimedScopeStack Push(TimedScope item) => new TimedScopeStack(item, this);
/// <summary>
/// Remove item from the stack and return the new stack
/// </summary>
/// <param name="scope">TimedScope stored at the top od the stack</param>
/// <returns>New stack with the top item removed</returns>
public TimedScopeStack Pop(out TimedScope scope)
{
scope = Item;
return Parent;
}
/// <summary>
/// Retrieve item from the top of the stack
/// </summary>
/// <returns></returns>
public TimedScope Peek() => Item;
/// <summary>
/// Constructor
/// </summary>
/// <param name="item">Item stored in the stack</param>
/// <param name="parent">Parent of this stack</param>
private TimedScopeStack(TimedScope item, TimedScopeStack parent)
{
Code.ExpectsArgument(parent, nameof(parent), TaggingUtilities.ReserveTag(0x23817713 /* tag_96x2t */));
Item = item;
Parent = parent;
}
/// <summary>
/// Constructor
/// </summary>
private TimedScopeStack()
{
Parent = this;
}
/// <summary>
/// Parent of this node
/// </summary>
private TimedScopeStack Parent { get; }
/// <summary>
/// Data item stored in this node
/// </summary>
private TimedScope Item { get; }
}
/// <summary>
/// Data key used to store the active time scopes on the call context
/// </summary>
private const string ActiveScopesDataKey = "TimedScope.ActiveScopes";
/// <summary>
/// Data key used to store set of all active time scopes on the call context
/// </summary>
private const string AllActiveScopesDataKey = "TimedScope.AllActiveScopes";
/// <summary>
/// Internal failure descriptions
/// </summary>
private enum InternalFailureDescription
{
/// <summary>
/// Failure description set when we change unknown error to system error
/// </summary>
UnknownResultAsSystemError,
}
/// <summary>
/// Running transaction Id, or Transactions.None
/// </summary>
public uint RunningTransaction { get; private set; }
}
}

Просмотреть файл

@ -0,0 +1,182 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Base class to store the value of the keys used when writing to the timedscope ULS logs.
/// </summary>
public static class TimedScopeDataKeys
{
/// <summary>
/// Keys used for internal telemetry framework purposes - should not be set/modified by feature code
/// </summary>
public static class InternalOnly
{
/// <summary>
/// Name of the Host Agent
/// </summary>
public const string AgentName = "AgentName";
/// <summary>
/// Depth of call within proxying infrastructure, Depth 0 is original caller
/// </summary>
public const string CallDepth = "CallDepth";
/// <summary>
/// Number of Cpu cycles used
/// </summary>
public const string CpuCycles = "CpuCycles";
/// <summary>
/// The key value used when writing the Duration value to the ULS log.
/// </summary>
public const string Duration = "Duration";
/// <summary>
/// The key value used when writing http request count
/// </summary>
public const string HttpRequestCount = "HttpRequestCount";
/// <summary>
/// The key value used when writing the unique instance Id for a Timed Scope
/// </summary>
public const string InstanceId = "InstanceId";
/// <summary>
/// The key value used when writing the timescope IsSuccessful value to the ULS log.
/// </summary>
public const string IsSuccessful = "IsSuccessful";
/// <summary>
/// Is this a root scope in a call stack
/// </summary>
public const string IsRoot = "IsRoot";
/// <summary>
/// Number of milliseconds thread spent in kernel mode
/// </summary>
public const string KernelModeDuration = "KernelModeDuration";
/// <summary>
/// The key value used when writing the timed scope result to the ULS log.
/// </summary>
public const string ScopeResult = "ScopeResult";
/// <summary>
/// Number of milliseconds thread spent in user mode
/// </summary>
public const string UserModeDuration = "UserModeDuration";
/// <summary>
/// The key value used when writing the service call count (WCF calls)
/// </summary>
public const string ServiceCallCount = "ServiceCallCount";
/// <summary>
/// The key value used when writing the timescope failure description to the ULS log.
/// </summary>
public const string FailureDescription = "FailureDescription";
/// <summary>
/// Identity of the host machine cluster
/// </summary>
public const string MachineCluster = "MachineCluster";
/// <summary>
/// Identity of the host machine
/// </summary>
public const string MachineId = "MachineId";
/// <summary>
/// Identity of the host machine role
/// </summary>
public const string MachineRole = "MachineRole";
/// <summary>
/// ULS event level from Replay
/// </summary>
public const string ReplayLevel = "Level";
/// <summary>
/// ULS message body from Replay
/// </summary>
public const string ReplayMessage = "Message";
/// <summary>
/// ULS Tag Id from Replay
/// </summary>
public const string ReplayTagId = "Tag";
/// <summary>
/// Result/Status code used explictly by Analytics for tracking scope outcome reason
/// </summary>
public const string ReasonCodeForAnalytics = "ReasonCodeForAnalytics";
/// <summary>
/// The sequence number of the scope within its correlation
/// </summary>
public const string SequenceNumber = "SequenceNumber";
/// <summary>
/// The key value used when writing the ScopeName value to the ULS log.
/// </summary>
public const string ScopeName = "ScopeName";
/// <summary>
/// The key value used when writing the StatusCode value to the ULS log.
/// </summary>
public const string StatusCode = "StatusCode";
/// <summary>
/// A Hash that uniquely identifies the user
/// </summary>
public const string UserHash = "UserHash";
}
/// <summary>
/// Key that can be used to store source category associated with an Object, e.g. office client type
/// for a specified app query
/// </summary>
public const string Category = "Category";
/// <summary>
/// Key that can be used to store additional meta data about the scope.
/// This is the third dimension surfaced in MDM after scope name and subtype.
/// Do not use this field for highly granular, user- or request-specific data!
/// </summary>
public const string ObjectMetaData = "ObjectMetaData";
/// <summary>
/// Key that can be used to store a constant string representing a Scope being used in a specific
/// scenario context - e.g. Purchase of a Trial vs PrePaid offer
/// </summary>
public const string SubType = "SubType";
}
}

Просмотреть файл

@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Store Timed Scope name and its description
/// </summary>
public class TimedScopeDefinition
{
/// <summary>
/// Name
/// </summary>
public string Name { get; }
/// <summary>
/// Description
/// </summary>
public string Description { get; }
/// <summary>
/// Description
/// </summary>
/// <remarks>Could be null</remarks>
public string LinkToOnCallEngineerHandbook { get; }
/// <summary>
/// Should the scope be logged only when explicitly demanded
/// </summary>
public bool OnDemand { get; }
/// <summary>
/// Does the scope capture user hashes that are suitable for unique user-based alerting?
/// </summary>
public bool CapturesUniqueUserHashes { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="name">Name</param>
/// <param name="description">Description</param>
/// <param name="linkToOnCallEngineerHandbook">Link to On Call Engineer Handbook</param>
/// <param name="onDemand">Should the scope be logged only when explicitly demanded</param>
/// <param name="capturesUniqueUserHashes">Does the scope capture user hashes that are suitable for unique user-based alerting?</param>
public TimedScopeDefinition(string name, string description = null, string linkToOnCallEngineerHandbook = null, bool onDemand = false, bool capturesUniqueUserHashes = false)
{
Code.ExpectsNotNullOrWhiteSpaceArgument(name, nameof(name), TaggingUtilities.ReserveTag(0x23817707 /* tag_96x2h */));
Name = name;
Description = description ?? string.Empty;
LinkToOnCallEngineerHandbook = linkToOnCallEngineerHandbook;
OnDemand = onDemand;
CapturesUniqueUserHashes = capturesUniqueUserHashes;
}
/// <summary>
/// Starts a scope
/// </summary>
/// <param name="correlationData">Correlation Data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="initialResult">Initial result to use</param>
/// <param name="customLogger">Optional custom timed scope logger</param>
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
/// <returns>A timed scope</returns>
public TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, TimedScopeResult initialResult = default(TimedScopeResult),
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
=> Create(correlationData, machineInformation, initialResult: initialResult, startScope: true, customLogger: customLogger,
replayEventConfigurator: replayEventConfigurator);
/// <summary>
/// Creates a scope (and starts by default)
/// </summary>
/// <param name="correlationData">Correlation Data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="initialResult">Initial result to use</param>
/// <param name="startScope">Should the scope be automatically started (for use in e.g. 'using' statement)</param>
/// <param name="customLogger">Optional custom timed scope logger</param>
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
/// <returns>A timed scope</returns>
public TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, TimedScopeResult initialResult = default(TimedScopeResult),
bool startScope = true, ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
{
TimedScope scope = TimedScope.Create(this, correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
if (startScope)
{
scope.Start();
}
return scope;
}
/// <summary>
/// Deprecated - Creates a scope
/// </summary>
/// <remarks>This overload is obsoleted. Use the overload with TimedScopeResult for new scopes instead.</remarks>
/// <param name="correlationData">Correlation data</param>
/// <param name="machineInformation">Machine Information</param>
/// <param name="initialResult">Initial result to use</param>
/// <param name="startScope">Should the scope be automatically started (for use in e.g. 'using' statement)</param>
/// <param name="customLogger">Optional custom timed scope logger</param>
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
/// <returns>A timed scope</returns>
public TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, bool? initialResult, bool startScope = true,
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
=> Create(correlationData, machineInformation, TimedScope.ConvertBoolResultToTimedScopeResult(initialResult), startScope, customLogger, replayEventConfigurator);
}
}

Просмотреть файл

@ -0,0 +1,302 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Represents an immutable scope based instance name in a strongly typed fashion
/// </summary>
[DataContract]
[Serializable]
[Bond.Schema]
public sealed class TimedScopeInstanceName : IEquatable<TimedScopeInstanceName>
{
/// <summary>
/// Separator used for separating SubType and MetaData
/// </summary>
private const char FieldsSeparator = '/';
/// <summary>
/// Separator used for separating FailureClassification suffix
/// </summary>
private const char FailureClassificationSeparator = '.';
/// <summary>
/// Complete strongly typed scope name
/// </summary>
[DataMember]
[Bond.Id(0)]
public TimedScopeName CompleteScopeName { get; private set; }
/// <summary>
/// Failure Classification
/// </summary>
[DataMember]
[Bond.Id(1)]
public TimedScopeResult Classification { get; private set; }
/// <summary>
/// Scope name
/// </summary>
public string Scope
{
get
{
return CompleteScopeName.Scope;
}
}
/// <summary>
/// SubType
/// </summary>
/// <remarks>The value is null if no subtype is specified</remarks>
public string SubType
{
get
{
return CompleteScopeName.SubType;
}
}
/// <summary>
/// MetaData
/// </summary>
/// <remarks>The value is null if no metadata is specified</remarks>
public string MetaData
{
get
{
return CompleteScopeName.MetaData;
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeName">Strongy typed cope name</param>
/// <param name="classification">Failure classification</param>
public TimedScopeInstanceName(TimedScopeName scopeName, TimedScopeResult classification)
{
Code.ExpectsArgument(scopeName, nameof(scopeName), TaggingUtilities.ReserveTag(0x23817705 /* tag_96x2f */));
CompleteScopeName = scopeName;
Classification = classification;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="scope">Scope name</param>
/// <param name="classification">Failure classification</param>
/// <param name="subType">SubType</param>
/// <param name="metaData">MetaData</param>
public TimedScopeInstanceName(string scope, TimedScopeResult classification, string subType = null, string metaData = null)
: this(new TimedScopeName(scope, subType, metaData), classification)
{
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="T:System.Object"/>.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = 23 * hash + CompleteScopeName.GetHashCode();
hash = 23 * hash + Classification.GetHashCode();
return hash;
}
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
/// </summary>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return this == obj as TimedScopeInstanceName;
}
/// <summary>
/// Determines whether the specified instance is equal to this object
/// </summary>
/// <param name="other">The other instance to compare with this instance</param>
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
public bool Equals(TimedScopeInstanceName other)
{
return this == other;
}
/// <summary>
/// Equality operator
/// </summary>
/// <param name="x">First object</param>
/// <param name="y">Second object</param>
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
public static bool operator ==(TimedScopeInstanceName x, TimedScopeInstanceName y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
{
return false;
}
return x.Classification.Equals(y.Classification)
&& x.CompleteScopeName.Equals(y.CompleteScopeName);
}
/// <summary>
/// Inequality operator
/// </summary>
/// <param name="x">First object</param>
/// <param name="y">Second object</param>
/// <returns><c>true</c> if they are not equal, <c>false</c> otherwise</returns>
public static bool operator !=(TimedScopeInstanceName x, TimedScopeInstanceName y)
{
return !(x == y);
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder(Scope);
builder.Append(FailureClassificationSeparator).Append(Classification);
if (SubType != null)
{
builder.Append(FieldsSeparator).Append(SubType);
}
if (MetaData != null)
{
builder.Append(FieldsSeparator).Append(MetaData);
}
return builder.ToString();
}
/// <summary>
/// Tries to parse a TimedScopeInstanceName instance from a string
/// </summary>
/// <param name="toParse">String to be parsed, cannot be null</param>
/// <param name="parsed">This output parameter is set to a new TimedScopeInstanceName instance when succeeded, null otherwise</param>
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
/// <returns>Success status</returns>
public static bool TryParse(string toParse, out TimedScopeInstanceName parsed, bool preferMetaData = false)
{
parsed = null;
if (string.IsNullOrWhiteSpace(toParse))
{
return false;
}
try
{
// The scope instance names have the following form: scope.classification[.description][/subtype][/metadata]
// We are going to extract the classification substring, the remaining string with the classification and
// description substring removed should form an ordinary timed scope name so we will parse it by TimedScopeName.TryParse.
// Split the string by the field separators ('/') first
string[] parts = toParse.Split(new[] { FieldsSeparator }, StringSplitOptions.None);
// The first part is further divided by classification separator. It should have two or three fields, the first is
// a scope, the second is a classification
string[] firstFieldsubParts = parts[0].Split(new[] { FailureClassificationSeparator }, StringSplitOptions.None);
if (firstFieldsubParts.Length < 2 || firstFieldsubParts.Length > 3)
{
return false;
}
string scope = firstFieldsubParts[0];
string classification = firstFieldsubParts[1];
// Try to parse the classification substring
TimedScopeResult parsedClassification;
if (!Enum.TryParse(classification, out parsedClassification))
{
return false;
}
// Reconstruct the scope name string without classification and try to parse it
parts[0] = scope;
string scopeName = string.Join(FieldsSeparator.ToString(CultureInfo.InvariantCulture), parts);
TimedScopeName parsedScopeName;
if (!TimedScopeName.TryParse(scopeName, out parsedScopeName, preferMetaData))
{
return false;
}
// Create a new instance
parsed = new TimedScopeInstanceName(parsedScopeName, parsedClassification);
return true;
}
catch (Exception exception)
{
// The parsing shouldn't throw but we catch exceptions to be safe
ULSLogging.ReportExceptionTag(0x23817706 /* tag_96x2g */, Categories.Common, exception,
"An unexpected exception occured during TimedScopeInstanceName.TryParse. Returning false.");
return false;
}
}
/// <summary>
/// Tries to parse a TimedScopeInstanceName instance from a string.
/// </summary>
/// <param name="toParse">String to be parsed, cannot be null</param>
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
/// <returns>New TimedScopeInstanceName instance if succeeded, <c>null</c> otherwise</returns>
public static TimedScopeInstanceName ParseOrReturnNull(string toParse, bool preferMetaData = false)
{
TimedScopeInstanceName name;
if (TryParse(toParse, out name, preferMetaData))
{
return name;
}
else
{
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,298 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Bond.Tag;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.Validation;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Represents Scope name, subtype and metada values in a strongly typed and immutable fashion
/// </summary>
[DataContract]
[Serializable]
[Bond.Schema]
public sealed class TimedScopeName : IEquatable<TimedScopeName>
{
/// <summary>
/// Separator used in the string representation
/// </summary>
private const char Separator = '/';
/// <summary>
/// Scope name
/// </summary>
[DataMember]
[Bond.Id(0)]
public string Scope { get; private set; }
/// <summary>
/// SubType
/// </summary>
/// <remarks>The value is null if no subtype is specified. Empty value is allowed.</remarks>
[DataMember]
[Bond.Id(1), Bond.Type(typeof(nullable<wstring>))]
public string SubType { get; private set; }
/// <summary>
/// MetaData
/// </summary>
/// <remarks>The value is null if no metadata is specified. Empty value is allowed.</remarks>
[DataMember]
[Bond.Id(2), Bond.Type(typeof(nullable<wstring>))]
public string MetaData { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="scope">Scope name</param>
/// <param name="subType">SubType</param>
/// <param name="metaData">MetaData</param>
public TimedScopeName(string scope, string subType = null, string metaData = null)
{
Code.ExpectsNotNullOrWhiteSpaceArgument(scope, nameof(scope), TaggingUtilities.ReserveTag(0x23817702 /* tag_96x2c */));
Scope = scope;
SubType = subType;
MetaData = metaData;
}
/// <summary>
/// Constructor that copies all fields (except the classification) from a TimedScopeInstanceName.
/// </summary>
/// <param name="copyFrom">Object to copy values from</param>
public TimedScopeName(TimedScopeInstanceName copyFrom)
{
Code.ExpectsArgument(copyFrom, nameof(copyFrom), TaggingUtilities.ReserveTag(0x23817703 /* tag_96x2d */));
Scope = copyFrom.Scope;
SubType = copyFrom.SubType;
MetaData = copyFrom.MetaData;
}
/// <summary>
/// Parameterless constructor for Bond serialization
/// </summary>
public TimedScopeName()
{
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="T:System.Object"/>.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = 23 * hash + Scope.GetHashCode();
hash = 23 * hash + (SubType == null ? 0 : SubType.GetHashCode());
hash = 23 * hash + (MetaData == null ? 0 : MetaData.GetHashCode());
return hash;
}
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
/// </summary>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return this == obj as TimedScopeName;
}
/// <summary>
/// Determines whether the specified TimedScopeName is equal to this object
/// </summary>
/// <param name="other">The other UserScopeEvent instance to compare with this instance</param>
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
public bool Equals(TimedScopeName other)
{
return this == other;
}
/// <summary>
/// Equality operator
/// </summary>
/// <param name="x">First object</param>
/// <param name="y">Second object</param>
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
public static bool operator ==(TimedScopeName x, TimedScopeName y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
{
return false;
}
return string.Equals(x.Scope, y.Scope, StringComparison.Ordinal)
&& string.Equals(x.SubType, y.SubType, StringComparison.Ordinal)
&& string.Equals(x.MetaData, y.MetaData, StringComparison.Ordinal);
}
/// <summary>
/// Inequality operator
/// </summary>
/// <param name="x">First object</param>
/// <param name="y">Second object</param>
/// <returns><c>true</c> if they are not equal, <c>false</c> otherwise</returns>
public static bool operator !=(TimedScopeName x, TimedScopeName y)
{
return !(x == y);
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder(Scope);
if (SubType != null)
{
builder.Append(Separator).Append(SubType);
}
if (MetaData != null)
{
builder.Append(Separator).Append(MetaData);
}
return builder.ToString();
}
/// <summary>
/// Creates an instance name with provided scope result
/// </summary>
/// <param name="result">Scope result</param>
/// <returns>Returns instance name</returns>
public TimedScopeInstanceName CreateInstanceName(TimedScopeResult result)
{
return new TimedScopeInstanceName(this, result);
}
/// <summary>
/// All instances (all timed scope results) of given timed scope name
/// </summary>
public IEnumerable<TimedScopeInstanceName> AllInstances
{
get
{
foreach (TimedScopeResult result in Enum.GetValues(typeof(TimedScopeResult)))
{
yield return CreateInstanceName(result);
}
}
}
/// <summary>
/// True if it has a subtype
/// </summary>
public bool HasSubType => SubType != null;
/// <summary>
/// True if it has a metadata
/// </summary>
public bool HasMetaData => MetaData != null;
/// <summary>
/// Tries to parse a TimedScopeName instance from a string
/// </summary>
/// <param name="toParse">String to be parsed, cannot be null</param>
/// <param name="parsed">This output parameter is set to a new TimedScopeName instance when succeeded, null otherwise</param>
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
/// <returns>Success status</returns>
public static bool TryParse(string toParse, out TimedScopeName parsed, bool preferMetaData = false)
{
parsed = null;
if (string.IsNullOrWhiteSpace(toParse))
{
return false;
}
try
{
// The scope name should have the following form: scope[/subtype][/metadata]
// We just split the string, check the number of fields and assign correct fields
string[] parts = toParse.Split(new[] { Separator }, StringSplitOptions.None);
string scope = parts[0];
string subType = null;
string metaData = null;
if (string.IsNullOrWhiteSpace(scope))
{
return false;
}
if (parts.Length == 2)
{
if (preferMetaData)
{
metaData = parts[1];
}
else
{
subType = parts[1];
}
}
else if (parts.Length == 3)
{
subType = parts[1];
metaData = parts[2];
}
else if (parts.Length > 3)
{
return false;
}
parsed = new TimedScopeName(scope, subType, metaData);
return true;
}
catch (Exception exception)
{
// The parsing shouldn't throw but we catch exceptions to be safe
ULSLogging.ReportExceptionTag(0x23817704 /* tag_96x2e */, Categories.Common, exception,
"An unexpected exception occured during TimedScopeName.TryParse. Returning false.");
return false;
}
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.Serialization;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Defines the possible scope results
/// </summary>
/// <remarks>
/// This enum is essential to OMEX monitoring solutions and generally should not change.
/// If values are added or removed, TimedScopeResultExtensions should be updated as well.
/// </remarks>
[DataContract]
public enum TimedScopeResult : int
{
/// <summary>
/// Result is unknown (default)
/// </summary>
/// <remarks>Result should always be set to one of the other values explicitly. Unknown causes an error to be logged, and the scope is assumed failed.</remarks>
[EnumMember]
[Obsolete("Default value, not to be used explicitly", error: true)]
Unknown = 0,
/// <summary>
/// Success
/// </summary>
[EnumMember]
Success = 1,
/// <summary>
/// System Error
/// </summary>
[EnumMember]
SystemError = 2,
/// <summary>
/// Expected Error (consolidating old UserError and PayloadError)
/// </summary>
[EnumMember]
ExpectedError = 6
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.Logging;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Extensions for TimedScopeResult enum
/// </summary>
public static class TimedScopeResultExtensions
{
/// <summary>
/// Decides whether the result is success
/// </summary>
/// <param name="result">The result</param>
/// <returns>Success flag (null if we don't know)</returns>
public static bool? IsSuccessful(this TimedScopeResult result)
{
switch (result)
{
case default(TimedScopeResult):
return null;
case TimedScopeResult.Success:
return true;
case TimedScopeResult.ExpectedError:
case TimedScopeResult.SystemError:
return false;
default:
ULSLogging.LogTraceTag(0x23817701 /* tag_96x2b */, Categories.TimingGeneral, Levels.Error, "IsSuccessful status unknown for timed scope result {0}", result);
return false;
}
}
/// <summary>
/// Decides whether we should replay events for scopes with given result
/// </summary>
/// <param name="result">The result</param>
/// <returns>true if we should replay events for this result; false otherwise</returns>
public static bool ShouldReplayEvents(this TimedScopeResult result)
{
switch (result)
{
case TimedScopeResult.SystemError:
return true;
default:
return false;
}
}
}
}

Просмотреть файл

@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Class for handling transaction data
/// </summary>
public class TransactionData
{
/// <summary>
/// Transaction id
/// </summary>
public uint TransactionId { get; set; }
/// <summary>
/// Transaction Context Id
/// </summary>
public uint TransactionContextId { get; set; }
/// <summary>
/// Transaction Step Number
/// </summary>
public uint TransactionStep { get; set; }
/// <summary>
/// Originator
/// </summary>
public string Originator { get; set; }
/// <summary>
/// Start tick
/// </summary>
public long StartTick { get; set; }
/// <summary>
/// Correlation Id
/// </summary>
public Guid CorrelationId { get; set; }
/// <summary>
/// Tracks the depth of the call through proxying layers
/// Original caller is Depth 0, subsequent hops are +1 each time
/// </summary>
/// <remarks>
/// WARNING: this field is used internally by the telemetry infrastructure and should
/// NOT be set to anything except 0 (i.e. leave as default!) by consumers making a service call
/// </remarks>
public uint CallDepth { get; set; }
/// <summary>
/// logging sequence number used for guarenteed ordering of correlation events across machine boundaries
/// </summary>
/// <remarks>
/// WARNING: this field is used internally by the telemetry infrastructure and should
/// NOT be set to anything except 0 (i.e. leave as default!) by consumers making a service call
/// </remarks>
public long EventSequenceNumber { get; set; }
/// <summary>
/// Result
/// </summary>
public uint Result { get; set; }
/// <summary>
/// User hash for the current correlation.
/// Used to propagate the value between internal S2S calls.
/// </summary>
/// <remarks>
/// <see cref="CorrelationData.UserHash"/> for details on how the field is computed.
/// </remarks>
public string UserHash { get; set; }
/// <summary>
/// The TestScenarioId for Response Injection
/// </summary>
public string TestScenarioId { get; set; }
/// <summary>
/// The TestScenarioRecordingState for Response Injection
/// </summary>
/// <remarks>
/// Values include 'play' and 'record'
/// </remarks>
public string TestScenarioRecordingState { get; set; }
/// <summary>
/// The TestSceanrio failure message, if any, from use of Response Injection
/// </summary>
public string TestScenarioFailureMessage { get; set; }
/// <summary>
/// Is this a fallback call
/// </summary>
public bool IsFallbackCall { get; set; }
/// <summary>
/// Construct an empty transaction data
/// </summary>
public TransactionData()
{
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Monitors timing and test transactions
/// </summary>
class TransactionMonitor
{
/// <summary>
/// The id of the running transaction
/// </summary>
/// <param name="correlation">current correlation</param>
/// <returns>id of the running transaction</returns>
public static uint RunningTransaction(CorrelationData correlation)
{
uint id = Transactions.None;
if (correlation != null)
{
id = correlation.TransactionId;
}
return id;
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace Microsoft.Omex.System.TimedScopes
{
/// <summary>
/// Set of known transactions
/// </summary>
public static class Transactions
{
/// <summary>
/// There is no running transaction
/// </summary>
public const uint None = 0;
/// <summary>
/// A transaction to exercise the pipeline is running
/// </summary>
/// <remarks>This runs through the pipeline for web services,
/// without actually running each request processor
/// </remarks>
public const uint ExercisePipeline = 1;
/// <summary>
/// A transaction to build dependency graphs
/// </summary>
/// <remarks>Builds up dependency graphs rather than processing the request</remarks>
public const uint BuildDependencyGraph = 2;
/// <summary>
/// Transaction Id specified by AppSure Validation Probes
/// </summary>
public const uint AppSureValidationProbe = 9997;
/// <summary>
/// Transaction Id specified by when monitoring by watchdogs/observers/CVTs to
/// mark that a test transaction is ongoing.
/// </summary>
/// <remarks>If a specific behaviour is required for the transaction, use one
/// of the transaction id's above instead</remarks>
public const uint MonitorEndpoint = 9998;
/// <summary>
/// Transaction Id specified by OMEX Test Probes
/// </summary>
public const uint TestProbe = 9999;
}
}

Просмотреть файл

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using Directives
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,8 +11,6 @@ using Microsoft.Omex.System.UnitTests.Shared;
using Microsoft.Omex.System.UnitTests.Shared.Configuration;
using Xunit;
#endregion
namespace Microsoft.Omex.Gating.UnitTests
{
/// <summary>

Просмотреть файл

@ -24,7 +24,7 @@
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersionCore)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="DefaultGates.xml" Link="DefaultGates.xml">

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Omex.System.UnitTests.Shared;
namespace Microsoft.Omex.System.AspNetCore.UnitTests
{
/// <summary>
/// Asp Net Core Unit test base class
/// </summary>
public abstract class AspNetCoreUnitTestsBase : UnitTestBase
{
/// <summary>
/// Static constructor
/// </summary>
static AspNetCoreUnitTestsBase()
{
HttpContextWrapper.Configure(m_httpContextAccessor.Value);
}
/// <summary>
/// Http context accessor
/// </summary>
protected static readonly Lazy<IHttpContextAccessor> m_httpContextAccessor = new Lazy<IHttpContextAccessor>(
() => new HttpContextAccessor(),
LazyThreadSafetyMode.PublicationOnly);
}
}

Просмотреть файл

@ -0,0 +1,379 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Assert = Xunit.Assert;
namespace Microsoft.Omex.System.AspNetCore.UnitTests
{
/// <summary>
/// Unit tests for Correlation
/// </summary>
[TestClass]
public class CorrelationUnitTests : AspNetCoreUnitTestsBase
{
[TestInitialize]
public void TestInitialize()
{
CallContextManagerInstance = CreateCallContextManager();
CallContextManagerInstance.CallContextOverride = new HttpCallContext(useLogicalCallContext: true);
MachineInformation = new UnitTestMachineInformation();
HttpCallContext callContext = CallContextManagerInstance.CallContextHandler(MachineInformation) as HttpCallContext;
callContext.ExistingCallContext();
callContext.StartCallContext();
}
[TestMethod]
public void CurrentCorrelation_WithNoActiveCorrelation_ShouldReturnNull()
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(new CorrelationData());
while (CallContextManagerInstance.CallContextHandler(MachineInformation).ExistingCallContext() != null)
{
CallContextManagerInstance.CallContextHandler(MachineInformation).EndCallContext();
}
Assert.Null(Correlation.CurrentCorrelation);
}
[TestMethod]
public void CorrelationStart_WithNullData_ShouldStartNewCorrelation()
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
Assert.NotNull(Correlation.CurrentCorrelation);
CallContextManagerInstance.CallContextOverride.EndCallContext();
}
[TestMethod]
public void CorrelationStart_MultipleCorrelations_ShouldCreateAHierarchy()
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
for (int i = 0; i < 3; i++)
{
Correlation.CorrelationStart(null);
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
for (int j = i; j > 0; j--)
{
CorrelationData parentCorrelation = currentCorrelation.ParentCorrelation;
Assert.NotNull(parentCorrelation);
Assert.NotEqual(currentCorrelation.VisibleId, parentCorrelation.VisibleId);
currentCorrelation = parentCorrelation;
}
Assert.Null(currentCorrelation.ParentCorrelation);
}
CallContextManagerInstance.CallContextOverride.EndCallContext();
}
[TestMethod]
public void CorrelationEnd_MultipleCorrelations_ShouldUnwindHierarchy()
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Guid[] correlations = new Guid[3];
for (int i = 0; i < 3; i++)
{
Correlation.CorrelationStart(null);
correlations[i] = Correlation.CurrentCorrelation.VisibleId;
}
for (int i = 2; i >= 0; i--)
{
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
Assert.Equal(correlations[i], currentCorrelation.VisibleId);
Correlation.CorrelationEnd();
}
Assert.Null(Correlation.CurrentCorrelation);
CallContextManagerInstance.CallContextOverride.EndCallContext();
}
[TestMethod]
public void CorrelationData_SetParent_ShouldThrowIfAlreadySet()
{
CorrelationData data = new CorrelationData();
data.ParentCorrelation = new CorrelationData();
Assert.True(true, "Should not throw when the parent correlation is not set");
Assert.Throws<InvalidOperationException>(
() => data.ParentCorrelation = new CorrelationData());
}
[TestMethod]
public void CorrelationData_SetParentCausingCircularReference_ShouldThrow()
{
CorrelationData data = new CorrelationData();
data.ParentCorrelation = new CorrelationData();
Assert.Throws<InvalidOperationException>(
() => data.ParentCorrelation.ParentCorrelation = data);
}
[TestMethod]
public void CorrelationData_CloneWithNullData_ShouldReturnNull()
{
CorrelationData data = null;
Assert.Null(data.Clone());
}
[TestMethod]
public void CorrelationData_Clone_ShouldReturnCopyOfCorrelationData()
{
CorrelationData data = new CorrelationData();
data.ParentCorrelation = new CorrelationData();
data.AddData(CorrelationData.TransactionIdKey, "1");
data.ShouldLogDirectly = true;
CorrelationData clone = data.Clone();
Assert.NotSame(data, clone);
Assert.Equal(data.VisibleId, clone.VisibleId);
Assert.Equal(data.ShouldLogDirectly, clone.ShouldLogDirectly);
Assert.Equal(data.HasData, clone.HasData);
}
[TestMethod]
public void CorrelationData_Add_WithNullKey_ShouldThrowException()
{
try
{
FailOnErrors = false;
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
Assert.Throws<ArgumentNullException>(() =>
{
Correlation.CorrelationAdd(null, "value");
});
}
finally
{
EndRequest();
}
}
[TestMethod]
public void CorrelationData_Add_WithNullValue_ShouldThrowException()
{
try
{
FailOnErrors = false;
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
Assert.Throws<ArgumentNullException>(() =>
{
Correlation.CorrelationAdd("key", null);
});
}
finally
{
EndRequest();
}
}
[TestMethod]
public void CorrelationData_NullObjectToTransactionData_ShouldReturnNull()
{
CorrelationData data = null;
Assert.Null(data.ToTransactionData());
}
[TestMethod]
public void CorrelationData_ToTransactionData_ShouldReturnSameData()
{
try
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
CorrelationData data = Correlation.CurrentCorrelation;
TransactionData transaction = data.ToTransactionData();
Assert.True(data.CallDepth == transaction.CallDepth, "Call depth properties should be equal.");
Assert.True(data.EventSequenceNumber == transaction.EventSequenceNumber, "EventSequenceNumber properties should be equal.");
Assert.True(data.TransactionId == transaction.TransactionId, "TransactionId properties should be equal.");
Assert.True(data.UserHash == transaction.UserHash, "UserHash properties should be equal.");
Assert.True(data.VisibleId == transaction.CorrelationId, "VisibleId and CorrelationId properties should be equal.");
}
finally
{
EndRequest();
}
}
[TestMethod]
public void CorrelationClear_ShouldClearAllCorrelations()
{
try
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
CorrelationData previousCorrelation = Correlation.CurrentCorrelation;
for (int i = 0; i < (new Random()).Next(3, 10); i++)
{
Correlation.CorrelationStart(null);
CorrelationData parentCorrelation = Correlation.CurrentCorrelation.ParentCorrelation;
Assert.Same(previousCorrelation, parentCorrelation);
previousCorrelation = Correlation.CurrentCorrelation;
}
Assert.NotNull(Correlation.CurrentCorrelation);
Correlation.CorrelationClear();
Assert.Null(Correlation.CurrentCorrelation);
}
finally
{
EndRequest();
}
}
[TestMethod]
public void SettingShouldLogDirectly_WithoutAnActiveCorrelation_ShouldSetResetShouldLogDirectly()
{
try
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(null);
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
Correlation.ShouldLogDirectly = true;
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
Correlation.ShouldLogDirectly = false;
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be reset to false.");
}
finally
{
EndRequest();
}
}
[TestMethod]
public void SettingShouldLogDirectly_OnCurrentCorrelation_ShouldSetOnCorrelationOnly()
{
try
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
Correlation.CorrelationStart(null);
Correlation.ShouldLogDirectly = true;
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
Correlation.CorrelationEnd();
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false after correlation ended.");
}
finally
{
EndRequest();
}
}
[TestMethod]
public void SettingShouldLogDirectly_OnBaseCorrelation_ShouldBeInheritedByCurrentCorrelation()
{
try
{
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
Correlation.CorrelationStart(null);
Correlation.ShouldLogDirectly = true;
Correlation.CorrelationStart(null);
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
Correlation.CorrelationClear();
}
finally
{
EndRequest();
}
}
private ICallContextManager CreateCallContextManager()
{
return new CallContextManager();
}
private Correlation Correlation { get; set; }
private ICallContextManager CallContextManagerInstance { get; set; }
private IMachineInformation MachineInformation { get; set; }
/// <summary>
/// Create an HttpContext and configure HttpContext.Current
/// </summary>
private static void SetHttpContextCurrent()
{
HttpContext httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("localhost");
m_httpContextAccessor.Value.HttpContext = httpContext;
}
/// <summary>
/// End a request
/// </summary>
private void EndRequest()
{
ICallContext context = CallContextManagerInstance.CallContextOverride;
ICallContext existingContext = context.ExistingCallContext();
Correlation.CorrelationEnd();
context.EndCallContext();
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;$(NetCoreVersion)</TargetFrameworks>
<Description>Contains unit tests for Omex libraries in AspNetCore.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
<PackageReference Include="MSTest.TestAdapter" Version="$(MSTestAdapterVersion)" />
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestAdapterVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />
<ProjectReference Include="..\..\src\System.AspNetCore\Microsoft.Omex.System.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\System.UnitTests.Shared\Microsoft.Omex.System.UnitTests.Shared.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.AspNetCore.Http;
using Microsoft.Omex.System.Context;
using Microsoft.Omex.System.Diagnostics;
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Assert = Xunit.Assert;
namespace Microsoft.Omex.System.AspNetCore.UnitTests
{
/// <summary>
/// Unit tests for verifying functionality of ReplayEventConfigurator class
/// </summary>
[TestClass]
public class ReplayEventConfiguratorUnitTests : AspNetCoreUnitTestsBase
{
[TestInitialize]
public void TestInitialize()
{
SetHttpContextCurrent();
CallContextManagerInstance = CreateCallContextManager();
CallContextManagerInstance.CallContextOverride = new HttpCallContext(useLogicalCallContext: true);
MachineInformation = new UnitTestMachineInformation();
HttpCallContext callContext = CallContextManagerInstance.CallContextHandler(MachineInformation) as HttpCallContext;
callContext.ExistingCallContext();
callContext.StartCallContext();
}
[TestCleanup]
public void end()
{
ICallContext context = CallContextManagerInstance.CallContextOverride;
ICallContext existingContext = context.ExistingCallContext();
Correlation.CorrelationEnd();
context.EndCallContext();
}
[TestMethod]
public void SuccessTimedScope_DoesntReplayLogs()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(new CorrelationData());
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
Assert.False(currentCorrelation.ShouldReplayUls);
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.Result = TimedScopeResult.Success;
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
configurator.ConfigureReplayEventsOnScopeEnd(scope);
}
Assert.False(currentCorrelation.ShouldReplayUls);
}
[TestMethod]
public void FailedTimedScope_ShouldReplayLogs()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
Mock<ILogEventCache> mockCache = new Mock<ILogEventCache>();
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(new CorrelationData(mockCache.Object));
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.Result = TimedScopeResult.SystemError;
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
configurator.ConfigureReplayEventsOnScopeEnd(scope);
}
Assert.True(currentCorrelation.ShouldReplayUls, "Logs should be replayed");
}
[TestMethod]
public void FailedDisabledTimedScope_DoesntReplayLogs()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
Correlation.CorrelationStart(new CorrelationData());
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.Result = TimedScopeResult.SystemError;
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(true);
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
configurator.ConfigureReplayEventsOnScopeEnd(scope);
}
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
}
private ICallContextManager CreateCallContextManager()
{
return new CallContextManager();
}
private Correlation Correlation { get; set; }
private ICallContextManager CallContextManagerInstance { get; set; }
private IMachineInformation MachineInformation { get; set; }
/// <summary>
/// Create an HttpContext and configure HttpContext.Current
/// </summary>
private static void SetHttpContextCurrent()
{
HttpContext httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("localhost");
m_httpContextAccessor.Value.HttpContext = httpContext;
}
}
}

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Omex.System.Caching;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Shared.Caching
{
/// <summary>

Просмотреть файл

@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using Microsoft.Omex.System.Data;
using Microsoft.Omex.System.Data.FileSystem;
using Microsoft.Omex.System.UnitTests.Shared;
using Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Data
{
/// <summary>

Просмотреть файл

@ -7,12 +7,11 @@
<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreVersion)'">
<Reference Include="System.Runtime" />
<Reference Include="System.Threading.Tasks" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreVersion)'">
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\System.UnitTests.Shared\Microsoft.Omex.System.UnitTests.Shared.csproj" />
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />

Просмотреть файл

@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using Microsoft.Omex.System.Monads;
using Microsoft.Omex.System.UnitTests.Shared;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Monads
{
/// <summary>

Просмотреть файл

@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Threading;
using Microsoft.Omex.System.Monads;
using Microsoft.Omex.System.UnitTests.Shared;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Monads
{
/// <summary>

Просмотреть файл

@ -1,16 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Threading;
using Microsoft.Omex.System.Monads;
using Microsoft.Omex.System.UnitTests.Shared;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Monads
{
/// <summary>

Просмотреть файл

@ -0,0 +1,262 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.UnitTests.Shared;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Omex.System.UnitTests.TimedScopes
{
/// <summary>
/// Unit tests for verifying functionality of TimedScopeInstanceName class
/// </summary>
public class TimedScopeInstanceNameUnitTests : UnitTestBase
{
/// <summary>
/// Test Scope
/// </summary>
private const string TestScope = "TestScope";
/// <summary>
/// Test Subtype
/// </summary>
private const string TestSubtype = "TestSubType";
/// <summary>
/// Test Metadata
/// </summary>
private const string TestMetadata = "TestMetadata";
/// <summary>
/// Test Classification
/// </summary>
private const TimedScopeResult TestClassification = TimedScopeResult.SystemError;
/// <summary>
/// Test Scope with classification suffix
/// </summary>
private const string TestScopeWithClassification = TestScope + ".SystemError";
/// <summary>
/// Valid scope name
/// </summary>
private const string OnlyScope = TestScopeWithClassification;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithSubtype = TestScopeWithClassification + "/" + TestSubtype;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithMetadata = TestScopeWithClassification + "/" + TestMetadata;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithSubtypeAndMetadata = TestScopeWithClassification + "/" + TestSubtype + "/" + TestMetadata;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithEmptySubtype = TestScopeWithClassification + "/";
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithEmptySubtypeAndMetadata = TestScopeWithClassification + "//" + TestMetadata;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeNameWithDescription = TestScopeWithClassification + ".Description/" + TestSubtype + "/" + TestMetadata;
/// <summary>
/// Constructor
/// </summary>
/// <param name="log">Log helper</param>
public TimedScopeInstanceNameUnitTests(ITestOutputHelper log)
{
}
[Fact]
public void Verify_ValidScopeInstanceNames_Parsed()
{
TimedScopeInstanceName scopeName;
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(OnlyScope, out scopeName)))
{
Assert.Equal(scopeName.Scope, TestScope);
Assert.Equal(null, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithSubtype, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(TestSubtype, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithMetadata, out scopeName, preferMetaData: true)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(null, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithSubtypeAndMetadata, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(TestSubtype, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeNameWithDescription, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(TestSubtype, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithEmptySubtype, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(string.Empty, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithEmptySubtypeAndMetadata, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(string.Empty, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
Assert.Equal(TestClassification, scopeName.Classification);
}
}
[Fact]
public void Verify_MalformedScopeNames_NotParsed()
{
TimedScopeInstanceName scopeName;
Assert.False(TimedScopeInstanceName.TryParse(".SystemError", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse(null, out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("///", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("...///", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("Scope.MalformedClassification/x", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("/x/y", out scopeName));
Assert.False(TimedScopeInstanceName.TryParse("x/y/z/a", out scopeName));
}
[Fact]
public void Verify_HashCodesAndEquality()
{
CheckSingleScope(OnlyScope);
CheckSingleScope(ScopeWithSubtype);
CheckSingleScope(ScopeWithMetadata);
CheckSingleScope(ScopeWithSubtypeAndMetadata);
CheckSingleScope(ScopeNameWithDescription);
CheckSingleScope(ScopeWithEmptySubtype);
CheckSingleScope(ScopeWithEmptySubtypeAndMetadata);
}
/// <summary>
/// Verifies that parsing the same string gives equal TimedScopeInstanceName instances
/// </summary>
/// <param name="scopeName">The string to be parsed</param>
private void CheckSingleScope(string scopeName)
{
TimedScopeInstanceName parsed1;
TimedScopeInstanceName parsed2;
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed1)) &&
VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed2)))
{
CheckHashCodesAndEquality(parsed1, parsed2);
}
}
/// <summary>
/// Check equality of two timed scope instance names by all possible ways
/// </summary>
/// <param name="x">First instance</param>
/// <param name="y">Second instance</param>
private void CheckHashCodesAndEquality(TimedScopeInstanceName x, TimedScopeInstanceName y)
{
Assert.Equal(x.GetHashCode(), y.GetHashCode());
Assert.True(x.Equals(y));
Assert.True(x.Equals((object)y));
Assert.True(x == y);
Assert.False(x != y);
}
[Fact]
public void Verify_ParseAndToStringRoundTrip_EqualStringRepresentation()
{
CheckRoundTrip(OnlyScope);
CheckRoundTrip(ScopeWithSubtype);
CheckRoundTrip(ScopeWithMetadata);
CheckRoundTrip(ScopeWithSubtypeAndMetadata);
CheckRoundTrip(ScopeWithEmptySubtype);
CheckRoundTrip(ScopeWithEmptySubtypeAndMetadata);
// We are not checking round trip for ScopeNameWithDescription as we do not store the description in the scope name
}
/// <summary>
/// Checks that parsing a string and then converting back to string gives the same string
/// </summary>
/// <param name="scopeName">String to be parsed and converted back</param>
private void CheckRoundTrip(string scopeName)
{
TimedScopeInstanceName parsed;
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed)))
{
Assert.Equal(parsed.ToString(), scopeName);
}
}
/// <summary>
/// Verifies that a condition is true and returns the value.
/// </summary>
/// <param name="condition">The condition to check.</param>
/// <param name="message">The logged message.</param>
/// <param name="args">Format string args.</param>
/// <returns>The evaluation result.</returns>
private bool VerifyTrueAndReturn(bool condition)
{
Assert.True(condition);
return condition;
}
}
}

Просмотреть файл

@ -0,0 +1,222 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.UnitTests.Shared;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Omex.System.UnitTests.TimedScopes
{
/// <summary>
/// Unit tests for verifying functionality of TimedScopeName class
/// </summary>
public class TimedScopeNameUnitTests : UnitTestBase
{
/// <summary>
/// Test Scope
/// </summary>
private const string TestScope = "TestScope";
/// <summary>
/// Test Subtype
/// </summary>
private const string TestSubtype = "TestSubType";
/// <summary>
/// Test Metadata
/// </summary>
private const string TestMetadata = "TestMetadata";
/// <summary>
/// Valid scope name
/// </summary>
private const string OnlyScope = TestScope;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithSubtype = TestScope + "/" + TestSubtype;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithMetadata = TestScope + "/" + TestMetadata;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithSubtypeAndMetadata = TestScope + "/" + TestSubtype + "/" + TestMetadata;
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithEmptySubtype = TestScope + "/";
/// <summary>
/// Valid scope name
/// </summary>
private const string ScopeWithEmptySubtypeAndMetadata = TestScope + "//" + TestMetadata;
/// <summary>
/// Constructor
/// </summary>
/// <param name="log">Log helper</param>
public TimedScopeNameUnitTests(ITestOutputHelper log)
{
}
[Fact]
public void Verify_ValidScopeNames_Parsed()
{
TimedScopeName scopeName;
if (VerifyTrueAndReturn(TimedScopeName.TryParse(OnlyScope, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(null, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
}
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithSubtype, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(TestSubtype, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
}
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithMetadata, out scopeName, preferMetaData: true)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(null, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
}
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithSubtypeAndMetadata, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(TestSubtype, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
}
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithEmptySubtype, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(string.Empty, scopeName.SubType);
Assert.Equal(null, scopeName.MetaData);
}
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithEmptySubtypeAndMetadata, out scopeName)))
{
Assert.Equal(TestScope, scopeName.Scope);
Assert.Equal(string.Empty, scopeName.SubType);
Assert.Equal(TestMetadata, scopeName.MetaData);
}
}
[Fact]
public void Verify_MalformedScopeNames_NotParsed()
{
TimedScopeName scopeName;
Assert.False(TimedScopeName.TryParse(null, out scopeName), "Malformed scope name should not be parsed");
Assert.False(TimedScopeName.TryParse("", out scopeName), "Malformed scope name should not be parsed");
Assert.False(TimedScopeName.TryParse("/x", out scopeName), "Malformed scope name should not be parsed");
Assert.False(TimedScopeName.TryParse("/x/y", out scopeName), "Malformed scope name should not be parsed");
Assert.False(TimedScopeName.TryParse("x/y/z/a", out scopeName), "Malformed scope name should not be parsed");
}
[Fact]
public void Verify_HashCodesAndEquality()
{
CheckSingleScope(OnlyScope);
CheckSingleScope(ScopeWithSubtype);
CheckSingleScope(ScopeWithMetadata);
CheckSingleScope(ScopeWithSubtypeAndMetadata);
CheckSingleScope(ScopeWithEmptySubtype);
CheckSingleScope(ScopeWithEmptySubtypeAndMetadata);
}
/// <summary>
/// Verifies that parsing the same string (even with changed letter casing) gives equal TimedScopeName instances
/// </summary>
/// <param name="scopeName">The string to be parsed</param>
private void CheckSingleScope(string scopeName)
{
TimedScopeName parsed1;
TimedScopeName parsed2;
if (VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed1)) &&
VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed2)))
{
CheckHashCodesAndEquality(parsed1, parsed2);
}
}
/// <summary>
/// Check equality of two timed scope name instances by all possible ways
/// </summary>
/// <param name="x">First instance</param>
/// <param name="y">Second instance</param>
private void CheckHashCodesAndEquality(TimedScopeName x, TimedScopeName y)
{
Assert.Equal(x.GetHashCode(), y.GetHashCode());
Assert.True(x.Equals(y));
Assert.True(x.Equals((object)y));
Assert.True(x == y);
Assert.False(x != y);
}
[Fact]
public void Verify_ParseAndToStringRoundTrip_EqualStringRepresentation()
{
CheckRoundTrip(OnlyScope);
CheckRoundTrip(ScopeWithSubtype);
CheckRoundTrip(ScopeWithMetadata);
CheckRoundTrip(ScopeWithSubtypeAndMetadata);
CheckRoundTrip(ScopeWithEmptySubtype);
CheckRoundTrip(ScopeWithEmptySubtypeAndMetadata);
}
/// <summary>
/// Checks that parsing a string and then converting back to string gives the same string
/// </summary>
/// <param name="scopeName">String to be parsed and converted back</param>
private void CheckRoundTrip(string scopeName)
{
TimedScopeName parsed;
if (VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed)))
{
Assert.Equal(parsed.ToString(), scopeName);
}
}
/// <summary>
/// Verifies that a condition is true and returns the value.
/// </summary>
/// <param name="condition">The condition to check.</param>
/// <param name="message">The logged message.</param>
/// <param name="args">Format string args.</param>
/// <returns>The evaluation result.</returns>
private bool VerifyTrueAndReturn(bool condition)
{
Assert.True(condition);
return condition;
}
}
}

Просмотреть файл

@ -0,0 +1,413 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Omex.System.Logging;
using Microsoft.Omex.System.TimedScopes;
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
using Microsoft.Omex.System.UnitTests.Shared;
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
using Microsoft.Omex.System.UnitTests.Shared.TimedScopes;
using Moq;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.Omex.System.UnitTests.TimedScopes
{
/// <summary>
/// Unit tests for verifying functionality of TimedScope class
/// </summary>
public sealed class TimedScopeUnitTests : UnitTestBase
{
[Fact]
public void Scope_TimedScopeLogger_IsCalled()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
TimedScope scope;
CorrelationData data = new CorrelationData();
using (scope = new TimedScopeDefinition("TestScope")
.Create(data, new UnitTestMachineInformation(), TimedScopeResult.SystemError, customLogger: timedScopeLoggerMock.Object,
replayEventConfigurator: replyEventConfiguratorMock.Object))
{
timedScopeLoggerMock.Verify(x => x.LogScopeStart(scope), Times.Once);
timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny<CorrelationData>()), Times.Never);
}
timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny<CorrelationData>()), Times.Once);
}
[Fact]
public void Start_ShouldConstructTimedScope_WithTimerActive()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(false, true, timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.True(scope.IsScopeActive, "Starting a scope should start the timer.");
Assert.True(scope.IsSuccessful.HasValue, "Starting a scope should start the timer.");
Assert.False(scope.IsSuccessful.Value, "IsSuccessful should be set to false.");
}
}
[Fact]
public void Create_ShouldConstructTimedScope_WithTimerInactive()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(true, false, timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.False(scope.IsScopeActive, "Creating a scope should not start the timer.");
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
Assert.True(scope.IsSuccessful.Value, "IsSuccessful should be set to true.");
}
}
[Fact]
public void AbortTimer_ShouldDisableTimerActive()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
scope.AbortTimer();
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
}
}
[Fact]
public void AbortTimer_ShouldDisableTimerActive_AndSetResultsToTrue()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
scope.AbortTimer(true);
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
Assert.True(scope.IsSuccessful.Value, "IsSuccesful should be set to true.");
}
}
[Fact]
public void AbortTimer_ShouldDisableTimerActive_AndSetResultsToFalse()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(scopeLogger: timedScopeLoggerMock.Object,
replayEventConfigurator: replyEventConfiguratorMock.Object, startScope: false))
{
Assert.False(scope.IsScopeActive, "Default scope started without an active scope should have timer active.");
scope.Start();
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
scope.AbortTimer(false);
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
Assert.False(scope.IsSuccessful.Value, "IsSuccesful should be set to false.");
}
}
[Fact]
public void AddLoggingValue_ShouldOutputValueInLogEvent()
{
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: timedScopeLoggerMock.Object,
replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.AddLoggingValue(TimedScopeDataKeys.Category, "MyCategory");
scope.End(new UnitTestMachineInformation());
// There should be one 'Ending' transaction log call with formatted output
foreach (LogEventArgs args in LoggedEvents)
{
if (args.Category == Categories.TimingGeneral)
{
if (args.FullMessage.Contains("Ending timed scope"))
{
Assert.Contains("Category:'MyCategory';", args.FullMessage, StringComparison.Ordinal);
}
}
}
}
}
[Fact]
public void FailedScope_ResultAndFailureDescription_ShouldOutputValueInLogEvent()
{
FailOnErrors = false;
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: unitTestTimedScopeLogger,
replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.Result = TimedScopeResult.ExpectedError;
scope.FailureDescription = UnitTestFailureDescription.ExampleDescription;
}
TimedScopeLogEvent scopeEvent = unitTestTimedScopeLogger.Events.SingleOrDefault();
if (VerifyNotNullAndReturn(scopeEvent, "Scope end event should be logged"))
{
Assert.Equal(scopeEvent.Result, TimedScopeResult.ExpectedError);
Assert.Equal(scopeEvent.FailureDescription, UnitTestFailureDescription.ExampleDescription.ToString());
}
}
[Fact]
public void SucceededScope_Result_ShouldOutputValueInLogEvent()
{
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: unitTestTimedScopeLogger,
replayEventConfigurator: replyEventConfiguratorMock.Object))
{
scope.Result = TimedScopeResult.Success;
}
TimedScopeLogEvent scopeEvent = unitTestTimedScopeLogger.Events.SingleOrDefault();
if (VerifyNotNullAndReturn(scopeEvent, "Timed scope should be logged"))
{
Assert.Equal(scopeEvent.Result, TimedScopeResult.Success);
}
}
[Fact]
public void AddLoggingValue_WithNullKey_ShouldLogError()
{
FailOnErrors = false;
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
scope.AddLoggingValue(null, "My Application.");
Assert.Equal(TraceErrors.Count(), 1);
LoggedEvents.Clear();
}
}
[Fact]
public void Start_DisposedTimedScope_ShoudLogError()
{
FailOnErrors = false;
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object);
scope.Dispose();
scope.Start();
Assert.Equal(TraceErrors.Count(), 1);
LoggedEvents.Clear();
}
[Fact]
public void Start_WithActiveTimer_ShouldLogError()
{
FailOnErrors = false;
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.True(scope.IsScopeActive, "Timer should be active.");
scope.Start();
Assert.Equal(TraceErrors.Count(), 1);
LoggedEvents.Clear();
}
}
[Fact]
public void End_WithDisposedTimedScope_ShouldLogError()
{
FailOnErrors = false;
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
{
Assert.True(scope.IsScopeActive, "Timer should be active.");
scope.Dispose();
Assert.False(scope.IsScopeActive, "Dispose should turn off timer.");
scope.End(new UnitTestMachineInformation());
Assert.Equal(TraceErrors.Count(), 1);
LoggedEvents.Clear();
}
}
[Fact]
public void Frequency_ShouldMatchFrequencyOfStopwatch()
{
Assert.Equal(TimedScope.Frequency, Stopwatch.Frequency);
}
[Fact]
public void NotSettingTimedScopeResult_ChangesToSystemError()
{
LoggedEvents.Clear();
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(unitTestTimedScopeLogger, replyEventConfiguratorMock.Object))
{
}
TimedScopeLogEvent evt = unitTestTimedScopeLogger.SingleTimedScopeEvent(TestHooks.DefaultTimedScopeName);
if (VerifyNotNullAndReturn(evt, "A scope event has been logged"))
{
Assert.Equal(evt.Result, TimedScopeResult.SystemError);
}
}
[Fact]
public void DefaultTimedScopeResult_IsSeparateValue()
{
// Default value should never be used explicitly and is marked as Obsolete for that reason, so it can't be used.
// But we need a separate default value to support the "not yet set" state. Verify the default is actually separate
// and does not map to any of the real result values.
TimedScopeResult result = default(TimedScopeResult);
Assert.NotEqual(TimedScopeResult.Success, result);
Assert.NotEqual(TimedScopeResult.SystemError, result);
Assert.NotEqual(TimedScopeResult.ExpectedError, result);
}
[Fact]
public void DefaultTimedScopeResult_LogsAsSystemError()
{
LoggedEvents.Clear();
CorrelationData data = new CorrelationData();
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
using (TimedScope.Create(data, new UnitTestMachineInformation(), TestHooks.DefaultTimedScopeName, "description", default(TimedScopeResult), unitTestTimedScopeLogger, replyEventConfiguratorMock.Object))
{
}
TimedScopeLogEvent evt = unitTestTimedScopeLogger.SingleTimedScopeEvent(TestHooks.DefaultTimedScopeName);
if (VerifyNotNullAndReturn(evt, "A scope event has been logged"))
{
Assert.Equal(TimedScopeResult.SystemError, evt.Result);
}
}
/// <summary>
/// Unit test failure descriptions
/// </summary>
private enum UnitTestFailureDescription
{
/// <summary>
/// Example description
/// </summary>
ExampleDescription,
}
/// <summary>
/// Verifies that a reference type is not null and returns the evaluation result.
/// </summary>
/// <typeparam name="T">The reference type.</typeparam>
/// <param name="value">The value.</param>
/// <param name="message">The logged message.</param>
/// <param name="args">Format string args.</param>
/// <returns>The evaluation result.</returns>
private bool VerifyNotNullAndReturn<T>(T value, string message, params object[] args) where T : class
{
try
{
Assert.NotNull(value);
return value != null;
}
catch (NotNullException)
{
Console.WriteLine(message, args);
throw;
}
}
/// <summary>
/// Trace errors
/// </summary>
private IEnumerable<LogEventArgs> TraceErrors
{
get
{
return LoggedEvents.Except(ReportExceptions).Where((args) => { return args.Level == Levels.Error; });
}
}
/// <summary>
/// Exceptions reported
/// </summary>
private IEnumerable<ReportExceptionEventArgs> ReportExceptions
{
get
{
IEnumerable<LogEventArgs> events = LoggedEvents;
return LoggedEvents.OfType<ReportExceptionEventArgs>();
}
}
}
}

Просмотреть файл

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,8 +9,6 @@ using Microsoft.Omex.System.UnitTests.Shared;
using Microsoft.Omex.System.Validation;
using Xunit;
#endregion
namespace Microsoft.Omex.System.UnitTests.Validation
{
/// <summary>