diff --git a/README.md b/README.md
index eef0aae..c2e13cb 100644
--- a/README.md
+++ b/README.md
@@ -101,56 +101,26 @@ In a new connection to each individual Azure SQL Database:
## Proxy Support ##
-When the plugin is installed behind a proxy, there are a few configuration options.
-
-**Default Proxy**
-
-Assuming the proxy is configured at the system level (Internet Options in Control Panel), just uncomment the default configuration so that it reads as follows:
-
-
-
-
-
-
-
-This configuration may be used with the username and password in the next section.
-
-If you are upgrading from version 1.0.8 or earlier, you'll need to add that snippet to the config.
-
-**Authenticated Proxy**
-
-Ensure the above `defaultProxy` configuration is in place before setting the credentials. The New Relic .NET SDK requires that you uncomment the default configuration and set the username and password as below:
-
-
-
-
-
-
-When specifying the username and password, you *must* configure the default proxy seen immediately above. The domain name before the user is optional.
-
-If you are upgrading from version 1.0.8 or earlier, you'll need to add that snippet to the config.
-
-**Authenticated Proxy with Proxy URL**
-
-The authenticated proxy with specified URL is a bit of a mix of the previous two settings. It is also mutually exclusive to the previous proxy setting. First, ensure the `` contain the proxy URL, username, and password:
-
-
-
-
-
-
-
-
-Then the proxy handler must be configured. This is *similar* to the previous proxy config above:
-
-
-
-
-
-
-
-If you have any questions regarding the expected look of the config file, please review the [latest source on GitHub](https://github.com/newrelic-platform/newrelic_microsoft_sqlserver_plugin/blob/develop/src/NewRelic.Microsoft.SqlServer.Plugin/app.config).
-
+For installations behind a proxy, the details are set in the config file.
+
+
+
+
+
+
+If you are upgrading from version 1.0.9 or earlier, you'll need to replace the previous proxy settings with this new snippet in the config.
## Logging
diff --git a/build/versions.targets b/build/versions.targets
index 4bed4ba..d95248b 100644
--- a/build/versions.targets
+++ b/build/versions.targets
@@ -3,7 +3,7 @@
10
- 9
+ 10
diff --git a/lib/NewRelic.Platform.Binding.DotNET.dll b/lib/NewRelic.Platform.Binding.DotNET.dll
index f7b1bb6..6a88243 100644
Binary files a/lib/NewRelic.Platform.Binding.DotNET.dll and b/lib/NewRelic.Platform.Binding.DotNET.dll differ
diff --git a/lib/NewRelic.Platform.Binding.DotNET.pdb b/lib/NewRelic.Platform.Binding.DotNET.pdb
deleted file mode 100644
index abfead9..0000000
Binary files a/lib/NewRelic.Platform.Binding.DotNET.pdb and /dev/null differ
diff --git a/src/Common/CommonAssemblyInfo.cs b/src/Common/CommonAssemblyInfo.cs
index 6681236..658dfce 100644
--- a/src/Common/CommonAssemblyInfo.cs
+++ b/src/Common/CommonAssemblyInfo.cs
@@ -7,6 +7,6 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
-[assembly: AssemblyVersion("1.0.9")]
-[assembly: AssemblyFileVersion("1.0.9")]
-[assembly: AssemblyInformationalVersion("1.0.9")]
+[assembly: AssemblyVersion("1.0.10")]
+[assembly: AssemblyFileVersion("1.0.10")]
+[assembly: AssemblyInformationalVersion("1.0.10")]
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlEndpointTests.cs b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlEndpointTests.cs
index ac54f24..24fdd7b 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlEndpointTests.cs
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlEndpointTests.cs
@@ -27,6 +27,29 @@ public IEnumerable ComponentGuidTestCases
}
}
+ //Tests fix for issue where Plugin gets 400's after server is unavailable for a time https://support.newrelic.com/tickets/55385
+ [Test]
+ [TestCase(1, TestName = "A Minute Since Success")]
+ [TestCase(5, TestName = "5 Minutes Since Success")]
+ [TestCase(30, TestName = "30 Minutes Since Success")]
+ [TestCase(60, TestName = "An Hour Since Success")]
+ [TestCase(120, TestName = "Two Hours Since Success")]
+ [TestCase(1440, TestName = "A Day Since Success")]
+ [TestCase(2880, TestName = "Two Days Since Success")]
+ public void Assert_duration_does_not_exceed_allowed_max(int minutesSinceLastSuccessful)
+ {
+ var endpoint = new SqlServerEndpoint("Foo",".",false);
+ endpoint.MetricReportSuccessful(DateTime.Now.AddMinutes(minutesSinceLastSuccessful * -1));
+
+ const int thirtyMinutesInSeconds = 30 * 60;
+ Assert.That(endpoint.Duration, Is.LessThanOrEqualTo(thirtyMinutesInSeconds), "Duration should never be longer than 30 minutes, regardless of last succssful reported time");
+
+ endpoint.MetricReportSuccessful(DateTime.Now.AddMinutes(-.5));
+ Assert.That(endpoint.Duration, Is.LessThanOrEqualTo(31), "Duration should reset to be around 30 seconds regardless of previous value");
+
+ }
+
+ [Test]
public void Assert_endpoint_appropriately_massages_duplicated_data()
{
var endpoint = Substitute.For("", "");
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/NewRelicConfigurationSection.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/NewRelicConfigurationSection.cs
index 8529132..580a1e0 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/NewRelicConfigurationSection.cs
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/NewRelicConfigurationSection.cs
@@ -4,12 +4,18 @@ namespace NewRelic.Microsoft.SqlServer.Plugin.Configuration
{
internal class NewRelicConfigurationSection : ConfigurationSection
{
- [ConfigurationProperty("service")]
+ [ConfigurationProperty("service", IsRequired = true)]
public ServiceElement Service
{
get { return ((ServiceElement) (base["service"])); }
}
+ [ConfigurationProperty("proxy")]
+ public ProxyElement Proxy
+ {
+ get { return ((ProxyElement)(base["proxy"])); }
+ }
+
[ConfigurationProperty("sqlServers")]
public SqlServerCollection SqlServers
{
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/ProxyElement.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/ProxyElement.cs
new file mode 100644
index 0000000..97373d4
--- /dev/null
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/ProxyElement.cs
@@ -0,0 +1,62 @@
+using System.Configuration;
+
+namespace NewRelic.Microsoft.SqlServer.Plugin.Configuration
+{
+ internal class ProxyElement : ConfigurationElement
+ {
+ ///
+ /// The proxy server host name. Required.
+ ///
+ [ConfigurationProperty("host", DefaultValue = "", IsKey = false, IsRequired = true)]
+ public string Host
+ {
+ get { return ((string)(base["host"])); }
+ set { base["host"] = value; }
+ }
+ ///
+ /// The proxy server port (optional - defaults to 8080).
+ ///
+ [ConfigurationProperty("port", DefaultValue = "8080", IsKey = false, IsRequired = false)]
+ public string Port
+ {
+ get { return ((string)(base["port"])); }
+ set { base["port"] = value; }
+ }
+ ///
+ /// The username used to authenticate with the proxy server (optional).
+ ///
+ [ConfigurationProperty("user", DefaultValue = "", IsKey = false, IsRequired = false)]
+ public string User
+ {
+ get { return ((string)(base["user"])); }
+ set { base["user"] = value; }
+ }
+ ///
+ /// The password used to authenticate with the proxy server (optional).
+ ///
+ [ConfigurationProperty("password", DefaultValue = "", IsKey = false, IsRequired = false)]
+ public string Password
+ {
+ get { return ((string)(base["password"])); }
+ set { base["password"] = value; }
+ }
+ ///
+ /// The domain used to authenticate with the proxy server (optional).
+ ///
+ [ConfigurationProperty("domain", DefaultValue = "", IsKey = false, IsRequired = false)]
+ public string Domain
+ {
+ get { return ((string)(base["domain"])); }
+ set { base["domain"] = value; }
+ }
+ ///
+ /// 'true' or 'false. Uses the credentials of the account running the plugin (optional - defaults to false).
+ ///
+ [ConfigurationProperty("useDefaultCredentials", DefaultValue = "false", IsKey = false, IsRequired = false)]
+ public string UseDefaultCredentials
+ {
+ get { return ((string)(base["useDefaultCredentials"])); }
+ set { base["useDefaultCredentials"] = value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/Settings.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/Settings.cs
index cc9b7cc..e709986 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/Settings.cs
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/Configuration/Settings.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Net;
using System.Reflection;
using System.Security.Principal;
using System.Text.RegularExpressions;
@@ -13,6 +14,7 @@ namespace NewRelic.Microsoft.SqlServer.Plugin.Configuration
public class Settings
{
private string _version;
+ private static string _ProxyDetails;
public Settings(ISqlEndpoint[] endpoints)
{
@@ -74,9 +76,72 @@ internal static Settings FromConfigurationSection(NewRelicConfigurationSection s
settings.ServiceName = service.ServiceName;
}
+ var webProxy = GetWebProxy(section, log);
+ if (webProxy != null)
+ {
+ WebRequest.DefaultWebProxy = webProxy;
+ }
+
return settings;
}
+ private static IWebProxy GetWebProxy(NewRelicConfigurationSection section, ILog log)
+ {
+ var proxyElement = section.Proxy;
+ if (proxyElement == null || !proxyElement.ElementInformation.IsPresent) return null;
+
+ Uri uri;
+ if (!Uri.TryCreate(proxyElement.Host, UriKind.RelativeOrAbsolute, out uri))
+ {
+ log.ErrorFormat("Proxy host '{0}' is not a valid URI, skipping proxy.", proxyElement.Host);
+ return null;
+ }
+
+ int port;
+ if (!int.TryParse(proxyElement.Port, out port))
+ {
+ log.ErrorFormat("Unable to parse proxy port from '{0}', skipping proxy. Expecting a number from 1-65535.", proxyElement.Port);
+ return null;
+ }
+
+ WebProxy webProxy;
+ try
+ {
+ webProxy = new WebProxy(proxyElement.Host, port);
+ }
+ catch (Exception e)
+ {
+ log.ErrorFormat("Proxy settings are invalid. {0}", e.Message);
+ return null;
+ }
+
+ if ("true".Equals(proxyElement.UseDefaultCredentials, StringComparison.InvariantCultureIgnoreCase))
+ {
+ webProxy.UseDefaultCredentials = true;
+ webProxy.Credentials = CredentialCache.DefaultCredentials;
+ _ProxyDetails = string.Format("Proxy Server: {0}:{1} with default credentials", proxyElement.Host, port);
+ }
+ else if (!string.IsNullOrEmpty(proxyElement.User))
+ {
+ if (string.IsNullOrEmpty(proxyElement.Domain))
+ {
+ webProxy.Credentials = new NetworkCredential(proxyElement.User, proxyElement.Password);
+ _ProxyDetails = string.Format("Proxy Server: {0}@{1}:{2}", proxyElement.User, proxyElement.Host, port);
+ }
+ else
+ {
+ webProxy.Credentials = new NetworkCredential(proxyElement.User, proxyElement.Password, proxyElement.Domain);
+ _ProxyDetails = string.Format("Proxy Server: {0}\\{1}@{2}:{3}", proxyElement.Domain, proxyElement.User, proxyElement.Host, port);
+ }
+ }
+ else
+ {
+ _ProxyDetails = string.Format("Proxy Server: {0}:{1}", proxyElement.Host, port);
+ }
+
+ return webProxy;
+ }
+
public void ToLog(ILog log)
{
// Pending review by New Relic before adding this information
@@ -87,6 +152,12 @@ public void ToLog(ILog log)
log.Info(" Windows Service: " + (Environment.UserInteractive ? "No" : "Yes"));
log.InfoFormat(@" User: {0}\{1}", Environment.UserDomainName, Environment.UserName);
log.Info(" Run as Administrator: " + (IsProcessElevated ? "Yes" : "No"));
+
+ if (_ProxyDetails != null)
+ {
+ log.Info(" " + _ProxyDetails);
+ }
+
log.Info(" Total Endpoints: " + Endpoints.Length);
log.Info(" Poll Interval Seconds: " + PollIntervalSeconds);
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/NewRelic.Microsoft.SqlServer.Plugin.csproj b/src/NewRelic.Microsoft.SqlServer.Plugin/NewRelic.Microsoft.SqlServer.Plugin.csproj
index 83196fe..e766740 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin/NewRelic.Microsoft.SqlServer.Plugin.csproj
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/NewRelic.Microsoft.SqlServer.Plugin.csproj
@@ -73,6 +73,7 @@
+
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/Queries/DatabaseDetails.SqlServer.sql b/src/NewRelic.Microsoft.SqlServer.Plugin/Queries/DatabaseDetails.SqlServer.sql
index 6da32c4..03f52d1 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin/Queries/DatabaseDetails.SqlServer.sql
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/Queries/DatabaseDetails.SqlServer.sql
@@ -2,9 +2,9 @@
-- Retrieves relevant data about each database
-- Assists support when a user reports a problem
-
-SELECT
- d.[name] AS DatabaseName,
+SELECT d.[name] AS DatabaseName,
+ * -- Use * as differnt databases seem to have different columns. This isn't critical data so we must fail gracefully.
+/*
d.[database_id],
d.[source_database_id],
d.[create_date],
@@ -70,5 +70,6 @@ SELECT
d.[containment],
d.[containment_desc],
d.[target_recovery_time_in_seconds]
+*/
FROM sys.databases d
/*{WHERE}*/
\ No newline at end of file
diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlEndpointBase.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlEndpointBase.cs
index d158dac..30da068 100644
--- a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlEndpointBase.cs
+++ b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlEndpointBase.cs
@@ -4,18 +4,23 @@
using System.Linq;
using System.Text;
+using log4net;
+
using NewRelic.Microsoft.SqlServer.Plugin.Configuration;
using NewRelic.Microsoft.SqlServer.Plugin.Core.Extensions;
using NewRelic.Microsoft.SqlServer.Plugin.Properties;
using NewRelic.Microsoft.SqlServer.Plugin.QueryTypes;
using NewRelic.Platform.Binding.DotNET;
-using log4net;
-
namespace NewRelic.Microsoft.SqlServer.Plugin
{
public abstract class SqlEndpointBase : ISqlEndpoint
{
+ ///
+ /// Metrics with a Duration greater than this value will be rejected by the server (400)
+ ///
+ private const int MaximumAllowedDuration = 1800;
+
private static readonly ILog _ErrorDetailOutputLogger = LogManager.GetLogger(Constants.ErrorDetailLogger);
private static readonly ILog _VerboseSqlOutputLogger = LogManager.GetLogger(Constants.VerboseSqlLogger);
@@ -54,7 +59,14 @@ public string[] IncludedDatabaseNames
public int Duration
{
- get { return (int) DateTime.Now.Subtract(_lastSuccessfulReportTime).TotalSeconds; }
+ get
+ {
+ var secondsSinceLastSuccessfulReport = (int) DateTime.Now.Subtract(_lastSuccessfulReportTime).TotalSeconds;
+
+ return secondsSinceLastSuccessfulReport <= MaximumAllowedDuration
+ ? secondsSinceLastSuccessfulReport
+ : MaximumAllowedDuration;
+ }
}
public void SetQueries(IEnumerable queries)
@@ -77,7 +89,7 @@ public void UpdateHistory(IQueryContext[] queryContexts)
{
queryContexts.ForEach(queryContext =>
{
- var queryHistory = QueryHistory.GetOrCreate(queryContext.QueryName);
+ Queue queryHistory = QueryHistory.GetOrCreate(queryContext.QueryName);
if (queryHistory.Count >= 2) //Only track up to last run of this query
{
queryHistory.Dequeue();
@@ -90,8 +102,8 @@ public PlatformData GeneratePlatformData(AgentData agentData)
{
var platformData = new PlatformData(agentData);
- var pendingComponentData = QueryHistory.Select(qh => ComponentDataRetriever.GetData(qh.Value.ToArray()))
- .Where(c => c != null).ToArray();
+ ComponentData[] pendingComponentData = QueryHistory.Select(qh => ComponentDataRetriever.GetData(qh.Value.ToArray()))
+ .Where(c => c != null).ToArray();
pendingComponentData.ForEach(platformData.AddComponent);
@@ -110,12 +122,13 @@ public virtual void ToLog(ILog log)
log.InfoFormat(" {0}: {1}", Name, safeConnectionString);
// Validate that connection string do not provide both Trusted Security AND user/password
- var hasUserCreds = !string.IsNullOrEmpty(safeConnectionString.UserID) || !string.IsNullOrEmpty(safeConnectionString.Password);
+ bool hasUserCreds = !string.IsNullOrEmpty(safeConnectionString.UserID) || !string.IsNullOrEmpty(safeConnectionString.Password);
if (safeConnectionString.IntegratedSecurity == hasUserCreds)
{
log.Error("==================================================");
log.ErrorFormat("Connection string for '{0}' may not contain both Integrated Security and User ID/Password credentials. " +
- "Review the readme.md and update the config file.", safeConnectionString.DataSource);
+ "Review the readme.md and update the config file.",
+ safeConnectionString.DataSource);
log.Error("==================================================");
}
}
@@ -133,7 +146,7 @@ protected IEnumerable ExecuteQueries(SqlQuery[] queries, string c
using (var conn = new SqlConnection(connectionString))
{
- foreach (var query in queries)
+ foreach (SqlQuery query in queries)
{
object[] results;
try
@@ -160,11 +173,11 @@ protected static void LogVerboseSqlResults(ISqlQuery query, IEnumerable