Skip to content

Commit

Permalink
Various updates. Happy path is working.
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr committed Jan 24, 2025
1 parent f42929e commit fe9c23e
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 272 deletions.
101 changes: 64 additions & 37 deletions src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
using System.Net;
using System.Threading;
using System.IO;
using System.Security.Policy;
using System.Runtime.InteropServices;
using Grpc.Core;

namespace NewRelic.Agent.Core.AgentHealth
{
Expand All @@ -42,26 +40,16 @@ public class AgentHealthReporter : ConfigurationBasedService, IAgentHealthReport
private InterlockedCounter _traceContextCreateSuccessCounter;
private InterlockedCounter _traceContextAcceptSuccessCounter;

private readonly HealthCheck _healthCheck;
private HealthCheck _healthCheck;
private bool _healthChecksFailed;
private string _healthCheckPath;

public AgentHealthReporter(IMetricBuilder metricBuilder, IScheduler scheduler)
{
_healthCheck = new()
{
IsHealthy = true,
Status = "Agent starting",
LastError = string.Empty
};

_metricBuilder = metricBuilder;
_scheduler = scheduler;

// Want this to start immediately and write out the first health check - only if enabled
if (_configuration.AgentControlEnabled)
{
Log.Info(">>>>>>>>>AgentHealthReporter: Starting health check");
_scheduler.ExecuteEvery(PublishAgentControlHealthCheck, TimeSpan.FromSeconds(_configuration.HealthFrequency), TimeSpan.Zero);
}
InitializeHealthChecks();

_scheduler.ExecuteEvery(LogPeriodicReport, _timeBetweenExecutions);
var agentHealthEvents = Enum.GetValues(typeof(AgentHealthEvent)) as AgentHealthEvent[];
Expand All @@ -76,6 +64,60 @@ public AgentHealthReporter(IMetricBuilder metricBuilder, IScheduler scheduler)
_traceContextCreateSuccessCounter = new InterlockedCounter();
}

private void InitializeHealthChecks()
{
Log.Finest("Initializing Agent Control health checks");

if (!_configuration.AgentControlEnabled)
{
Log.Debug("Agent Control is disabled. Health checks will not be reported.");
return;
}

_healthCheck = new() { IsHealthy = true, Status = "Agent starting", LastError = string.Empty };

// make sure the delivery location is a file URI
var fileUri = new Uri(_configuration.HealthDeliveryLocation);
if (fileUri.Scheme != Uri.UriSchemeFile)
{
Log.Warn("Agent Control is enabled but the provided agent_control.health.delivery_location is not a file URL. Health checks will be disabled.");
_healthChecksFailed = true;
return;
}

_healthCheckPath = fileUri.LocalPath;

// verify the directory exists
if (!Directory.Exists(_healthCheckPath))
{
Log.Warn("Agent Control is enabled but the path specified in agent_control.health.delivery_location does not exist. Health checks will be disabled.");
_healthChecksFailed = true;
return;
}

// verify the directory is writeable
try
{
var tempFileName = Path.GetRandomFileName();
using (var writer = new StreamWriter(Path.Combine(_healthCheckPath, tempFileName)))
{
writer.Write("test");
}

File.Delete(Path.Combine(_healthCheckPath, tempFileName));
}
catch (Exception ex)
{
Log.Warn(ex, "Agent Control is enabled but the path specified in agent_control.health.delivery_location is not writeable. Health checks will be disabled.");
_healthChecksFailed = true;
return;
}

Log.Debug("Agent Control health checks will be published every {HealthCheckInterval} seconds", _configuration.HealthFrequency);
// schedule the health check and issue the first one immediately
_scheduler.ExecuteEvery(PublishAgentControlHealthCheck, TimeSpan.FromSeconds(_configuration.HealthFrequency), TimeSpan.Zero);
}

public override void Dispose()
{
_scheduler.StopExecuting(LogPeriodicReport);
Expand Down Expand Up @@ -719,37 +761,23 @@ public void SetAgentControlStatus((bool IsHealthy, string Code, string Status) h

public void PublishAgentControlHealthCheck()
{
if (!_configuration.AgentControlEnabled || _healthChecksFailed)
if (_healthChecksFailed)
return;

var fileUri = new Uri(_configuration.HealthDeliveryLocation);
if (fileUri.Scheme != Uri.UriSchemeFile)
{
Log.Debug("The provided agent_control.health.delivery_location is not a file URL, skipping agent control health check: " + _configuration.HealthDeliveryLocation);
_healthChecksFailed = true;
return;
}
var healthCheckYaml = _healthCheck.ToYaml();

// Ensure the path is cleaned up for Windows by removing a possible leading slash
var cleanedPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? fileUri.LocalPath.TrimStart('/') : fileUri.LocalPath;
// verify the directory exists and is writeable
if (!Directory.Exists(cleanedPath))
{
Log.Warn("Agent Control is enabled but the path specified in agent_control.health.delivery_location does not exist.");
_healthChecksFailed = true;
}
Log.Finest("Publishing Agent Control health check report: {HealthCheckYaml}", healthCheckYaml);

try
{
using StreamWriter writer = new StreamWriter(Path.Combine(cleanedPath, _healthCheck.FileName));
writer.WriteAsync(_healthCheck.ToYaml()).GetAwaiter().GetResult();
using StreamWriter writer = new StreamWriter(Path.Combine(_healthCheckPath, _healthCheck.FileName));
writer.Write(healthCheckYaml);
}
catch (Exception ex)
{
Log.Warn(ex, "Agent Control is enabled but the path specified in agent_control.health.delivery_location is not writeable.");
Log.Warn(ex, "Failed to write Agent Control health check report. Health checks will be disabled.");
_healthChecksFailed = true;
}

}

#endregion
Expand Down Expand Up @@ -849,7 +877,6 @@ private bool TryGetCount(InterlockedLongCounter counter, out long metricCount)
}

private ConcurrentBag<DestinationInteractionSample> _externalApiDataUsageSamples = new ConcurrentBag<DestinationInteractionSample>();
private bool _healthChecksFailed;

public void ReportSupportabilityDataUsage(string api, string apiArea, long dataSent, long dataReceived)
{
Expand Down
40 changes: 23 additions & 17 deletions src/Agent/NewRelic/Agent/Core/AgentHealth/HealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,40 @@ public class HealthCheck
/// <param name="statusParams"></param>
public void TrySetHealth((bool IsHealthy, string Code, string Status) healthStatus, params string[] statusParams)
{
// Threading!
if (IsHealthy != healthStatus.IsHealthy)
lock (this)
{
IsHealthy = healthStatus.IsHealthy;
}

if (!Status.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase))
{
if (statusParams != null && statusParams.Length > 0)
if (IsHealthy != healthStatus.IsHealthy)
{
Status = string.Format(Status, statusParams);
IsHealthy = healthStatus.IsHealthy;
}
else

if (!Status.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase))
{
Status = healthStatus.Status;
if (statusParams != null && statusParams.Length > 0)
{
Status = string.Format(Status, statusParams);
}
else
{
Status = healthStatus.Status;
}
}
}

if (!LastError.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase))
{
LastError = healthStatus.Code;
if (!LastError.Equals(healthStatus.Code, StringComparison.OrdinalIgnoreCase))
{
LastError = healthStatus.Code;
}
}
}

public string ToYaml()
{
StatusTime = DateTime.UtcNow;
return $"healthy: {IsHealthy}\nstatus: {Status}\nlast_error: {LastError}\nstatus_time_unix_nano: {StatusTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}\nstart_time_unix_nano: {StartTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}";
lock (this)
{
StatusTime = DateTime.UtcNow;
return
$"healthy: {IsHealthy}\nstatus: {Status}\nlast_error: {LastError}\nstart_time_unix_nano: {StartTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}\nstatus_time_unix_nano: {StatusTime.ToUnixTimeMilliseconds() * NanoSecondsPerMillisecond}";
}
}
}
}
15 changes: 15 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Config/BootstrapConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public interface IBootstrapConfiguration
string ServerlessFunctionVersion { get; }
bool AzureFunctionModeDetected { get; }
bool GCSamplerV2Enabled { get; }

bool AgentControlEnabled { get; }
string HealthDeliveryLocation { get; }
int HealthFrequency { get; }
}

/// <summary>
Expand Down Expand Up @@ -139,6 +143,17 @@ public string AgentEnabledAt
public bool AzureFunctionModeDetected => ConfigLoaderHelpers.GetEnvironmentVar("FUNCTIONS_WORKER_RUNTIME") != null;

public bool GCSamplerV2Enabled { get; private set;}
public bool AgentControlEnabled => ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_ENABLED").TryToBoolean(out var enabled) && enabled;
public string HealthDeliveryLocation => ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION");

public int HealthFrequency
{
get
{
var healthFrequencyString = ConfigLoaderHelpers.GetEnvironmentVar("NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY");
return int.TryParse(healthFrequencyString, out var healthFrequency) ? healthFrequency : 5;
}
}

private bool CheckServerlessModeEnabled(configuration localConfiguration)
{
Expand Down
131 changes: 0 additions & 131 deletions src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ public partial class configuration

private configurationCloud cloudField;

private configurationAgent_control agent_controlField;

private bool agentEnabledField;

private bool rootAgentEnabledField;
Expand All @@ -122,7 +120,6 @@ public partial class configuration
/// </summary>
public configuration()
{
this.agent_controlField = new configurationAgent_control();
this.cloudField = new configurationCloud();
this.codeLevelMetricsField = new configurationCodeLevelMetrics();
this.processHostField = new configurationProcessHost();
Expand Down Expand Up @@ -615,18 +612,6 @@ public configurationCloud cloud
}
}

public configurationAgent_control agent_control
{
get
{
return this.agent_controlField;
}
set
{
this.agent_controlField = value;
}
}

/// <summary>
/// Set this to true to enable the agent.
/// </summary>
Expand Down Expand Up @@ -6187,122 +6172,6 @@ public virtual configurationCloudAws Clone()
#endregion
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")]
public partial class configurationAgent_control
{

private configurationAgent_controlHealth healthField;

private bool enabledField;

/// <summary>
/// configurationAgent_control class constructor
/// </summary>
public configurationAgent_control()
{
this.healthField = new configurationAgent_controlHealth();
this.enabledField = false;
}

public configurationAgent_controlHealth health
{
get
{
return this.healthField;
}
set
{
this.healthField = value;
}
}

[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute(false)]
public bool enabled
{
get
{
return this.enabledField;
}
set
{
this.enabledField = value;
}
}

#region Clone method
/// <summary>
/// Create a clone of this configurationAgent_control object
/// </summary>
public virtual configurationAgent_control Clone()
{
return ((configurationAgent_control)(this.MemberwiseClone()));
}
#endregion
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")]
public partial class configurationAgent_controlHealth
{

private string deliveryLocationField;

private int frequencyField;

/// <summary>
/// configurationAgent_controlHealth class constructor
/// </summary>
public configurationAgent_controlHealth()
{
this.deliveryLocationField = "file:///newrelic/apm/health";
this.frequencyField = 5;
}

[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute("file:///newrelic/apm/health")]
public string deliveryLocation
{
get
{
return this.deliveryLocationField;
}
set
{
this.deliveryLocationField = value;
}
}

[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute(5)]
public int frequency
{
get
{
return this.frequencyField;
}
set
{
this.frequencyField = value;
}
}

#region Clone method
/// <summary>
/// Create a clone of this configurationAgent_controlHealth object
/// </summary>
public virtual configurationAgent_controlHealth Clone()
{
return ((configurationAgent_controlHealth)(this.MemberwiseClone()));
}
#endregion
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")]
Expand Down
Loading

0 comments on commit fe9c23e

Please sign in to comment.