Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New Garbage Collection Metrics Sampler for .NET 6+ #2838

Merged
merged 15 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading