diff --git a/README.md b/README.md index c2e13cb..267b2a4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A plugin for monitoring Microsoft SQL Server using the New Relic platform. 1. Run a text editor **as administrator** and open the file `INSTALLDIR\NewRelic.Microsoft.SqlServer.Plugin.exe.config`. 2. Find the setting `` and replace `YOUR_KEY_HERE` with your New Relic license key. 3. Configure one or more SQL Servers or Azure SQL Databases - * In the `` section, add a `` setting for a SQL Server. + * In the `` section, add a `` setting for _each_ SQL Server instance you wish to monitor. * `name="Production Database"` The name of your server is visible on the New Relic dashboard. * `connectionString="Server=prd.domain.com,1433;Database=master;Trusted_Connection=True;"` Any valid connection string to your database. * In the `` section, add a `` setting for _each_ Windows Azure SQL Database. diff --git a/build/SQL-Azure.png b/build/SQL-Azure.png new file mode 100644 index 0000000..d5f6ba9 Binary files /dev/null and b/build/SQL-Azure.png differ diff --git a/build/SQL-Server.png b/build/SQL-Server.png new file mode 100644 index 0000000..8b054b4 Binary files /dev/null and b/build/SQL-Server.png differ diff --git a/build/versions.targets b/build/versions.targets index d95248b..6a65dbc 100644 --- a/build/versions.targets +++ b/build/versions.targets @@ -3,7 +3,7 @@ 1 0 - 10 + 11 diff --git a/src/Common/CommonAssemblyInfo.cs b/src/Common/CommonAssemblyInfo.cs index 658dfce..4b2cf58 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.10")] -[assembly: AssemblyFileVersion("1.0.10")] -[assembly: AssemblyInformationalVersion("1.0.10")] +[assembly: AssemblyVersion("1.0.11")] +[assembly: AssemblyFileVersion("1.0.11")] +[assembly: AssemblyInformationalVersion("1.0.11")] diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/NewRelic.Microsoft.SqlServer.Plugin.Tests.csproj b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/NewRelic.Microsoft.SqlServer.Plugin.Tests.csproj index 3442e84..26cc28e 100644 --- a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/NewRelic.Microsoft.SqlServer.Plugin.Tests.csproj +++ b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/NewRelic.Microsoft.SqlServer.Plugin.Tests.csproj @@ -70,7 +70,7 @@ - + diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitorQueryTests.cs b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitorQueryTests.cs new file mode 100644 index 0000000..256aeb7 --- /dev/null +++ b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitorQueryTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; + +using NewRelic.Microsoft.SqlServer.Plugin.Core; +using NewRelic.Microsoft.SqlServer.Plugin.QueryTypes; +using NewRelic.Platform.Binding.DotNET; + +using NSubstitute; + +using NUnit.Framework; + +namespace NewRelic.Microsoft.SqlServer.Plugin +{ + [TestFixture] + public class SqlMonitorQueryTests + { + protected class FakeQueryType + { + public long Long { get; set; } + public int Integer { get; set; } + public short Short { get; set; } + public byte Byte { get; set; } + public decimal Decimal { get; set; } + public string Comment { get; set; } + public DateTime EventTime { get; set; } + } + + [Test] + public void Assert_only_numerics_are_returned() + { + MetricMapper[] metricMappers = MetricQuery.GetMappers(typeof (FakeQueryType)); + Assert.That(metricMappers, Is.Not.Null); + + // Keep these out of order to ensure we don't depend on it + var expected = new[] {"Long", "Integer", "Short", "Decimal", "Byte"}; + string[] actual = metricMappers.Select(m => m.MetricName).ToArray(); + Assert.That(actual, Is.EquivalentTo(expected), "Properties discovered and mapped wrong"); + } + + [Test] + [TestCase(MetricTransformEnum.Delta)] + [TestCase(MetricTransformEnum.Simple)] + public void Assert_query_sets_appropriate_metric_transform(MetricTransformEnum metricTransformEnum) + { + var attribute = new SqlServerQueryAttribute("FileIO.sql", "Foo/Bar") {MetricTransformEnum = metricTransformEnum}; + var sqlQuery = new SqlQuery(typeof (FileIoView), attribute, Substitute.For(), ""); + + Assert.That(sqlQuery.MetricTransformEnum, Is.EqualTo(attribute.MetricTransformEnum), "SqlQuery did not set correct value from attribute for MetricTransformEnum"); + } + + [Test] + public void Assert_results_are_mapped_into_metrics() + { + var fakes = new[] + { + new FakeQueryType + { + Long = 42, + Integer = 27, + Short = 12, + Byte = 255, + Decimal = 407.54m, + Comment = "Utterly worthless... except for logging", + EventTime = DateTime.Now, + } + }; + + var sqlQuery = new SqlQuery(typeof (FakeQueryType), new SqlServerQueryAttribute("foo.sql", "Fake/"), Substitute.For(), ""); + + var componentData = new ComponentData(); + sqlQuery.AddMetrics(new QueryContext(sqlQuery) {ComponentData = componentData, Results = fakes,}); + + var expected = new[] {"Fake/Long", "Fake/Integer", "Fake/Short", "Fake/Decimal", "Fake/Byte"}; + string[] actual = componentData.Metrics.Keys.ToArray(); + Assert.That(actual, Is.EquivalentTo(expected), "Properties discovered and mapped wrong"); + } + } +} diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitoryQueryTests.cs b/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitoryQueryTests.cs deleted file mode 100644 index db24f6e..0000000 --- a/src/NewRelic.Microsoft.SqlServer.Plugin.Tests/SqlMonitoryQueryTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Linq; - -using NSubstitute; - -using NUnit.Framework; - -using NewRelic.Microsoft.SqlServer.Plugin.Core; -using NewRelic.Platform.Binding.DotNET; - -namespace NewRelic.Microsoft.SqlServer.Plugin -{ - [TestFixture] - public class SqlMonitoryQueryTests - { - protected class FakeQueryType - { - public long Long { get; set; } - public int Integer { get; set; } - public short Short { get; set; } - public byte Byte { get; set; } - public decimal Decimal { get; set; } - public string Comment { get; set; } - public DateTime EventTime { get; set; } - } - - [Test] - public void Assert_only_numerics_are_returned() - { - var metricMappers = SqlQuery.GetMappers(typeof (FakeQueryType)); - Assert.That(metricMappers, Is.Not.Null); - - // Keep these out of order to ensure we don't depend on it - var expected = new[] {"Long", "Integer", "Short", "Decimal", "Byte"}; - var actual = metricMappers.Select(m => m.MetricName).ToArray(); - Assert.That(actual, Is.EquivalentTo(expected), "Properties discovered and mapped wrong"); - } - - [Test] - public void Assert_results_are_mapped_into_metrics() - { - var fakes = new[] - { - new FakeQueryType - { - Long = 42, - Integer = 27, - Short = 12, - Byte = 255, - Decimal = 407.54m, - Comment = "Utterly worthless... except for logging", - EventTime = DateTime.Now, - } - }; - - var sqlQuery = new SqlQuery(typeof (FakeQueryType), new SqlServerQueryAttribute("foo.sql", "Fake/"), Substitute.For(), ""); - - var componentData = new ComponentData(); - sqlQuery.AddMetrics(new QueryContext(sqlQuery) {ComponentData = componentData, Results = fakes,}); - - var expected = new[] {"Fake/Long", "Fake/Integer", "Fake/Short", "Fake/Decimal", "Fake/Byte"}; - var actual = componentData.Metrics.Keys.ToArray(); - Assert.That(actual, Is.EquivalentTo(expected), "Properties discovered and mapped wrong"); - } - } -} diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/MetricQuery.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/MetricQuery.cs index aa7815b..e837be2 100644 --- a/src/NewRelic.Microsoft.SqlServer.Plugin/MetricQuery.cs +++ b/src/NewRelic.Microsoft.SqlServer.Plugin/MetricQuery.cs @@ -11,20 +11,21 @@ public class MetricQuery : IMetricQuery { private readonly MetricMapper[] _metricMappers; - public MetricQuery(Type queryType, string queryName, string resultTypeName) + public MetricQuery(Type queryType, string queryName, string resultTypeName, MetricTransformEnum metricTransformEnum = MetricTransformEnum.Simple) { QueryType = queryType; QueryName = queryName; ResultTypeName = resultTypeName; _metricMappers = GetMappers(QueryType); MetricPattern = string.Format("Component/{0}", QueryType.Name); + MetricTransformEnum = metricTransformEnum; } public Type QueryType { get; private set; } public string QueryName { get; private set; } - public MetricTransformEnum MetricTransformEnum { get; set; } + public MetricTransformEnum MetricTransformEnum { get; protected set; } public string MetricPattern { get; protected set; } @@ -52,4 +53,4 @@ internal static MetricMapper[] GetMappers(Type type) .ToArray(); } } -} \ No newline at end of file +} diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlQuery.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlQuery.cs index c9a0f6c..9e4af16 100644 --- a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlQuery.cs +++ b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlQuery.cs @@ -23,7 +23,7 @@ public class SqlQuery : MetricQuery, ISqlQuery internal QueryAttribute QueryAttribute; public SqlQuery(Type queryType, QueryAttribute attribute, IDapperWrapper dapperWrapper, string commandText = null) - : base(queryType, attribute.QueryName, queryType.Name) + : base(queryType, attribute.QueryName, queryType.Name, attribute.MetricTransformEnum) { _dapperWrapper = dapperWrapper; QueryAttribute = attribute; diff --git a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlServerEndpoint.cs b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlServerEndpoint.cs index 292ccf6..0a5a527 100644 --- a/src/NewRelic.Microsoft.SqlServer.Plugin/SqlServerEndpoint.cs +++ b/src/NewRelic.Microsoft.SqlServer.Plugin/SqlServerEndpoint.cs @@ -3,13 +3,13 @@ using System.Data.SqlClient; using System.Linq; +using log4net; + using NewRelic.Microsoft.SqlServer.Plugin.Configuration; using NewRelic.Microsoft.SqlServer.Plugin.Core; using NewRelic.Microsoft.SqlServer.Plugin.Properties; using NewRelic.Microsoft.SqlServer.Plugin.QueryTypes; -using log4net; - namespace NewRelic.Microsoft.SqlServer.Plugin { public class SqlServerEndpoint : SqlEndpointBase @@ -35,15 +35,15 @@ public SqlServerEndpoint(string name, string connectionString, bool includeSyste if (includeSystemDatabases && includedDbs.Any()) { - var systemDbsToAdd = Constants.SystemDatabases - .Where(dbName => includedDbs.All(db => db.Name != dbName)) - .Select(dbName => new Database {Name = dbName}); + IEnumerable systemDbsToAdd = Constants.SystemDatabases + .Where(dbName => includedDbs.All(db => db.Name != dbName)) + .Select(dbName => new Database {Name = dbName}); includedDbs.AddRange(systemDbsToAdd); } else if (!includeSystemDatabases) { - var systemDbsToAdd = Constants.SystemDatabases - .Where(dbName => excludedDbs.All(db => db != dbName)); + IEnumerable systemDbsToAdd = Constants.SystemDatabases + .Where(dbName => excludedDbs.All(db => db != dbName)); excludedDbs.AddRange(systemDbsToAdd); } @@ -71,7 +71,8 @@ internal static string FormatProperties(string name, string connectionString, st } /// - /// Replaces the database name on results with the when possible. + /// Replaces the database name on results with the + /// when possible. /// /// /// @@ -82,19 +83,19 @@ internal static void ApplyDatabaseDisplayNames(IEnumerable includedDat return; } - var renameMap = includedDatabases.Where(d => !string.IsNullOrEmpty(d.DisplayName)).ToDictionary(d => d.Name.ToLower(), d => d.DisplayName); + Dictionary renameMap = includedDatabases.Where(d => !string.IsNullOrEmpty(d.DisplayName)).ToDictionary(d => d.Name.ToLower(), d => d.DisplayName); if (!renameMap.Any()) { return; } - var databaseMetrics = results.OfType().Where(d => !string.IsNullOrEmpty(d.DatabaseName)).ToArray(); + IDatabaseMetric[] databaseMetrics = results.OfType().Where(d => !string.IsNullOrEmpty(d.DatabaseName)).ToArray(); if (!databaseMetrics.Any()) { return; } - foreach (var databaseMetric in databaseMetrics) + foreach (IDatabaseMetric databaseMetric in databaseMetrics) { string displayName; if (renameMap.TryGetValue(databaseMetric.DatabaseName.ToLower(), out displayName)) @@ -118,17 +119,17 @@ public override void ToLog(ILog log) try { var queryLocator = new QueryLocator(new DapperWrapper()); - var serverDetailsQuery = queryLocator.PrepareQueries(new[] {typeof (SqlServerDetails),}, false).Single(); - var databasesDetailsQuery = queryLocator.PrepareQueries(new[] {typeof (DatabaseDetails),}, false).Single(); + SqlQuery serverDetailsQuery = queryLocator.PrepareQueries(new[] {typeof (SqlServerDetails),}, false).Single(); + SqlQuery databasesDetailsQuery = queryLocator.PrepareQueries(new[] {typeof (DatabaseDetails),}, false).Single(); using (var conn = new SqlConnection(ConnectionString)) { // Log the server details - var serverDetails = serverDetailsQuery.Query(conn, this).Single(); - LogVerboseSqlResults(serverDetailsQuery, new [] { serverDetails }); + SqlServerDetails serverDetails = serverDetailsQuery.Query(conn, this).Single(); + LogVerboseSqlResults(serverDetailsQuery, new[] {serverDetails}); log.InfoFormat(" {0} {1} {2} ({3})", serverDetails.SQLTitle, serverDetails.Edition, serverDetails.ProductLevel, serverDetails.ProductVersion); // Sotre these for reporting below - var databasesDetails = databasesDetailsQuery.DatabaseMetricQuery(conn, this).ToArray(); + DatabaseDetails[] databasesDetails = databasesDetailsQuery.DatabaseMetricQuery(conn, this).ToArray(); LogVerboseSqlResults(databasesDetailsQuery, databasesDetails); databaseDetailsByName = databasesDetails.ToDictionary(d => d.DatabaseName); } @@ -140,13 +141,13 @@ public override void ToLog(ILog log) databaseDetailsByName = null; } - var hasExplicitIncludedDatabases = IncludedDatabaseNames.Any(); + bool hasExplicitIncludedDatabases = IncludedDatabaseNames.Any(); if (hasExplicitIncludedDatabases) { // Show the user the databases we'll be working from - foreach (var database in IncludedDatabases) + foreach (Database database in IncludedDatabases) { - var message = " Including DB: " + database.Name; + string message = " Including DB: " + database.Name; // When the details are reachable, show them if (databaseDetailsByName != null) @@ -155,7 +156,12 @@ public override void ToLog(ILog log) if (databaseDetailsByName.TryGetValue(database.Name, out details)) { message += string.Format(" [CompatibilityLevel={0};State={1}({2});CreateDate={3:yyyy-MM-dd};UserAccess={4}({5})]", - details.compatibility_level, details.state_desc, details.state, details.create_date, details.user_access_desc, details.user_access); + details.compatibility_level, + details.state_desc, + details.state, + details.create_date, + details.user_access_desc, + details.user_access); } else { @@ -171,16 +177,22 @@ public override void ToLog(ILog log) { // The user didn't specifically include any databases // Report details for all of the DBs we expect to gather metrics against - foreach (var details in databaseDetailsByName.Values) + foreach (DatabaseDetails details in databaseDetailsByName.Values) { log.InfoFormat(" Including DB: {0} [CompatibilityLevel={1};State={2}({3});CreateDate={4:yyyy-MM-dd};UserAccess={5}({6})]", - details.DatabaseName, details.compatibility_level, details.state_desc, details.state, details.create_date, details.user_access_desc, details.user_access); + details.DatabaseName, + details.compatibility_level, + details.state_desc, + details.state, + details.create_date, + details.user_access_desc, + details.user_access); } } // If there are included DB's, log the Excluded DB's as DEBUG info. - var logger = hasExplicitIncludedDatabases ? (Action) log.Debug : log.Info; - foreach (var database in ExcludedDatabaseNames) + Action logger = hasExplicitIncludedDatabases ? (Action) log.Debug : log.Info; + foreach (string database in ExcludedDatabaseNames) { logger(" Excluding DB: " + database); } @@ -195,16 +207,16 @@ protected override object[] OnQueryExecuted(ISqlQuery query, object[] results, I public override IEnumerable ExecuteQueries(ILog log) { - var queries = base.ExecuteQueries(log); + IEnumerable queries = base.ExecuteQueries(log); - foreach (var query in queries) + foreach (IQueryContext query in queries) { yield return query; if (query.QueryType != typeof (RecompileSummary)) continue; // Manually add this summary metric - var max = GetMaxRecompileSummaryMetric(query.Results); + IQueryContext max = GetMaxRecompileSummaryMetric(query.Results); if (max != null) { yield return max; @@ -213,7 +225,7 @@ public override IEnumerable ExecuteQueries(ILog log) } /// - /// Reports a maximum set of values for the recompiles to support the Summary Metric on the New Relic dashboard + /// Reports a maximum set of values for the recompiles to support the Summary Metric on the New Relic dashboard /// /// /// @@ -221,7 +233,7 @@ internal IQueryContext GetMaxRecompileSummaryMetric(IEnumerable results) { if (results == null) return null; - var recompileSummaries = results.OfType().ToArray(); + RecompileSummary[] recompileSummaries = results.OfType().ToArray(); if (!recompileSummaries.Any()) return null; var max = new RecompileMaximums