Skip to content

Commit

Permalink
feat: New Garbage Collection Metrics Sampler for .NET 6+ (#2838)
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr authored Oct 24, 2024
1 parent d312d30 commit f24a5da
Show file tree
Hide file tree
Showing 31 changed files with 1,272 additions and 56 deletions.
11 changes: 11 additions & 0 deletions src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ private void CollectOneTimeMetrics()
ReportInfiniteTracingOneTimeMetrics();
ReportIfLoggingDisabled();
ReportIfInstrumentationIsDisabled();
ReportIfGCSamplerV2IsEnabled();
}

public void CollectMetrics()
Expand Down Expand Up @@ -838,5 +839,15 @@ private void ReportIfInstrumentationIsDisabled()
ReportSupportabilityGaugeMetric(MetricNames.SupportabilityIgnoredInstrumentation, ignoredCount);
}
}

private void ReportIfGCSamplerV2IsEnabled()
{
if (_configuration.GCSamplerV2Enabled)
{
ReportSupportabilityCountMetric(MetricNames.SupportabilityGCSamplerV2Enabled);
}

}

}
}
4 changes: 2 additions & 2 deletions src/Agent/NewRelic/Agent/Core/AgentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private AgentManager()
}

_container = AgentServices.GetContainer();
AgentServices.RegisterServices(_container, bootstrapConfig.ServerlessModeEnabled);
AgentServices.RegisterServices(_container, bootstrapConfig.ServerlessModeEnabled, bootstrapConfig.GCSamplerV2Enabled);

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

AgentServices.StartServices(_container, bootstrapConfig.ServerlessModeEnabled);
AgentServices.StartServices(_container, bootstrapConfig.ServerlessModeEnabled, bootstrapConfig.GCSamplerV2Enabled);

// Setup the internal API first so that AgentApi can use it.
InternalApi.SetAgentApiImplementation(agentApi);
Expand Down
36 changes: 36 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NewRelic.Agent.Core.Configuration;
using NewRelic.Agent.Core.Utilities;
using NewRelic.Agent.Extensions.Logging;
using NewRelic.Agent.Core.SharedInterfaces;
using NewRelic.Agent.Extensions.SystemExtensions.Collections.Generic;

namespace NewRelic.Agent.Core.Config
{
Expand All @@ -21,6 +24,7 @@ public interface IBootstrapConfiguration
string ServerlessFunctionName { get; }
string ServerlessFunctionVersion { get; }
bool AzureFunctionModeDetected { get; }
bool GCSamplerV2Enabled { get; }
}

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

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

public bool GCSamplerV2Enabled { get; private set;}

private bool CheckServerlessModeEnabled(configuration localConfiguration)
{
// We may need these later even if we don't use it now.
Expand All @@ -154,6 +161,11 @@ private bool CheckServerlessModeEnabled(configuration localConfiguration)
return localConfiguration.serverlessModeEnabled;
}

private bool CheckGCSamplerV2Enabled(bool localConfigurationGcSamplerV2Enabled)
{
return localConfigurationGcSamplerV2Enabled || (ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_GC_SAMPLER_V2_ENABLED").TryToBoolean(out var enabledViaEnvVariable) && enabledViaEnvVariable);
}

private void SetAgentEnabledValues()
{
_agentEnabledWithProvenance = TryGetAgentEnabledFromWebConfig();
Expand Down Expand Up @@ -204,6 +216,30 @@ private ValueWithProvenance<bool> TryGetAgentEnabledSetting(Func<string, ValueWi
return null;
}

private Dictionary<string, string> TransformAppSettings(configuration localConfiguration)
{
if (localConfiguration.appSettings == null)
return new Dictionary<string, string>();

return localConfiguration.appSettings
.Where(setting => setting != null)
.Select(setting => new KeyValuePair<string, string>(setting.key, setting.value))
.ToDictionary(IEnumerableExtensions.DuplicateKeyBehavior.KeepFirst);
}

private bool TryGetAppSettingAsBoolWithDefault(configuration localConfiguration, string key, bool defaultValue)
{
var value = TransformAppSettings(localConfiguration).GetValueOrDefault(key);

bool parsedBool;
var parsedSuccessfully = bool.TryParse(value, out parsedBool);
if (!parsedSuccessfully)
return defaultValue;

return parsedBool;
}


private class BootstrapLogConfig : ILogConfig
{
private readonly string _directoryFromLocalConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1912,7 +1912,6 @@ public bool UtilizationDetectAzureFunction
}
}


public int? UtilizationLogicalProcessors
{
get
Expand Down Expand Up @@ -2163,7 +2162,8 @@ public string AzureFunctionResourceIdWithFunctionName(string functionName)
return string.Empty;
}

return $"{AzureFunctionResourceId}/functions/{functionName}"; }
return $"{AzureFunctionResourceId}/functions/{functionName}";
}

public string AzureFunctionResourceGroupName
{
Expand Down Expand Up @@ -2466,6 +2466,8 @@ public TimeSpan StackExchangeRedisCleanupCycle
}
}

public bool GCSamplerV2Enabled => _bootstrapConfiguration.GCSamplerV2Enabled;

#endregion

#region Helpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ public ReportedConfiguration(IConfiguration configuration)

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

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

public IReadOnlyDictionary<string, string> GetAppSettings()
{
Expand Down
29 changes: 23 additions & 6 deletions src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using System.Collections.Generic;
#if NETFRAMEWORK
using System.Threading;
#endif
using NewRelic.Agent.Api;
using NewRelic.Agent.Configuration;
using NewRelic.Agent.Core.AgentHealth;
Expand Down Expand Up @@ -58,7 +60,8 @@ public static IContainer GetContainer()
/// </summary>
/// <param name="container"></param>
/// <param name="serverlessModeEnabled"></param>
public static void RegisterServices(IContainer container, bool serverlessModeEnabled)
/// <param name="gcSamplerV2Enabled"></param>
public static void RegisterServices(IContainer container, bool serverlessModeEnabled, bool gcSamplerV2Enabled)
{
// we register this factory instead of just loading the storage contexts here because deferring the logic gives us a logger
container.RegisterFactory<IEnumerable<IContextStorageFactory>>(ExtensionsLoader.LoadContextStorageFactories);
Expand Down Expand Up @@ -91,9 +94,18 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
container.Register<IPerformanceCounterProxyFactory, PerformanceCounterProxyFactory>();
container.Register<GcSampler, GcSampler>();
#else
container.RegisterInstance<Func<ISampledEventListener<Dictionary<GCSampleType, float>>>>(() => new GCEventsListener());
container.RegisterInstance<Func<GCSamplerNetCore.SamplerIsApplicableToFrameworkResult>>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault);
container.Register<GCSamplerNetCore, GCSamplerNetCore>();
if (gcSamplerV2Enabled)
{
container.Register<IGCSamplerV2ReflectionHelper, GCSamplerV2ReflectionHelper>();
container.Register<IGCSampleTransformerV2, GCSampleTransformerV2>();
container.Register<GCSamplerV2, GCSamplerV2>();
}
else
{
container.RegisterInstance<Func<ISampledEventListener<Dictionary<GCSampleType, float>>>>(() => new GCEventsListener());
container.RegisterInstance<Func<GCSamplerNetCore.SamplerIsApplicableToFrameworkResult>>(GCSamplerNetCore.FXsamplerIsApplicableToFrameworkDefault);
container.Register<GCSamplerNetCore, GCSamplerNetCore>();
}
#endif

container.Register<IBrowserMonitoringPrereqChecker, BrowserMonitoringPrereqChecker>();
Expand Down Expand Up @@ -225,7 +237,7 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
/// <summary>
/// Starts all of the services needed by resolving them.
/// </summary>
public static void StartServices(IContainer container, bool serverlessModeEnabled)
public static void StartServices(IContainer container, bool serverlessModeEnabled, bool gcSamplerV2Enabled)
{
if (!serverlessModeEnabled)
container.Resolve<AssemblyResolutionService>();
Expand All @@ -242,7 +254,12 @@ public static void StartServices(IContainer container, bool serverlessModeEnable
samplerStartThread.Start();
#else
if (!serverlessModeEnabled)
container.Resolve<GCSamplerNetCore>().Start();
{
if (!gcSamplerV2Enabled)
container.Resolve<GCSamplerNetCore>().Start();
else
container.Resolve<GCSamplerV2>().Start();
}
#endif
if (!serverlessModeEnabled)
{
Expand Down
15 changes: 15 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ public static string GetSupportabilityInstallType(string installType)
public const string SupportabilityLoggingFatalError = "Supportability/DotNET/AgentLogging/DisabledDueToError";

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

#endregion Supportability

Expand Down Expand Up @@ -1034,6 +1035,20 @@ public static string GetThreadpoolThroughputStatsName(ThreadpoolThroughputStatsT

{ GCSampleType.LOHSize , "GC/LOH/Size" },
{ GCSampleType.LOHSurvived, "GC/LOH/Survived" },

{ GCSampleType.LOHCollectionCount, "GC/LOH/Collections" },
{ GCSampleType.POHCollectionCount, "GC/POH/Collections" },

{ GCSampleType.TotalHeapMemory, "GC/Heap/Total" },
{ GCSampleType.TotalCommittedMemory, "GC/Heap/Committed" },
{ GCSampleType.TotalAllocatedMemory, "GC/Heap/Allocated" },

{ GCSampleType.Gen0FragmentationSize, "GC/Gen0/Fragmentation" },
{ GCSampleType.Gen1FragmentationSize, "GC/Gen1/Fragmentation" },
{ GCSampleType.Gen2FragmentationSize, "GC/Gen2/Fragmentation" },
{ GCSampleType.LOHFragmentationSize, "GC/LOH/Fragmentation" },
{ GCSampleType.POHFragmentationSize, "GC/POH/Fragmentation" },
{ GCSampleType.POHSize, "GC/POH/Size" }
};

public static string GetGCMetricName(GCSampleType sampleType)
Expand Down
90 changes: 90 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Samplers/GCSampleType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

namespace NewRelic.Agent.Core.Samplers
{
public enum GCSampleType
{
/// <summary>
/// Gen 0 heap size as of the current sample
/// </summary>
Gen0Size,
Gen0Promoted,
/// <summary>
/// Gen 1 heap size as of the current sample
/// </summary>
Gen1Size,
Gen1Promoted,
/// <summary>
/// Gen 2 heap size as of the current sample
/// </summary>
Gen2Size,
Gen2Survived,
/// <summary>
/// Large object heap size as of the current sample
/// </summary>
LOHSize,
LOHSurvived,
HandlesCount,
InducedCount,
PercentTimeInGc,
/// <summary>
/// Gen 0 heap collection count since the last sample
/// </summary>
Gen0CollectionCount,
/// <summary>
/// Gen 1 heap collection count since the last sample
/// </summary>
Gen1CollectionCount,
/// <summary>
/// Gen 2 heap collection count since the last sample
/// </summary>
Gen2CollectionCount,

// the following are supported by GCSamplerV2 only
/// <summary>
/// Pinned object heap size
/// </summary>
POHSize,
/// <summary>
/// Large object heap collection count since the last sample
/// </summary>
LOHCollectionCount,
/// <summary>
/// Pinned object heap collection count since the last sample
/// </summary>
POHCollectionCount,
/// <summary>
/// Total heap memory in use as of the current sample
/// </summary>
TotalHeapMemory,
/// <summary>
/// Total committed memory in use as of the current sample
/// </summary>
TotalCommittedMemory,
/// <summary>
/// Total heap memory allocated since the last sample
/// </summary>
TotalAllocatedMemory,
/// <summary>
/// Fragmentation of the Gen 0 heap as of the current sample
/// </summary>
Gen0FragmentationSize,
/// <summary>
/// Fragmentation of the Gen 1 heap as of the current sample
/// </summary>
Gen1FragmentationSize,
/// <summary>
/// Fragmentation of the Gen 2 heap as of the current sample
/// </summary>
Gen2FragmentationSize,
/// <summary>
/// Fragmentation of the Large Object heap as of the current sample
/// </summary>
LOHFragmentationSize,
/// <summary>
/// Fragmentation of the Pinned Object heap as of the current sample
/// </summary>
POHFragmentationSize,
}
}
Loading

0 comments on commit f24a5da

Please sign in to comment.