/// <summary> /// Creates an XML-renderable Extension object that represents the instrumentation settings /// specified by the supplied list of targets. /// </summary> /// <param name="targets">The set of targets to be instrumented.</param> /// <returns>An Extension object representing the in-memory New Relic custom /// instrumentation configuration XML document.</returns> public static Extension Render(IEnumerable<InstrumentationTarget> targets) { Instrumentation toReturn = new Instrumentation(); // Group the targets by metric name... var byMetricName = targets.GroupBy(x => x.MetricName ?? string.Empty); foreach (var groupedByMetricName in byMetricName.OrderBy(x => x.Key)) { // Then by name var byName = groupedByMetricName.GroupBy(x => x.Name ?? string.Empty); foreach (var groupedByName in byName.OrderBy(x => x.Key)) { // Then by priority var byPriority = groupedByName.GroupBy(x => x.TransactionNamingPriority ?? string.Empty); foreach (var groupedByPriority in byPriority.OrderBy(x => x.Key)) { // Then group by metric var byMetric = groupedByPriority.GroupBy(x => x.Metric); foreach (var groupedByMetric in byMetric.OrderBy(x => x.Key)) { string metricName = groupedByMetricName.Key == string.Empty ? null : groupedByMetricName.Key; string name = groupedByName.Key == string.Empty ? null : groupedByName.Key; string priority = groupedByPriority.Key == string.Empty ? null : groupedByPriority.Key; TracerFactory tracerFactory = new TracerFactory(metricName, name, priority, groupedByMetric.Key); var byType = groupedByMetric.GroupBy(x => x.Target.DeclaringType); foreach (var groupedByType in byType.OrderBy(x => x.Key.Assembly.FullName).ThenBy(x => x.Key.FullName)) { Match match = GetMatchFromType(groupedByType.Key); // Each item in the groupedByType enumerable is a method to be instrumented foreach (var toInstrument in groupedByType.OrderBy(x => x.Target.Name)) { ExactMethodMatcher methodMatcher = GetMatcherFromTarget(toInstrument); match.Matches.Add(methodMatcher); } // De-dupe the method matchers, in case we have some parameterless // entries and some with - as the parameterless ones will take precedence // anyway, there's no point keeping the others HashSet<ExactMethodMatcher> toDelete = new HashSet<ExactMethodMatcher>(); foreach (var matcher in match.Matches.Where(x => string.IsNullOrWhiteSpace(x.ParameterTypes))) { toDelete.UnionWith(match.Matches.Where(x => x.MethodName == matcher.MethodName && !string.IsNullOrWhiteSpace(x.ParameterTypes))); } match.Matches.RemoveAll(x => toDelete.Contains(x)); tracerFactory.MatchDefinitions.Add(match); } toReturn.TracerFactories.Add(tracerFactory); } } } } return new Extension() { Instrumentation = toReturn }; }
public void Merge_CreatesNewFactories_WhenFactoryDefinitionsDifferent() { TracerFactory first = new TracerFactory(); Match firstMatch = new Match() { AssemblyName = "Test", ClassName = "TestClass" }; firstMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType1", "ParamType2" })); first.MatchDefinitions.Add(firstMatch); TracerFactory second = new TracerFactory("DifferentFactory", "TestFactory", "1", Metric.Scoped); Match secondMatch = new Match() { AssemblyName = firstMatch.AssemblyName, ClassName = firstMatch.ClassName }; secondMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType1", "ParamType2" })); second.MatchDefinitions.Add(secondMatch); Extension firstExtension = new Extension(); firstExtension.Instrumentation.TracerFactories.Add(first); Extension secondExtension = new Extension(); secondExtension.Instrumentation.TracerFactories.Add(second); Extension merged = Extension.Merge(firstExtension, secondExtension); Assert.AreEqual(2, merged.Instrumentation.TracerFactories.Count); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count(x => x.MetricName == "DifferentFactory" && x.Metric == Metric.Scoped)); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count(x => x.Name == "TestFactory" && x.Metric == Metric.Scoped)); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count(x => x.TransactionNamingPriority == "1" && x.Metric == Metric.Scoped)); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count(x => x.Metric == Metric.Unspecified)); var different = merged.Instrumentation.TracerFactories.Where(x => x.MetricName == "DifferentFactory").First(); var other = merged.Instrumentation.TracerFactories.Where(x => x != different).First(); Assert.AreEqual(1, different.MatchDefinitions.Count); Assert.AreEqual(1, other.MatchDefinitions.Count); Assert.AreEqual("Test", different.MatchDefinitions.First().AssemblyName); Assert.AreEqual("Test", other.MatchDefinitions.First().AssemblyName); Assert.AreEqual("TestClass", different.MatchDefinitions.First().ClassName); Assert.AreEqual("TestClass", other.MatchDefinitions.First().ClassName); Assert.AreEqual(1, different.MatchDefinitions.First().Matches.Count()); Assert.AreEqual(1, other.MatchDefinitions.First().Matches.Count()); Assert.AreEqual(1, different.MatchDefinitions.First().Matches.Count(x => x.MethodName == "TestMethod1" && x.ParameterTypes == "ParamType1,ParamType2")); Assert.AreEqual(1, other.MatchDefinitions.First().Matches.Count(x => x.MethodName == "TestMethod1" && x.ParameterTypes == "ParamType1,ParamType2")); }
public void Merge_CombinesExactMethodsMatchers_WhenContextSame() { TracerFactory first = new TracerFactory("MetricName", "Name", "1", Metric.Scoped); Match firstMatch = new Match() { AssemblyName = "Test", ClassName = "TestClass" }; firstMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType1", "ParamType2" })); first.MatchDefinitions.Add(firstMatch); TracerFactory second = new TracerFactory("MetricName", "Name", "1", Metric.Scoped); Match secondMatch = new Match() { AssemblyName = firstMatch.AssemblyName, ClassName = firstMatch.ClassName }; secondMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType3" })); second.MatchDefinitions.Add(secondMatch); Extension firstExtension = new Extension(); firstExtension.Instrumentation.TracerFactories.Add(first); Extension secondExtension = new Extension(); secondExtension.Instrumentation.TracerFactories.Add(second); Extension merged = Extension.Merge(firstExtension, secondExtension); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count); var firstFactory = merged.Instrumentation.TracerFactories.First(); Assert.AreEqual("MetricName", firstFactory.MetricName); Assert.AreEqual("Name", firstFactory.Name); Assert.AreEqual("1", firstFactory.TransactionNamingPriority); Assert.AreEqual(Metric.Scoped, firstFactory.Metric); Assert.IsNotNull(firstFactory.MatchDefinitions); var firstFactoryMatches = firstFactory.MatchDefinitions; Assert.AreEqual(1, firstFactoryMatches.Count()); var firstFactoryMatch = firstFactoryMatches.First(); Assert.AreEqual("Test", firstFactoryMatch.AssemblyName); Assert.AreEqual("TestClass", firstFactoryMatch.ClassName); var methodMatchers = firstFactoryMatch.Matches; Assert.AreEqual(2, methodMatchers.Count); Assert.AreEqual(2, methodMatchers.Count(x => x.MethodName == "TestMethod1")); Assert.AreEqual(1, methodMatchers.Count(x => x.MethodName == "TestMethod1" && x.ParameterTypes == "ParamType1,ParamType2")); Assert.AreEqual(1, methodMatchers.Count(x => x.MethodName == "TestMethod1" && x.ParameterTypes == "ParamType3")); }
/// <summary> /// Creates an XML-renderable Extension object that represents the instrumentation settings /// specified by the supplied list of targets. /// </summary> /// <param name="targets">The set of targets to be instrumented.</param> /// <returns>An Extension object representing the in-memory New Relic custom /// instrumentation configuration XML document.</returns> public static Extension Render(IEnumerable <InstrumentationTarget> targets) { Instrumentation toReturn = new Instrumentation(); // Group the targets by metric name... var byMetricName = targets.GroupBy(x => x.MetricName ?? string.Empty); foreach (var groupedByMetricName in byMetricName.OrderBy(x => x.Key)) { // Then by name var byName = groupedByMetricName.GroupBy(x => x.Name ?? string.Empty); foreach (var groupedByName in byName.OrderBy(x => x.Key)) { // Then by priority var byPriority = groupedByName.GroupBy(x => x.TransactionNamingPriority ?? string.Empty); foreach (var groupedByPriority in byPriority.OrderBy(x => x.Key)) { // Then group by metric var byMetric = groupedByPriority.GroupBy(x => x.Metric); foreach (var groupedByMetric in byMetric.OrderBy(x => x.Key)) { string metricName = groupedByMetricName.Key == string.Empty ? null : groupedByMetricName.Key; string name = groupedByName.Key == string.Empty ? null : groupedByName.Key; string priority = groupedByPriority.Key == string.Empty ? null : groupedByPriority.Key; TracerFactory tracerFactory = new TracerFactory(metricName, name, priority, groupedByMetric.Key); var byType = groupedByMetric.GroupBy(x => x.Target.DeclaringType); foreach (var groupedByType in byType.OrderBy(x => x.Key.Assembly.FullName).ThenBy(x => x.Key.FullName)) { Match match = GetMatchFromType(groupedByType.Key); // Each item in the groupedByType enumerable is a method to be instrumented foreach (var toInstrument in groupedByType.OrderBy(x => x.Target.Name)) { ExactMethodMatcher methodMatcher = GetMatcherFromTarget(toInstrument); match.Matches.Add(methodMatcher); } // De-dupe the method matchers, in case we have some parameterless // entries and some with - as the parameterless ones will take precedence // anyway, there's no point keeping the others HashSet <ExactMethodMatcher> toDelete = new HashSet <ExactMethodMatcher>(); foreach (var matcher in match.Matches.Where(x => string.IsNullOrWhiteSpace(x.ParameterTypes))) { toDelete.UnionWith(match.Matches.Where(x => x.MethodName == matcher.MethodName && !string.IsNullOrWhiteSpace(x.ParameterTypes))); } match.Matches.RemoveAll(x => toDelete.Contains(x)); tracerFactory.MatchDefinitions.Add(match); } toReturn.TracerFactories.Add(tracerFactory); } } } } return(new Extension() { Instrumentation = toReturn }); }
/// <summary> /// Produces a new Extension object that represents the union of all instrumentation /// targets specified in the supplied Extension objects. /// </summary> /// <param name="toMerge">The Extension objects to be merged.</param> /// <returns>A single Extension object that represents the combined instrumentation /// footprint described by all of the supplied Extension objects.</returns> public static Extension Merge(params Extension[] toMerge) { Extension toReturn = new Extension(); var matchRecords = new Dictionary<DenormalisedExactMatchRecord, HashSet<ExactMethodMatcher>>(); foreach (var factory in toMerge.SelectMany(x => x.Instrumentation.TracerFactories)) { foreach (var match in factory.MatchDefinitions) { DenormalisedExactMatchRecord matchRecord = new DenormalisedExactMatchRecord { Metric = factory.Metric, MetricName = factory.MetricName, Name = factory.Name, TransactionNamingPriority = factory.TransactionNamingPriority, AssemblyName = match.AssemblyName, ClassName = match.ClassName }; HashSet<ExactMethodMatcher> matchers = null; if (!matchRecords.TryGetValue(matchRecord, out matchers)) { matchers = matchRecords[matchRecord] = new HashSet<ExactMethodMatcher>(); } matchers.UnionWith(match.Matches.Select(x => new ExactMethodMatcher { MethodName = x.MethodName, ParameterTypes = x.ParameterTypes })); } } // Group the records by factory details, then by assy/classname pair var keysByFactoryDetails = matchRecords.Keys.GroupBy(x => new { Metric = x.Metric, MetricName = x.MetricName, Name = x.Name, TransactionNamingPriority = x.TransactionNamingPriority}); foreach (var factoryDetail in keysByFactoryDetails) { TracerFactory toAdd = new TracerFactory(factoryDetail.Key.MetricName, factoryDetail.Key.Name, factoryDetail.Key.TransactionNamingPriority, factoryDetail.Key.Metric); var byClassDetail = factoryDetail.GroupBy(x => new { AssemblyName = x.AssemblyName, ClassName = x.ClassName }); foreach (var classDetail in byClassDetail) { Match matchToAdd = new Match(classDetail.Key.AssemblyName, classDetail.Key.ClassName); var matchRecord = new DenormalisedExactMatchRecord { Metric = toAdd.Metric, MetricName = toAdd.MetricName, Name = toAdd.Name, TransactionNamingPriority = toAdd.TransactionNamingPriority, AssemblyName = matchToAdd.AssemblyName, ClassName = matchToAdd.ClassName }; matchToAdd.Matches = matchRecords[matchRecord].OrderBy(x => x.MethodName).ThenBy(x => x.ParameterTypes).ToList(); toAdd.MatchDefinitions.Add(matchToAdd); } toAdd.MatchDefinitions = toAdd.MatchDefinitions.OrderBy(x => x.AssemblyName).ThenBy(x => x.ClassName).ToList(); toReturn.Instrumentation.TracerFactories.Add(toAdd); } // Have tracer factories prefer unspecified metrics and metric names first, then others later toReturn.Instrumentation.TracerFactories = toReturn .Instrumentation .TracerFactories .OrderBy(x => x.Metric == Metric.Unspecified ? -1 : (int) x.Metric) .ThenBy(x => x.MetricName ?? string.Empty) .ThenBy(x => x.Name ?? string.Empty) .ThenBy(x => x.TransactionNamingPriority ?? string.Empty) .ToList(); return toReturn; }
public void Merge_DoesNotDuplicateMethodMatchers_WhenContextSame() { TracerFactory first = new TracerFactory(); Match firstMatch = new Match() { AssemblyName = "Test", ClassName = "TestClass" }; firstMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType1", "ParamType2" })); first.MatchDefinitions.Add(firstMatch); TracerFactory second = new TracerFactory(); Match secondMatch = new Match() { AssemblyName = firstMatch.AssemblyName, ClassName = firstMatch.ClassName }; secondMatch.Matches.Add(new ExactMethodMatcher("TestMethod1", new[] { "ParamType1", "ParamType2" })); second.MatchDefinitions.Add(secondMatch); Extension firstExtension = new Extension(); firstExtension.Instrumentation.TracerFactories.Add(first); Extension secondExtension = new Extension(); secondExtension.Instrumentation.TracerFactories.Add(second); Extension merged = Extension.Merge(firstExtension, secondExtension); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.Count()); Assert.IsTrue(string.IsNullOrWhiteSpace(merged.Instrumentation.TracerFactories.First().MetricName)); Assert.AreEqual(Metric.Unspecified, merged.Instrumentation.TracerFactories.First().Metric); Assert.AreEqual(1, merged.Instrumentation.TracerFactories.First().MatchDefinitions.Count); var firstMergedMatch = merged.Instrumentation.TracerFactories.First().MatchDefinitions.First(); Assert.AreEqual("Test", firstMergedMatch.AssemblyName); Assert.AreEqual("TestClass", firstMergedMatch.ClassName); Assert.AreEqual(1, firstMergedMatch.Matches.Count); Assert.AreEqual("TestMethod1", firstMergedMatch.Matches.First().MethodName); Assert.AreEqual("ParamType1,ParamType2", string.Join(",", firstMergedMatch.Matches.First().ParameterTypes)); }
/// <summary> /// Produces a new Extension object that represents the union of all instrumentation /// targets specified in the supplied Extension objects. /// </summary> /// <param name="toMerge">The Extension objects to be merged.</param> /// <returns>A single Extension object that represents the combined instrumentation /// footprint described by all of the supplied Extension objects.</returns> public static Extension Merge(params Extension[] toMerge) { Extension toReturn = new Extension(); var matchRecords = new Dictionary <DenormalisedExactMatchRecord, HashSet <ExactMethodMatcher> >(); foreach (var factory in toMerge.SelectMany(x => x.Instrumentation.TracerFactories)) { foreach (var match in factory.MatchDefinitions) { DenormalisedExactMatchRecord matchRecord = new DenormalisedExactMatchRecord { Metric = factory.Metric, MetricName = factory.MetricName, Name = factory.Name, TransactionNamingPriority = factory.TransactionNamingPriority, AssemblyName = match.AssemblyName, ClassName = match.ClassName }; HashSet <ExactMethodMatcher> matchers = null; if (!matchRecords.TryGetValue(matchRecord, out matchers)) { matchers = matchRecords[matchRecord] = new HashSet <ExactMethodMatcher>(); } matchers.UnionWith(match.Matches.Select(x => new ExactMethodMatcher { MethodName = x.MethodName, ParameterTypes = x.ParameterTypes })); } } // Group the records by factory details, then by assy/classname pair var keysByFactoryDetails = matchRecords.Keys.GroupBy(x => new { Metric = x.Metric, MetricName = x.MetricName, Name = x.Name, TransactionNamingPriority = x.TransactionNamingPriority }); foreach (var factoryDetail in keysByFactoryDetails) { TracerFactory toAdd = new TracerFactory(factoryDetail.Key.MetricName, factoryDetail.Key.Name, factoryDetail.Key.TransactionNamingPriority, factoryDetail.Key.Metric); var byClassDetail = factoryDetail.GroupBy(x => new { AssemblyName = x.AssemblyName, ClassName = x.ClassName }); foreach (var classDetail in byClassDetail) { Match matchToAdd = new Match(classDetail.Key.AssemblyName, classDetail.Key.ClassName); var matchRecord = new DenormalisedExactMatchRecord { Metric = toAdd.Metric, MetricName = toAdd.MetricName, Name = toAdd.Name, TransactionNamingPriority = toAdd.TransactionNamingPriority, AssemblyName = matchToAdd.AssemblyName, ClassName = matchToAdd.ClassName }; matchToAdd.Matches = matchRecords[matchRecord].OrderBy(x => x.MethodName).ThenBy(x => x.ParameterTypes).ToList(); toAdd.MatchDefinitions.Add(matchToAdd); } toAdd.MatchDefinitions = toAdd.MatchDefinitions.OrderBy(x => x.AssemblyName).ThenBy(x => x.ClassName).ToList(); toReturn.Instrumentation.TracerFactories.Add(toAdd); } // Have tracer factories prefer unspecified metrics and metric names first, then others later toReturn.Instrumentation.TracerFactories = toReturn .Instrumentation .TracerFactories .OrderBy(x => x.Metric == Metric.Unspecified ? -1 : (int)x.Metric) .ThenBy(x => x.MetricName ?? string.Empty) .ThenBy(x => x.Name ?? string.Empty) .ThenBy(x => x.TransactionNamingPriority ?? string.Empty) .ToList(); return(toReturn); }