├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── NewRelic.Platform.Sdk.FunctionalTests ├── App.config ├── ContextTest.cs ├── NewRelic.Platform.Sdk.FunctionalTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── RunnerTest.cs ├── TestAgent.cs ├── TestAgentFactory.cs └── config │ ├── newrelic.json │ └── plugin.json ├── NewRelic.Platform.Sdk.UnitTests ├── AgentTest.cs ├── App.config ├── Binding │ ├── ComponentDataTest.cs │ ├── ContextTest.cs │ ├── MetricDataTest.cs │ └── RequestDataTest.cs ├── MockContext.cs ├── NewRelic.Platform.Sdk.UnitTests.csproj ├── Processors │ └── EpochProcessorTest.cs ├── Properties │ └── AssemblyInfo.cs ├── RunnerTest.cs ├── TestAgent.cs ├── TestConfig.cs ├── TestSerializationHelper.cs ├── Utils │ └── LoggerTests.cs └── config │ ├── newrelic.json │ └── plugin.json ├── NewRelic.Platform.Sdk.sln ├── NewRelic.Platform.Sdk ├── Agent.cs ├── AgentFactory.cs ├── App.config ├── Binding │ ├── AgentData.cs │ ├── ComponentData.cs │ ├── Context.cs │ ├── IContext.cs │ ├── MetricData.cs │ └── RequestData.cs ├── Configuration │ ├── INewRelicConfig.cs │ ├── LogLevel.cs │ └── NewRelicConfig.cs ├── Extensions │ └── ExtensionsForAssembly.cs ├── Externals │ ├── NLog v2.1.0 │ │ ├── NLog.dll │ │ └── NLog.xml │ └── Newtonsoft.Json v5.8 │ │ ├── Newtonsoft.Json.dll │ │ └── Newtonsoft.Json.xml ├── NewRelic.Platform.Sdk.csproj ├── Processors │ ├── EpochProcessor.cs │ └── IProcessor.cs ├── Properties │ └── AssemblyInfo.cs ├── Runner.cs ├── Utils │ ├── Constants.cs │ ├── JsonHelper.cs │ ├── Logger.cs │ ├── NewRelicServiceException.cs │ └── StringExtensions.cs └── config │ └── newrelic.template.json ├── README.md └── dist ├── newrelic_platform_dotnet_sdk_v1.0.0.0.zip └── newrelic_platform_dotnet_sdk_v1.0.0.1.zip /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | 6 | # mstest test results 7 | TestResults 8 | 9 | ## Ignore Visual Studio temporary files, build results, and 10 | ## files generated by popular Visual Studio add-ons. 11 | 12 | # Ignore generated assembly version info file 13 | AssemblyInfoVersion.cs 14 | 15 | # User-specific files 16 | *.suo 17 | *.user 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Rr]elease/ 23 | x64/ 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.log 40 | *.vspscc 41 | *.vssscc 42 | .builds 43 | 44 | # Visual C++ cache files 45 | ipch/ 46 | *.aps 47 | *.ncb 48 | *.opensdf 49 | *.sdf 50 | 51 | # Visual Studio profiler 52 | *.psess 53 | *.vsp 54 | *.vspx 55 | 56 | # Guidance Automation Toolkit 57 | *.gpState 58 | 59 | # ReSharper is a .NET coding add-in 60 | _ReSharper* 61 | 62 | # NCrunch 63 | *.ncrunch* 64 | .*crunch*.local.xml 65 | 66 | # Installshield output folder 67 | [Ee]xpress 68 | 69 | # DocProject is a documentation generator add-in 70 | DocProject/buildhelp/ 71 | DocProject/Help/*.HxT 72 | DocProject/Help/*.HxC 73 | DocProject/Help/*.hhc 74 | DocProject/Help/*.hhk 75 | DocProject/Help/*.hhp 76 | DocProject/Help/Html2 77 | DocProject/Help/html 78 | 79 | # Click-Once directory 80 | publish 81 | 82 | # Publish Web Output 83 | *.Publish.xml 84 | 85 | # NuGet Packages Directory 86 | packages 87 | 88 | # Windows Azure Build Output 89 | csx 90 | *.build.csdef 91 | 92 | # Windows Store app package directory 93 | AppPackages/ 94 | 95 | # Others 96 | [Bb]in 97 | [Oo]bj 98 | sql 99 | TestResults 100 | [Tt]est[Rr]esult* 101 | *.Cache 102 | ClientBin 103 | [Ss]tyle[Cc]op.* 104 | ~$* 105 | *.dbmdl 106 | Generated_Code #added for RIA/Silverlight projects 107 | 108 | # Backup & report files from converting an old project file to a newer 109 | # Visual Studio version. Backup files are not needed, because we have git ;-) 110 | _UpgradeReport_Files/ 111 | Backup*/ 112 | UpgradeLog*.XML 113 | 114 | newrelic.json 115 | *.orig 116 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## New Relic Platform .NET SDK Change Log 2 | 3 | ### v1.0.0.1 - 2/8/2015 4 | 5 | * Fixed a bug where failures communicating with New Relic would crash a plugin. 6 | 7 | ### v1.0.0.0 - 8/29/2014 8 | 9 | * Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 New Relic, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/ContextTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NewRelic.Platform.Sdk.Binding; 4 | using System.Net; 5 | using NewRelic.Platform.Sdk.Utils; 6 | 7 | namespace NewRelic.Platform.Sdk.FunctionalTests 8 | { 9 | [TestClass] 10 | [DeploymentItem(@"config/newrelic.json", "config")] 11 | [DeploymentItem(@"config/plugin.json", "config")] 12 | public class ContextTest 13 | { 14 | [TestMethod] 15 | public void TestSendMetricsToServiceSucceeds() 16 | { 17 | var context = new Context() { Version = "1.0.0" }; 18 | 19 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest1", "TestMetric", "unit", 2); 20 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest1", "TestMetric", "unit", 3); 21 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest1", "TestMetric", "unit", 4); 22 | 23 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest2", "TestMetric", "unit", 5); 24 | 25 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest3", "TestMetric", "unit", 6); 26 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest3", "TestMetric", "unit", 7); 27 | 28 | var requestData = context.RequestData; 29 | Assert.IsTrue(requestData.HasComponents(), "Request data should have components before send"); 30 | Assert.AreEqual(3, ((List)requestData.Serialize()["components"]).Count, "There should be three components present"); 31 | 32 | // Will throw an exception for any client errors (400-499) 33 | context.SendMetricsToService(); 34 | 35 | requestData = context.RequestData; 36 | Assert.IsFalse(requestData.HasComponents(), "Request data should be cleared after a successful send"); 37 | } 38 | 39 | [TestMethod] 40 | public void TestSendMetricsToServiceFailsWithNoVersion() 41 | { 42 | var context = new Context(); 43 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest", "TestMetric", "unit", 2); 44 | 45 | try 46 | { 47 | context.SendMetricsToService(); 48 | Assert.Fail("Exception should be thrown when no version is set"); 49 | } 50 | catch (NewRelicServiceException nrse) 51 | { 52 | Assert.AreEqual(HttpStatusCode.BadRequest, nrse.StatusCode, "Service should respond with 400 when no version is set"); 53 | } 54 | } 55 | 56 | [TestMethod] 57 | public void TestSendMetricsToServiceFailsWithBadLicense() 58 | { 59 | var context = new Context("GarbageLicense") { Version = "1.0.0" }; 60 | context.ReportMetric("com.newrelic.sdkfunctest", "FunctionalTest", "TestMetric", "unit", 2); 61 | 62 | try 63 | { 64 | context.SendMetricsToService(); 65 | Assert.Fail("Exception should be thrown when no version is set"); 66 | } 67 | catch (NewRelicServiceException nrse) 68 | { 69 | Assert.AreEqual(HttpStatusCode.Forbidden, nrse.StatusCode, "Service should respond with 403 when invalid license key is sent"); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/NewRelic.Platform.Sdk.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {E2720282-8274-42FC-9488-717E55FA27D3} 10 | Library 11 | Properties 12 | NewRelic.Platform.Sdk.FunctionalTests 13 | NewRelic.Platform.Sdk.FunctionalTests 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 37 | 38 | 39 | 40 | 41 | 42 | 3.5 43 | 44 | 45 | 46 | 47 | False 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6} 60 | NewRelic.Platform.Sdk 61 | 62 | 63 | 64 | 65 | 66 | Always 67 | 68 | 69 | Always 70 | 71 | 72 | 73 | 80 | 81 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("NewRelic.Platform.Sdk.FunctionalTests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("NewRelic.Platform.Sdk.FunctionalTests")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("d1d53a0e-10f1-44ad-b6e7-d1e22fa0c59c")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | [assembly: AssemblyVersion("1.0.0.0")] 34 | [assembly: AssemblyFileVersion("1.0.0.0")] 35 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/RunnerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace NewRelic.Platform.Sdk.FunctionalTests 5 | { 6 | [TestClass] 7 | [DeploymentItem(@"config/newrelic.json", "config")] 8 | [DeploymentItem(@"config/plugin.json", "config")] 9 | public class RunnerTest 10 | { 11 | [TestMethod] 12 | public void TestRunnerSetupAndRunSucceeds() 13 | { 14 | Runner runner = new Runner(); 15 | 16 | runner.Add(new TestAgentFactory()); 17 | runner.Add(new TestAgent("FunctionalTest3", 4)); 18 | runner.Add(new TestAgent("FunctionalTest4", 5)); 19 | 20 | runner.SetupAndRunWithLimit(1); 21 | 22 | Assert.AreEqual(4, runner.Agents.Count); 23 | } 24 | 25 | [TestMethod] 26 | public void TestRunnerSetupFailsWithNullAgent() 27 | { 28 | try 29 | { 30 | Runner runner = new Runner(); 31 | Agent agent = null; 32 | runner.Add(agent); 33 | Assert.Fail("Runner should raise exception when null agent is passed"); 34 | } 35 | catch (ArgumentNullException) 36 | { 37 | // Expected 38 | } 39 | } 40 | 41 | [TestMethod] 42 | public void TestRunnerReportContinuesWithNullAgentName() 43 | { 44 | Runner runner = new Runner(); 45 | runner.Add(new TestAgent("", 4)); 46 | runner.SetupAndRunWithLimit(1); // Should not raise an exception 47 | } 48 | 49 | [TestMethod] 50 | public void TestRunnerReportContinuesWithNegativeValue() 51 | { 52 | Runner runner = new Runner(); 53 | runner.Add(new TestAgent("FunctionalTest", -10)); 54 | runner.SetupAndRunWithLimit(1); // Should not raise an exception 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/TestAgent.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.FunctionalTests 2 | { 3 | internal class TestAgent : Agent 4 | { 5 | public override string Guid { get { return "com.newrelic.sdkfunctest"; } } 6 | public override string Version { get { return "1.0.0"; } } 7 | 8 | private string _name; 9 | private float _value; 10 | 11 | public TestAgent(string name, float value) 12 | { 13 | _name = name; 14 | _value = value; 15 | } 16 | 17 | public override string GetAgentName() 18 | { 19 | return _name; 20 | } 21 | 22 | public override void PollCycle() 23 | { 24 | ReportMetric("Category/TestMetric", "unit", _value); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/TestAgentFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NewRelic.Platform.Sdk.FunctionalTests 4 | { 5 | class TestAgentFactory : AgentFactory 6 | { 7 | // This will return the deserialized properties from the specified configuration file 8 | // It will be invoked once per JSON object in the configuration file 9 | public override Agent CreateAgentWithConfiguration(IDictionary properties) 10 | { 11 | string name = (string)properties["name"]; 12 | double value = (double)properties["value"]; 13 | 14 | return new TestAgent(name, (float)value); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/config/newrelic.json: -------------------------------------------------------------------------------- 1 | { 2 | "license_key": "YOUR_LICENSE_KEY_HERE" 3 | } 4 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.FunctionalTests/config/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "agents" : [ 3 | { 4 | "name" : "FunctionalTest1", 5 | "value": 2.0 6 | }, 7 | { 8 | "name" : "FunctionalTest2", 9 | "value": 3.0 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/AgentTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace NewRelic.Platform.Sdk.UnitTests 4 | { 5 | [TestClass] 6 | public class AgentTest 7 | { 8 | [TestMethod] 9 | public void TestValidPollCycle() 10 | { 11 | var agent = new TestAgent("TestAgent"); 12 | var context = new MockContext(); 13 | agent.PrepareToRun(context); 14 | agent.PollCycle(); 15 | 16 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(context.RequestData); 17 | Assert.AreEqual(1, componentsList.Count); 18 | 19 | var componentMap = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestAgent"); 20 | Assert.AreEqual("TestAgent", componentMap["name"]); 21 | 22 | var metrics = TestSerializationHelper.GetMetricsMapFromComponentMap(componentMap); 23 | 24 | Assert.AreEqual(1, metrics.Count); 25 | } 26 | 27 | [TestMethod] 28 | public void TestMultipleValidPollCycle() 29 | { 30 | var component = new TestAgent("TestAgent"); 31 | var context = new MockContext(); 32 | component.PrepareToRun(context); 33 | 34 | for (int i = 0; i < 3; i++) 35 | { 36 | component.PollCycle(); 37 | } 38 | 39 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(context.RequestData); 40 | Assert.AreEqual(1, componentsList.Count); 41 | 42 | var componentMap = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestAgent"); 43 | Assert.AreEqual("TestAgent", componentMap["name"]); 44 | 45 | var metrics = TestSerializationHelper.GetMetricsMapFromComponentMap(componentMap); 46 | Assert.AreEqual(1, metrics.Count); 47 | Assert.AreEqual(3, TestSerializationHelper.GetValueFromMetricMap(metrics, "Component/Category/TestMetric[unit]", MetricValues.Count)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Binding/ComponentDataTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using NewRelic.Platform.Sdk.Binding; 5 | 6 | namespace NewRelic.Platform.Sdk.UnitTests.Binding 7 | { 8 | [TestClass] 9 | public class ComponentDataTest 10 | { 11 | [TestMethod] 12 | public void TestAddSingleMetricSucceeds() 13 | { 14 | var component = new ComponentData("TestName", "com.newrelic.test"); 15 | var metric = new MetricData("Test/Metric", "units", 3); 16 | 17 | Assert.AreEqual("TestName", component.Name); 18 | Assert.AreEqual("com.newrelic.test", component.Guid); 19 | Assert.AreEqual(0, TestSerializationHelper.GetMetricsMapFromComponent(component).Count, "Should be zero metrics"); 20 | 21 | component.AddMetric(metric); 22 | 23 | var serializedComponent = component.Serialize(DateTime.Now.Subtract(TimeSpan.FromSeconds(60))); 24 | var metrics = TestSerializationHelper.GetMetricsMapFromComponent(component); 25 | Assert.AreEqual(1, metrics.Count, "Should be one metric"); 26 | Assert.IsTrue(metrics.Keys.Any(key => key == metric.FullName), "Metric name should be a valid key"); 27 | Assert.AreEqual(3, TestSerializationHelper.GetValueFromMetricMap(metrics, metric.FullName, MetricValues.Value)); 28 | } 29 | 30 | [TestMethod] 31 | public void TestAddMultipleMetricsSucceeds() 32 | { 33 | var component = new ComponentData("TestName", "com.newrelic.test"); 34 | var metric1 = new MetricData("Test/Metric1", "units", 2); 35 | var metric2 = new MetricData("Test/Metric2", "units", 3); 36 | var metric3 = new MetricData("Test/Metric3", "units", 4); 37 | 38 | Assert.AreEqual(0, TestSerializationHelper.GetMetricsMapFromComponent(component).Count, "Should be zero metrics"); 39 | 40 | component.AddMetric(metric1); 41 | component.AddMetric(metric2); 42 | component.AddMetric(metric3); 43 | 44 | var serializedComponent = component.Serialize(DateTime.Now.Subtract(TimeSpan.FromSeconds(60))); 45 | var metrics = TestSerializationHelper.GetMetricsMapFromComponent(component); 46 | Assert.AreEqual(3, metrics.Count, "Should be three metrics"); 47 | 48 | Assert.AreEqual(2, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.Value)); 49 | Assert.AreEqual(3, TestSerializationHelper.GetValueFromMetricMap(metrics, metric2.FullName, MetricValues.Value)); 50 | Assert.AreEqual(4, TestSerializationHelper.GetValueFromMetricMap(metrics, metric3.FullName, MetricValues.Value)); 51 | } 52 | 53 | [TestMethod] 54 | public void TestAggregatingMetricsSucceeds() 55 | { 56 | var component = new ComponentData("TestName", "com.newrelic.test"); 57 | var metric1 = new MetricData("Test/Metric", "units", 2); 58 | var metric2 = new MetricData("Test/Metric", "units", 3); 59 | var metric3 = new MetricData("Test/Metric", "units", 4); 60 | var metric4 = new MetricData("Test/Metric", "otherunits", 5); 61 | 62 | Assert.AreEqual(0, TestSerializationHelper.GetMetricsMapFromComponent(component).Count, "Should be zero metrics"); 63 | 64 | component.AddMetric(metric1); 65 | component.AddMetric(metric2); 66 | component.AddMetric(metric3); 67 | component.AddMetric(metric4); 68 | 69 | var serializedComponent = component.Serialize(DateTime.Now.Subtract(TimeSpan.FromSeconds(60))); 70 | var metrics = TestSerializationHelper.GetMetricsMapFromComponent(component); 71 | Assert.AreEqual(2, metrics.Count, "Should be two metrics"); 72 | Assert.AreEqual(9, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.Value)); 73 | Assert.AreEqual(3, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.Count)); 74 | Assert.AreEqual(2, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.MinValue)); 75 | Assert.AreEqual(4, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.MaxValue)); 76 | Assert.AreEqual(29, TestSerializationHelper.GetValueFromMetricMap(metrics, metric1.FullName, MetricValues.SumOfSquares)); 77 | 78 | Assert.AreEqual(5, TestSerializationHelper.GetValueFromMetricMap(metrics, metric4.FullName, MetricValues.Value)); 79 | Assert.AreEqual(1, TestSerializationHelper.GetValueFromMetricMap(metrics, metric4.FullName, MetricValues.Count)); 80 | Assert.AreEqual(5, TestSerializationHelper.GetValueFromMetricMap(metrics, metric4.FullName, MetricValues.MinValue)); 81 | Assert.AreEqual(5, TestSerializationHelper.GetValueFromMetricMap(metrics, metric4.FullName, MetricValues.MaxValue)); 82 | Assert.AreEqual(25, TestSerializationHelper.GetValueFromMetricMap(metrics, metric4.FullName, MetricValues.SumOfSquares)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Binding/ContextTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NewRelic.Platform.Sdk.Binding; 4 | 5 | namespace NewRelic.Platform.Sdk.UnitTests.Binding 6 | { 7 | [TestClass] 8 | public class ContextTest 9 | { 10 | [TestMethod] 11 | public void TestContextInitialization() 12 | { 13 | var context = new Context(); 14 | 15 | Assert.IsNotNull(context.ServiceUri, "The initialized context does not have a default service URI"); 16 | } 17 | 18 | [TestMethod] 19 | public void TestContextInitializationWithLicenseKey() 20 | { 21 | string license = "FakeyMcFakeKey"; 22 | var context = new Context(license); 23 | 24 | Assert.AreEqual(license, context.LicenseKey); 25 | } 26 | 27 | [TestMethod] 28 | public void TestReportValidMetric() 29 | { 30 | var context = new Context(); 31 | context.ReportMetric("com.newrelic.test", "TestComponent", "Category/TestMetric", "unit", 5); // Assert no throws 32 | } 33 | 34 | [TestMethod] 35 | public void TestReportBadMetricNullGuid() 36 | { 37 | try 38 | { 39 | var context = new Context(); 40 | context.ReportMetric("", "TestComponent", "Category/TestMetric", "unit", 2); 41 | Assert.Fail("Reporting metric with null guid should fail"); 42 | } 43 | catch (ArgumentNullException) 44 | { 45 | // Expected 46 | } 47 | } 48 | 49 | [TestMethod] 50 | public void TestReportBadMetricEmptyComponent() 51 | { 52 | try 53 | { 54 | var context = new Context(); 55 | context.ReportMetric("com.newrelic.test", "", "Category/TestMetric", "unit", 2); 56 | Assert.Fail("Reporting metric with empty component should fail"); 57 | } 58 | catch (ArgumentNullException) 59 | { 60 | // Expected 61 | } 62 | } 63 | 64 | [TestMethod] 65 | public void TestReportBadMetricNegativeValue() 66 | { 67 | try 68 | { 69 | var context = new Context(); 70 | context.ReportMetric("com.newrelic.test", "TestComponent", "Category/TestMetric", "unit", -2); 71 | Assert.Fail("Reporting metric with null guid should fail"); 72 | } 73 | catch (ArgumentException) 74 | { 75 | // Expected 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Binding/MetricDataTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NewRelic.Platform.Sdk.Binding; 4 | 5 | namespace NewRelic.Platform.Sdk.UnitTests.Binding 6 | { 7 | [TestClass] 8 | public class MetricDataTest 9 | { 10 | [TestMethod] 11 | public void TestOverloadedConstructorSucceeds() 12 | { 13 | var metric = new MetricData("Test/Metric", "units", 2); 14 | 15 | Assert.AreEqual("Component/Test/Metric[units]", metric.FullName); 16 | Assert.AreEqual(2, metric.Value); 17 | Assert.AreEqual(1, metric.Count); 18 | Assert.AreEqual(2, metric.MinValue); 19 | Assert.AreEqual(2, metric.MaxValue); 20 | Assert.AreEqual(4, metric.SumOfSquares); 21 | } 22 | 23 | [TestMethod] 24 | public void TestAggregationSucceeds() 25 | { 26 | var metric1 = new MetricData("Test/Metric1", "units", 2); 27 | var metric2 = new MetricData("Test/Metric2", "units", 3); 28 | 29 | metric1.AggregateWith(metric2); 30 | 31 | Assert.AreEqual("Component/Test/Metric1[units]", metric1.FullName); 32 | Assert.AreEqual(5, metric1.Value); 33 | Assert.AreEqual(2, metric1.Count); 34 | Assert.AreEqual(2, metric1.MinValue); 35 | Assert.AreEqual(3, metric1.MaxValue); 36 | Assert.AreEqual(13, metric1.SumOfSquares); 37 | } 38 | 39 | [TestMethod] 40 | public void TestSerializeMethod() 41 | { 42 | var metric = new MetricData("Test/Metric", "units", 3); 43 | var value = metric.Serialize(); 44 | 45 | Assert.IsTrue(value is Array); 46 | Assert.AreEqual((float)value.GetValue(0), metric.Value); 47 | Assert.AreEqual((float)value.GetValue(1), metric.Count); 48 | Assert.AreEqual((float)value.GetValue(2), metric.MinValue); 49 | Assert.AreEqual((float)value.GetValue(3), metric.MaxValue); 50 | Assert.AreEqual((float)value.GetValue(4), metric.SumOfSquares); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Binding/RequestDataTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using NewRelic.Platform.Sdk.Binding; 5 | 6 | namespace NewRelic.Platform.Sdk.UnitTests.Binding 7 | { 8 | [TestClass] 9 | public class RequestDataTest 10 | { 11 | [TestMethod] 12 | public void TestAddSingleMetricSucceeds() 13 | { 14 | var requestData = new RequestData(); 15 | 16 | Assert.AreEqual(0, TestSerializationHelper.GetComponentsListFromRequestData(requestData).Count); 17 | 18 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric", "unit", 2); 19 | 20 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 21 | Assert.AreEqual(1, componentsList.Count); 22 | 23 | var component = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent"); 24 | Assert.AreEqual("com.newrelic.test", component["guid"]); 25 | Assert.AreEqual("TestComponent", component["name"]); 26 | 27 | var metrics = TestSerializationHelper.GetMetricsMapFromComponentMap(component); 28 | 29 | Assert.AreEqual(1, metrics.Count); 30 | 31 | AssertMetricValues(metrics, "Component/Test/Metric[unit]", 2, 1, 2, 2, 4); 32 | } 33 | 34 | [TestMethod] 35 | public void TestAddMultipleMetricsSucceeds() 36 | { 37 | var requestData = new RequestData(); 38 | 39 | Assert.AreEqual(0, TestSerializationHelper.GetComponentsListFromRequestData(requestData).Count); 40 | 41 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric1", "unit", 2); 42 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric2", "unit", 3); 43 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric3", "unit", 4); 44 | 45 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 46 | Assert.AreEqual(1, componentsList.Count); 47 | 48 | var component = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent"); 49 | var metrics = TestSerializationHelper.GetMetricsMapFromComponentMap(component); 50 | 51 | Assert.AreEqual(3, metrics.Count); 52 | AssertMetricValues(metrics, "Component/Test/Metric1[unit]", 2, 1, 2, 2, 4); 53 | AssertMetricValues(metrics, "Component/Test/Metric3[unit]", 4, 1, 4, 4, 16); 54 | } 55 | 56 | [TestMethod] 57 | public void TestAggregateMetricsSucceeds() 58 | { 59 | var requestData = new RequestData(); 60 | 61 | Assert.AreEqual(0, TestSerializationHelper.GetComponentsListFromRequestData(requestData).Count); 62 | 63 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric", "unit", 2); 64 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric", "unit", 3); 65 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric", "unit", 4); 66 | 67 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 68 | Assert.AreEqual(1, componentsList.Count); 69 | 70 | var component = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent"); 71 | var metrics = TestSerializationHelper.GetMetricsMapFromComponentMap(component); 72 | 73 | Assert.AreEqual(1, metrics.Count); 74 | AssertMetricValues(metrics, "Component/Test/Metric[unit]", 9, 3, 2, 4, 29); 75 | } 76 | 77 | [TestMethod] 78 | public void TestMultipleComponentsSucceeds() 79 | { 80 | var requestData = new RequestData(); 81 | 82 | Assert.AreEqual(0, TestSerializationHelper.GetComponentsListFromRequestData(requestData).Count); 83 | 84 | requestData.AddMetric("com.newrelic.test", "TestComponent1", "Test/Metric", "unit", 2); 85 | requestData.AddMetric("com.newrelic.test", "TestComponent2", "Test/Metric", "unit", 3); 86 | requestData.AddMetric("com.newrelic.test", "TestComponent3", "Test/Metric", "unit", 4); 87 | 88 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 89 | Assert.AreEqual(3, componentsList.Count); 90 | 91 | var component1 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent1"); 92 | var component2 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent2"); 93 | var component3 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent3"); 94 | 95 | var metrics1 = TestSerializationHelper.GetMetricsMapFromComponentMap(component1); 96 | var metrics2 = TestSerializationHelper.GetMetricsMapFromComponentMap(component2); 97 | var metrics3 = TestSerializationHelper.GetMetricsMapFromComponentMap(component3); 98 | 99 | Assert.AreEqual(1, metrics1.Count); 100 | Assert.AreEqual(1, metrics2.Count); 101 | Assert.AreEqual(1, metrics3.Count); 102 | } 103 | 104 | [TestMethod] 105 | public void TestAggregateWithMultipleComponentsSucceeds() 106 | { 107 | var requestData = new RequestData(); 108 | 109 | Assert.AreEqual(0, TestSerializationHelper.GetComponentsListFromRequestData(requestData).Count); 110 | 111 | requestData.AddMetric("com.newrelic.test", "TestComponent1", "Test/Metric", "unit", 2); 112 | requestData.AddMetric("com.newrelic.test", "TestComponent1", "Test/Metric", "unit", 2); 113 | 114 | requestData.AddMetric("com.newrelic.test", "TestComponent2", "Test/Metric", "unit", 3); 115 | requestData.AddMetric("com.newrelic.test", "TestComponent2", "Test/Metric", "unit", 3); 116 | 117 | requestData.AddMetric("com.newrelic.test", "TestComponent3", "Test/Metric", "unit", 4); 118 | requestData.AddMetric("com.newrelic.test", "TestComponent3", "Test/Metric", "unit", 4); 119 | 120 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 121 | Assert.AreEqual(3, componentsList.Count); 122 | 123 | var component1 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent1"); 124 | var component2 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent2"); 125 | var component3 = TestSerializationHelper.GetComponentMapFromComponentsList(componentsList, "TestComponent3"); 126 | 127 | var metrics1 = TestSerializationHelper.GetMetricsMapFromComponentMap(component1); 128 | var metrics2 = TestSerializationHelper.GetMetricsMapFromComponentMap(component2); 129 | var metrics3 = TestSerializationHelper.GetMetricsMapFromComponentMap(component3); 130 | 131 | Assert.AreEqual(1, metrics1.Count); 132 | Assert.AreEqual(1, metrics2.Count); 133 | Assert.AreEqual(1, metrics3.Count); 134 | 135 | AssertMetricValues(metrics2, "Component/Test/Metric[unit]", 6, 2, 3, 3, 18); 136 | } 137 | 138 | [TestMethod] 139 | public void TestResetComponentsSucceeds() 140 | { 141 | var requestData = new RequestData(); 142 | 143 | requestData.AddMetric("com.newrelic.test", "TestComponent1", "Test/Metric", "unit", 2); 144 | requestData.AddMetric("com.newrelic.test", "TestComponent2", "Test/Metric", "unit", 3); 145 | requestData.AddMetric("com.newrelic.test", "TestComponent3", "Test/Metric", "unit", 4); 146 | 147 | var beforeResetList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 148 | Assert.AreEqual(3, beforeResetList.Count); 149 | 150 | requestData.Reset(); 151 | 152 | var firstAfterResetList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 153 | Assert.AreEqual(0, firstAfterResetList.Count); 154 | 155 | requestData.AddMetric("com.newrelic.test", "TestComponent1", "Test/Metric", "unit", 2); 156 | requestData.AddMetric("com.newrelic.test", "TestComponent2", "Test/Metric", "unit", 3); 157 | requestData.AddMetric("com.newrelic.test", "TestComponent3", "Test/Metric", "unit", 4); 158 | 159 | var secondAfterResetList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 160 | Assert.AreEqual(3, secondAfterResetList.Count); 161 | } 162 | 163 | [TestMethod] 164 | public void TestDifferentGuidDoesntAggregate() 165 | { 166 | var requestData = new RequestData(); 167 | 168 | requestData.AddMetric("com.newrelic.test", "TestComponent", "Test/Metric", "unit", 2); 169 | requestData.AddMetric("com.newrelic.anothertest", "TestComponent", "Test/Metric", "unit", 2); 170 | 171 | var componentsList = TestSerializationHelper.GetComponentsListFromRequestData(requestData); 172 | Assert.AreEqual(2, componentsList.Count); 173 | } 174 | 175 | [TestMethod] 176 | public void TestAggregationLimitIsRespected() 177 | { 178 | var requestData = new RequestData(); 179 | requestData.SetAggregationLimit(DateTime.Now.AddMinutes(-20)); 180 | 181 | Assert.IsTrue(requestData.IsPastAggregationLimit(), "Aggregation limit is not being respected"); 182 | } 183 | 184 | private void AssertMetricValues(IDictionary metrics, string metricName, float value, int count, float min, float max, float sumOfSquares) 185 | { 186 | Assert.AreEqual(value, TestSerializationHelper.GetValueFromMetricMap(metrics, metricName, MetricValues.Value)); 187 | Assert.AreEqual(count, TestSerializationHelper.GetValueFromMetricMap(metrics, metricName, MetricValues.Count)); 188 | Assert.AreEqual(min, TestSerializationHelper.GetValueFromMetricMap(metrics, metricName, MetricValues.MinValue)); 189 | Assert.AreEqual(max, TestSerializationHelper.GetValueFromMetricMap(metrics, metricName, MetricValues.MaxValue)); 190 | Assert.AreEqual(sumOfSquares, TestSerializationHelper.GetValueFromMetricMap(metrics, metricName, MetricValues.SumOfSquares)); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/MockContext.cs: -------------------------------------------------------------------------------- 1 | using NewRelic.Platform.Sdk.Binding; 2 | 3 | namespace NewRelic.Platform.Sdk.UnitTests 4 | { 5 | internal class MockContext : IContext 6 | { 7 | public string Version { get { return "1.0.0"; } set { } } 8 | 9 | public RequestData RequestData { get; set; } 10 | 11 | public MockContext() 12 | { 13 | this.RequestData = new RequestData(); 14 | } 15 | 16 | public void ReportMetric(string guid, string componentName, string metricName, string units, float? value) 17 | { 18 | this.RequestData.AddMetric(guid, componentName, metricName, units, value.Value); 19 | } 20 | 21 | public void ReportMetric(string guid, string componentName, string metricName, string units, float value, int count, float min, float max, float sumOfSquares) 22 | { 23 | this.RequestData.AddMetric(guid, componentName, metricName, units, value, count, min, max, sumOfSquares); 24 | } 25 | 26 | public void SendMetricsToService() 27 | { 28 | this.RequestData.Reset(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/NewRelic.Platform.Sdk.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {27A3FE83-DED5-474D-B869-753A1566D578} 10 | Library 11 | Properties 12 | NewRelic.Platform.Sdk.UnitTests 13 | NewRelic.Platform.Sdk.UnitTests 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 37 | 38 | 39 | 40 | 41 | False 42 | ..\NewRelic.Platform.Sdk\Externals\NLog v2.1.0\NLog.dll 43 | 44 | 45 | 46 | 47 | 3.5 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | False 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6} 77 | NewRelic.Platform.Sdk 78 | 79 | 80 | 81 | 82 | 83 | Always 84 | 85 | 86 | Always 87 | 88 | 89 | 90 | 97 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Processors/EpochProcessorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using NewRelic.Platform.Sdk.Processors; 4 | 5 | namespace NewRelic.Platform.Sdk.UnitTests.Processors 6 | { 7 | [TestClass] 8 | public class EpochProcessorTest 9 | { 10 | [TestMethod] 11 | public void TestTwoValidPollCycles() 12 | { 13 | var processor = new EpochProcessor(); 14 | var firstProcess = processor.Process(5); 15 | ResetTimer(processor, 1); 16 | var secondProcess = processor.Process(6); 17 | 18 | Assert.IsNull(firstProcess); 19 | Assert.AreEqual(1.0, Convert.ToDouble(secondProcess.Value), 0.1); 20 | } 21 | 22 | [TestMethod] 23 | public void TestNullSecondPollCycle() 24 | { 25 | var processor = new EpochProcessor(); 26 | var firstProcess = processor.Process(5); 27 | ResetTimer(processor, 1); 28 | var secondProcess = processor.Process(null); 29 | ResetTimer(processor, 1); 30 | var thirdProcess = processor.Process(6); 31 | ResetTimer(processor, 1); 32 | var fourthProcess = processor.Process(7); 33 | 34 | Assert.IsNull(firstProcess); 35 | Assert.IsNull(secondProcess); 36 | Assert.IsNull(thirdProcess); 37 | Assert.AreEqual(1.0, Convert.ToDouble(fourthProcess.Value), 0.1); 38 | } 39 | 40 | [TestMethod] 41 | public void TestNullFirstPollCycle() 42 | { 43 | var processor = new EpochProcessor(); 44 | var firstProcess = processor.Process(null); 45 | ResetTimer(processor, 1); 46 | var secondProcess = processor.Process(5); 47 | ResetTimer(processor, 1); 48 | var thirdProcess = processor.Process(6); 49 | 50 | Assert.IsNull(firstProcess); 51 | Assert.IsNull(secondProcess); 52 | Assert.AreEqual(1.0, Convert.ToDouble(thirdProcess.Value), 0.1); 53 | } 54 | 55 | private void ResetTimer(EpochProcessor processor, int seconds) 56 | { 57 | processor.LastValue = DateTime.Now.Subtract(TimeSpan.FromSeconds(seconds)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("NewRelic.Platform.Sdk.UnitTests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("NewRelic.Platform.Sdk.UnitTests")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM agents. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("0061d6b0-cf0d-47db-975b-3c4c5d759134")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | [assembly: AssemblyVersion("1.0.0.0")] 34 | [assembly: AssemblyFileVersion("1.0.0.0")] 35 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/RunnerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using NewRelic.Platform.Sdk.Configuration; 5 | 6 | namespace NewRelic.Platform.Sdk.UnitTests 7 | { 8 | [TestClass] 9 | [DeploymentItem(@"config/newrelic.json", @"config")] 10 | public class RunnerTest 11 | { 12 | [TestMethod] 13 | public void RunnerSetsUpProxy() 14 | { 15 | INewRelicConfig config = new TestConfig 16 | { 17 | ProxyHost = "Host", 18 | ProxyPort = 8080, 19 | ProxyUserName = "UserName", 20 | ProxyPassword = "Password", 21 | }; 22 | 23 | Runner runner = new Runner(config); 24 | IWebProxy proxy = WebRequest.DefaultWebProxy; 25 | Uri proxyAddress = proxy.GetProxy(new Uri("http://www.google.com")); 26 | NetworkCredential credential = (NetworkCredential)proxy.Credentials; 27 | 28 | Assert.IsTrue(string.Equals(config.ProxyHost, proxyAddress.Host, StringComparison.InvariantCultureIgnoreCase), "Host is incorrect."); 29 | Assert.AreEqual(config.ProxyPort, proxyAddress.Port, "Port is incorrect."); 30 | Assert.AreEqual(config.ProxyUserName, credential.UserName, "Username is incorrect."); 31 | Assert.AreEqual(config.ProxyPassword, credential.Password, "Password is incorrect."); 32 | } 33 | 34 | [TestMethod] 35 | [ExpectedException(typeof(InvalidOperationException))] 36 | public void PortMustBeSpecified() 37 | { 38 | INewRelicConfig config = new TestConfig 39 | { 40 | ProxyHost = "Host", 41 | ProxyUserName = "UserName", 42 | ProxyPassword = "Password", 43 | }; 44 | 45 | Runner runner = new Runner(config); 46 | } 47 | 48 | [TestMethod] 49 | public void ProxySetupAcceptsEmptyCredentials() 50 | { 51 | INewRelicConfig config = new TestConfig 52 | { 53 | ProxyHost = "Host", 54 | ProxyPort = 8080, 55 | }; 56 | 57 | Runner runner = new Runner(config); 58 | IWebProxy proxy = WebRequest.DefaultWebProxy; 59 | Uri proxyAddress = proxy.GetProxy(new Uri("http://www.google.com")); 60 | NetworkCredential credential = (NetworkCredential)proxy.Credentials; 61 | 62 | Assert.IsNull(credential, "No credentials were provided, this should be null."); 63 | } 64 | 65 | [TestMethod] 66 | public void ProxySetupEmptyPassword() 67 | { 68 | INewRelicConfig config = new TestConfig 69 | { 70 | ProxyHost = "Host", 71 | ProxyPort = 8080, 72 | ProxyUserName = "Username", 73 | }; 74 | 75 | Runner runner = new Runner(config); 76 | IWebProxy proxy = WebRequest.DefaultWebProxy; 77 | Uri proxyAddress = proxy.GetProxy(new Uri("http://www.google.com")); 78 | NetworkCredential credential = (NetworkCredential)proxy.Credentials; 79 | 80 | Assert.AreEqual(config.ProxyUserName, credential.UserName, "Username is incorrect."); 81 | Assert.IsTrue(string.IsNullOrEmpty(credential.Password), "Password should be empty."); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/TestAgent.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.UnitTests 2 | { 3 | internal class TestAgent : Agent 4 | { 5 | public override string Guid { get { return "com.newrelic.test"; } } 6 | public override string Version { get { return "1.0.0"; } } 7 | 8 | private string _name; 9 | 10 | public TestAgent(string name) 11 | { 12 | _name = name; 13 | } 14 | 15 | public override string GetAgentName() 16 | { 17 | return _name; 18 | } 19 | 20 | public override void PollCycle() 21 | { 22 | ReportMetric("Category/TestMetric", "unit", 2); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/TestConfig.cs: -------------------------------------------------------------------------------- 1 | using NewRelic.Platform.Sdk.Configuration; 2 | 3 | namespace NewRelic.Platform.Sdk.UnitTests 4 | { 5 | internal class TestConfig : INewRelicConfig 6 | { 7 | public string Endpoint { get; set; } 8 | 9 | public string LicenseKey { get; set; } 10 | 11 | public int? PollInterval { get; set; } 12 | 13 | public LogLevel LogLevel { get; set; } 14 | 15 | public string LogFileName { get; set; } 16 | 17 | public string LogFilePath { get; set; } 18 | 19 | public long LogLimitInKiloBytes { get; set; } 20 | 21 | public string ProxyHost { get; set; } 22 | 23 | public int? ProxyPort { get; set; } 24 | 25 | public string ProxyUserName { get; set; } 26 | 27 | public string ProxyPassword { get; set; } 28 | 29 | public int? NewRelicMaxIterations { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/TestSerializationHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NewRelic.Platform.Sdk.Binding; 5 | 6 | namespace NewRelic.Platform.Sdk.UnitTests 7 | { 8 | internal static class TestSerializationHelper 9 | { 10 | internal static List GetComponentsListFromRequestData(RequestData requestData) 11 | { 12 | return (List)requestData.Serialize()["components"]; 13 | } 14 | 15 | internal static IDictionary GetMetricsMapFromComponent(ComponentData component) 16 | { 17 | return ((IDictionary)component.Serialize(DateTime.Now.Subtract(TimeSpan.FromSeconds(60)))["metrics"]); 18 | } 19 | 20 | internal static List GetComponentsListFromDictionary(IDictionary dictionary) 21 | { 22 | return (List)dictionary["components"]; 23 | } 24 | 25 | internal static IDictionary GetComponentMapFromComponentsList(List components, string name) 26 | { 27 | var castedComponents = components.Cast>(); 28 | return castedComponents.FirstOrDefault(c => c["name"].ToString() == name); 29 | } 30 | 31 | internal static IDictionary GetMetricsMapFromComponentMap(IDictionary component) 32 | { 33 | return (IDictionary)component["metrics"]; 34 | } 35 | 36 | internal static float GetValueFromMetricMap(IDictionary metrics, string name, MetricValues type) 37 | { 38 | return (float)((Array)metrics[name]).GetValue((int)type); 39 | } 40 | } 41 | 42 | internal enum MetricValues 43 | { 44 | Value = 0, 45 | Count = 1, 46 | MinValue = 2, 47 | MaxValue = 3, 48 | SumOfSquares = 4 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/Utils/LoggerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using NewRelic.Platform.Sdk.Utils; 6 | using NLog.Config; 7 | using NLog.Targets; 8 | using NLogManager = NLog.LogManager; 9 | 10 | namespace NewRelic.Platform.Sdk.UnitTests.Utils 11 | { 12 | [TestClass] 13 | public class LoggerTests 14 | { 15 | private TestConfig testConfig; 16 | 17 | [TestInitialize] 18 | public void Initialize() 19 | { 20 | // setup a default config 21 | this.testConfig = new TestConfig 22 | { 23 | LogFileName = "default.log", 24 | LogFilePath = @".\logs", 25 | LogLevel = Configuration.LogLevel.Info, 26 | LogLimitInKiloBytes = 5000, 27 | }; 28 | } 29 | 30 | [TestCleanup] 31 | public void Cleanup() 32 | { 33 | string logPath = Path.Combine(this.testConfig.LogFilePath, this.testConfig.LogFileName); 34 | if (File.Exists(logPath)) 35 | { 36 | File.Delete(logPath); 37 | } 38 | } 39 | 40 | [TestMethod] 41 | public void LoggingIsConfiguredProperly() 42 | { 43 | this.testConfig = new TestConfig 44 | { 45 | LogLevel = Configuration.LogLevel.Error, 46 | LogFilePath = @".\path\to\logs", 47 | LogFileName = "test_log.log", 48 | LogLimitInKiloBytes = 10000, 49 | }; 50 | 51 | Logger logger = Logger.GetLogger("LoggerTests", this.testConfig); 52 | LoggingConfiguration nLogConfig = NLogManager.Configuration; 53 | 54 | Assert.AreEqual(2, nLogConfig.AllTargets.Count, "There should be exactly 2 targets"); 55 | 56 | Target consoleTarget = nLogConfig.AllTargets.FirstOrDefault(t => t.Name.Equals("Console", StringComparison.InvariantCultureIgnoreCase)); 57 | Assert.IsNotNull(consoleTarget, "There is no console target"); 58 | 59 | FileTarget fileTarget = (FileTarget)nLogConfig.AllTargets.FirstOrDefault(t => t.Name.Equals("File", StringComparison.InvariantCultureIgnoreCase)); 60 | Assert.IsNotNull(fileTarget, "There is no file target"); 61 | Assert.IsTrue(fileTarget.FileName.ToString().Contains(Path.Combine(this.testConfig.LogFilePath, this.testConfig.LogFileName)), "The file name is incorrect."); 62 | Assert.AreEqual(this.testConfig.LogLimitInKiloBytes * 1024, fileTarget.ArchiveAboveSize, "The log limit is incorrect."); 63 | } 64 | 65 | [TestMethod] 66 | public void LogsAreEmittedBasedOnLogLevel() 67 | { 68 | Logger logger = Logger.GetLogger("LoggerTests", this.testConfig); 69 | string logMessage = "This is a log message"; 70 | logger.Info(logMessage); 71 | NLogManager.Shutdown(); 72 | 73 | string pathToLogFile = Path.Combine(this.testConfig.LogFilePath, this.testConfig.LogFileName); 74 | Assert.IsTrue(File.Exists(pathToLogFile), "Log file was not created"); 75 | Assert.IsTrue(File.ReadAllText(pathToLogFile).Trim().EndsWith(logMessage), "The log doesn't contain the log message."); 76 | } 77 | 78 | [TestMethod] 79 | public void LogsAreIgnoredBasedOnLogLevel() 80 | { 81 | Logger logger = Logger.GetLogger("LoggerTests", this.testConfig); 82 | string logMessage = "This is a log message"; 83 | logger.Debug(logMessage); 84 | NLogManager.Shutdown(); 85 | 86 | string pathToLogFile = Path.Combine(this.testConfig.LogFilePath, this.testConfig.LogFileName); 87 | Assert.IsFalse(File.Exists(pathToLogFile), "Log file should not be created."); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/config/newrelic.json: -------------------------------------------------------------------------------- 1 | { 2 | "license_key": "YOUR_LICENSE_KEY_HERE" 3 | } 4 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.UnitTests/config/plugin.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "FunctionalTest1", 4 | "value": 2.0 5 | }, 6 | { 7 | "name" : "FunctionalTest2", 8 | "value": 3.0 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.Platform.Sdk", "NewRelic.Platform.Sdk\NewRelic.Platform.Sdk.csproj", "{6A9DC6C5-280E-42B9-AE30-B66803DF20B6}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.Platform.Sdk.UnitTests", "NewRelic.Platform.Sdk.UnitTests\NewRelic.Platform.Sdk.UnitTests.csproj", "{27A3FE83-DED5-474D-B869-753A1566D578}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewRelic.Platform.Sdk.FunctionalTests", "NewRelic.Platform.Sdk.FunctionalTests\NewRelic.Platform.Sdk.FunctionalTests.csproj", "{E2720282-8274-42FC-9488-717E55FA27D3}" 9 | EndProject 10 | Global 11 | GlobalSection(TestCaseManagementSettings) = postSolution 12 | CategoryFile = NewRelic.Platform.Sdk.vsmdi 13 | EndGlobalSection 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|Mixed Platforms = Debug|Mixed Platforms 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|Mixed Platforms = Release|Mixed Platforms 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 26 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 27 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 31 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Release|Mixed Platforms.Build.0 = Release|Any CPU 32 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6}.Release|x86.ActiveCfg = Release|Any CPU 33 | {27A3FE83-DED5-474D-B869-753A1566D578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {27A3FE83-DED5-474D-B869-753A1566D578}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {27A3FE83-DED5-474D-B869-753A1566D578}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 36 | {27A3FE83-DED5-474D-B869-753A1566D578}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 37 | {27A3FE83-DED5-474D-B869-753A1566D578}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {27A3FE83-DED5-474D-B869-753A1566D578}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {27A3FE83-DED5-474D-B869-753A1566D578}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {27A3FE83-DED5-474D-B869-753A1566D578}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 41 | {27A3FE83-DED5-474D-B869-753A1566D578}.Release|Mixed Platforms.Build.0 = Release|Any CPU 42 | {27A3FE83-DED5-474D-B869-753A1566D578}.Release|x86.ActiveCfg = Release|Any CPU 43 | {E2720282-8274-42FC-9488-717E55FA27D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {E2720282-8274-42FC-9488-717E55FA27D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {E2720282-8274-42FC-9488-717E55FA27D3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 46 | {E2720282-8274-42FC-9488-717E55FA27D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 47 | {E2720282-8274-42FC-9488-717E55FA27D3}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {E2720282-8274-42FC-9488-717E55FA27D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {E2720282-8274-42FC-9488-717E55FA27D3}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {E2720282-8274-42FC-9488-717E55FA27D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 51 | {E2720282-8274-42FC-9488-717E55FA27D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU 52 | {E2720282-8274-42FC-9488-717E55FA27D3}.Release|x86.ActiveCfg = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(SolutionProperties) = preSolution 55 | HideSolutionNode = FALSE 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Agent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NewRelic.Platform.Sdk.Binding; 3 | 4 | namespace NewRelic.Platform.Sdk 5 | { 6 | /// 7 | /// An abstract utility class to programmatically create Agents. 8 | /// Each agent will have its PollCycle method invoked once per poll interval. 9 | /// 10 | public abstract class Agent 11 | { 12 | public abstract string Guid { get; } 13 | public abstract string Version { get; } 14 | 15 | private IContext _context; 16 | 17 | public Agent() 18 | { 19 | } 20 | 21 | /// 22 | /// Each Agent that shares a Context reference will be sent in a single request. 23 | /// 24 | /// 25 | public void PrepareToRun(IContext context) 26 | { 27 | if (context == null) 28 | { 29 | throw new ArgumentNullException("context", "You must pass an initialized context when calling PrepareToRun()"); 30 | } 31 | 32 | _context = context; 33 | 34 | if(string.IsNullOrEmpty(_context.Version)) 35 | { 36 | _context.Version = Version; 37 | } 38 | } 39 | 40 | /// 41 | /// Registers a metric for this poll cycle that will be sent when all Agents complete their PollCycle. 42 | /// 43 | /// 44 | /// 45 | /// 46 | public void ReportMetric(string metricName, string units, float? value) 47 | { 48 | _context.ReportMetric(this.Guid, this.GetAgentName(), metricName, units, value); 49 | } 50 | 51 | /// 52 | /// Each descended class has this method invoked once per poll interval. Consumers should invoke ReportMetric() 53 | /// within this method in order to have metrics sent to the service. 54 | /// 55 | public abstract void PollCycle(); 56 | 57 | /// 58 | /// A human readable string denotes the name of this Agent in the New Relic service. 59 | /// 60 | /// 61 | public abstract string GetAgentName(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/AgentFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Configuration; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Reflection; 6 | using NewRelic.Platform.Sdk.Extensions; 7 | using NewRelic.Platform.Sdk.Utils; 8 | 9 | namespace NewRelic.Platform.Sdk 10 | { 11 | /// 12 | /// An abstract utility class provided to easily create Agents from a configuration file 13 | /// 14 | public abstract class AgentFactory 15 | { 16 | private const string ConfigurationFilePath = @"config\plugin.json"; 17 | 18 | private static Logger s_log = Logger.GetLogger("AgentFactory"); 19 | 20 | internal List CreateAgents() 21 | { 22 | List agentConfigurations = ReadJsonFile(); 23 | List agents = new List(); 24 | 25 | foreach (object properties in agentConfigurations) 26 | { 27 | agents.Add(CreateAgentWithConfiguration((IDictionary)properties)); 28 | } 29 | 30 | return agents; 31 | } 32 | 33 | internal List ReadJsonFile() 34 | { 35 | if (!File.Exists(ConfigurationFilePath)) 36 | { 37 | throw new FileNotFoundException(string.Format( 38 | CultureInfo.InvariantCulture, 39 | "Unable to locate plugin configuration file at {0}", 40 | Path.GetFullPath(ConfigurationFilePath))); 41 | } 42 | 43 | string localPath = Path.Combine(Assembly.GetExecutingAssembly().GetLocalPath(), ConfigurationFilePath); 44 | IDictionary configContents = JsonHelper.Deserialize(File.ReadAllText(ConfigurationFilePath)) as IDictionary; 45 | List agentProperties; 46 | 47 | if (configContents != null) 48 | { 49 | agentProperties = configContents["agents"] as List; 50 | } 51 | else 52 | { 53 | agentProperties = null; 54 | } 55 | 56 | if (agentProperties == null) 57 | { 58 | throw new ConfigurationErrorsException("The contents of 'plugin.json' are invalid. Please ensure you have a root 'agents' property that contains an array of JSON objects."); 59 | } 60 | 61 | return agentProperties; 62 | } 63 | 64 | /// 65 | /// The AgentFactory will read configuration data from specified JSON file and invoke this method for each 66 | /// object configuration containing an IDictionary of the deserialized properties 67 | /// 68 | /// 69 | /// 70 | public abstract Agent CreateAgentWithConfiguration(IDictionary properties); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/AgentData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | 4 | namespace NewRelic.Platform.Sdk.Binding 5 | { 6 | internal class AgentData 7 | { 8 | private string _host = System.Environment.MachineName; 9 | private int _pid = Process.GetCurrentProcess().Id; 10 | 11 | internal string Version { get; set; } 12 | 13 | internal AgentData() 14 | { 15 | } 16 | 17 | internal IDictionary Serialize() 18 | { 19 | IDictionary output = new Dictionary(); 20 | output.Add("host", _host); 21 | output.Add("version", this.Version); 22 | output.Add("pid", _pid); 23 | return output; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/ComponentData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NewRelic.Platform.Sdk.Binding 5 | { 6 | internal class ComponentData 7 | { 8 | #region Properties 9 | private string _name; 10 | private string _guid; 11 | 12 | internal string Name { get { return _name; } } 13 | internal string Guid { get { return _guid; } } 14 | #endregion 15 | 16 | private IDictionary _metrics; 17 | 18 | internal ComponentData(string name, string guid) 19 | { 20 | _name = name; 21 | _guid = guid; 22 | 23 | _metrics = new Dictionary(); 24 | } 25 | 26 | internal void AddMetric(MetricData metric) 27 | { 28 | if (!_metrics.ContainsKey(metric.FullName)) 29 | { 30 | _metrics.Add(metric.FullName, metric); 31 | } 32 | else 33 | { 34 | _metrics[metric.FullName].AggregateWith(metric); 35 | } 36 | } 37 | 38 | internal IDictionary Serialize(DateTime lastSuccessfulReport) 39 | { 40 | IDictionary output = new Dictionary(); 41 | 42 | output.Add("name", this.Name); 43 | output.Add("guid", this.Guid); 44 | output.Add("duration", this.CalculateDuration(lastSuccessfulReport)); 45 | output.Add("metrics", this.SerializeMetrics()); 46 | 47 | return output; 48 | } 49 | 50 | private IDictionary SerializeMetrics() 51 | { 52 | IDictionary output = new Dictionary(); 53 | 54 | foreach (MetricData metric in _metrics.Values) 55 | { 56 | output.Add(metric.FullName, metric.Serialize()); 57 | } 58 | 59 | return output; 60 | } 61 | 62 | private int CalculateDuration(DateTime lastSuccessfulReport) 63 | { 64 | return Convert.ToInt32((DateTime.Now - lastSuccessfulReport).TotalSeconds); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/Context.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using NewRelic.Platform.Sdk.Configuration; 5 | using NewRelic.Platform.Sdk.Utils; 6 | 7 | namespace NewRelic.Platform.Sdk.Binding 8 | { 9 | public class Context : IContext 10 | { 11 | private RequestData _requestData; 12 | private string _licenseKey; 13 | private readonly INewRelicConfig _newRelicConfig; 14 | 15 | private static Logger s_log = Logger.GetLogger("Context"); 16 | 17 | internal string ServiceUri 18 | { 19 | get 20 | { 21 | return this._newRelicConfig.Endpoint; 22 | } 23 | } 24 | 25 | internal string LicenseKey 26 | { 27 | get 28 | { 29 | return _licenseKey ?? this._newRelicConfig.LicenseKey; 30 | } 31 | } 32 | 33 | public string Version 34 | { 35 | get 36 | { 37 | return _requestData.Version; 38 | } 39 | set 40 | { 41 | _requestData.Version = value; 42 | } 43 | } 44 | 45 | // Exposed to enable functional testing for plugin developers 46 | public RequestData RequestData 47 | { 48 | get 49 | { 50 | return _requestData; 51 | } 52 | } 53 | 54 | /// 55 | /// This class is responsible for maintaining metrics that have been reported as well as sending them to the New Relic 56 | /// service. Any Components that share a Request reference will have their data sent to the service in one request. 57 | /// 58 | /// The configuration that should be used. 59 | public Context(INewRelicConfig config = null) 60 | : this(null, config) 61 | { 62 | } 63 | 64 | /// 65 | /// This class is responsible for maintaining metrics that have been reported as well as sending them to the New Relic 66 | /// service. Any Components that share a Request reference will have their data sent to the service in one request. 67 | /// 68 | /// The New Relic license key. 69 | /// The configuration that should be used. 70 | public Context(string licenseKey, INewRelicConfig config = null) 71 | { 72 | this._newRelicConfig = config ?? NewRelicConfig.Instance; 73 | 74 | this._licenseKey = licenseKey; 75 | this._requestData = new RequestData(); 76 | 77 | s_log.Debug("Using service URL: {0}", this.ServiceUri); 78 | s_log.Debug("Using license key: {0}", this.LicenseKey); 79 | } 80 | 81 | /// 82 | /// Prepare a New Relic metric to be delivered to the service at the end of this poll cycle. 83 | /// 84 | /// A string guid identifying the plugin this is associated with (e.g. 'com.yourcompany.pluginname') 85 | /// A string name identifying the plugin agent that will appear in the service UI (e.g. 'MyPlugin') 86 | /// A string name representing the name of the metric (e.g. 'Category/Name') 87 | /// The units of the metric you are sending to the service (e.g. 'byte/second') 88 | /// The non-negative float value representing this value 89 | public void ReportMetric(string guid, string componentName, string metricName, string units, float? value) 90 | { 91 | s_log.Debug("Reporting metric: {0} -> {1}[{2}]={3}", componentName, metricName, units, value); 92 | if (string.IsNullOrEmpty(guid)) 93 | { 94 | throw new ArgumentNullException("guid", "Null parameter was passed to ReportMetric()"); 95 | } 96 | 97 | if (string.IsNullOrEmpty(componentName)) 98 | { 99 | throw new ArgumentNullException("componentName", "Null parameter was passed to ReportMetric()"); 100 | } 101 | 102 | if (string.IsNullOrEmpty(metricName)) 103 | { 104 | throw new ArgumentNullException("metricName", "Null parameter was passed to ReportMetric()"); 105 | } 106 | 107 | if (string.IsNullOrEmpty(units)) 108 | { 109 | throw new ArgumentNullException("units", "Null parameter was passed to ReportMetric()"); 110 | } 111 | 112 | // Ensure the value is not null since EpochProcessors will return 0 on initial processing 113 | if (value.HasValue) 114 | { 115 | if (value.Value < 0) 116 | { 117 | throw new ArgumentException("New Relic Platform does not currently support negative values", "value"); 118 | } 119 | 120 | _requestData.AddMetric(guid, componentName, metricName, units, value.Value); 121 | } 122 | } 123 | 124 | /// 125 | /// Prepare a New Relic metric to be delivered to the service at the end of this poll cycle. 126 | /// 127 | /// A string guid identifying the plugin this is associated with (e.g. 'com.yourcompany.pluginname') 128 | /// A string name identifying the plugin agent that will appear in the service UI (e.g. 'MyPlugin') 129 | /// A string name representing the name of the metric (e.g. 'Category/Name') 130 | /// The units of the metric you are sending to the service (e.g. 'byte/second') 131 | /// The non-negative float value representing this value 132 | /// The int value representing how many poll cycle this metric has been aggregated for 133 | /// The non-negative float value representing this min value for this poll cycle 134 | /// The non-negative float value representing this max value for this poll cycle 135 | /// The non-negative float value representing the sum of square values for this poll cycle 136 | public void ReportMetric(string guid, string componentName, string metricName, string units, float value, int count, float min, float max, float sumOfSquares) 137 | { 138 | s_log.Debug("Reporting metric: {0} -> {1}[{2}]={3}", componentName, metricName, units, value); 139 | if (string.IsNullOrEmpty(guid)) 140 | { 141 | throw new ArgumentNullException("guid", "Null parameter was passed to ReportMetric()"); 142 | } 143 | 144 | if (string.IsNullOrEmpty(componentName)) 145 | { 146 | throw new ArgumentNullException("componentName", "Null parameter was passed to ReportMetric()"); 147 | } 148 | 149 | if (string.IsNullOrEmpty(metricName)) 150 | { 151 | throw new ArgumentNullException("metricName", "Null parameter was passed to ReportMetric()"); 152 | } 153 | 154 | if (string.IsNullOrEmpty(units)) 155 | { 156 | throw new ArgumentNullException("units", "Null parameter was passed to ReportMetric()"); 157 | } 158 | 159 | if (count < 1) 160 | { 161 | throw new ArgumentException("New Relic Platform does not support count values less than 1", "count"); 162 | } 163 | 164 | // Ensure the value is not null since EpochProcessors will return 0 on initial processing 165 | if (value < 0 || min < 0 || max < 0 || sumOfSquares < 0) 166 | { 167 | throw new ArgumentException("New Relic Platform does not currently support negative values"); 168 | } 169 | 170 | _requestData.AddMetric(guid, componentName, metricName, units, value, count, min, max, sumOfSquares); 171 | } 172 | 173 | /// 174 | /// Will send all metrics that were reported to the Context since its last successful delivery to the New Relic service. 175 | /// The context will aggregate values for calls that fail due to transient issues. 176 | /// 177 | public void SendMetricsToService() 178 | { 179 | s_log.Info("Preparing to send metrics to service"); 180 | 181 | // Check to see if the Agent data is valid before sending data 182 | if (!ValidateRequestData()) 183 | { 184 | return; 185 | } 186 | 187 | var request = (HttpWebRequest)WebRequest.Create(this.ServiceUri); 188 | request.Method = "POST"; 189 | request.ContentType = "application/json"; 190 | request.Accept = "application/json"; 191 | request.Headers["X-License-Key"] = this.LicenseKey; 192 | 193 | using (var writer = new StreamWriter(request.GetRequestStream())) 194 | { 195 | var str = JsonHelper.Serialize(_requestData.Serialize()); 196 | s_log.Debug("Sending JSON: {0}", str); 197 | writer.Write(str); 198 | } 199 | 200 | HandleServiceResponse(request); 201 | } 202 | 203 | private bool ValidateRequestData() 204 | { 205 | // Do not send any data if no agents reported 206 | if (!_requestData.HasComponents()) 207 | { 208 | s_log.Info("No metrics reported for this poll cycle, continuing..."); 209 | _requestData.Reset(); // Reset the aggregation timer 210 | return false; 211 | } 212 | 213 | // Reset the agent data if we have been aggregating for too long 214 | if (_requestData.IsPastAggregationLimit()) 215 | { 216 | s_log.Warn("The service has not been successfully contacted in several minutes. Starting a fresh aggregation cycle."); 217 | _requestData.Reset(); // Reset the aggregation timer 218 | return false; 219 | } 220 | 221 | return true; 222 | } 223 | 224 | private void HandleServiceResponse(HttpWebRequest request) 225 | { 226 | try 227 | { 228 | using (var response = (HttpWebResponse)request.GetResponse()) 229 | { 230 | using (var reader = new StreamReader(response.GetResponseStream())) 231 | { 232 | if (response.StatusCode == HttpStatusCode.OK) 233 | { 234 | s_log.Info("Metrics successfully sent"); 235 | // Reset aggregation after successful delivery 236 | _requestData.Reset(); 237 | } 238 | } 239 | } 240 | } 241 | catch (WebException we) 242 | { 243 | if( we.Response != null ) 244 | { 245 | using (var response = (HttpWebResponse)we.Response) 246 | { 247 | using( var reader = new StreamReader( response.GetResponseStream() ) ) 248 | { 249 | var body = reader.ReadToEnd(); 250 | 251 | if( response.StatusCode == HttpStatusCode.ServiceUnavailable ) 252 | { 253 | // Collector is being updated 254 | s_log.Info( "New Relic Service is currently undergoing an upgrade" ); 255 | } 256 | else if( response.StatusCode == HttpStatusCode.Forbidden 257 | && string.Equals( Constants.DisableNewRelic, body ) ) 258 | { 259 | // Remotely disabled 260 | s_log.Fatal( "Remotely disabled by New Relic service" ); 261 | throw new NewRelicServiceException( response.StatusCode, "Remotely disabled by New Relic Service", we ); 262 | } 263 | else 264 | { 265 | // Log unknown exception 266 | s_log.Error( "Unexpected response from the New Relic service. StatusCode: {0} ({1}), BodyContents: {2}", 267 | response.StatusCode, response.StatusDescription, body ); 268 | 269 | // Rethrow if this is a client exception, keep trying if it is a server error 270 | if( (int)response.StatusCode >= 400 && (int)response.StatusCode < 500 ) 271 | { 272 | throw new NewRelicServiceException( response.StatusCode, body, we ); 273 | } 274 | } 275 | } 276 | } 277 | } 278 | else 279 | { 280 | // Log unknown exception 281 | s_log.Error( "No response from the New Relic service." ); 282 | } 283 | } // End catch() 284 | } // End HandleServiceResponse() 285 | } // End Context 286 | } 287 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/IContext.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.Binding 2 | { 3 | public interface IContext 4 | { 5 | string Version { get; set; } 6 | 7 | RequestData RequestData { get; } 8 | 9 | /// 10 | /// Prepare a New Relic metric to be delivered to the service at the end of this poll cycle. 11 | /// 12 | /// A string guid identifying the plugin this is associated with (e.g. 'com.yourcompany.pluginname') 13 | /// A string name identifying the plugin agent that will appear in the service UI (e.g. 'MyPlugin') 14 | /// A string name representing the name of the metric (e.g. 'Category/Name') 15 | /// The units of the metric you are sending to the service (e.g. 'byte/second') 16 | /// The non-negative float value representing this value 17 | void ReportMetric(string guid, string componentName, string metricName, string units, float? value); 18 | 19 | /// 20 | /// Prepare a New Relic metric to be delivered to the service at the end of this poll cycle. 21 | /// 22 | /// A string guid identifying the plugin this is associated with (e.g. 'com.yourcompany.pluginname') 23 | /// A string name identifying the plugin agent that will appear in the service UI (e.g. 'MyPlugin') 24 | /// A string name representing the name of the metric (e.g. 'Category/Name') 25 | /// The units of the metric you are sending to the service (e.g. 'byte/second') 26 | /// The non-negative float value representing this value 27 | /// The int value representing how many poll cycle this metric has been aggregated for 28 | /// The non-negative float value representing this min value for this poll cycle 29 | /// The non-negative float value representing this max value for this poll cycle 30 | /// The non-negative float value representing the sum of square values for this poll cycle 31 | void ReportMetric(string guid, string componentName, string metricName, string units, float value, int count, float min, float max, float sumOfSquares); 32 | 33 | /// 34 | /// Will send all metrics that were reported to the Context since its last successful delivery to the New Relic service. 35 | /// The context will aggregate values for calls that fail due to transient issues. 36 | /// 37 | void SendMetricsToService(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/MetricData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NewRelic.Platform.Sdk.Binding 4 | { 5 | internal class MetricData 6 | { 7 | #region Properties 8 | private string _name; 9 | private string _units; 10 | private int _count; 11 | private float _value; 12 | private float _minValue; 13 | private float _maxValue; 14 | private float _sumOfSquares; 15 | 16 | internal int Count { get { return _count; } } 17 | internal float Value { get { return _value; } } 18 | internal float MinValue { get { return _minValue; } } 19 | internal float MaxValue { get { return _maxValue; } } 20 | internal float SumOfSquares { get { return _sumOfSquares; } } 21 | internal string FullName { get { return string.Format("Component/{0}[{1}]", _name, _units); } } 22 | #endregion 23 | 24 | internal MetricData(string name, string units, float value) : this(name, units, 1, value, value, value, value * value) 25 | { 26 | } 27 | 28 | internal MetricData(string name, string units, int count, float value, float minValue, float maxValue, float sumOfSquares) 29 | { 30 | this._name = name; 31 | this._units = units; 32 | this._count = count; 33 | this._value = value; 34 | this._minValue = minValue; 35 | this._maxValue = maxValue; 36 | this._sumOfSquares = sumOfSquares; 37 | } 38 | 39 | internal void AggregateWith(MetricData other) 40 | { 41 | this._count += other.Count; 42 | this._value += other.Value; 43 | this._minValue = Math.Min(_minValue, other.MinValue); 44 | this._maxValue = Math.Max(_maxValue, other.MaxValue); 45 | this._sumOfSquares += other.SumOfSquares; 46 | } 47 | 48 | internal Array Serialize() 49 | { 50 | return new float[] { _value, _count, _minValue, _maxValue, _sumOfSquares }; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Binding/RequestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NewRelic.Platform.Sdk.Binding 5 | { 6 | public class RequestData 7 | { 8 | private DateTime _aggregationStartTime; 9 | 10 | private AgentData _agent; 11 | private IDictionary _components; 12 | 13 | public string Version 14 | { 15 | get { return _agent.Version; } 16 | set { _agent.Version = value; } 17 | } 18 | 19 | public RequestData() 20 | { 21 | _agent = new AgentData(); 22 | _components = new Dictionary(); 23 | _aggregationStartTime = DateTime.Now.Subtract(TimeSpan.FromSeconds(Constants.DefaultDuration)); // Set with the default duration 24 | } 25 | 26 | public void AddMetric(string guid, string componentName, string metricName, string units, float value) 27 | { 28 | string key = string.Format("{0}:{1}", componentName, guid); 29 | 30 | if (!_components.ContainsKey(key)) 31 | { 32 | ComponentData componentData = new ComponentData(componentName, guid); 33 | _components.Add(key, componentData); 34 | } 35 | 36 | _components[key].AddMetric(new MetricData(metricName, units, value)); 37 | } 38 | 39 | public void AddMetric(string guid, string componentName, string metricName, string units, float value, int count, float min, float max, float sumOfSquares) 40 | { 41 | string key = string.Format("{0}:{1}", componentName, guid); 42 | 43 | if (!_components.ContainsKey(key)) 44 | { 45 | ComponentData componentData = new ComponentData(componentName, guid); 46 | _components.Add(key, componentData); 47 | } 48 | 49 | _components[key].AddMetric(new MetricData(metricName, units, count, value, min, max, sumOfSquares)); 50 | } 51 | 52 | public bool HasComponents() 53 | { 54 | return _components.Count > 0; 55 | } 56 | 57 | public bool IsPastAggregationLimit() 58 | { 59 | return Convert.ToInt32((DateTime.Now - _aggregationStartTime).TotalMinutes) > Constants.DefaultAggregationLimit; 60 | } 61 | 62 | public void Reset() 63 | { 64 | _components = new Dictionary(); 65 | _aggregationStartTime = DateTime.Now; 66 | } 67 | 68 | public IDictionary Serialize() 69 | { 70 | IDictionary output = new Dictionary(); 71 | output.Add("agent", _agent.Serialize()); 72 | output.Add("components", SerializeComponents()); 73 | return output; 74 | } 75 | 76 | private List SerializeComponents() 77 | { 78 | List output = new List(); 79 | 80 | foreach (var componentData in _components.Values) 81 | { 82 | output.Add(componentData.Serialize(_aggregationStartTime)); 83 | } 84 | 85 | return output; 86 | } 87 | 88 | #region Test Helpers 89 | /// 90 | /// DO NOT USE: Exposed for testing 91 | /// 92 | internal void SetAggregationLimit(DateTime aggregationStart) 93 | { 94 | _aggregationStartTime = aggregationStart; 95 | } 96 | #endregion 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Configuration/INewRelicConfig.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.Configuration 2 | { 3 | public interface INewRelicConfig 4 | { 5 | string Endpoint { get; set; } 6 | 7 | string LicenseKey { get; set; } 8 | 9 | LogLevel LogLevel { get; set; } 10 | 11 | string LogFileName { get; set; } 12 | 13 | string LogFilePath { get; set; } 14 | 15 | long LogLimitInKiloBytes { get; set; } 16 | 17 | int? NewRelicMaxIterations { get; set; } 18 | 19 | string ProxyHost { get; set; } 20 | 21 | string ProxyPassword { get; set; } 22 | 23 | int? ProxyPort { get; set; } 24 | 25 | string ProxyUserName { get; set; } 26 | 27 | int? PollInterval { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Configuration/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.Configuration 2 | { 3 | public enum LogLevel 4 | { 5 | Debug = 0, 6 | Info, 7 | Warn, 8 | Error, 9 | Fatal, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Configuration/NewRelicConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Reflection; 4 | using NewRelic.Platform.Sdk.Extensions; 5 | using Newtonsoft.Json; 6 | 7 | namespace NewRelic.Platform.Sdk.Configuration 8 | { 9 | public class NewRelicConfig : INewRelicConfig 10 | { 11 | private static NewRelicConfig ConfigInstance; 12 | private const string ConfigPath = @"config\newrelic.json"; 13 | private const string DefaultEndpoint = "https://platform-api.newrelic.com/platform/v1/metrics"; 14 | private const string DefaultLogFileName = "newrelic_plugin.log"; 15 | private const string DefaultLogFilePath = @"logs"; 16 | private const int DefaultLogLimitInKiloBytes = 25600; 17 | 18 | private NewRelicConfig() 19 | { 20 | // set default values 21 | this.Endpoint = DefaultEndpoint; 22 | this.LogLevel = LogLevel.Info; 23 | this.LogFileName = DefaultLogFileName; 24 | this.LogFilePath = Path.Combine(Assembly.GetExecutingAssembly().GetLocalPath(), DefaultLogFilePath); 25 | this.LogLimitInKiloBytes = DefaultLogLimitInKiloBytes; 26 | } 27 | 28 | [JsonProperty(PropertyName = "license_key")] 29 | public string LicenseKey { get; set; } 30 | 31 | [JsonProperty(PropertyName = "endpoint")] 32 | public string Endpoint { get; set; } 33 | 34 | [JsonProperty(PropertyName = "log_level")] 35 | public LogLevel LogLevel { get; set; } 36 | 37 | [JsonProperty(PropertyName = "log_filename")] 38 | public string LogFileName { get; set; } 39 | 40 | [JsonProperty(PropertyName = "log_file_path")] 41 | public string LogFilePath { get; set; } 42 | 43 | [JsonProperty(PropertyName = "log_limit_in_kbytes")] 44 | public long LogLimitInKiloBytes { get; set; } 45 | 46 | [JsonProperty(PropertyName = "proxy_host")] 47 | public string ProxyHost { get; set; } 48 | 49 | [JsonProperty(PropertyName = "proxy_port")] 50 | public int? ProxyPort { get; set; } 51 | 52 | [JsonProperty(PropertyName = "proxy_username")] 53 | public string ProxyUserName { get; set; } 54 | 55 | [JsonProperty(PropertyName = "proxy_password")] 56 | public string ProxyPassword { get; set; } 57 | 58 | [JsonProperty(PropertyName = "poll_interval")] 59 | public int? PollInterval { get; set; } 60 | 61 | // Exposed for testing 62 | public int? NewRelicMaxIterations { get; set; } 63 | 64 | public static NewRelicConfig Instance 65 | { 66 | get 67 | { 68 | if (ConfigInstance == null) 69 | { 70 | string assemblyPath = Assembly.GetExecutingAssembly().GetLocalPath(); 71 | string configPath = Path.Combine(assemblyPath, ConfigPath); 72 | 73 | if (!File.Exists(configPath)) 74 | { 75 | throw new FileNotFoundException(string.Format( 76 | CultureInfo.InvariantCulture, 77 | "New relic configuration needs to be located at {0}", 78 | configPath)); 79 | } 80 | 81 | ConfigInstance = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); 82 | } 83 | 84 | return ConfigInstance; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Extensions/ExtensionsForAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace NewRelic.Platform.Sdk.Extensions 6 | { 7 | public static class ExtensionsForAssembly 8 | { 9 | public static string GetLocalPath(this Assembly assembly) 10 | { 11 | Uri uri = new Uri(assembly.CodeBase); 12 | return Path.GetDirectoryName(uri.LocalPath); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Externals/NLog v2.1.0/NLog.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_dotnet_sdk/cd82eaafeaccbd230f96ee679eabdb7a9bfa26cc/NewRelic.Platform.Sdk/Externals/NLog v2.1.0/NLog.dll -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Externals/Newtonsoft.Json v5.8/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_dotnet_sdk/cd82eaafeaccbd230f96ee679eabdb7a9bfa26cc/NewRelic.Platform.Sdk/Externals/Newtonsoft.Json v5.8/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/NewRelic.Platform.Sdk.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {6A9DC6C5-280E-42B9-AE30-B66803DF20B6} 9 | Library 10 | Properties 11 | NewRelic.Platform.Sdk 12 | NewRelic.Platform.Sdk 13 | v3.5 14 | 512 15 | Client 16 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | $(SolutionDir)\build\$(Platform)-$(Configuration)\$(AssemblyName)\ 36 | 37 | 38 | 39 | Externals\Newtonsoft.Json v5.8\Newtonsoft.Json.dll 40 | 41 | 42 | False 43 | Externals\NLog v2.1.0\NLog.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Processors/EpochProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NewRelic.Platform.Sdk.Processors 4 | { 5 | /// 6 | /// A processor for processing metrics over a period of time (e.g. operations/second) 7 | /// Use by giving a constantly growing metric each poll cycle and it will give a rate 8 | /// differential over the time of the poll interval. 9 | /// 10 | /// For example: Process(5) Process(6) with a Poll Interval of 1 minute gives 6-5 units / 1 minute 11 | /// or 1 unit per minute. 12 | /// 13 | public class EpochProcessor : IProcessor 14 | { 15 | private float? _lastVal = null; 16 | private DateTime? _lastTime = null; 17 | 18 | public float? Process(float? val) 19 | { 20 | var now = DateTime.Now; 21 | float? returnVal = null; 22 | 23 | if (val.HasValue && _lastVal.HasValue && _lastTime.HasValue && now > _lastTime) 24 | { 25 | int timeDiff = Convert.ToInt32((now - _lastTime.Value).TotalSeconds); 26 | if (timeDiff > 0) 27 | { 28 | returnVal = (val.Value - _lastVal.Value) / timeDiff; 29 | 30 | // Negative values are not supported 31 | if (returnVal < 0) 32 | { 33 | return null; 34 | } 35 | } 36 | } 37 | 38 | _lastVal = val; 39 | _lastTime = now; 40 | 41 | return returnVal; 42 | } 43 | 44 | #region Test Helpers 45 | 46 | /// 47 | /// DO NOT USE: Exposed for test purposes 48 | /// 49 | internal DateTime LastValue { set { _lastTime = value; } } 50 | 51 | #endregion 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Processors/IProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.Processors 2 | { 3 | /// 4 | /// A general purpose interface for processing metric values 5 | /// 6 | public interface IProcessor 7 | { 8 | /// 9 | /// Process a value for metric reporting 10 | /// 11 | /// 12 | /// 13 | float? Process(float? val); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NewRelic.Platform.Sdk")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NewRelic.Platform.Sdk")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM agents. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a2b63c8c-fedd-4456-a2ac-ecbc378d50e5")] 24 | 25 | [assembly: InternalsVisibleTo("NewRelic.Platform.Sdk.UnitTests")] 26 | [assembly: InternalsVisibleTo("NewRelic.Platform.Sdk.FunctionalTests")] 27 | 28 | // Version information for an assembly consists of the following four values: 29 | // 30 | // Major Version 31 | // Minor Version 32 | // Build Number 33 | // Revision 34 | // 35 | // You can specify all the values or you can default the Build and Revision Numbers 36 | // by using the '*' as shown below: 37 | // [assembly: AssemblyVersion("1.0.*")] 38 | [assembly: AssemblyVersion("1.0.0.1")] 39 | [assembly: AssemblyFileVersion("1.0.0.1")] 40 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Runner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading; 6 | using NewRelic.Platform.Sdk.Binding; 7 | using NewRelic.Platform.Sdk.Configuration; 8 | using NewRelic.Platform.Sdk.Utils; 9 | 10 | namespace NewRelic.Platform.Sdk 11 | { 12 | public class Runner 13 | { 14 | private List _factories; 15 | private List _agents; 16 | private readonly INewRelicConfig newRelicConfig; 17 | 18 | private static Logger s_log = Logger.GetLogger("Runner"); 19 | 20 | public Runner(INewRelicConfig config = null) 21 | { 22 | this.newRelicConfig = config ?? NewRelicConfig.Instance; 23 | 24 | _factories = new List(); 25 | _agents = new List(); 26 | 27 | this.SetupProxy( 28 | this.newRelicConfig.ProxyHost, 29 | this.newRelicConfig.ProxyPort, 30 | this.newRelicConfig.ProxyUserName, 31 | this.newRelicConfig.ProxyPassword); 32 | 33 | // used for testing purposes 34 | _limit = this.newRelicConfig.NewRelicMaxIterations.GetValueOrDefault(); 35 | _limitRun = this.newRelicConfig.NewRelicMaxIterations.HasValue; 36 | } 37 | 38 | /// 39 | /// Add an instance of an Agent to the Runner. Any agents added prior to invoking SetupAndRun() will have their 40 | /// PollCycle() method invoked each polling interval. 41 | /// 42 | /// 43 | public void Add(Agent agent) 44 | { 45 | if (agent == null) 46 | { 47 | throw new ArgumentNullException("agent", "You must pass in a non-null agent"); 48 | } 49 | 50 | s_log.Info("Adding new agent: {0}", agent.GetAgentName()); 51 | _agents.Add(agent); 52 | } 53 | 54 | /// 55 | /// Add an instance of a factory to the Runner. Any factories added prior to invoking SetupAndRun() will have 56 | /// their CreateAgentWithConfiguration() method invoked which will create a list of Agents initialized through 57 | /// the factory's configuration file that will be used for polling intervals. 58 | /// 59 | /// 60 | public void Add(AgentFactory factory) 61 | { 62 | if (factory == null) 63 | { 64 | throw new ArgumentNullException("factory", "You must pass in a non-null factory"); 65 | } 66 | 67 | s_log.Info("Adding new factory {0}", factory.GetType()); 68 | _factories.Add(factory); 69 | } 70 | 71 | /// 72 | /// This method only returns during a fatal error. It will initialize agents if necessary, and then begin polling once 73 | /// per configurable PollInterval invoking registered Agent's PollCycle() methods. Then sending the data to the New Relic service. 74 | /// 75 | public void SetupAndRun() 76 | { 77 | if (_factories.Count == 0 && _agents.Count == 0) 78 | { 79 | throw new InvalidOperationException("You must first call 'Add()' at least once with a valid factory or agent"); 80 | } 81 | 82 | // Initialize agents if they added an AgentFactory, otherwise they have explicitly added initialized agents already 83 | if (_factories.Count > 0) 84 | { 85 | InitializeFactoryAgents(); 86 | } 87 | 88 | // Initialize agents with the same Context so they aggregate to a single a request 89 | var context = new Context(); 90 | 91 | foreach (var agent in _agents) 92 | { 93 | agent.PrepareToRun(context); 94 | } 95 | 96 | var pollInterval = GetPollInterval(); // Fetch poll interval here so we can report any issues early 97 | 98 | while (true) 99 | { 100 | // Invoke each Agent's PollCycle method, logging any exceptions that occur 101 | try 102 | { 103 | foreach (var agent in _agents) 104 | { 105 | agent.PollCycle(); 106 | } 107 | } 108 | catch(Exception e) 109 | { 110 | s_log.Error("Error error occurred during PollCycle", e); 111 | } 112 | 113 | try 114 | { 115 | context.SendMetricsToService(); 116 | 117 | // Enables limited runs for tests that want to invoke the service 118 | if (_limitRun && --_limit == 0) 119 | { 120 | return; 121 | } 122 | 123 | Thread.Sleep(pollInterval); 124 | } 125 | catch (Exception e) 126 | { 127 | s_log.Fatal("Fatal error occurred. Shutting down the application", e); 128 | throw e; 129 | } 130 | } 131 | } 132 | 133 | protected virtual void SetupProxy(string hostname, int? port, string username, string password) 134 | { 135 | if (hostname.IsValidString()) 136 | { 137 | UriBuilder builder = new UriBuilder(hostname); 138 | 139 | if (port.HasValue) 140 | { 141 | builder.Port = port.Value; 142 | } 143 | else 144 | { 145 | throw new InvalidOperationException("When setting up a proxy, port is required."); 146 | } 147 | 148 | ICredentials credentials; 149 | if (username.IsValidString()) 150 | { 151 | credentials = new NetworkCredential(username, password); 152 | } 153 | else 154 | { 155 | credentials = null; 156 | } 157 | 158 | IWebProxy proxy = new WebProxy 159 | { 160 | Address = builder.Uri, 161 | Credentials = credentials, 162 | }; 163 | 164 | WebRequest.DefaultWebProxy = proxy; 165 | } 166 | } 167 | 168 | private void InitializeFactoryAgents() 169 | { 170 | foreach (AgentFactory factory in _factories) 171 | { 172 | _agents = _agents.Union(factory.CreateAgents()).ToList(); 173 | } 174 | } 175 | 176 | private int GetPollInterval() 177 | { 178 | int pollInterval = 60; 179 | return pollInterval *= 1000; // Convert to milliseconds since that's what system calls expect; 180 | } 181 | 182 | #region Test Helpers 183 | 184 | /// 185 | /// DO NOT USE: Exposed for test purposes 186 | /// 187 | private int _limit; 188 | private bool _limitRun; 189 | 190 | internal List Agents { get { return _agents; } } 191 | 192 | internal void SetupAndRunWithLimit(int limit) 193 | { 194 | _limitRun = true; 195 | _limit = limit; 196 | SetupAndRun(); 197 | } 198 | 199 | #endregion 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Utils/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk 2 | { 3 | internal static class Constants 4 | { 5 | public static readonly string DefaultServiceUri = "https://collector.newrelic.com/platform/v1/metrics"; 6 | 7 | public static readonly int DefaultDuration = 60; 8 | public static readonly int DefaultAggregationLimit = 10; 9 | 10 | public static readonly string DisableNewRelic = "DISABLE_NEWRELIC"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Utils/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json.Linq; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | 6 | namespace NewRelic.Platform.Sdk.Utils 7 | { 8 | public static class JsonHelper 9 | { 10 | /// 11 | /// Serializes a list of simple .NET types (e.g. IDictionary, List, and primitives) into a JSON string 12 | /// 13 | /// 14 | /// 15 | public static string Serialize(object obj) 16 | { 17 | return JsonConvert.SerializeObject(obj, new KeyValuePairConverter()); 18 | } 19 | 20 | /// 21 | /// Deserializes a JSON into a list of simple .NET types (e.g. IDictionary, List, and primitives) 22 | /// 23 | /// 24 | /// 25 | public static object Deserialize(string json) 26 | { 27 | return JsonToDotNetType(JToken.Parse(json)); 28 | } 29 | 30 | private static object JsonToDotNetType(JToken token) 31 | { 32 | if (token.Type == JTokenType.Object) 33 | { 34 | IDictionary dict = new Dictionary(); 35 | foreach (JProperty prop in ((JObject)token).Properties()) 36 | { 37 | dict.Add(prop.Name, JsonToDotNetType(prop.Value)); 38 | } 39 | return dict; 40 | } 41 | else if (token.Type == JTokenType.Array) 42 | { 43 | List list = new List(); 44 | foreach (JToken value in token) 45 | { 46 | list.Add(JsonToDotNetType(value)); 47 | } 48 | return list; 49 | } 50 | else 51 | { 52 | return ((JValue)token).Value; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Utils/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using NewRelic.Platform.Sdk.Configuration; 5 | using NLog.Config; 6 | using NLog.Targets; 7 | using NLogger = NLog.Logger; 8 | using NLogLevel = NLog.LogLevel; 9 | using NLogManager = NLog.LogManager; 10 | 11 | namespace NewRelic.Platform.Sdk.Utils 12 | { 13 | public sealed class Logger 14 | { 15 | private NLogger nLogger; 16 | private static bool Configured = false; 17 | private static object ConfigureLock = new object(); 18 | 19 | public static LogLevel LogLevel { get; private set; } 20 | 21 | private Logger(string className) 22 | { 23 | this.nLogger = NLogManager.GetLogger(className); 24 | } 25 | 26 | public void Debug(string format, params object[] items) 27 | { 28 | this.nLogger.Debug(format, items); 29 | } 30 | 31 | public void Info(string format, params object[] items) 32 | { 33 | this.nLogger.Info(format, items); 34 | } 35 | 36 | public void Warn(string format, params object[] items) 37 | { 38 | this.nLogger.Warn(format, items); 39 | } 40 | 41 | public void Error(string format, params object[] items) 42 | { 43 | this.nLogger.Error(format, items); 44 | } 45 | 46 | public void Fatal(string format, params object[] items) 47 | { 48 | this.nLogger.Fatal(format, items); 49 | } 50 | 51 | public static Logger GetLogger(string className) 52 | { 53 | return GetLogger(className, null); 54 | } 55 | 56 | private static void Configure(INewRelicConfig config) 57 | { 58 | config = config ?? NewRelicConfig.Instance; 59 | LoggingConfiguration loggingConfiguration = new LoggingConfiguration(); 60 | ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget { Name = "Console" }; 61 | loggingConfiguration.AddTarget("Console", consoleTarget); 62 | loggingConfiguration.LoggingRules.Add(new LoggingRule("*", ToNLogLevel(config.LogLevel), consoleTarget)); 63 | 64 | // Store the LogLevel so it can be fetched by consumers 65 | LogLevel = config.LogLevel; 66 | 67 | if (config.LogFilePath.IsValidString() && config.LogFileName.IsValidString()) 68 | { 69 | long archiveAboveSize = config.LogLimitInKiloBytes == 0 ? long.MaxValue : config.LogLimitInKiloBytes * 1024; 70 | FileTarget fileTarget = new FileTarget 71 | { 72 | KeepFileOpen = true, 73 | ConcurrentWrites = false, 74 | FileName = Path.Combine(config.LogFilePath, config.LogFileName), 75 | MaxArchiveFiles = 1, 76 | ArchiveAboveSize = archiveAboveSize, 77 | Name = "File", 78 | }; 79 | 80 | loggingConfiguration.AddTarget("File", fileTarget); 81 | loggingConfiguration.LoggingRules.Add(new LoggingRule("*", ToNLogLevel(config.LogLevel), fileTarget)); 82 | } 83 | 84 | NLogManager.Configuration = loggingConfiguration; 85 | } 86 | 87 | private static NLogLevel ToNLogLevel(LogLevel level) 88 | { 89 | switch (level) 90 | { 91 | case LogLevel.Debug: 92 | return NLogLevel.Debug; 93 | case LogLevel.Error: 94 | return NLogLevel.Error; 95 | case LogLevel.Fatal: 96 | return NLogLevel.Fatal; 97 | case LogLevel.Info: 98 | return NLogLevel.Info; 99 | case LogLevel.Warn: 100 | return NLogLevel.Warn; 101 | default: 102 | throw new ArgumentException(string.Format( 103 | CultureInfo.InvariantCulture, 104 | "{0} is not a valid LogLevel", 105 | level.ToString())); 106 | } 107 | } 108 | 109 | // methods in this region should only be called directly by tests 110 | #region CalledByTests 111 | 112 | internal static Logger GetLogger(string className, INewRelicConfig config) 113 | { 114 | if (!Configured || config != null) 115 | { 116 | lock (ConfigureLock) 117 | { 118 | if (!Configured || config != null) 119 | { 120 | Configure(config); 121 | Configured = true; 122 | } 123 | } 124 | } 125 | 126 | return new Logger(className); 127 | } 128 | 129 | #endregion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Utils/NewRelicServiceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace NewRelic.Platform.Sdk.Utils 5 | { 6 | public class NewRelicServiceException : Exception 7 | { 8 | private HttpStatusCode _statusCode; 9 | private string _bodyContents; 10 | 11 | public HttpStatusCode StatusCode { get { return _statusCode; } } 12 | public string BodyContents { get { return _bodyContents; } } 13 | 14 | public NewRelicServiceException(HttpStatusCode statusCode, string body, Exception exception) : base(exception.Message, exception) 15 | { 16 | _statusCode = statusCode; 17 | _bodyContents = body; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/Utils/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace NewRelic.Platform.Sdk.Utils 2 | { 3 | public static class StringExtensions 4 | { 5 | public static bool IsValidString(this string str) 6 | { 7 | bool result = !string.IsNullOrEmpty(str) && str.Trim().Length != 0; 8 | return result; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NewRelic.Platform.Sdk/config/newrelic.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "license_key": "YOUR_LICENSE_KEY_HERE", 3 | "log_level": "info", 4 | "log_file_name": "newrelic_plugin.log", 5 | "log_file_path": "logs" 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Archived header](https://github.com/newrelic/open-source-office/raw/master/examples/categories/images/Archived.png)](https://github.com/newrelic/open-source-office/blob/master/examples/categories/index.md#archived) 2 | 3 | # New Relic Platform - .NET SDK # 4 | 5 | ## Requirements ## 6 | 7 | * .NET Client Profile >= 3.5 8 | * New Relic account on http://newrelic.com 9 | * Something to monitor 10 | 11 | ## Get Started Writing a Platform Plugin ## 12 | 13 | The following will guide you through the process of writing a Platform plugin with our .NET SDK. For additional information, please refer to our getting started [documentation](http://newrelic.com/docs/platform/plugin-development). 14 | 15 | ### Key Concepts ### 16 | 17 | Core classes to be aware of in the SDK: 18 | 19 | * **Agent** - When writing a plugin, you will create a class that extends the SDK's abstract base Agent class and overrides several key methods such as `PollCycle()` and `GetAgentName()`. Each instance of an Agent should correspond to an instance of a thing you are monitoring (e.g. You may have a MSSQLAgent.cs file that extends Agent, so each instance of MSSQLAgent would represent a different MS SQL Server you are monitoring.) 20 | 21 | * **AgentFactory** - AgentFactory is an abstract base class that is meant to help facilitate creation of Agents from the well-defined configuration file 'plugin.json'. 22 | 23 | * **Runner** - Runner is the main entrypoint class for the SDK. Essentially you will create an instance of a Runner, assign either your programmatically configured Agents or your AgentFactory to it, and then call `SetupAndRun()` which will begin invoking the `PollCycle()` method on each of your Agents once per polling interval. 24 | 25 | * **Processors** - Processors are used to help with more complex metrics. For example, many metrics are simply scalar values (e.g. Number of database connections, bytes read, etc) but many times you care about the metric in relation to something else such as time. We provide processors (such as the EpochProcessor) to do the heavy lifting for you. That is, instead of just reporting 'Number of database connections' you can run that number through an EpochProcessor each poll interval and end up with 'Number of database connections per minute'. 26 | 27 | * **Logger** - The Logger class is provided to help you easily log data within your plugin in a consistent manner to the SDK. Simply create a new instance of a Logger using the `Logger.getLogger()` method and you will immediately be able provide robust logging to yourself and your plugin's consumers. 28 | 29 | ### Creating your Plugin ### 30 | 31 | * [Step 0 - Know what you want to monitor](#step-0--know-what-you-want-to-monitor) 32 | * [Step 1 - Create your Agent class](#step-1-create--your-agent-class) 33 | * [Step 2 - Initialize your Agent instances](#step-2--initialize-your-agent-instances) 34 | * [Step 3 - Setup your Agents with the Runner](#step-3--setup-your-agents-with-the-runner) 35 | * [Step 4 - Packaging and distribution](#step-4--packaging-and-distribution) 36 | 37 | #### Step 0 - Know what you want to monitor #### 38 | 39 | It is very important to consider what dashboards you'd like to show for whatever it is you are monitoring in the New Relic UI before you begin development. The reason for this is that metric names are very important when aggregating data in dashboards in the New Relic UI. For more information, check out the following documentation site on metric naming [here](https://docs.newrelic.com/docs/plugin-dev/metric-naming-reference). 40 | 41 | #### Step 1 - Create your Agent class #### 42 | 43 | Every plugin should have one class that extends the SDK's Agent class. This can be thought of as the blueprint for whatever it is you are monitoring (e.g. if you are creating a plugin to monitor MS SQL instances you should create a single MSSQLAgent class that extends from Agent). There are three distinct things that must be done for each of your Agent subclasses: 44 | 45 | * Implement abstract properties and overload constructor 46 | * Override the GetAgentName() method 47 | * Override the PollCycle() method 48 | 49 | ##### Implement abstract properties and overload constructor ##### 50 | 51 | The only requirement for this step is that implement two well-defined properties to the base SDK Agent class' constructor: 52 | 53 | * *GUID* - This is a string field representing the unique identifier for your plugin. We highly recommend using package notation (i.e. com.mycompany.pluginname) 54 | * *Version* - This is a string field representing the version of your plugin which should follow the [semantic versioning](http://semver.org/) scheme (e.g. "1.2.3") 55 | 56 | This is not a requirement, but we highly recommend overloading the constructor to take in any instance-specific fields to simplify Agent creation for Step 2. 57 | 58 | Example: 59 | 60 | ``` 61 | public override string Guid { get { return "com.yourorg.pluginname"; } } 62 | public override string Version { get { return "1.0.0"; } } 63 | 64 | public ExampleAgent(string name, string host, int port) 65 | { 66 | this.Name = name; 67 | this.Host = host; 68 | this.Port = port; 69 | } 70 | ``` 71 | 72 | 73 | ##### Override the GetAgentName() method ##### 74 | 75 | This method determines the name that will appear in the New Relic UI for a given instance. Continuing the MS SQL analogy, you would likely use the hostname of each MS SQL Server you are monitoring to make things easy to trace. 76 | 77 | Example: 78 | 79 | ``` 80 | private string name; 81 | 82 | public ExampleAgent(string name, /* other fields */) { 83 | this.Name = name; 84 | 85 | // other initialization 86 | } 87 | 88 | @Override 89 | public string GetAgentName() { 90 | return this.Name; 91 | } 92 | ``` 93 | 94 | ##### Override the PollCycle() method ##### 95 | 96 | This method will be invoked once for each Agent per polling interval while your plugin is running. This is where your logic to gather and report metric values should live. For each metric value you'd like to report, simply call the `ReportMetric()` method within your `PollCycle()` code. After each Agent's `PollCycle()` method has been invoked all reported metrics will be sent to the New Relic service in a REST request. 97 | 98 | The `ReportMetric()` method signature is the following: 99 | 100 | ``` 101 | /// 102 | /// Registers a metric for this poll cycle that will be sent when all Agents complete their PollCycle. 103 | /// 104 | /// The name of the metric 105 | /// The units of the metric 106 | /// The value of the metric 107 | public void ReportMetric(string metricName, string units, float value) 108 | ``` 109 | 110 | Example: 111 | 112 | ``` 113 | // Initialize processors for each metric that is processed over time 114 | private IProcessor connectionsProcessor = new EpochProcessor();; 115 | private IProcessor bytesReadProcessor = new EpochProcessor();; 116 | 117 | … 118 | 119 | @Override 120 | public void PollCycle() { 121 | int numConnections = getNumConnections(); 122 | int bytesRead = getNumberBytesRead(); 123 | 124 | // Report two metrics for each value from the server, 125 | // One for the plain scalar value and one that processes the value over time 126 | ReportMetric("Connections/Count", "connections", numConnections); 127 | ReportMetric("Connections/Rate", "connections/sec", connectionsProcessor.Process(numConnections)); 128 | 129 | ReportMetric("BytesRead/Count", "bytes", bytesRead); 130 | ReportMetric("BytesRead/Rate", "bytes/sec", bytesReadProcessor.Process(bytesRead)); 131 | } 132 | ``` 133 | 134 | That's it, your Agent class is ready, now all you need to do is initialize them and set them up to run with the Runner! 135 | 136 | #### Step 2 - Initialize your Agent instances #### 137 | 138 | A plugin's value comes from the ability to dynamically configure instance information so it can be reused by others to monitor their infrastructure without code changes. This requires instance data to come from a configuration file, which was traditionally up to the plugin developer to define. In version 2 and later of the SDK, this information has been standardized into the 'plugin.json' file. 139 | 140 | ##### Working with the 'plugin.json' file ##### 141 | 142 | The 'plugin.json' (located in the '.\config' directory) file is used by all Platform Plugins to configure instance-specific information such as the hostnames you are going to monitor or the user/password combos to access them. The file is standard JSON and only requires that you have a root-level property named "agents" that contains an array of objects. Each object in that array should correspond to an instance of something you are monitoring. Essentially each JSON object in the "agents" array should have all the fields necessary to initialize an instance of your Agent class created in Step 1. You can pass whatever fields or create as many Agents as you'd like here. 143 | 144 | Example 'plugin.json' file: 145 | 146 | ``` 147 | { 148 | "agents": [ 149 | { 150 | "name" : "AgentName1", 151 | "host" : "host1.com", 152 | "port" : 8080 153 | }, 154 | { 155 | "name" : "AgentName2", 156 | "host" : "host2.com", 157 | "port" : 8081 158 | }, 159 | ... 160 | ] 161 | } 162 | ``` 163 | ##### Creating an AgentFactory class ##### 164 | 165 | Since all plugins require JSON configuration, it would be annoying for every plugin developer to have to rewrite the logic to parse a JSON file and initialize their Agents, so instead the SDK provides an AgentFactory class that you can use to automatically turn your JSON configuration into initialized Agents. Simply extend the AgentFactory class and override the `CreateConfiguredAgent()` method. When the SDK first starts, it will parse the 'plugin.json' file for you and map the JSON options into a dictionary of strings to objects that you can then use to initialize and return an instance of your Agent. 166 | 167 | Example: 168 | 169 | ``` 170 | public override Agent CreateAgentWithConfiguration(IDictionary properties) 171 | { 172 | string name = (string)properties["name"]; 173 | string host = (string)properties["host"]; 174 | int port = (int)properties["port"]; 175 | 176 | return new ExampleAgent(name, host, port); 177 | } 178 | ``` 179 | 180 | #### Step 3 - Set up your Agents with the Runner #### 181 | 182 | You're almost there! The last coding step is to create an instance of the Runner class and pass it your AgentFactory class. This part is very simple, and is best demonstrated through a code example (since most plugins should be identical for this step): 183 | 184 | ``` 185 | class Program 186 | { 187 | static int Main(string[] args) 188 | { 189 | try 190 | { 191 | Runner runner = new Runner(); 192 | 193 | runner.Add(new ExampleAgentFactory()); 194 | 195 | runner.SetupAndRun(); 196 | } 197 | catch (Exception e) 198 | { 199 | Console.WriteLine("Exception occurred, unable to continue.\n", e.Message); 200 | return -1; 201 | } 202 | 203 | return 0; 204 | } 205 | } 206 | ``` 207 | 208 | #### Step 4 - Packaging and distribution #### 209 | 210 | Traditionally plugin developers were responsible for deciding how to distribute their plugin. Everything from what compression format should be used to where should configuration files be located was up to each individual plugin author. This created a serious problem for plugin consumers since every new piece of infrastructure that they wanted to monitor required reading a lot of documentation around how to set that particular plugin up. Enter the New Relic Platform Installer (NPI) tool, a simple, light-weight command line utility that allows someone to easily download, configure and manage plugins. (Read more [here](https://discuss.newrelic.com/category/platform-plugins/platform-installer)). 211 | 212 | In order to make your plugin NPI-compatible simply ensure the following: 213 | 214 | * Your plugin was written with the .NET SDK. 215 | * Your plugin is packaged using the tar.gz compression protocol. 216 | * Your executable program is named 'plugin.exe' and is located in the root of your plugin folder. 217 | * Plugin configuration is read from 'plugin.json' file in the '.\config'. 218 | * You do not use any relative references in your code. 219 | * Your plugin contains a 'plugin.template.json' and a 'newrelic.template.json' file in the '.\config' directory. 220 | 221 | Once your plugin is NPI-compatible from a code perspective, place it somewhere that is accessible for consumers to download. Most customers add the compressed distributable to a 'dist' folder in their GitHub repository. From there, go through our publisher flow and be sure to mark your plugin for "NPI" distribution. 222 | 223 | #### Next Steps #### 224 | 225 | That's it, you've written your first plugin. If anything wasn't clear or you'd like a more in-depth code example, check out our [example Wikipedia plugin](https://github.com/newrelic-platform/newrelic_java_wikipedia_plugin). 226 | 227 | Once you're comfortable with the plugin and you're ready to set up some dashboards check out [this](https://docs.newrelic.com/docs/plugin-dev/publishing-your-plugin) link for a detailed look at the publishing process. 228 | 229 | ## Configuration Options ## 230 | 231 | All plugins have two configuration files. One that is standard across all plugins containing information like New Relic License Key, logging information, or proxy settings, and one that is for plugin-specific configuration options. These configuration files both live within the `config` directory of a plugin. 232 | 233 | ### newrelic.json ### 234 | 235 | The `newrelic.json` configuration file is where New Relic specific configuration lives. 236 | 237 | Example: 238 | 239 | ``` 240 | { 241 | "license_key": "NEW_RELIC_LICENSE_KEY" 242 | } 243 | ``` 244 | 245 | #### Config Options #### 246 | 247 | `license_key` - _(required)_ the New Relic license key 248 | 249 | `log_level` - _(optional)_ the log level. Valid values: `debug`, `info`, `warn`, `error`, `fatal`. Defaults to `info`. 250 | 251 | `log_file_name` - _(optional)_ the log file name. Defaults to `newrelic_plugin.log`. 252 | 253 | `log_file_path` - _(optional)_ the log file path. Defaults to `logs`. 254 | 255 | `log_limit_in_kbytes` - _(optional)_ the log file limit in kilobytes. Defaults to `25600` (25 MB). If limit is set to `0`, the log file size would not be limited. 256 | 257 | `proxy_host` - _(optional)_ the proxy host. Ex. `webcache.example.com` 258 | 259 | `proxy_port` - _(optional)_ the proxy port. Ex. `8080`. Defaults to `80` if a `proxy_host` is set. 260 | 261 | `proxy_username` - _(optional)_ the proxy username 262 | 263 | `proxy_password` - _(optional)_ the proxy password 264 | 265 | ### plugin.json ### 266 | 267 | The `plugin.json` configuration file is where plugin specific configuration lives. A registered `AgentFactory` will receive a map of key-value pairs from within the `agents` JSON section. 268 | 269 | Example: 270 | 271 | ``` 272 | { 273 | "agents": [ 274 | { 275 | "name" : "Localhost", 276 | "host" : "localhost", 277 | "user" : "username", 278 | "password" : "password", 279 | "timeout" : 5, 280 | "multiplier" : 1.5 281 | } 282 | ], 283 | "categories": { 284 | "big": [1, 2, 3], 285 | "enabled": false 286 | } 287 | } 288 | ``` 289 | 290 | ## Logging ## 291 | 292 | The SDK provides a simple logging framework that will log to both the console and to a configurable logging file. The logging configuration is managed through the `newrelic.json` file and the available options are outlined above in the [Config Options](#config-options) section. 293 | 294 | Example configuration: 295 | 296 | ``` 297 | { 298 | "log_level": "debug", 299 | "log_file_name": "newrelic_plugin.log", 300 | "log_file_path": "./path/to/logs/newrelic", 301 | "log_limit_in_kbytes": 1024 302 | } 303 | ``` 304 | 305 | **Note:** All logging configuration options are optional. 306 | 307 | Example usage: 308 | 309 | ``` 310 | using NewRelic.Platform.Sdk.Utils.Logger; 311 | ... 312 | private static Logger s_log = Logger.GetLogger("ExampleClassName"); 313 | ... 314 | s_log.Debug("debug message"); 315 | s_log.Info("info message", "\tsecond message"); 316 | s_log.Error(new RuntimeException(), "error!"); 317 | ... 318 | ``` 319 | 320 | For better visibility in logging, it is recommended to create one static `Logger` instance per class and reuse it. 321 | 322 | ## Support ## 323 | 324 | Reach out to us at 325 | [support.newrelic.com](http://support.newrelic.com/). 326 | There you'll find documentation, FAQs, and forums where you can submit 327 | suggestions and discuss with staff and other users. 328 | 329 | Also available is community support on IRC: we generally use #newrelic 330 | on irc.freenode.net 331 | 332 | Find a bug? E-mail support @ New Relic, or post it to [support.newrelic.com](http://support.newrelic.com/). 333 | 334 | Thank you! 335 | 336 | ## Contributing 337 | 338 | You are welcome to send pull requests to us - however, by doing so you agree that you are granting New Relic a non-exclusive, non-revokable, no-cost license to use the code, algorithms, patents, and ideas in that code in our products if we so choose. You also agree the code is provided as-is and you provide no warranties as to its fitness or correctness for any purpose. 339 | -------------------------------------------------------------------------------- /dist/newrelic_platform_dotnet_sdk_v1.0.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_dotnet_sdk/cd82eaafeaccbd230f96ee679eabdb7a9bfa26cc/dist/newrelic_platform_dotnet_sdk_v1.0.0.0.zip -------------------------------------------------------------------------------- /dist/newrelic_platform_dotnet_sdk_v1.0.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_dotnet_sdk/cd82eaafeaccbd230f96ee679eabdb7a9bfa26cc/dist/newrelic_platform_dotnet_sdk_v1.0.0.1.zip --------------------------------------------------------------------------------