Skip to content

Commit

Permalink
Add capability to disable EventSources
Browse files Browse the repository at this point in the history
  • Loading branch information
karolz-ms committed Oct 20, 2017
1 parent 60b65ec commit 6bf39f4
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ project.lock.json
/nugets/

.vscode
.vs

*.ide
*.ide-journal

21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,31 @@ This input listens to EventSource traces. EventSource classes can be created in
}
```

*Top Object*
*Top object*

| Field | Values/Types | Required | Description |
| :---- | :-------------- | :------: | :---------- |
| `type` | "EventSource" | Yes | Specifies the input type. For this input, it must be "EventSource". |
| `sources` | JSON array | Yes | Specifies the EventSource objects to collect. |

*Sources Object*
*Source object (element of the sources array)*

| Field | Values/Types | Required | Description |
| :---- | :-------------- | :------: | :---------- |
| `providerName` | provider name | Yes | Specifies the name of the EventSource to track. |
| `level` | Critial, Error, Warning, Informational, Verbose, LogAlways | No | Specifies the collection trace level. Traces with equal or higher severity than specified are collected. For example, if Warning is specified, then Critial, Error, and Warning traces are collected. Default is LogAlways, which means "provider decides what events are raised", which usually results in all events being raised. |
|`keywords` | An integer | No | A bitmask that specifies what events to collect. Only events with keyword matching the bitmask are collected, except if it's 0, which means everything is collected. Default is 0. |
| `providerName` | EventSource name | Yes(*) | Specifies the name of the EventSource to track. |
| `providerNamePrefix` | EventSource name prefix | Yes(*) | Specifies the name prefix of EventSource(s) to track. For example, if the value is "Microsoft-ServiceFabric", all EventSources that have names starting with Microsoft-ServiceFabric (Microsoft-ServiceFabric-Services, Microsoft-ServiceFabric-Actors and so on) will be tracked. |
| `disabledProviderNamePrefix` | provider name | Yes(*) | Specifies the name prefix of the EventSource(s) that must be ignored. No events from these sources will be captured(***). |
| `level` | Critial, Error, Warning, Informational, Verbose, LogAlways | No(**) | Specifies the collection trace level. Traces with equal or higher severity than specified are collected. For example, if Warning is specified, then Critial, Error, and Warning traces are collected. Default is LogAlways, which means "provider decides what events are raised", which usually results in all events being raised. |
|`keywords` | An integer | No(**) | A bitmask that specifies what events to collect. Only events with keyword matching the bitmask are collected, except if it's 0, which means everything is collected. Default is 0. |

*Remarks*

(*) Out of `providerName`, `providerNamePrefix` and `disabledProviderNamePrefix`, only one can be used for a single source. In other words, with a single source one can enable an EventSource by name, or enable a set of EventSources by prefix, or disable a set of EventSources by prefix.

(**) `level` and `keywords` can be used for enabling EventSources, but not for disabling them. Disabling events using level and keywords is not supported (but one can use level and/or keywords to *selectively enable* a subset of events from a given EventSource).

(***) There is an issue with .NET frameworks 4.6 and 4.7, and .NET Core framework 1.1 and 2.0 where dynamically created EventSource events are dispatched to all listeners, regardless whether listeners subscribe to events from these EventSources; for more information see https://github.com/dotnet/coreclr/issues/14434 `disabledProviderNamePrefix` property can be usesd to suppress these events.<br/>
Disabling EventSources is not recommended under normal circumstances, as it introduces a slight performance penalty. Instead, selectively enable necessary events through combination of EventSource names, event levels, and keywords.

#### PerformanceCounter
*Nuget Package*: [**Microsoft.Diagnostics.EventFlow.Inputs.PerformanceCounter**](https://www.nuget.org/packages/Microsoft.Diagnostics.EventFlow.Inputs.PerformanceCounter/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

using System;
using System.Linq;
using System.Diagnostics.Tracing;
using Validation;

namespace Microsoft.Diagnostics.EventFlow.Configuration
{
Expand All @@ -12,13 +15,62 @@ namespace Microsoft.Diagnostics.EventFlow.Configuration
public class EventSourceConfiguration
{
public string ProviderName { get; set; }
public EventLevel Level { get; set; }
public EventKeywords Keywords { get; set; }
public EventLevel Level { get; set; } = EventLevel.LogAlways;
public EventKeywords Keywords { get; set; } = EventKeywords.All;
public string DisabledProviderNamePrefix { get; set; }
public string ProviderNamePrefix { get; set; }

public EventSourceConfiguration()
public bool Validate()
{
Level = EventLevel.LogAlways;
Keywords = (EventKeywords) ~0;
bool[] enabledSettings = new bool[]
{
!string.IsNullOrWhiteSpace(ProviderName),
!string.IsNullOrWhiteSpace(ProviderNamePrefix),
!string.IsNullOrWhiteSpace(DisabledProviderNamePrefix)
};

// One and only one setting should be present
if (enabledSettings.Count(setting => setting) != 1)
{
return false;
}

// If disabling, Keywords and Level must remain at default values (the are ineffective)
if (!string.IsNullOrWhiteSpace(DisabledProviderNamePrefix))
{
if (Keywords != EventKeywords.All || Level != EventLevel.LogAlways)
{
return false;
}
}

return true;
}

public bool Enables(EventSource eventSource)
{
Requires.NotNull(eventSource, nameof(eventSource));

if (!string.IsNullOrWhiteSpace(ProviderName))
{
return eventSource.Name == ProviderName;
}
else if (!string.IsNullOrWhiteSpace(ProviderNamePrefix))
{
return eventSource.Name?.StartsWith(ProviderNamePrefix, StringComparison.Ordinal) ?? false;
}
else return false;
}

public bool Disables(EventSource eventSource)
{
Requires.NotNull(eventSource, nameof(eventSource));

if (!string.IsNullOrWhiteSpace(DisabledProviderNamePrefix))
{
return eventSource.Name?.StartsWith(DisabledProviderNamePrefix, StringComparison.Ordinal) ?? false;
}
else return false;
}

public override bool Equals(object obj)
Expand All @@ -29,12 +81,33 @@ public override bool Equals(object obj)
return false;
}

return ProviderName == other.ProviderName && Level == other.Level && Keywords == other.Keywords;
if (ProviderName != other.ProviderName ||
ProviderNamePrefix != other.ProviderNamePrefix ||
DisabledProviderNamePrefix != other.DisabledProviderNamePrefix ||
Level != other.Level ||
Keywords != other.Keywords)
{
return false;
}

return true;
}

public override int GetHashCode()
{
return ProviderName?.GetHashCode() ?? 0;
if (ProviderName != null)
{
return ProviderName.GetHashCode();
}
else if (ProviderNamePrefix != null)
{
return ProviderNamePrefix.GetHashCode();
}
else if (DisabledProviderNamePrefix != null)
{
return DisabledProviderNamePrefix.GetHashCode();
}
else return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class EventSourceInput : EventListener, IObservable<EventData>, IDisposab
private ConcurrentQueue<EventSource> eventSourcesPresentAtConstruction;
private EventFlowSubject<EventData> subject;
private Task initialization;
private ConcurrentDictionary<string, bool> disabledSources;
private Action<EventWrittenEventArgs> OnEventWrittenImpl;

public EventSourceInput(IConfiguration configuration, IHealthReporter healthReporter)
{
Expand Down Expand Up @@ -55,7 +57,8 @@ public EventSourceInput(IReadOnlyCollection<EventSourceConfiguration> eventSourc
Requires.NotNull(eventSources, nameof(eventSources));
Requires.NotNull(healthReporter, nameof(healthReporter));

Initialize(eventSources, healthReporter);
var myEventSources = new List<EventSourceConfiguration>(eventSources);
Initialize(myEventSources, healthReporter);
}

public IEnumerable<EventSourceConfiguration> EventSources { get; private set; }
Expand Down Expand Up @@ -83,7 +86,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventArgs)
// and not that useful for production tracing. However, TPL EventSource must be enabled to get hierarchical activity IDs.
if (!TplActivities.TplEventSourceGuid.Equals(eventArgs.EventSource.Guid))
{
this.subject.OnNext(eventArgs.ToEventData(this.healthReporter, nameof(EventSourceInput)));
this.OnEventWrittenImpl(eventArgs);
}
}

Expand Down Expand Up @@ -155,46 +158,95 @@ private void EnableAsNecessary(EventSource eventSource)
}
else
{
EventSourceConfiguration provider = this.EventSources.FirstOrDefault(p => p.ProviderName == eventSource.Name);
if (provider != null)
foreach(EventSourceConfiguration sourceConfiguration in this.EventSources)
{
// LIMITATION: There is a known issue where if we listen to the FrameworkEventSource, the dataflow pipeline may hang when it
// tries to process the Threadpool event. The reason is the dataflow pipeline itself is using Task library for scheduling async
// tasks, which then itself also fires Threadpool events on FrameworkEventSource at unexpected locations, and trigger deadlocks.
// Hence, we like special case this and mask out Threadpool events.
EventKeywords keywords = provider.Keywords;
if (provider.ProviderName == "System.Diagnostics.Eventing.FrameworkEventSource")
if (sourceConfiguration.Enables(eventSource))
{
// Turn off the Threadpool | ThreadTransfer keyword. Definition is at http://referencesource.microsoft.com/#mscorlib/system/diagnostics/eventing/frameworkeventsource.cs
// However, if keywords was to begin with, then we need to set it to All first, which is 0xFFFFF....
if (keywords == 0)
// LIMITATION: There is a known issue where if we listen to the FrameworkEventSource, the dataflow pipeline may hang when it
// tries to process the Threadpool event. The reason is the dataflow pipeline itself is using Task library for scheduling async
// tasks, which then itself also fires Threadpool events on FrameworkEventSource at unexpected locations, and trigger deadlocks.
// Hence, we like special case this and mask out Threadpool events.
EventKeywords keywords = sourceConfiguration.Keywords;
if (sourceConfiguration.ProviderName == "System.Diagnostics.Eventing.FrameworkEventSource")
{
keywords = EventKeywords.All;
// Turn off the Threadpool | ThreadTransfer keyword. Definition is at http://referencesource.microsoft.com/#mscorlib/system/diagnostics/eventing/frameworkeventsource.cs
// However, if keywords was not set, then we need to set it to All first, which is 0xFFFFF....
if (keywords == 0)
{
keywords = EventKeywords.All;
}
keywords &= (EventKeywords)~0x12;
}
keywords &= (EventKeywords)~0x12;

this.EnableEvents(eventSource, sourceConfiguration.Level, keywords);
}
this.EnableEvents(eventSource, provider.Level, keywords);
}
}
}

private void Initialize(IReadOnlyCollection<EventSourceConfiguration> eventSources, IHealthReporter healthReporter)
private void Initialize(List<EventSourceConfiguration> eventSources, IHealthReporter healthReporter)
{
this.healthReporter = healthReporter;
this.subject = new EventFlowSubject<EventData>();

if (eventSources.Count() == 0)
{
healthReporter.ReportWarning($"{nameof(EventSourceInput)}: no event sources configured", EventFlowContextIdentifiers.Configuration);
}

var invalidConfigurationItems = new List<EventSourceConfiguration>();
foreach(var eventSourceConfiguration in eventSources)
{
if (!eventSourceConfiguration.Validate())
{
healthReporter.ReportProblem($"{nameof(EventSourceInput)}: configuration for one of the sources is invalid", EventFlowContextIdentifiers.Configuration);
invalidConfigurationItems.Add(eventSourceConfiguration);
}
}
// eventSources is a collection created by us, so we can modify it as necessary
eventSources.RemoveAll(config => invalidConfigurationItems.Contains(config));
this.EventSources = eventSources;
if (this.EventSources.Count() == 0)

bool haveDisabledSources = this.EventSources.Any(config => !string.IsNullOrWhiteSpace(config.DisabledProviderNamePrefix));
if (haveDisabledSources)
{
healthReporter.ReportWarning($"{nameof(EventSourceInput)}: no event sources configured", nameof(EventSourceInput));
this.disabledSources = new ConcurrentDictionary<string, bool>();
this.OnEventWrittenImpl = BroadcastEventIfSourceNotDisabled;
}

else
{
this.OnEventWrittenImpl = BroadcastEvent;
}

// Make sure the constructor has run to completion before enabling any sources.
this.initialization = Task.Run(() =>
{
this.constructed = true;
EnableInitialSources();
});
}

private void BroadcastEventIfSourceNotDisabled(EventWrittenEventArgs eventArgs)
{
string eventSourceName = eventArgs.EventSource.Name;
if (eventSourceName != null)
{
bool isDisabled;
if (!this.disabledSources.TryGetValue(eventSourceName, out isDisabled))
{
isDisabled = this.EventSources.Any(eventSourceConfiguration => eventSourceConfiguration.Disables(eventArgs.EventSource));
this.disabledSources[eventSourceName] = isDisabled;
}

if (isDisabled) { return; }
}

BroadcastEvent(eventArgs);
}

private void BroadcastEvent(EventWrittenEventArgs eventArgs)
{
this.subject.OnNext(eventArgs.ToEventData(this.healthReporter, nameof(EventSourceInput)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Provides an input implementation for capturing diagnostics data sourced through System.Diagnostics.Tracing.EventSource infrastructure.</Description>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<VersionPrefix>1.1.4</VersionPrefix>
<VersionPrefix>1.2.0</VersionPrefix>
<Authors>Microsoft</Authors>
<TargetFrameworks>netstandard1.6;net46</TargetFrameworks>
<DelaySign>true</DelaySign>
Expand Down
Loading

0 comments on commit 6bf39f4

Please sign in to comment.