Skip to content

Commit f24a5da

Browse files
authored
feat: New Garbage Collection Metrics Sampler for .NET 6+ (#2838)
1 parent d312d30 commit f24a5da

File tree

31 files changed

+1272
-56
lines changed

31 files changed

+1272
-56
lines changed

src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs

+11
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ private void CollectOneTimeMetrics()
683683
ReportInfiniteTracingOneTimeMetrics();
684684
ReportIfLoggingDisabled();
685685
ReportIfInstrumentationIsDisabled();
686+
ReportIfGCSamplerV2IsEnabled();
686687
}
687688

688689
public void CollectMetrics()
@@ -838,5 +839,15 @@ private void ReportIfInstrumentationIsDisabled()
838839
ReportSupportabilityGaugeMetric(MetricNames.SupportabilityIgnoredInstrumentation, ignoredCount);
839840
}
840841
}
842+
843+
private void ReportIfGCSamplerV2IsEnabled()
844+
{
845+
if (_configuration.GCSamplerV2Enabled)
846+
{
847+
ReportSupportabilityCountMetric(MetricNames.SupportabilityGCSamplerV2Enabled);
848+
}
849+
850+
}
851+
841852
}
842853
}

src/Agent/NewRelic/Agent/Core/AgentManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private AgentManager()
115115
}
116116

117117
_container = AgentServices.GetContainer();
118-
AgentServices.RegisterServices(_container, bootstrapConfig.ServerlessModeEnabled);
118+
AgentServices.RegisterServices(_container, bootstrapConfig.ServerlessModeEnabled, bootstrapConfig.GCSamplerV2Enabled);
119119

120120
// Resolve IConfigurationService (so that it starts listening to config change events) and then publish the serialized event
121121
_container.Resolve<IConfigurationService>();
@@ -162,7 +162,7 @@ private AgentManager()
162162
Log.Info("The New Relic agent is operating in serverless mode.");
163163
}
164164

165-
AgentServices.StartServices(_container, bootstrapConfig.ServerlessModeEnabled);
165+
AgentServices.StartServices(_container, bootstrapConfig.ServerlessModeEnabled, bootstrapConfig.GCSamplerV2Enabled);
166166

167167
// Setup the internal API first so that AgentApi can use it.
168168
InternalApi.SetAgentApiImplementation(agentApi);

src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs

+36
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
7+
using System.Linq;
68
using NewRelic.Agent.Core.Configuration;
79
using NewRelic.Agent.Core.Utilities;
810
using NewRelic.Agent.Extensions.Logging;
911
using NewRelic.Agent.Core.SharedInterfaces;
12+
using NewRelic.Agent.Extensions.SystemExtensions.Collections.Generic;
1013

1114
namespace NewRelic.Agent.Core.Config
1215
{
@@ -21,6 +24,7 @@ public interface IBootstrapConfiguration
2124
string ServerlessFunctionName { get; }
2225
string ServerlessFunctionVersion { get; }
2326
bool AzureFunctionModeDetected { get; }
27+
bool GCSamplerV2Enabled { get; }
2428
}
2529

2630
/// <summary>
@@ -64,6 +68,7 @@ public BootstrapConfiguration(configuration localConfiguration, string configura
6468
public BootstrapConfiguration(configuration localConfiguration, string configurationFileName, Func<string, ValueWithProvenance<string>> getWebConfigSettingWithProvenance, IConfigurationManagerStatic configurationManagerStatic, IProcessStatic processStatic, Predicate<string> checkDirectoryExists, Func<string, string> getFullPath)
6569
{
6670
ServerlessModeEnabled = CheckServerlessModeEnabled(localConfiguration);
71+
GCSamplerV2Enabled = CheckGCSamplerV2Enabled(TryGetAppSettingAsBoolWithDefault(localConfiguration, "GCSamplerV2Enabled", false));
6772
DebugStartupDelaySeconds = localConfiguration.debugStartupDelaySeconds;
6873
ConfigurationFileName = configurationFileName;
6974
LogConfig = new BootstrapLogConfig(localConfiguration.log, processStatic, checkDirectoryExists, getFullPath);
@@ -133,6 +138,8 @@ public string AgentEnabledAt
133138

134139
public bool AzureFunctionModeDetected => ConfigLoaderHelpers.GetEnvironmentVar("FUNCTIONS_WORKER_RUNTIME") != null;
135140

141+
public bool GCSamplerV2Enabled { get; private set;}
142+
136143
private bool CheckServerlessModeEnabled(configuration localConfiguration)
137144
{
138145
// We may need these later even if we don't use it now.
@@ -154,6 +161,11 @@ private bool CheckServerlessModeEnabled(configuration localConfiguration)
154161
return localConfiguration.serverlessModeEnabled;
155162
}
156163

164+
private bool CheckGCSamplerV2Enabled(bool localConfigurationGcSamplerV2Enabled)
165+
{
166+
return localConfigurationGcSamplerV2Enabled || (ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_GC_SAMPLER_V2_ENABLED").TryToBoolean(out var enabledViaEnvVariable) && enabledViaEnvVariable);
167+
}
168+
157169
private void SetAgentEnabledValues()
158170
{
159171
_agentEnabledWithProvenance = TryGetAgentEnabledFromWebConfig();
@@ -204,6 +216,30 @@ private ValueWithProvenance<bool> TryGetAgentEnabledSetting(Func<string, ValueWi
204216
return null;
205217
}
206218

219+
private Dictionary<string, string> TransformAppSettings(configuration localConfiguration)
220+
{
221+
if (localConfiguration.appSettings == null)
222+
return new Dictionary<string, string>();
223+
224+
return localConfiguration.appSettings
225+
.Where(setting => setting != null)
226+
.Select(setting => new KeyValuePair<string, string>(setting.key, setting.value))
227+
.ToDictionary(IEnumerableExtensions.DuplicateKeyBehavior.KeepFirst);
228+
}
229+
230+
private bool TryGetAppSettingAsBoolWithDefault(configuration localConfiguration, string key, bool defaultValue)
231+
{
232+
var value = TransformAppSettings(localConfiguration).GetValueOrDefault(key);
233+
234+
bool parsedBool;
235+
var parsedSuccessfully = bool.TryParse(value, out parsedBool);
236+
if (!parsedSuccessfully)
237+
return defaultValue;
238+
239+
return parsedBool;
240+
}
241+
242+
207243
private class BootstrapLogConfig : ILogConfig
208244
{
209245
private readonly string _directoryFromLocalConfig;

src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,6 @@ public bool UtilizationDetectAzureFunction
19121912
}
19131913
}
19141914

1915-
19161915
public int? UtilizationLogicalProcessors
19171916
{
19181917
get
@@ -2163,7 +2162,8 @@ public string AzureFunctionResourceIdWithFunctionName(string functionName)
21632162
return string.Empty;
21642163
}
21652164

2166-
return $"{AzureFunctionResourceId}/functions/{functionName}"; }
2165+
return $"{AzureFunctionResourceId}/functions/{functionName}";
2166+
}
21672167

21682168
public string AzureFunctionResourceGroupName
21692169
{
@@ -2466,6 +2466,8 @@ public TimeSpan StackExchangeRedisCleanupCycle
24662466
}
24672467
}
24682468

2469+
public bool GCSamplerV2Enabled => _bootstrapConfiguration.GCSamplerV2Enabled;
2470+
24692471
#endregion
24702472

24712473
#region Helpers

src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,8 @@ public ReportedConfiguration(IConfiguration configuration)
711711

712712
public string AzureFunctionResourceIdWithFunctionName(string functionName) => _configuration.AzureFunctionResourceIdWithFunctionName(functionName);
713713

714+
[JsonProperty("gc_sampler_v2.enabled")]
715+
public bool GCSamplerV2Enabled => _configuration.GCSamplerV2Enabled;
714716

715717
public IReadOnlyDictionary<string, string> GetAppSettings()
716718
{

src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs

+23-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using System;
55
using System.Collections.Generic;
6+
#if NETFRAMEWORK
67
using System.Threading;
8+
#endif
79
using NewRelic.Agent.Api;
810
using NewRelic.Agent.Configuration;
911
using NewRelic.Agent.Core.AgentHealth;
@@ -58,7 +60,8 @@ public static IContainer GetContainer()
5860
/// </summary>
5961
/// <param name="container"></param>
6062
/// <param name="serverlessModeEnabled"></param>
61-
public static void RegisterServices(IContainer container, bool serverlessModeEnabled)
63+
/// <param name="gcSamplerV2Enabled"></param>
64+
public static void RegisterServices(IContainer container, bool serverlessModeEnabled, bool gcSamplerV2Enabled)
6265
{
6366
// we register this factory instead of just loading the storage contexts here because deferring the logic gives us a logger
6467
container.RegisterFactory<IEnumerable<IContextStorageFactory>>(ExtensionsLoader.LoadContextStorageFactories);
@@ -91,9 +94,18 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
9194
container.Register<IPerformanceCounterProxyFactory, PerformanceCounterProxyFactory>();
9295
container.Register<GcSampler, GcSampler>();
9396
#else
94-
container.RegisterInstance<Func<ISampledEventListener<Dictionary<GCSampleType, float>>>>(() => new GCEventsListener());
95-
container.RegisterInstance<Func<GCSamplerNetCore.SamplerIsApplicableToFrameworkResult>>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault);
96-
container.Register<GCSamplerNetCore, GCSamplerNetCore>();
97+
if (gcSamplerV2Enabled)
98+
{
99+
container.Register<IGCSamplerV2ReflectionHelper, GCSamplerV2ReflectionHelper>();
100+
container.Register<IGCSampleTransformerV2, GCSampleTransformerV2>();
101+
container.Register<GCSamplerV2, GCSamplerV2>();
102+
}
103+
else
104+
{
105+
container.RegisterInstance<Func<ISampledEventListener<Dictionary<GCSampleType, float>>>>(() => new GCEventsListener());
106+
container.RegisterInstance<Func<GCSamplerNetCore.SamplerIsApplicableToFrameworkResult>>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault);
107+
container.Register<GCSamplerNetCore, GCSamplerNetCore>();
108+
}
97109
#endif
98110

99111
container.Register<IBrowserMonitoringPrereqChecker, BrowserMonitoringPrereqChecker>();
@@ -225,7 +237,7 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
225237
/// <summary>
226238
/// Starts all of the services needed by resolving them.
227239
/// </summary>
228-
public static void StartServices(IContainer container, bool serverlessModeEnabled)
240+
public static void StartServices(IContainer container, bool serverlessModeEnabled, bool gcSamplerV2Enabled)
229241
{
230242
if (!serverlessModeEnabled)
231243
container.Resolve<AssemblyResolutionService>();
@@ -242,7 +254,12 @@ public static void StartServices(IContainer container, bool serverlessModeEnable
242254
samplerStartThread.Start();
243255
#else
244256
if (!serverlessModeEnabled)
245-
container.Resolve<GCSamplerNetCore>().Start();
257+
{
258+
if (!gcSamplerV2Enabled)
259+
container.Resolve<GCSamplerNetCore>().Start();
260+
else
261+
container.Resolve<GCSamplerV2>().Start();
262+
}
246263
#endif
247264
if (!serverlessModeEnabled)
248265
{

src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs

+15
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ public static string GetSupportabilityInstallType(string installType)
837837
public const string SupportabilityLoggingFatalError = "Supportability/DotNET/AgentLogging/DisabledDueToError";
838838

839839
public const string SupportabilityIgnoredInstrumentation = SupportabilityDotnetPs + "IgnoredInstrumentation";
840+
public const string SupportabilityGCSamplerV2Enabled = SupportabilityDotnetPs + "GCSamplerV2/Enabled";
840841

841842
#endregion Supportability
842843

@@ -1034,6 +1035,20 @@ public static string GetThreadpoolThroughputStatsName(ThreadpoolThroughputStatsT
10341035

10351036
{ GCSampleType.LOHSize , "GC/LOH/Size" },
10361037
{ GCSampleType.LOHSurvived, "GC/LOH/Survived" },
1038+
1039+
{ GCSampleType.LOHCollectionCount, "GC/LOH/Collections" },
1040+
{ GCSampleType.POHCollectionCount, "GC/POH/Collections" },
1041+
1042+
{ GCSampleType.TotalHeapMemory, "GC/Heap/Total" },
1043+
{ GCSampleType.TotalCommittedMemory, "GC/Heap/Committed" },
1044+
{ GCSampleType.TotalAllocatedMemory, "GC/Heap/Allocated" },
1045+
1046+
{ GCSampleType.Gen0FragmentationSize, "GC/Gen0/Fragmentation" },
1047+
{ GCSampleType.Gen1FragmentationSize, "GC/Gen1/Fragmentation" },
1048+
{ GCSampleType.Gen2FragmentationSize, "GC/Gen2/Fragmentation" },
1049+
{ GCSampleType.LOHFragmentationSize, "GC/LOH/Fragmentation" },
1050+
{ GCSampleType.POHFragmentationSize, "GC/POH/Fragmentation" },
1051+
{ GCSampleType.POHSize, "GC/POH/Size" }
10371052
};
10381053

10391054
public static string GetGCMetricName(GCSampleType sampleType)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
namespace NewRelic.Agent.Core.Samplers
5+
{
6+
public enum GCSampleType
7+
{
8+
/// <summary>
9+
/// Gen 0 heap size as of the current sample
10+
/// </summary>
11+
Gen0Size,
12+
Gen0Promoted,
13+
/// <summary>
14+
/// Gen 1 heap size as of the current sample
15+
/// </summary>
16+
Gen1Size,
17+
Gen1Promoted,
18+
/// <summary>
19+
/// Gen 2 heap size as of the current sample
20+
/// </summary>
21+
Gen2Size,
22+
Gen2Survived,
23+
/// <summary>
24+
/// Large object heap size as of the current sample
25+
/// </summary>
26+
LOHSize,
27+
LOHSurvived,
28+
HandlesCount,
29+
InducedCount,
30+
PercentTimeInGc,
31+
/// <summary>
32+
/// Gen 0 heap collection count since the last sample
33+
/// </summary>
34+
Gen0CollectionCount,
35+
/// <summary>
36+
/// Gen 1 heap collection count since the last sample
37+
/// </summary>
38+
Gen1CollectionCount,
39+
/// <summary>
40+
/// Gen 2 heap collection count since the last sample
41+
/// </summary>
42+
Gen2CollectionCount,
43+
44+
// the following are supported by GCSamplerV2 only
45+
/// <summary>
46+
/// Pinned object heap size
47+
/// </summary>
48+
POHSize,
49+
/// <summary>
50+
/// Large object heap collection count since the last sample
51+
/// </summary>
52+
LOHCollectionCount,
53+
/// <summary>
54+
/// Pinned object heap collection count since the last sample
55+
/// </summary>
56+
POHCollectionCount,
57+
/// <summary>
58+
/// Total heap memory in use as of the current sample
59+
/// </summary>
60+
TotalHeapMemory,
61+
/// <summary>
62+
/// Total committed memory in use as of the current sample
63+
/// </summary>
64+
TotalCommittedMemory,
65+
/// <summary>
66+
/// Total heap memory allocated since the last sample
67+
/// </summary>
68+
TotalAllocatedMemory,
69+
/// <summary>
70+
/// Fragmentation of the Gen 0 heap as of the current sample
71+
/// </summary>
72+
Gen0FragmentationSize,
73+
/// <summary>
74+
/// Fragmentation of the Gen 1 heap as of the current sample
75+
/// </summary>
76+
Gen1FragmentationSize,
77+
/// <summary>
78+
/// Fragmentation of the Gen 2 heap as of the current sample
79+
/// </summary>
80+
Gen2FragmentationSize,
81+
/// <summary>
82+
/// Fragmentation of the Large Object heap as of the current sample
83+
/// </summary>
84+
LOHFragmentationSize,
85+
/// <summary>
86+
/// Fragmentation of the Pinned Object heap as of the current sample
87+
/// </summary>
88+
POHFragmentationSize,
89+
}
90+
}

0 commit comments

Comments
 (0)