├── .gitattributes ├── .gitignore ├── .nuget ├── nuget.config └── packages.config ├── CHANGELOG.md ├── LICENSE ├── Metrics.InfluxDB.sln ├── Publishing ├── Metrics.NET.InfluxDB.nuspec └── push-nuget.bat ├── README.md ├── SharedAssemblyInfo.cs ├── Src ├── Metrics.InfluxDB.SamplesConsole │ ├── App.config │ ├── Metrics.InfluxDB.SamplesConsole.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── Metrics.InfluxDB.Tests │ ├── InfluxdbTestUtils.cs │ ├── InfluxdbTests.cs │ ├── Metrics.InfluxDB.Tests.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config └── Metrics.InfluxDB │ ├── Adapters │ ├── InfluxdbConverter.cs │ ├── InfluxdbFormatter.cs │ ├── InfluxdbHttpWriter.cs │ ├── InfluxdbJsonWriter.cs │ ├── InfluxdbUdpWriter.cs │ └── InfluxdbWriter.cs │ ├── InfluxdbBaseReport.cs │ ├── InfluxdbConfigExtensions.cs │ ├── InfluxdbHttpReport.cs │ ├── InfluxdbJsonReport.cs │ ├── InfluxdbReport.cs │ ├── InfluxdbUdpReport.cs │ ├── Metrics.InfluxDB.csproj │ ├── Model │ ├── InfluxBatch.cs │ ├── InfluxConfig.cs │ ├── InfluxField.cs │ ├── InfluxLineProtocol.cs │ ├── InfluxPrecision.cs │ ├── InfluxRecord.cs │ ├── InfluxTag.cs │ └── InfluxUtils.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── packages.config ├── build.bat ├── build.sh └── create-nuget.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.cs diff=csharp 3 | *.sln merge=union 4 | *.csproj merge=union 5 | *.vbproj merge=union 6 | *.fsproj merge=union 7 | *.dbproj merge=union 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | Docs 4 | Publishing/lib 5 | *.nupkg 6 | .nuget/nuget.exe 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.sln.docstates 12 | *.userprefs 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | x64/ 18 | build/ 19 | bld/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | #NUNIT 28 | *.VisualState.xml 29 | TestResult.xml 30 | 31 | # Build Results of an ATL Project 32 | [Dd]ebugPS/ 33 | [Rr]eleasePS/ 34 | dlldata.c 35 | 36 | *_i.c 37 | *_p.c 38 | *_i.h 39 | *.ilk 40 | *.meta 41 | *.obj 42 | *.pch 43 | *.pdb 44 | *.pgc 45 | *.pgd 46 | *.rsp 47 | *.sbr 48 | *.tlb 49 | *.tli 50 | *.tlh 51 | *.tmp 52 | *.tmp_proj 53 | *.log 54 | *.vspscc 55 | *.vssscc 56 | .builds 57 | *.pidb 58 | *.svclog 59 | *.scc 60 | 61 | # Chutzpah Test files 62 | _Chutzpah* 63 | 64 | # Visual C++ cache files 65 | ipch/ 66 | *.aps 67 | *.ncb 68 | *.opensdf 69 | *.sdf 70 | *.cachefile 71 | 72 | # Visual Studio profiler 73 | *.psess 74 | *.vsp 75 | *.vspx 76 | 77 | # TFS 2012 Local Workspace 78 | $tf/ 79 | 80 | # Guidance Automation Toolkit 81 | *.gpState 82 | 83 | # ReSharper is a .NET coding add-in 84 | _ReSharper*/ 85 | *.[Rr]e[Ss]harper 86 | *.DotSettings.user 87 | 88 | # JustCode is a .NET coding addin-in 89 | .JustCode 90 | 91 | # TeamCity is a build add-in 92 | _TeamCity* 93 | 94 | # DotCover is a Code Coverage Tool 95 | *.dotCover 96 | 97 | # NCrunch 98 | *.ncrunch* 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | 129 | # NuGet Packages Directory 130 | packages/ 131 | ## TODO: If the tool you use requires repositories.config uncomment the next line 132 | #!packages/repositories.config 133 | 134 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 135 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 136 | !packages/build/ 137 | 138 | # Windows Azure Build Output 139 | csx/ 140 | *.build.csdef 141 | 142 | # Windows Store app package directory 143 | AppPackages/ 144 | 145 | # Others 146 | sql/ 147 | *.Cache 148 | ClientBin/ 149 | [Ss]tyle[Cc]op.* 150 | ~$* 151 | *~ 152 | *.dbmdl 153 | *.dbproj.schemaview 154 | *.pfx 155 | *.publishsettings 156 | node_modules/ 157 | 158 | # RIA/Silverlight projects 159 | Generated_Code/ 160 | 161 | # Backup & report files from converting an old project file to a newer 162 | # Visual Studio version. Backup files are not needed, because we have git ;-) 163 | _UpgradeReport_Files/ 164 | Backup*/ 165 | UpgradeLog*.XML 166 | UpgradeLog*.htm 167 | 168 | # SQL Server files 169 | *.mdf 170 | *.ldf 171 | 172 | # Business Intelligence projects 173 | *.rdl.data 174 | *.bim.layout 175 | *.bim_*.settings 176 | 177 | # Microsoft Fakes 178 | FakesAssemblies/ 179 | -------------------------------------------------------------------------------- /.nuget/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.5.0 / 2017-07-05 (0.5.0-pre / 2017-02-04) 2 | * separation from the core Metrics.NET library. For changes previous to this version, see the changelog of the core library. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Jason O'Bryan, The Recognos Metrics.NET Team 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Metrics.InfluxDB.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metrics.InfluxDB", "Src\Metrics.InfluxDB\Metrics.InfluxDB.csproj", "{1B6C2147-30DB-4C58-AD92-5FD34937F9A4}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metrics.InfluxDB.Tests", "Src\Metrics.InfluxDB.Tests\Metrics.InfluxDB.Tests.csproj", "{1D9413B0-4B25-48AC-9298-554284D409DE}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1555B234-F26A-4035-B319-22B968388198}" 11 | ProjectSection(SolutionItems) = preProject 12 | .nuget\nuget.config = .nuget\nuget.config 13 | .nuget\packages.config = .nuget\packages.config 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{6FC413CE-2AD2-4A89-A11E-B8A34112DFEA}" 17 | ProjectSection(SolutionItems) = preProject 18 | build.bat = build.bat 19 | build.sh = build.sh 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Publishing", "Publishing", "{5812CC1A-41DB-4BAA-A7B2-03318A9C6BA2}" 23 | ProjectSection(SolutionItems) = preProject 24 | create-nuget.bat = create-nuget.bat 25 | Publishing\Metrics.NET.InfluxDB.nuspec = Publishing\Metrics.NET.InfluxDB.nuspec 26 | Publishing\push-nuget.bat = Publishing\push-nuget.bat 27 | EndProjectSection 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EDB9E6D3-9939-4F18-A2F5-05C2363B9D72}" 30 | ProjectSection(SolutionItems) = preProject 31 | CHANGELOG.md = CHANGELOG.md 32 | LICENSE = LICENSE 33 | README.md = README.md 34 | SharedAssemblyInfo.cs = SharedAssemblyInfo.cs 35 | EndProjectSection 36 | EndProject 37 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metrics.InfluxDB.SamplesConsole", "Src\Metrics.InfluxDB.SamplesConsole\Metrics.InfluxDB.SamplesConsole.csproj", "{3D15CA8C-06EA-4302-829D-A201871DB1FC}" 38 | EndProject 39 | Global 40 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 41 | Debug|Any CPU = Debug|Any CPU 42 | Release|Any CPU = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 45 | {1B6C2147-30DB-4C58-AD92-5FD34937F9A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {1B6C2147-30DB-4C58-AD92-5FD34937F9A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {1B6C2147-30DB-4C58-AD92-5FD34937F9A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {1B6C2147-30DB-4C58-AD92-5FD34937F9A4}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {1D9413B0-4B25-48AC-9298-554284D409DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {1D9413B0-4B25-48AC-9298-554284D409DE}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {1D9413B0-4B25-48AC-9298-554284D409DE}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {1D9413B0-4B25-48AC-9298-554284D409DE}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {3D15CA8C-06EA-4302-829D-A201871DB1FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {3D15CA8C-06EA-4302-829D-A201871DB1FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {3D15CA8C-06EA-4302-829D-A201871DB1FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {3D15CA8C-06EA-4302-829D-A201871DB1FC}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /Publishing/Metrics.NET.InfluxDB.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metrics.NET.InfluxDbReporting 5 | 0.5.0 6 | Metrics.NET.InfluxDbReporting 7 | Jason O'Bryan, The Recognos Metrics.NET Team 8 | Jason O'Bryan, The Recognos Metrics.NET Team 9 | https://github.com/Recognos/Metrics.NET.InfluxDB/blob/master/LICENSE 10 | https://github.com/Recognos/Metrics.NET.InfluxDB 11 | http://www.erata.net/Metrics.NET/metrics_32.png 12 | false 13 | This library provides support for InfluxDB reporters in the Metrics.NET monitoring and reporting library. Supports InfluxDB Line Protocol for InfluxDB v0.9.1 and above, and JSON Protocol for InfluxDB v0.9.1 and below. 14 | InfluxDB reporter integration for the Metrics.NET monitoring and reporting library. 15 | Jason O'Bryan, The Recognos Metrics.NET Team 16 | influxdb, influx, metrics, measurements, charts, timer, histogram, duration, health checks 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Publishing/push-nuget.bat: -------------------------------------------------------------------------------- 1 | ..\.nuget\nuget.exe push Metrics.NET.InfluxDbReporting.0.5.0.nupkg -Source https://www.nuget.org -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metrics.NET.InfluxDB 2 | 3 | InfluxDB reporter integration for Metrics.NET. 4 | 5 | Current version: **v0.5.0-pre** 6 | 7 | This library provides support for InfluxDB reporters using the [Metrics.NET](https://github.com/Recognos/Metrics.NET) monitoring and reporting library. 8 | 9 | Supports InfluxDB Line Protocol for InfluxDB v0.9.1 and above. 10 | Supports InfluxDB JSON Protocol for InfluxDB v0.9.1 and below. 11 | 12 | The JSON protocol, which was previously used by the InfluxDB reporter, has been [deprecated](https://docs.influxdata.com/influxdb/v0.13/write_protocols/json/) and removed in InfluxDB versions greater than v0.9.1. The expected way to write metric data to InfluxDB after this point is by using the [LineProtocol syntax](https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/) defined in the InfluxDB docs. 13 | 14 | This library adds support for the newer versions of InfluxDB which use the line protocol syntax and supports both the HTTP and UDP protocols. 15 | 16 | More documentation is availale in the [wiki](https://github.com/Recognos/Metrics.NET.InfluxDB/wiki). 17 | 18 | [Changelog](https://github.com/Recognos/Metrics.NET.InfluxDB/blob/master/CHANGELOG.md) 19 | 20 | [Metrics.NET Repository](https://github.com/Recognos/Metrics.NET) 21 | 22 | ## Configuration 23 | 24 | Configuration can be done using the different overloads of the `MetricsReports` configuration object. For example, the following code will add an InfluxDB reporter using the HTTP protocol for a database named `testdb` on host `10.0.10.24:80`: 25 | ``` 26 | Metric.Config 27 | .WithReporting(report => report 28 | .WithInfluxDbHttp("10.0.10.24", "testdb", reportInterval, null, c => c 29 | .WithConverter(new DefaultConverter().WithGlobalTags("host=web1,env=dev")) 30 | .WithFormatter(new DefaultFormatter().WithLowercase(true)) 31 | .WithWriter(new InfluxdbHttpWriter(c, 1000)))); 32 | ``` 33 | 34 | ## Extensibility 35 | 36 | The InfluxDB report has been refactored to separate the writing process into a separate `InfluxdbWriter` which is responsible for writing the data using the whichever protocol is chosen. This also allows extending the model to support other or future types of protocols and formats defined by InfluxDB. 37 | 38 | Writing data to InfluxDB is done in 3 different and extendable steps: 39 | - `InfluxdbConverter`: Converts the metric data into `InfluxRecords`, which are the data model object for InfluxDB datapoints. 40 | - `InfluxdbFormatter`: Formats the metric name and tag/field names used in the InfluxDB tables. For example, this can convert all names to lowercase or replace any spaces with underscores. 41 | - `InfluxdbWriter`: Writes the `InfluxRecords` to the database. Derived implementations exist for the HTTP and UDP protocols. 42 | 43 | ## Building 44 | 45 | _NOTE: All build scripts must be run from the repository root._ 46 | 47 | Run `build.bat` to compile and test the `Metrics.InfluxDB.dll` assmebly. Output gets copied to the `.\bin\` directory. 48 | 49 | Run `create-nuget.bat` to build the solution and create a nuget `.nupkg` package in the `.\Publishing\` directory. 50 | 51 | ## License 52 | 53 | This library will keep the same license as the main [Metrics.NET project](https://github.com/Recognos/Metrics.NET). 54 | 55 | The main metrics project is released under the terms: 56 | Copyright (c) 2016 The Recognos Metrics.NET Team 57 | Published under Apache Software License 2.0, see [LICENSE](https://github.com/Recognos/Metrics.NET/blob/master/LICENSE) 58 | 59 | This library (Metrics.NET.InfluxDB) is released under the Apache 2.0 License (see [LICENSE](https://github.com/Recognos/Metrics.NET.InfluxDB/blob/master/LICENSE)) 60 | Copyright (c) 2016 Jason O'Bryan, The Recognos Metrics.NET Team 61 | -------------------------------------------------------------------------------- /SharedAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyConfiguration("")] 5 | [assembly: AssemblyCompany("The Recognos Metrics.NET Team")] 6 | [assembly: AssemblyProduct("Metrics.NET")] 7 | [assembly: AssemblyCopyright("Copyright Jason O'Bryan, The Recognos Metrics.NET Team © 2017")] 8 | [assembly: ComVisible(false)] 9 | [assembly: AssemblyVersion("0.5.0")] 10 | [assembly: AssemblyFileVersion("0.5.0")] 11 | [assembly: AssemblyInformationalVersion("0.5.0")] -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.SamplesConsole/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.SamplesConsole/Metrics.InfluxDB.SamplesConsole.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3D15CA8C-06EA-4302-829D-A201871DB1FC} 8 | Exe 9 | Properties 10 | Metrics.InfluxDB.SamplesConsole 11 | Metrics.InfluxDB.SamplesConsole 12 | v4.5 13 | 512 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | ..\..\bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | ..\..\bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\packages\Metrics.NET.0.3.7\lib\net45\Metrics.dll 39 | True 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {1b6c2147-30db-4c58-ad92-5fd34937f9a4} 61 | Metrics.InfluxDB 62 | 63 | 64 | 65 | 72 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.SamplesConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Metrics.InfluxDB.SamplesConsole 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | Metric.Config 10 | .WithReporting(config => config 11 | .WithInfluxDbHttp(new Uri(""), TimeSpan.FromSeconds(10))); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.SamplesConsole/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("Metrics.InfluxDB.SamplesConsole")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Metrics.InfluxDB.SamplesConsole")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. 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("3d15ca8c-06ea-4302-829d-a201871db1fc")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.SamplesConsole/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.Tests/InfluxdbTestUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | using Metrics.InfluxDB.Adapters; 6 | using Metrics.InfluxDB.Model; 7 | 8 | namespace Metrics.InfluxDB.Tests 9 | { 10 | /// 11 | /// Defines a test case for an . 12 | /// 13 | public class TagTestCase 14 | { 15 | public InfluxTag Tag { get; set; } 16 | public String Output { get; set; } 17 | 18 | public TagTestCase(String key, String value, String output) 19 | : this(new InfluxTag(key, value), output) { 20 | } 21 | 22 | public TagTestCase(InfluxTag tag, String output) { 23 | Tag = tag; 24 | Output = output; 25 | } 26 | 27 | public Object[] ToArray() { return this; } 28 | 29 | public static implicit operator Object[] (TagTestCase item) { 30 | return new Object[] { item.Tag, item.Output }; 31 | } 32 | } 33 | 34 | /// 35 | /// Defines a test case for an . 36 | /// 37 | public class FieldTestCase 38 | { 39 | public InfluxField Field { get; set; } 40 | public String Output { get; set; } 41 | 42 | public FieldTestCase(String key, Object value, String output) 43 | : this(new InfluxField(key, value), output) { 44 | } 45 | 46 | public FieldTestCase(InfluxField field, String output) { 47 | Field = field; 48 | Output = output; 49 | } 50 | 51 | public Object[] ToArray() { return this; } 52 | 53 | public static implicit operator Object[] (FieldTestCase item) { 54 | return new Object[] { item.Field, item.Output }; 55 | } 56 | } 57 | 58 | 59 | /// 60 | /// An implementation used for unit testing. This writer keeps a list of all batches flushed to the writer. 61 | /// 62 | public class InfluxdbTestWriter : InfluxdbLineWriter 63 | { 64 | /// 65 | /// The list of all batches flushed by the writer. 66 | /// 67 | public List FlushHistory { get; } = new List(); 68 | 69 | /// 70 | /// A copy of the last batch that was flushed by the writer. 71 | /// 72 | public InfluxBatch LastBatch { get; private set; } = new InfluxBatch(); 73 | 74 | /// 75 | /// Creates a new with the specified configuration and batch size. 76 | /// 77 | /// The InfluxDB configuration. 78 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 79 | public InfluxdbTestWriter(InfluxConfig config, Int32 batchSize = 0) 80 | : base(config, batchSize) { 81 | } 82 | 83 | 84 | protected override Byte[] WriteToTransport(Byte[] bytes) { 85 | var lastBatch = LastBatch = new InfluxBatch(Batch.ToArray()); 86 | FlushHistory.Add(lastBatch); 87 | return null; 88 | } 89 | } 90 | 91 | /// 92 | /// An implementation used for unit testing. This writer keeps a list of all batches flushed to the writer. 93 | /// 94 | public class InfluxdbHttpWriterExt : InfluxdbHttpWriter 95 | { 96 | /// 97 | /// The list of all batches flushed by the writer. 98 | /// 99 | public List FlushHistory { get; } = new List(); 100 | 101 | /// 102 | /// A copy of the last batch that was flushed by the writer. 103 | /// 104 | public InfluxBatch LastBatch { get; private set; } = new InfluxBatch(); 105 | 106 | 107 | /// 108 | /// Creates a new with the specified URI. 109 | /// 110 | /// The HTTP URI of the InfluxDB server. 111 | public InfluxdbHttpWriterExt(Uri influxDbUri) 112 | : base(influxDbUri) { 113 | } 114 | 115 | /// 116 | /// Creates a new with the specified URI. 117 | /// 118 | /// The InfluxDB configuration. 119 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 120 | public InfluxdbHttpWriterExt(InfluxConfig config, Int32 batchSize = 0) 121 | : base(config, batchSize) { 122 | } 123 | 124 | 125 | protected override Byte[] WriteToTransport(Byte[] bytes) { 126 | var lastBatch = LastBatch = new InfluxBatch(Batch.ToArray()); 127 | FlushHistory.Add(lastBatch); 128 | 129 | Debug.WriteLine($"[HTTP] InfluxDB LineProtocol Write (count={lastBatch.Count} bytes={formatSize(bytes.Length)})"); 130 | Stopwatch sw = Stopwatch.StartNew(); 131 | Byte[] res = base.WriteToTransport(bytes); 132 | Debug.WriteLine($"[HTTP] Uploaded {lastBatch.Count} measurements to InfluxDB in {sw.ElapsedMilliseconds:n0}ms. :: Bytes written: {formatSize(bytes.Length)} - Response string ({formatSize(res.Length)}): {Encoding.UTF8.GetString(res)}"); 133 | return res; 134 | } 135 | } 136 | 137 | /// 138 | /// An implementation used for unit testing. This writer keeps a list of all batches flushed to the writer. 139 | /// 140 | public class InfluxdbUdpWriterExt : InfluxdbUdpWriter 141 | { 142 | /// 143 | /// The list of all batches flushed by the writer. 144 | /// 145 | public List FlushHistory { get; } = new List(); 146 | 147 | /// 148 | /// A copy of the last batch that was flushed by the writer. 149 | /// 150 | public InfluxBatch LastBatch { get; private set; } = new InfluxBatch(); 151 | 152 | 153 | /// 154 | /// Creates a new with the specified URI. 155 | /// 156 | /// The UDP URI of the InfluxDB server. 157 | public InfluxdbUdpWriterExt(Uri influxDbUri) 158 | : base(influxDbUri) { 159 | } 160 | 161 | /// 162 | /// Creates a new with the specified URI. 163 | /// 164 | /// The InfluxDB configuration. 165 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 166 | public InfluxdbUdpWriterExt(InfluxConfig config, Int32 batchSize = 0) 167 | : base(config, batchSize) { 168 | } 169 | 170 | 171 | protected override Byte[] WriteToTransport(Byte[] bytes) { 172 | var lastBatch = LastBatch = new InfluxBatch(Batch.ToArray()); 173 | FlushHistory.Add(lastBatch); 174 | 175 | Debug.WriteLine($"[UDP] InfluxDB LineProtocol Write (count={lastBatch.Count} bytes={formatSize(bytes.Length)})"); 176 | Stopwatch sw = Stopwatch.StartNew(); 177 | Byte[] res = base.WriteToTransport(bytes); 178 | Debug.WriteLine($"[UDP] Uploaded {lastBatch.Count} measurements to InfluxDB in {sw.ElapsedMilliseconds:n0}ms. :: Bytes written: {formatSize(bytes.Length)} - Response string ({formatSize(res.Length)}): {Encoding.UTF8.GetString(res)}"); 179 | return res; 180 | } 181 | } 182 | 183 | /// 184 | /// An implementation used for unit testing. This writer keeps a list of all batches flushed to the writer. 185 | /// 186 | public class InfluxdbJsonWriterExt : InfluxdbJsonWriter 187 | { 188 | /// 189 | /// The list of all batches flushed by the writer. 190 | /// 191 | public List FlushHistory { get; } = new List(); 192 | 193 | /// 194 | /// A copy of the last batch that was flushed by the writer. 195 | /// 196 | public InfluxBatch LastBatch { get; private set; } = new InfluxBatch(); 197 | 198 | 199 | /// 200 | /// Creates a new with the specified URI. 201 | /// 202 | /// The JSON URI of the InfluxDB server. 203 | public InfluxdbJsonWriterExt(Uri influxDbUri) 204 | : base(influxDbUri) { 205 | } 206 | 207 | /// 208 | /// Creates a new with the specified URI. 209 | /// 210 | /// The InfluxDB configuration. 211 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 212 | public InfluxdbJsonWriterExt(InfluxConfig config, Int32 batchSize = 0) 213 | : base(config, batchSize) { 214 | } 215 | 216 | 217 | protected override Byte[] WriteToTransport(Byte[] bytes) { 218 | var lastBatch = LastBatch = new InfluxBatch(Batch.ToArray()); 219 | FlushHistory.Add(lastBatch); 220 | 221 | Debug.WriteLine($"[JSON] InfluxDB LineProtocol Write (count={lastBatch.Count} bytes={formatSize(bytes.Length)})"); 222 | Stopwatch sw = Stopwatch.StartNew(); 223 | Byte[] res = base.WriteToTransport(bytes); 224 | Debug.WriteLine($"[JSON] Uploaded {lastBatch.Count} measurements to InfluxDB in {sw.ElapsedMilliseconds:n0}ms. :: Bytes written: {formatSize(bytes.Length)} - Response string ({formatSize(res.Length)}): {Encoding.UTF8.GetString(res)}"); 225 | return res; 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.Tests/InfluxdbTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using Metrics.InfluxDB; 6 | using Metrics.InfluxDB.Model; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace Metrics.InfluxDB.Tests 11 | { 12 | public class InfluxdbTests 13 | { 14 | 15 | [Fact] 16 | public void InfluxTag_CanParse_InvalidValueReturnsEmpty() { 17 | // invalid input strings 18 | InfluxTag empty = InfluxTag.Empty; 19 | String nullReason = "Because the input string should contain a single key and value separated by an equals sign."; 20 | InfluxUtils.ToInfluxTag("key").Should().Be(empty, nullReason); 21 | InfluxUtils.ToInfluxTag("key=").Should().Be(empty, nullReason); 22 | InfluxUtils.ToInfluxTag("=value").Should().Be(empty, nullReason); 23 | InfluxUtils.ToInfluxTag("key=value1=value2").Should().Be(empty, nullReason); 24 | InfluxUtils.ToInfluxTag("key,value").Should().Be(empty, nullReason); 25 | InfluxUtils.ToInfluxTag("key==").Should().Be(empty, nullReason); 26 | InfluxUtils.ToInfluxTag("==val").Should().Be(empty, nullReason); 27 | } 28 | 29 | [Fact] 30 | public void InfluxTag_CanParse_SingleFromString() { 31 | // valid input strings 32 | InfluxUtils.ToInfluxTag("key=value").Should().Be(new InfluxTag("key", "value")); 33 | InfluxUtils.ToInfluxTag("key with spaces=value with spaces").Should().Be(new InfluxTag("key with spaces", "value with spaces")); 34 | InfluxUtils.ToInfluxTag("key,with,commas=value,with,commas").Should().Be(new InfluxTag("key,with,commas", "value,with,commas")); 35 | InfluxUtils.ToInfluxTag("key\"with\"quot=value\"with\"quot").Should().Be(new InfluxTag("key\"with\"quot", "value\"with\"quot")); 36 | } 37 | 38 | [Fact] 39 | public void InfluxTag_CanParse_MultiFromCommaSeparatedString() { 40 | // comma-separated single string 41 | InfluxUtils.ToInfluxTags("key").Should().BeEmpty(); 42 | InfluxUtils.ToInfluxTags("key1=value1,key2=value2").Should().BeEquivalentTo(new InfluxTag("key1", "value1"), new InfluxTag("key2", "value2")); 43 | InfluxUtils.ToInfluxTags("key1,key2=value2,key3,key4").Should().BeEquivalentTo(new InfluxTag("key2", "value2")); 44 | } 45 | 46 | [Fact] 47 | public void InfluxTag_CanParse_MultiFromStringArray() { 48 | // string[] array 49 | InfluxUtils.ToInfluxTags("key1", "key2").Should().BeEmpty(); 50 | InfluxUtils.ToInfluxTags("key1=value1", "key2=value2").Should().BeEquivalentTo(new InfluxTag("key1", "value1"), new InfluxTag("key2", "value2")); 51 | InfluxUtils.ToInfluxTags("key1", "key2=value2", "key3", "key4").Should().BeEquivalentTo(new InfluxTag("key2", "value2")); 52 | } 53 | 54 | [Fact] 55 | public void InfluxTag_CanParse_FromSingleMetricTags() { 56 | InfluxUtils.ToInfluxTags(new MetricTags("key1", "key2")).Should().BeEmpty(); 57 | InfluxUtils.ToInfluxTags(new MetricTags("key1=value1", "key2")).Should().BeEquivalentTo(new InfluxTag("key1", "value1")); 58 | InfluxUtils.ToInfluxTags(new MetricTags("key1", "key2=value2")).Should().BeEquivalentTo(new InfluxTag("key2", "value2")); 59 | InfluxUtils.ToInfluxTags(new MetricTags("key1=value1", "key2=value2")).Should().BeEquivalentTo(new InfluxTag("key1", "value1"), new InfluxTag("key2", "value2")); 60 | } 61 | 62 | [Fact] 63 | public void InfluxTag_CanParse_FromMultiMetricTags() { 64 | InfluxUtils.ToInfluxTags(new MetricTags("key1", "key2"), new MetricTags("key3", "key4")).Should().BeEmpty(); 65 | InfluxUtils.ToInfluxTags(new MetricTags("key1=value1", "key2"), new MetricTags("key3=value3", "key4")).Should().BeEquivalentTo(new InfluxTag("key1", "value1"), new InfluxTag("key3", "value3")); 66 | InfluxUtils.ToInfluxTags(new MetricTags("key1", "key2=value2"), new MetricTags("key3", "key4=value4")).Should().BeEquivalentTo(new InfluxTag("key2", "value2"), new InfluxTag("key4", "value4")); 67 | InfluxUtils.ToInfluxTags(new MetricTags("key1=value1", "key2=value2"), new MetricTags("key3=value3", "key4=value4")).Should().BeEquivalentTo(new InfluxTag("key1", "value1"), new InfluxTag("key2", "value2"), new InfluxTag("key3", "value3"), new InfluxTag("key4", "value4")); 68 | } 69 | 70 | 71 | [Fact] 72 | public void InfluxField_SupportsValidValueTypes() { 73 | var validTypes = InfluxUtils.ValidValueTypes; 74 | foreach (var type in validTypes) 75 | InfluxUtils.IsValidValueType(type).Should().BeTrue(); 76 | } 77 | 78 | [Theory] 79 | [MemberData(nameof(TagTestCasesArray))] 80 | public void InfluxTag_FormatsTo_LineProtocol(InfluxTag tag, String output) { 81 | tag.ToLineProtocol().Should().Be(output); 82 | tag.ToString().Should().Be(output); 83 | } 84 | 85 | [Theory] 86 | [MemberData(nameof(FieldTestCasesArray))] 87 | public void InfluxField_FormatsTo_LineProtocol(InfluxField field, String output) { 88 | field.ToLineProtocol().Should().Be(output); 89 | field.ToString().Should().Be(output); 90 | } 91 | 92 | [Fact] 93 | public void InfluxRecord_FormatsTo_LineProtocol() { 94 | // test values 95 | var testNow = new DateTime(2016, 6, 1, 0, 0, 0, DateTimeKind.Utc); 96 | var testTags = TagTestCases.Select(tc => tc.Tag); 97 | var testFields = FieldTestCases.Select(tc => tc.Field); 98 | var precision = InfluxConfig.Default.Precision; 99 | 100 | // expected values 101 | String expTime = InfluxLineProtocol.FormatTimestamp(testNow, precision); 102 | String expTags = String.Join(",", TagTestCases.Select(tc => tc.Output)); 103 | String expFields = String.Join(",", FieldTestCases.Select(tc => tc.Output)); 104 | String expOutput = String.Format("test_name,{0} {1} {2}", expTags, expFields, expTime); 105 | 106 | // assert line values match expected 107 | new InfluxRecord("name spaces", new[] { new InfluxField("field1", 123456) }) 108 | .ToLineProtocol(precision).Should().Be(@"name\ spaces field1=123456i"); 109 | new InfluxRecord("test_name", new[] { new InfluxTag("tag1", "value1") }, new[] { new InfluxField("field1", 123456) }) 110 | .ToLineProtocol(precision).Should().Be(@"test_name,tag1=value1 field1=123456i"); 111 | new InfluxRecord("test_name", new[] { new InfluxTag("tag1", "value1"), new InfluxTag("tag2", "value2") }, new[] { new InfluxField("field1", 123456), new InfluxField("field2", true) }) 112 | .ToLineProtocol(precision).Should().Be(@"test_name,tag1=value1,tag2=value2 field1=123456i,field2=True"); 113 | new InfluxRecord("test_name", new[] { new InfluxTag("tag1", "value1") }, new[] { new InfluxField("field1", "test string") }, testNow) 114 | .ToLineProtocol(precision).Should().Be($@"test_name,tag1=value1 field1=""test string"" {expTime}"); 115 | new InfluxRecord("test_name", testTags, testFields, testNow) 116 | .ToLineProtocol(precision).Should().Be(expOutput); 117 | } 118 | 119 | [Fact] 120 | public void InfluxBatch_FormatsTo_LineProtocol() { 121 | var testNow = new DateTime(2016, 6, 1, 0, 0, 0, DateTimeKind.Utc); 122 | var testTags = TagTestCases.Select(tc => tc.Tag); 123 | var testFields = FieldTestCases.Select(tc => tc.Field); 124 | var precision = InfluxConfig.Default.Precision; 125 | var expTime = InfluxLineProtocol.FormatTimestamp(testNow, precision); 126 | 127 | // test with empty batch 128 | InfluxBatch batch = new InfluxBatch(); 129 | batch.ToLineProtocol(precision).Should().BeEmpty(); 130 | 131 | // test with single record 132 | batch.Add(new InfluxRecord("test_name", new[] { new InfluxTag("tag1", "value1") }, new[] { new InfluxField("field1", 123456) })); 133 | batch.ToLineProtocol(precision).Should().NotEndWith("\n").And.Be(@"test_name,tag1=value1 field1=123456i"); 134 | batch.Clear(); 135 | 136 | // test with multiple records 137 | batch.Add(new InfluxRecord("test_name1", new[] { new InfluxTag("tag1", "value1") }, new[] { new InfluxField("field1", 123456) })); 138 | batch.Add(new InfluxRecord("test_name2", new[] { new InfluxTag("tag2", "value2") }, new[] { new InfluxField("field2", 234561) })); 139 | batch.Add(new InfluxRecord("test_name3", new[] { new InfluxTag("tag3", "value3") }, new[] { new InfluxField("field3", 345612) }, testNow)); 140 | batch.Add(new InfluxRecord("test_name4", new[] { new InfluxTag("tag4", "value4") }, new[] { new InfluxField("field4", 456123) }, testNow)); 141 | batch.Add(new InfluxRecord("test_name5", new[] { new InfluxTag("tag5", "value5") }, new[] { new InfluxField("field5", 561234) }, testNow)); 142 | 143 | String expOutput = String.Join("\n", 144 | $@"test_name1,tag1=value1 field1=123456i", 145 | $@"test_name2,tag2=value2 field2=234561i", 146 | $@"test_name3,tag3=value3 field3=345612i {expTime}", 147 | $@"test_name4,tag4=value4 field4=456123i {expTime}", 148 | $@"test_name5,tag5=value5 field5=561234i {expTime}" 149 | ); 150 | 151 | batch.ToLineProtocol(precision).Should().NotEndWith("\n").And.Be(expOutput); 152 | } 153 | 154 | [Fact] 155 | public void InfluxReport_CanAddRecords_ForGauge() { 156 | var config = new InfluxConfig("localhost", "testdb"); 157 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 158 | var report = new InfluxdbHttpReport(config); 159 | var context = new DefaultMetricsContext("TestContext"); 160 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 161 | var metricsData = context.DataProvider.CurrentMetricsData; 162 | 163 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 164 | writer.LastBatch.Should().BeEmpty("Because running a report with no metrics should not result in any records."); 165 | 166 | context.Gauge("test_gauge", () => 123.456, Unit.Bytes, new MetricTags("key1=value1,tag2,tag3,key4=value4")); 167 | metricsData = context.DataProvider.CurrentMetricsData; 168 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 169 | writer.LastBatch.Should().HaveCount(1); 170 | 171 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 172 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"testcontext.test_gauge,key1=value1,key4=value4 value=123.456 {expTime}"); 173 | } 174 | 175 | [Fact] 176 | public void InfluxReport_CanAddRecords_ForCounter() { 177 | var config = new InfluxConfig("localhost", "testdb"); 178 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 179 | var report = new InfluxdbHttpReport(config); 180 | var context = new DefaultMetricsContext("TestContext"); 181 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 182 | var metricsData = context.DataProvider.CurrentMetricsData; 183 | var counter = context.Counter("test_counter", Unit.Bytes, new MetricTags("key1=value1,tag2,tag3,key4=value4")); 184 | 185 | // add normally 186 | counter.Increment(300); 187 | metricsData = context.DataProvider.CurrentMetricsData; 188 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 189 | writer.LastBatch.Should().HaveCount(1); 190 | 191 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 192 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"testcontext.test_counter,key1=value1,key4=value4 count=300i {expTime}"); 193 | 194 | // add with set item 195 | counter.Increment("item1,item2=ival2,item3=ival3", 100); 196 | metricsData = context.DataProvider.CurrentMetricsData; 197 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 198 | writer.LastBatch.Should().HaveCount(2); 199 | 200 | expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 201 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"testcontext.test_counter,key1=value1,key4=value4 count=400i {expTime}"); 202 | writer.LastBatch[1].ToLineProtocol(precision).Should().Be($@"testcontext.test_counter,item2=ival2,item3=ival3,key1=value1,key4=value4 count=100i,percent=25 {expTime}"); 203 | } 204 | 205 | [Fact] 206 | public void InfluxReport_CanAddRecords_ForMeter() { 207 | var config = new InfluxConfig("localhost", "testdb"); 208 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 209 | var report = new InfluxdbHttpReport(config); 210 | var context = new DefaultMetricsContext("TestContext"); 211 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 212 | var metricsData = context.DataProvider.CurrentMetricsData; 213 | var meter = context.Meter("test_meter", Unit.Bytes, TimeUnit.Seconds, new MetricTags("key1=value1,tag2,tag3,key4=value4")); 214 | 215 | // add normally 216 | meter.Mark(300); 217 | metricsData = context.DataProvider.CurrentMetricsData; 218 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 219 | writer.LastBatch.Should().HaveCount(1); 220 | 221 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 222 | writer.LastBatch[0].ToLineProtocol(precision).Should().StartWith($@"testcontext.test_meter,key1=value1,key4=value4 count=300i,mean_rate=").And.EndWith($@",1_min_rate=0,5_min_rate=0,15_min_rate=0 {expTime}"); ; 223 | 224 | // add with set item 225 | meter.Mark("item1,item2=ival2,item3=ival3", 100); 226 | metricsData = context.DataProvider.CurrentMetricsData; 227 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 228 | writer.LastBatch.Should().HaveCount(2); 229 | 230 | expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 231 | writer.LastBatch[0].ToLineProtocol(precision).Should().StartWith($@"testcontext.test_meter,key1=value1,key4=value4 count=400i,mean_rate=").And.EndWith($@",1_min_rate=0,5_min_rate=0,15_min_rate=0 {expTime}"); 232 | writer.LastBatch[1].ToLineProtocol(precision).Should().StartWith($@"testcontext.test_meter,item2=ival2,item3=ival3,key1=value1,key4=value4 count=100i,percent=25,mean_rate=").And.EndWith($@",1_min_rate=0,5_min_rate=0,15_min_rate=0 {expTime}"); 233 | } 234 | 235 | [Fact] 236 | public void InfluxReport_CanAddRecords_ForHistogram() { 237 | var config = new InfluxConfig("localhost", "testdb"); 238 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 239 | var report = new InfluxdbHttpReport(config); 240 | var context = new DefaultMetricsContext("TestContext"); 241 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 242 | var metricsData = context.DataProvider.CurrentMetricsData; 243 | var hist = context.Histogram("test_hist", Unit.Bytes, SamplingType.Default, new MetricTags("key1=value1,tag2,tag3,key4=value4")); 244 | 245 | // add normally 246 | hist.Update(300); 247 | metricsData = context.DataProvider.CurrentMetricsData; 248 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 249 | writer.LastBatch.Should().HaveCount(1); 250 | 251 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 252 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"testcontext.test_hist,key1=value1,key4=value4 count=1i,last=300,min=300,mean=300,max=300,stddev=0,median=300,sample_size=1i,percentile_75%=300,percentile_95%=300,percentile_98%=300,percentile_99%=300,percentile_99.9%=300 {expTime}"); 253 | 254 | // add with set item 255 | hist.Update(100, "item1,item2=ival2,item3=ival3"); 256 | metricsData = context.DataProvider.CurrentMetricsData; 257 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 258 | writer.LastBatch.Should().HaveCount(1); 259 | 260 | expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 261 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"testcontext.test_hist,key1=value1,key4=value4 count=2i,last=100,min=100,mean=200,max=300,stddev=100,median=300,sample_size=2i,percentile_75%=300,percentile_95%=300,percentile_98%=300,percentile_99%=300,percentile_99.9%=300 {expTime}"); 262 | } 263 | 264 | [Fact] 265 | public void InfluxReport_CanAddRecords_ForTimer() { 266 | var config = new InfluxConfig("localhost", "testdb"); 267 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 268 | var report = new InfluxdbHttpReport(config); 269 | var context = new DefaultMetricsContext("TestContext"); 270 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 271 | var metricsData = context.DataProvider.CurrentMetricsData; 272 | var timer = context.Timer("test_timer", Unit.Bytes, SamplingType.Default, TimeUnit.Seconds, TimeUnit.Seconds, new MetricTags("key1=value1,tag2,tag3,key4=value4")); 273 | 274 | // add normally 275 | timer.Record(100, TimeUnit.Seconds); 276 | metricsData = context.DataProvider.CurrentMetricsData; 277 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 278 | writer.LastBatch.Should().HaveCount(1); 279 | 280 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 281 | writer.LastBatch[0].ToLineProtocol(precision).Should().StartWith($@"testcontext.test_timer,key1=value1,key4=value4 active_sessions=0i,total_time=100i,count=1i,").And.EndWith($@",1_min_rate=0,5_min_rate=0,15_min_rate=0,last=100,min=100,mean=100,max=100,stddev=0,median=100,sample_size=1i,percentile_75%=100,percentile_95%=100,percentile_98%=100,percentile_99%=100,percentile_99.9%=100 {expTime}"); 282 | 283 | // add with set item 284 | timer.Record(50, TimeUnit.Seconds, "item1,item2=ival2,item3=ival3"); 285 | metricsData = context.DataProvider.CurrentMetricsData; 286 | report.RunReport(metricsData, hsFunc, CancellationToken.None); 287 | writer.LastBatch.Should().HaveCount(1); 288 | 289 | expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 290 | writer.LastBatch[0].ToLineProtocol(precision).Should().StartWith($@"testcontext.test_timer,key1=value1,key4=value4 active_sessions=0i,total_time=150i,count=2i,").And.EndWith($@",1_min_rate=0,5_min_rate=0,15_min_rate=0,last=50,min=50,mean=75,max=100,stddev=25,median=100,sample_size=2i,percentile_75%=100,percentile_95%=100,percentile_98%=100,percentile_99%=100,percentile_99.9%=100 {expTime}"); 291 | } 292 | 293 | [Fact] 294 | public void InfluxReport_CanAddRecords_ForHealthCheck() { 295 | var config = new InfluxConfig("localhost", "testdb"); 296 | var writer = new InfluxdbTestWriter(config); config.Writer = writer; 297 | var report = new InfluxdbHttpReport(config); 298 | var context = new DefaultMetricsContext("TestContext"); 299 | var precision = config.Precision ?? InfluxConfig.Default.Precision; 300 | var metricsData = context.DataProvider.CurrentMetricsData; 301 | 302 | HealthChecks.UnregisterAllHealthChecks(); 303 | HealthChecks.RegisterHealthCheck("Health Check 1", () => HealthCheckResult.Healthy($"Healthy check!")); 304 | HealthChecks.RegisterHealthCheck("Health Check 2", () => HealthCheckResult.Unhealthy($"Unhealthy check!")); 305 | HealthChecks.RegisterHealthCheck("Health Check 3,tag3=key3", () => HealthCheckResult.Healthy($"Healthy check!")); 306 | HealthChecks.RegisterHealthCheck("Health Check 4,tag 4=key 4", () => HealthCheckResult.Healthy($"Healthy check!")); 307 | HealthChecks.RegisterHealthCheck("Name=Health Check 5,tag5=key5", () => HealthCheckResult.Healthy($"Healthy check!")); 308 | 309 | metricsData = context.DataProvider.CurrentMetricsData; 310 | report.RunReport(metricsData, () => HealthChecks.GetStatus(), CancellationToken.None); 311 | HealthChecks.UnregisterAllHealthChecks(); // unreg first in case something below throws 312 | writer.LastBatch.Should().HaveCount(5); 313 | 314 | var expTime = InfluxLineProtocol.FormatTimestamp(metricsData.Timestamp, precision); 315 | writer.LastBatch[0].ToLineProtocol(precision).Should().Be($@"health_checks,name=health_check_1 ishealthy=True,message=""Healthy check!"" {expTime}"); 316 | writer.LastBatch[1].ToLineProtocol(precision).Should().Be($@"health_checks,name=health_check_2 ishealthy=False,message=""Unhealthy check!"" {expTime}"); 317 | writer.LastBatch[2].ToLineProtocol(precision).Should().Be($@"health_checks,name=health_check_3,tag3=key3 ishealthy=True,message=""Healthy check!"" {expTime}"); 318 | writer.LastBatch[3].ToLineProtocol(precision).Should().Be($@"health_checks,name=health_check_4,tag_4=key\ 4 ishealthy=True,message=""Healthy check!"" {expTime}"); 319 | writer.LastBatch[4].ToLineProtocol(precision).Should().Be($@"health_checks,name=health\ check\ 5,tag5=key5 ishealthy=True,message=""Healthy check!"" {expTime}"); 320 | } 321 | 322 | 323 | 324 | #region Tag and Field Test Cases and Other Static Members 325 | 326 | public static IEnumerable TagTestCases = new[] { 327 | new TagTestCase("key1", "value1", @"key1=value1"), 328 | new TagTestCase("key2 with spaces", "value2 with spaces", @"key2\ with\ spaces=value2\ with\ spaces"), 329 | new TagTestCase("key3,with,commas", "value3,with,commas", @"key3\,with\,commas=value3\,with\,commas"), 330 | new TagTestCase("key4=with=equals", "value4=with=equals", @"key4\=with\=equals=value4\=with\=equals"), 331 | new TagTestCase("key5\"with\"quot", "value5\"with\"quot", "key5\"with\"quot=value5\"with\"quot"), 332 | new TagTestCase("key6\" with,all=", "value6\" with,all=", @"key6""\ with\,all\==value6""\ with\,all\="), 333 | }; 334 | 335 | public static IEnumerable FieldTestCases = new[] { 336 | new FieldTestCase("field1_int1", 100, @"field1_int1=100i"), 337 | new FieldTestCase("field1_int2", -100, @"field1_int2=-100i"), 338 | new FieldTestCase("field2_double1", 123456789.123456, @"field2_double1=123456789.123456"), 339 | new FieldTestCase("field2_double2", -123456789.123456, @"field2_double2=-123456789.123456"), 340 | new FieldTestCase("field2_double3", Math.PI, @"field2_double3=3.1415926535897931"), 341 | new FieldTestCase("field2_double4", Double.MinValue, @"field2_double4=-1.7976931348623157E+308"), 342 | new FieldTestCase("field2_double5", Double.MaxValue, @"field2_double5=1.7976931348623157E+308"), 343 | new FieldTestCase("field3_bool1", true, @"field3_bool1=True"), 344 | new FieldTestCase("field3_bool2", false, @"field3_bool2=False"), 345 | new FieldTestCase("field4_string1", "string value1", @"field4_string1=""string value1"""), 346 | new FieldTestCase("field4_string2", "string\"value2", @"field4_string2=""string\""value2"""), 347 | new FieldTestCase("field5 spaces", 100, @"field5\ spaces=100i"), 348 | new FieldTestCase("field6,commas", 100, @"field6\,commas=100i"), 349 | new FieldTestCase("field7=equals", 100, @"field7\=equals=100i"), 350 | new FieldTestCase("field8\"quote", 100, @"field8""quote=100i"), 351 | }; 352 | 353 | // these must be defined after the above are defined so the variables are not null 354 | public static IEnumerable TagTestCasesArray = TagTestCases.Select(t => t.ToArray()); 355 | public static IEnumerable FieldTestCasesArray = FieldTestCases.Select(t => t.ToArray()); 356 | 357 | 358 | private static readonly Func hsFunc = () => new HealthStatus(); 359 | 360 | #endregion 361 | 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.Tests/Metrics.InfluxDB.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {1D9413B0-4B25-48AC-9298-554284D409DE} 9 | Library 10 | Properties 11 | Metrics.InfluxDB.Tests 12 | Metrics.InfluxDB.Tests 13 | v4.5 14 | 512 15 | 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | ..\..\bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | ..\..\bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\packages\FluentAssertions.4.14.0\lib\net45\FluentAssertions.dll 39 | True 40 | 41 | 42 | ..\..\packages\FluentAssertions.4.14.0\lib\net45\FluentAssertions.Core.dll 43 | True 44 | 45 | 46 | ..\..\packages\Metrics.NET.0.3.7\lib\net45\Metrics.dll 47 | True 48 | 49 | 50 | 51 | 52 | 53 | ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 54 | True 55 | 56 | 57 | ..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 58 | True 59 | 60 | 61 | ..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 62 | True 63 | 64 | 65 | ..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 66 | True 67 | 68 | 69 | 70 | 71 | Properties\SharedAssemblyInfo.cs 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | {1b6c2147-30db-4c58-ad92-5fd34937f9a4} 80 | Metrics.InfluxDB 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Metrics.InfluxDB.Tests")] 4 | [assembly: AssemblyDescription("Unit tests for the InfluxDB reporter for the Metrics.NET monitoring and reporting library.")] 5 | 6 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using Metrics.MetricData; 6 | using Metrics.InfluxDB.Model; 7 | 8 | namespace Metrics.InfluxDB.Adapters 9 | { 10 | /// 11 | /// This class converts Metrics.NET metric values into objects. 12 | /// 13 | public abstract class InfluxdbConverter 14 | { 15 | 16 | /// 17 | /// Gets or sets the current timestamp. This value is used when creating new instances. 18 | /// 19 | public DateTime? Timestamp { get; set; } 20 | 21 | /// 22 | /// Gets or sets the global tags. Global tags are added to all created instances. 23 | /// 24 | public MetricTags GlobalTags { get; set; } 25 | 26 | 27 | /// 28 | /// Creates a new using the default precision defined by . 29 | /// 30 | public InfluxdbConverter() 31 | : this(null) { 32 | } 33 | 34 | /// 35 | /// Creates a new using the specified precision and tags. 36 | /// 37 | /// The global tags that are added to all created instances. 38 | public InfluxdbConverter(MetricTags? globalTags = null) { 39 | GlobalTags = globalTags ?? MetricTags.None; 40 | } 41 | 42 | 43 | 44 | /// 45 | /// Creates a new instance for the gauge value. 46 | /// 47 | /// The measurement name. 48 | /// Any additional tags to add to the , these tags overwrite any global tags with the same name. 49 | /// The metric unit. 50 | /// The metric value object. 51 | /// A list of instances for the specified metric value. 52 | public IEnumerable GetRecords(String name, MetricTags tags, Unit unit, Double value) { 53 | yield return GetRecord(name, tags, new[] { 54 | new InfluxField("Value", value), 55 | }); 56 | } 57 | 58 | /// 59 | /// Creates a new instance for the counter value and any set items. 60 | /// 61 | /// The measurement name. 62 | /// Any additional tags to add to the , these tags overwrite any global tags with the same name. 63 | /// The metric unit. 64 | /// The metric value object. 65 | /// A list of instances for the specified metric value. 66 | public IEnumerable GetRecords(String name, MetricTags tags, Unit unit, CounterValue value) { 67 | yield return GetRecord(name, tags, new[] { 68 | new InfluxField("Count", value.Count), 69 | }); 70 | 71 | foreach (var i in value.Items) { 72 | yield return GetRecord(name, i.Item, tags, new[] { 73 | new InfluxField("Count", i.Count), 74 | new InfluxField("Percent", i.Percent), 75 | }); 76 | } 77 | } 78 | 79 | /// 80 | /// Creates a new instance for the histogram value. 81 | /// 82 | /// The measurement name. 83 | /// Any additional tags to add to the , these tags overwrite any global tags with the same name. 84 | /// The metric unit. 85 | /// The metric value object. 86 | /// A list of instances for the specified metric value. 87 | public IEnumerable GetRecords(String name, MetricTags tags, Unit unit, MeterValue value) { 88 | if (value == null) throw new ArgumentNullException(nameof(value)); 89 | 90 | yield return GetRecord(name, tags, new[] { 91 | new InfluxField("Count", value.Count), 92 | new InfluxField("Mean Rate", value.MeanRate), 93 | new InfluxField("1 Min Rate", value.OneMinuteRate), 94 | new InfluxField("5 Min Rate", value.FiveMinuteRate), 95 | new InfluxField("15 Min Rate", value.FifteenMinuteRate), 96 | }); 97 | 98 | foreach (var i in value.Items) { 99 | yield return GetRecord(name, i.Item, tags, new[] { 100 | new InfluxField("Count", i.Value.Count), 101 | new InfluxField("Percent", i.Percent), 102 | new InfluxField("Mean Rate", i.Value.MeanRate), 103 | new InfluxField("1 Min Rate", i.Value.OneMinuteRate), 104 | new InfluxField("5 Min Rate", i.Value.FiveMinuteRate), 105 | new InfluxField("15 Min Rate", i.Value.FifteenMinuteRate), 106 | }); 107 | } 108 | } 109 | 110 | /// 111 | /// Creates a new instance for the histogram value. 112 | /// 113 | /// The measurement name. 114 | /// Any additional tags to add to the , these tags overwrite any global tags with the same name. 115 | /// The metric unit. 116 | /// The metric value object. 117 | /// A list of instances for the specified metric value. 118 | public IEnumerable GetRecords(String name, MetricTags tags, Unit unit, HistogramValue value) { 119 | if (value == null) throw new ArgumentNullException(nameof(value)); 120 | 121 | yield return GetRecord(name, tags, new[] { 122 | new InfluxField("Count", value.Count), 123 | new InfluxField("Last", value.LastValue), 124 | new InfluxField("Min", value.Min), 125 | new InfluxField("Mean", value.Mean), 126 | new InfluxField("Max", value.Max), 127 | new InfluxField("StdDev", value.StdDev), 128 | new InfluxField("Median", value.Median), 129 | new InfluxField("Sample Size", value.SampleSize), 130 | new InfluxField("Percentile 75%", value.Percentile75), 131 | new InfluxField("Percentile 95%", value.Percentile95), 132 | new InfluxField("Percentile 98%", value.Percentile98), 133 | new InfluxField("Percentile 99%", value.Percentile99), 134 | new InfluxField("Percentile 99.9%", value.Percentile999), 135 | 136 | // ignored histogram values 137 | //new InfluxField("Last User Value", value.LastUserValue), 138 | //new InfluxField("Min User Value", value.MinUserValue), 139 | //new InfluxField("Max User Value", value.MaxUserValue), 140 | }); 141 | } 142 | 143 | /// 144 | /// Creates new instances for the timer values and any items in the meter item sets. 145 | /// 146 | /// The measurement name. 147 | /// Any additional tags to add to the , these tags overwrite any global tags with the same name. 148 | /// The metric unit. 149 | /// The metric value object. 150 | /// A list of instances for the specified metric value. 151 | public IEnumerable GetRecords(String name, MetricTags tags, Unit unit, TimerValue value) { 152 | if (value == null) throw new ArgumentNullException(nameof(value)); 153 | 154 | yield return GetRecord(name, tags, new[] { 155 | new InfluxField("Active Sessions", value.ActiveSessions), 156 | new InfluxField("Total Time", value.TotalTime), 157 | new InfluxField("Count", value.Rate.Count), 158 | new InfluxField("Mean Rate", value.Rate.MeanRate), 159 | new InfluxField("1 Min Rate", value.Rate.OneMinuteRate), 160 | new InfluxField("5 Min Rate", value.Rate.FiveMinuteRate), 161 | new InfluxField("15 Min Rate", value.Rate.FifteenMinuteRate), 162 | new InfluxField("Last", value.Histogram.LastValue), 163 | new InfluxField("Min", value.Histogram.Min), 164 | new InfluxField("Mean", value.Histogram.Mean), 165 | new InfluxField("Max", value.Histogram.Max), 166 | new InfluxField("StdDev", value.Histogram.StdDev), 167 | new InfluxField("Median", value.Histogram.Median), 168 | new InfluxField("Sample Size", value.Histogram.SampleSize), 169 | new InfluxField("Percentile 75%", value.Histogram.Percentile75), 170 | new InfluxField("Percentile 95%", value.Histogram.Percentile95), 171 | new InfluxField("Percentile 98%", value.Histogram.Percentile98), 172 | new InfluxField("Percentile 99%", value.Histogram.Percentile99), 173 | new InfluxField("Percentile 99.9%", value.Histogram.Percentile999), 174 | 175 | // ignored histogram values 176 | //new InfluxField("Last User Value", value.Histogram.LastUserValue), 177 | //new InfluxField("Min User Value", value.Histogram.MinUserValue), 178 | //new InfluxField("Max User Value", value.Histogram.MaxUserValue), 179 | }); 180 | 181 | // NOTE: I'm not sure if this is needed, it appears the timer only adds set item values 182 | // to the histogram and not to the meter. I'm not sure if this is a bug or by design. 183 | foreach (var i in value.Rate.Items) { 184 | yield return GetRecord(name, i.Item, tags, new[] { 185 | new InfluxField("Count", i.Value.Count), 186 | new InfluxField("Percent", i.Percent), 187 | new InfluxField("Mean Rate", i.Value.MeanRate), 188 | new InfluxField("1 Min Rate", i.Value.OneMinuteRate), 189 | new InfluxField("5 Min Rate", i.Value.FiveMinuteRate), 190 | new InfluxField("15 Min Rate", i.Value.FifteenMinuteRate), 191 | }); 192 | } 193 | } 194 | 195 | /// 196 | /// Creates new instances for each HealthCheck result in the specified . 197 | /// 198 | /// The health status. 199 | /// 200 | public IEnumerable GetRecords(HealthStatus status) { 201 | foreach (var result in status.Results) { 202 | //var name = InfluxUtils.LowerAndReplaceSpaces(result.Name); 203 | //var nameWithTags = Regex.IsMatch(result.Name, "^[Nn]ame=") ? result.Name : $"Name={result.Name}"; 204 | var split = Regex.Split(result.Name, @"(? t.Trim()).Where(t => t.Length > 0).ToArray(); 205 | if (!Regex.IsMatch(split[0], "^[Nn]ame=")) split[0] = $"Name={InfluxUtils.LowerAndReplaceSpaces(split[0])}"; 206 | var name = String.Join(",", split); 207 | yield return GetRecord("Health Checks", name, new[] { 208 | new InfluxField("IsHealthy", result.Check.IsHealthy), 209 | new InfluxField("Message", result.Check.Message), 210 | }); 211 | } 212 | } 213 | 214 | 215 | 216 | /// 217 | /// Creates a new from the specified name, tags, and fields. 218 | /// This uses the timestamp defined on this metrics converter instance. 219 | /// 220 | /// The measurement or series name. This value is required and cannot be null or empty. 221 | /// The optional tags to associate with this record. 222 | /// The values for the output fields. 223 | /// A new from the specified name, tags, and fields. 224 | public InfluxRecord GetRecord(String name, MetricTags tags, IEnumerable fields) { 225 | return GetRecord(name, null, tags, fields); 226 | } 227 | 228 | /// 229 | /// Creates a new from the specified name, item name, tags, and fields. 230 | /// This uses the timestamp defined on this metrics converter instance. 231 | /// 232 | /// The measurement or series name. This value is required and cannot be null or empty. 233 | /// The set item name. Can contain comma-separated key/value pairs. 234 | /// The optional tags to associate with this record. 235 | /// The values for the output fields. 236 | /// A new from the specified name, tags, and fields. 237 | public InfluxRecord GetRecord(String name, String itemName, MetricTags tags, IEnumerable fields) { 238 | var jtags = InfluxUtils.JoinTags(itemName, GlobalTags, tags); // global tags must be first so they can get overridden 239 | var record = new InfluxRecord(name, jtags, fields, Timestamp); 240 | return record; 241 | } 242 | } 243 | 244 | /// 245 | /// The default implementation which is simply a concrete type that derives from 246 | /// the abstract base class and provides no additional implementation on top of the base class implementation. 247 | /// 248 | public class DefaultConverter : InfluxdbConverter 249 | { 250 | /// 251 | /// Creates a new using the specified tags. 252 | /// 253 | /// The global tags that are added to all created instances. 254 | public DefaultConverter(MetricTags? globalTags = null) 255 | : base(globalTags) { 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Metrics.InfluxDB.Model; 5 | 6 | namespace Metrics.InfluxDB.Adapters 7 | { 8 | /// 9 | /// This class provides functions to format the context name and metric 10 | /// name into a string used to identify the table to insert records into. 11 | /// The formatter is also used to format the 12 | /// before writing it to the database. This also optionally formats the column 13 | /// names by converting the case and replacing spaces with another character. 14 | /// 15 | public abstract class InfluxdbFormatter 16 | { 17 | 18 | #region Formatter Delegates 19 | 20 | /// 21 | /// The delegate used for formatting context names. 22 | /// 23 | /// The context stack. 24 | /// The current context name. 25 | /// The formatted context name. 26 | public delegate String ContextFormatterDelegate(IEnumerable contextStack, String contextName); 27 | 28 | /// 29 | /// The delegate used for formatting metric names. 30 | /// 31 | /// The context name. 32 | /// The metric name. 33 | /// The metric units. 34 | /// The metric tags. 35 | /// The formatted metric name. 36 | public delegate String MetricFormatterDelegate(String context, String name, Unit unit, String[] tags); 37 | 38 | /// 39 | /// The delegate used for formatting tag key names. 40 | /// 41 | /// The tag key name. 42 | /// The formatted tag key name. 43 | public delegate String TagKeyFormatterDelegate(String tagKey); 44 | 45 | /// 46 | /// The delegate used for formatting field key names. 47 | /// 48 | /// The field key name. 49 | /// The formatted field key name. 50 | public delegate String FieldKeyFormatterDelegate(String fieldKey); 51 | 52 | #endregion 53 | 54 | #region Public Data Members 55 | 56 | /// 57 | /// Formats the context stack and context name into a custom context name string. 58 | /// 59 | public ContextFormatterDelegate ContextNameFormatter { get; set; } 60 | 61 | /// 62 | /// Formats the context name and metric into a string used as the table to insert records into. 63 | /// 64 | public MetricFormatterDelegate MetricNameFormatter { get; set; } 65 | 66 | /// 67 | /// Formats a tag key into a string used as the column name in the InfluxDB table. 68 | /// 69 | public TagKeyFormatterDelegate TagKeyFormatter { get; set; } 70 | 71 | /// 72 | /// Formats a field key into a string used as the column name in the InfluxDB table. 73 | /// 74 | public FieldKeyFormatterDelegate FieldKeyFormatter { get; set; } 75 | 76 | /// 77 | /// If not null, will replace all space characters in context names, metric names, 78 | /// tag keys, and field keys with the specified string. The default value is an underscore. 79 | /// 80 | public String ReplaceSpaceChar { get; set; } 81 | 82 | /// 83 | /// If set to true will convert all context names, metric names, tag keys, and field keys 84 | /// to lowercase. If false, it does not modify the names. The default value is true. 85 | /// 86 | public Boolean LowercaseNames { get; set; } 87 | 88 | #endregion 89 | 90 | 91 | /// 92 | /// Creates a new with default values. 93 | /// The default formatters convert identifiers to lowercase, replace spaces with underscores, and if applicable, join multiple identifiers with periods. 94 | /// 95 | public InfluxdbFormatter() { 96 | } 97 | 98 | /// 99 | /// Creates a new with default values, including the and properties. 100 | /// The default formatters convert identifiers to lowercase, replace spaces with underscores, and if applicable, join multiple identifiers with periods. 101 | /// 102 | /// If true, converts the string to lowercase. 103 | /// The character(s) to replace all space characters with (underscore by default). If null, spaces are not replaced. 104 | public InfluxdbFormatter(Boolean lowercase, String replaceChars) 105 | : this() { 106 | LowercaseNames = lowercase; 107 | ReplaceSpaceChar = replaceChars; 108 | } 109 | 110 | 111 | /// 112 | /// Formats the context name using the if it is set, otherwise returns null. 113 | /// This will apply lowercasing and replace space characters if it is configured on this instance. 114 | /// 115 | /// The list of parent context names. 116 | /// The current context name. 117 | /// The context name after applying the formatters and transformations, or null if the is not set. 118 | public virtual String FormatContextName(IEnumerable contextStack, String contextName) { 119 | String value = ContextNameFormatter?.Invoke(contextStack, contextName); 120 | if (value == null) return null; // return null so that caller knows that it can call its own default implementation if it has one 121 | return InfluxUtils.LowerAndReplaceSpaces(value, LowercaseNames, ReplaceSpaceChar); 122 | } 123 | 124 | /// 125 | /// Formats the metric name using the if it is set, otherwise returns null. 126 | /// This will apply lowercasing and replace space characters if it is configured on this instance. 127 | /// 128 | /// The metrics context name. 129 | /// The metric name. 130 | /// The metric units. 131 | /// The metric tags. 132 | /// The metric name after applying the formatters and transformations, or null if the is not set. 133 | public virtual String FormatMetricName(String context, String name, Unit unit, String[] tags) { 134 | String value = MetricNameFormatter?.Invoke(context, name, unit, tags); 135 | if (value == null) return null; // return null so that caller knows that it can call its own default implementation if it has one 136 | return InfluxUtils.LowerAndReplaceSpaces(value, LowercaseNames, ReplaceSpaceChar); 137 | } 138 | 139 | /// 140 | /// Formats the tag key name using the if it is set, otherwise uses the unmodified key value. 141 | /// This will apply lowercasing and replace space characters if it is configured on this instance. 142 | /// 143 | /// The string value to format. 144 | /// The tag key name after applying the formatters and transformations. 145 | public virtual String FormatTagKey(String tagKey) { 146 | String value = TagKeyFormatter?.Invoke(tagKey) ?? tagKey; 147 | return InfluxUtils.LowerAndReplaceSpaces(value, LowercaseNames, ReplaceSpaceChar); 148 | } 149 | 150 | /// 151 | /// Formats the field key name using the if it is set, otherwise uses the unmodified key value. 152 | /// This will apply lowercasing and replace space characters if it is configured on this instance. 153 | /// 154 | /// The string value to format. 155 | /// The field key name after applying the formatters and transformations. 156 | public virtual String FormatFieldKey(String fieldKey) { 157 | String value = FieldKeyFormatter?.Invoke(fieldKey) ?? fieldKey; 158 | return InfluxUtils.LowerAndReplaceSpaces(value, LowercaseNames, ReplaceSpaceChar); 159 | } 160 | 161 | /// 162 | /// Formats the measurement name, tag keys, and field keys on the specified 163 | /// with the defined tag and key formatters and returns the same record instance. 164 | /// 165 | /// The to format the tag and field keys for. 166 | /// The same instance with the tag and field keys formatted. 167 | public virtual InfluxRecord FormatRecord(InfluxRecord record) { 168 | record.Name = FormatMetricName(null, record.Name, Unit.None, null) ?? record.Name; 169 | 170 | for (int i = 0; i < record.Tags.Count; i++) { 171 | InfluxTag tag = record.Tags[i]; 172 | String fmtKey = FormatTagKey(tag.Key); 173 | record.Tags[i] = new InfluxTag(fmtKey, tag.Value); 174 | } 175 | 176 | for (int i = 0; i < record.Fields.Count; i++) { 177 | InfluxField field = record.Fields[i]; 178 | String fmtKey = FormatFieldKey(field.Key); 179 | record.Fields[i] = new InfluxField(fmtKey, field.Value); 180 | } 181 | 182 | return record; 183 | } 184 | 185 | } 186 | 187 | /// 188 | /// The default formatter used for formatting records. Has some modifications over the default 189 | /// implementation to generate cleaner output that more closely follows the InfluxDB naming conventions. 190 | /// 191 | public class DefaultFormatter : InfluxdbFormatter 192 | { 193 | /// 194 | /// Default InfluxDB formatters and settings. 195 | /// 196 | public static class Default 197 | { 198 | /// 199 | /// The default context name formatter which formats the context stack and context name into a custom context name string. 200 | /// 201 | public static ContextFormatterDelegate ContextNameFormatter { get; } 202 | 203 | /// 204 | /// The default metric name formatter which formats the context name and metric into a string used as the table to insert records into. 205 | /// 206 | public static MetricFormatterDelegate MetricNameFormatter { get; } 207 | 208 | /// 209 | /// The default tag key formatter which formats a tag key into a string used as the column name in the InfluxDB table. 210 | /// 211 | public static TagKeyFormatterDelegate TagKeyFormatter { get; } 212 | 213 | /// 214 | /// The default field key formatter which formats a field key into a string used as the column name in the InfluxDB table. 215 | /// 216 | public static FieldKeyFormatterDelegate FieldKeyFormatter { get; } 217 | 218 | /// 219 | /// The default character used to replace space characters in identifier names. This value is an underscore. 220 | /// 221 | public static String ReplaceSpaceChar { get; set; } 222 | 223 | /// 224 | /// The default value for whether to convert identifier names to lowercase. This value is true. 225 | /// 226 | public static Boolean LowercaseNames { get; } 227 | 228 | static Default() { 229 | ContextNameFormatter = (contextStack, contextName) => String.Join(".", contextStack.Concat(new[] { contextName }).Where(c => !String.IsNullOrWhiteSpace(c))); 230 | MetricNameFormatter = (context, name, unit, tags) => $"{context}.{name}".Trim(' ', '.'); 231 | TagKeyFormatter = key => key; 232 | FieldKeyFormatter = key => key; 233 | ReplaceSpaceChar = "_"; 234 | LowercaseNames = true; 235 | } 236 | } 237 | 238 | /// 239 | /// Creates a new with default values. 240 | /// The default formatters convert identifiers to lowercase, replace spaces with underscores, and if applicable, joins multiple identifiers with periods. 241 | /// 242 | public DefaultFormatter() 243 | : base() { 244 | ContextNameFormatter = Default.ContextNameFormatter; 245 | MetricNameFormatter = Default.MetricNameFormatter; 246 | TagKeyFormatter = Default.TagKeyFormatter; 247 | FieldKeyFormatter = Default.FieldKeyFormatter; 248 | ReplaceSpaceChar = Default.ReplaceSpaceChar; 249 | LowercaseNames = Default.LowercaseNames; 250 | } 251 | 252 | /// 253 | /// Creates a new with default values, including the and properties. 254 | /// The default formatters convert identifiers to lowercase, replace spaces with underscores, and if applicable, joins multiple identifiers with periods. 255 | /// 256 | /// If true, converts the string to lowercase. 257 | /// The character(s) to replace all space characters with (underscore by default). If null, spaces are not replaced. 258 | public DefaultFormatter(Boolean lowercase, String replaceChars) 259 | : this() { 260 | LowercaseNames = lowercase; 261 | ReplaceSpaceChar = replaceChars; 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbHttpWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using Metrics.InfluxDB.Model; 7 | 8 | namespace Metrics.InfluxDB.Adapters 9 | { 10 | /// 11 | /// This class writes s formatted in the LineProtocol to the InfluxDB server using HTTP POST. 12 | /// 13 | public class InfluxdbHttpWriter : InfluxdbLineWriter 14 | { 15 | 16 | private readonly Uri influxDbUri; 17 | 18 | 19 | /// 20 | /// Creates a new with the specified URI. 21 | /// 22 | /// The HTTP URI of the InfluxDB server. 23 | public InfluxdbHttpWriter(Uri influxDbUri) 24 | : this(new InfluxConfig(influxDbUri)) { 25 | } 26 | 27 | /// 28 | /// Creates a new with the specified configuration and batch size. 29 | /// 30 | /// The InfluxDB configuration. 31 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 32 | public InfluxdbHttpWriter(InfluxConfig config, Int32 batchSize = 0) 33 | : base(config, batchSize) { 34 | 35 | if (String.IsNullOrEmpty(config.Hostname)) 36 | throw new ArgumentNullException(nameof(config.Hostname)); 37 | if (String.IsNullOrEmpty(config.Database)) 38 | throw new ArgumentNullException(nameof(config.Database)); 39 | 40 | this.influxDbUri = FormatInfluxUri(config); 41 | if (influxDbUri == null) 42 | throw new ArgumentNullException(nameof(influxDbUri)); 43 | if (influxDbUri.Scheme != Uri.UriSchemeHttp && influxDbUri.Scheme != Uri.UriSchemeHttps) 44 | throw new ArgumentException($"The URI scheme must be either http or https. Scheme: {influxDbUri.Scheme}", nameof(influxDbUri)); 45 | } 46 | 47 | 48 | /// 49 | /// Creates an HTTP URI for InfluxDB using the values specified in the object. 50 | /// 51 | /// The configuration object to get the relevant fields to build the HTTP URI from. 52 | /// A new InfluxDB URI using the configuration specified in the parameter. 53 | protected static Uri FormatInfluxUri(InfluxConfig config) { 54 | UInt16 port = (config.Port ?? 0) > 0 ? config.Port.Value : InfluxConfig.Default.PortHttp; 55 | return InfluxUtils.FormatInfluxUri(InfluxUtils.SchemeHttp, config.Hostname, port, config.Database, config.Username, config.Password, config.RetentionPolicy, config.Precision); 56 | } 57 | 58 | /// 59 | /// Writes the byte array to the InfluxDB server in a single HTTP POST operation. 60 | /// 61 | /// The bytes to write to the InfluxDB server. 62 | /// The HTTP response from the server after writing the message. 63 | protected override Byte[] WriteToTransport(Byte[] bytes) { 64 | try { 65 | using (var client = new WebClient()) { 66 | var result = client.UploadData(influxDbUri, bytes); 67 | return result; 68 | } 69 | } catch (WebException ex) { 70 | String response = new StreamReader(ex.Response?.GetResponseStream() ?? Stream.Null).ReadToEnd(); 71 | String firstNLines = "\n" + String.Join("\n", Encoding.UTF8.GetString(bytes).Split('\n').Take(5)) + "\n"; 72 | MetricsErrorHandler.Handle(ex, $"Error while uploading {Batch.Count} measurements ({formatSize(bytes.Length)}) to InfluxDB over HTTP [{influxDbUri}] [ResponseStatus: {ex.Status}] [Response: {response}] - First 5 lines: {firstNLines}"); 73 | return Encoding.UTF8.GetBytes(response); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbJsonWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using Metrics.Json; 8 | using Metrics.Logging; 9 | using Metrics.InfluxDB.Model; 10 | 11 | namespace Metrics.InfluxDB.Adapters 12 | { 13 | /// 14 | /// This class writes s formatted as a JSON object to the InfluxDB server using HTTP POST. 15 | /// NOTE: This protocol is only supported in InfluxDB version v0.9.1 and earlier. 16 | /// 17 | public class InfluxdbJsonWriter : InfluxdbWriter 18 | { 19 | // TODO: loggers are internal in Metrics.NET 20 | //private static readonly ILog log = LogProvider.GetCurrentClassLogger(); 21 | 22 | private readonly InfluxConfig config; 23 | private readonly Uri influxDbUri; 24 | 25 | 26 | /// 27 | /// Creates a new with the specified URI. 28 | /// NOTE: This protocol is only supported in InfluxDB version v0.9.1 and earlier. 29 | /// 30 | /// The HTTP URI of the InfluxDB server. 31 | public InfluxdbJsonWriter(Uri influxDbUri) 32 | : this(new InfluxConfig(influxDbUri)) { 33 | } 34 | 35 | /// 36 | /// Creates a new with the specified URI. 37 | /// NOTE: This protocol is only supported in InfluxDB version v0.9.1 and earlier. 38 | /// 39 | /// The InfluxDB configuration. 40 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 41 | public InfluxdbJsonWriter(InfluxConfig config, Int32 batchSize = 0) 42 | : base(batchSize) { 43 | this.config = config; 44 | if (config == null) 45 | throw new ArgumentNullException(nameof(config)); 46 | if (String.IsNullOrEmpty(config.Database)) 47 | throw new ArgumentNullException(nameof(config.Database)); 48 | if (config.Precision != InfluxPrecision.Seconds) 49 | //log.Warn($"InfluxDB timestamp precision '{config.Precision}' is not supported by the JSON protocol, defaulting to {InfluxPrecision.Seconds}."); 50 | throw new ArgumentException($"InfluxDB timestamp precision '{config.Precision}' is not supported by the JSON protocol, which only supports {InfluxPrecision.Seconds} precision.", nameof(config.Precision)); 51 | 52 | this.influxDbUri = FormatInfluxUri(config); 53 | if (influxDbUri == null) 54 | throw new ArgumentNullException(nameof(influxDbUri)); 55 | if (influxDbUri.Scheme != Uri.UriSchemeHttp && influxDbUri.Scheme != Uri.UriSchemeHttps) 56 | throw new ArgumentException($"The URI scheme must be either http or https. Scheme: {influxDbUri.Scheme}", nameof(influxDbUri)); 57 | } 58 | 59 | 60 | /// 61 | /// Creates an HTTP JSON URI for InfluxDB using the values specified in the object. 62 | /// 63 | /// The configuration object to get the relevant fields to build the HTTP URI from. 64 | /// A new InfluxDB JSON URI using the configuration specified in the parameter. 65 | protected Uri FormatInfluxUri(InfluxConfig config) { 66 | InfluxPrecision prec = config.Precision ?? InfluxConfig.Default.Precision; 67 | return new Uri($@"http://{config.Hostname}:{config.Port}/db/{config.Database}/series?u={config.Username}&p={config.Password}&time_precision={prec.ToShortName()}"); 68 | } 69 | 70 | 71 | /// 72 | /// Gets the byte representation of the in JSON syntax using UTF8 encoding. 73 | /// 74 | /// The batch to get the bytes for. 75 | /// The byte representation of the batch. 76 | protected override Byte[] GetBatchBytes(InfluxBatch batch) { 77 | var strBatch = ToJson(batch); 78 | var bytes = Encoding.UTF8.GetBytes(strBatch); 79 | System.Diagnostics.Debug.WriteLine($"[NEW JSON]:\n{strBatch}"); 80 | return bytes; 81 | } 82 | 83 | /// 84 | /// Writes the byte array to the InfluxDB server in a single HTTP POST operation. 85 | /// 86 | /// The bytes to write to the InfluxDB server. 87 | /// The HTTP response from the server after writing the message. 88 | protected override Byte[] WriteToTransport(Byte[] bytes) { 89 | try { 90 | using (var client = new WebClient()) { 91 | var result = client.UploadData(influxDbUri, bytes); 92 | return result; 93 | } 94 | } catch (WebException ex) { 95 | String response = new StreamReader(ex.Response?.GetResponseStream() ?? Stream.Null).ReadToEnd(); 96 | String firstNLines = "\n" + String.Join("\n", Encoding.UTF8.GetString(bytes).Split('\n').Take(5)) + "\n"; 97 | MetricsErrorHandler.Handle(ex, $"Error while uploading {Batch.Count} measurements ({formatSize(bytes.Length)}) to InfluxDB over JSON HTTP [{influxDbUri}] [ResponseStatus: {ex.Status}] [Response: {response}] - First 5 lines: {firstNLines}"); 98 | return Encoding.UTF8.GetBytes(response); 99 | } 100 | } 101 | 102 | 103 | #region Format JSON Object Methods 104 | 105 | private static String ToJson(InfluxBatch batch) { 106 | return new CollectionJsonValue(batch.Select(r => ToJsonObject(r))).AsJson(); 107 | } 108 | 109 | private static JsonObject ToJsonObject(InfluxRecord record) { 110 | if (record == null) 111 | throw new ArgumentNullException(nameof(record)); 112 | if (String.IsNullOrWhiteSpace(record.Name)) 113 | throw new ArgumentNullException(nameof(record.Name), "The measurement name must be specified."); 114 | if (record.Fields.Count == 0) 115 | throw new ArgumentNullException(nameof(record.Fields), $"Must specify at least one field. Metric name: {record.Name}"); 116 | 117 | var cols = record.Tags.Select(t => t.Key).Concat(record.Fields.Select(f => f.Key)); 118 | var data = record.Tags.Select(t => t.Value).Concat(record.Fields.Select(f => f.Value)).Select(v => FormatValue(v)); 119 | return ToJsonObject(record.Name, record.Timestamp ?? DateTime.Now, cols, data); 120 | } 121 | 122 | private static JsonObject ToJsonObject(String name, DateTime timestamp, IEnumerable columns, IEnumerable data) { 123 | var cols = new[] { "time" }.Concat(columns); 124 | var points = new[] { new LongJsonValue(ToUnixTime(timestamp)) }.Concat(data); 125 | 126 | return new JsonObject(new[] { 127 | new JsonProperty("name", name), 128 | new JsonProperty("columns", cols), 129 | new JsonProperty("points", new JsonValueArray(new[] { new JsonValueArray(points) })) 130 | }); 131 | } 132 | 133 | /// 134 | /// Formats the field value in the appropriate line protocol format based on the type of the value object. 135 | /// The value type must be a string, boolean, or integral or floating-point type. 136 | /// 137 | /// The field value to format. 138 | /// The field value formatted as a string used in the line protocol format. 139 | private static JsonValue FormatValue(Object value) { 140 | Type type = value?.GetType(); 141 | if (value == null) 142 | throw new ArgumentNullException(nameof(value)); 143 | if (!InfluxUtils.IsValidValueType(type)) 144 | throw new ArgumentException(nameof(value), $"Value is not one of the supported types: {type} - Valid types: {String.Join(", ", InfluxUtils.ValidValueTypes.Select(t => t.Name))}"); 145 | 146 | if (InfluxUtils.IsIntegralType(type)) 147 | return FormatValue(Convert.ToInt64(value)); 148 | if (InfluxUtils.IsFloatingPointType(type)) 149 | return FormatValue(Convert.ToDouble(value)); 150 | if (value is String) 151 | return FormatValue((String)value); 152 | if (value is Char) 153 | return FormatValue(value.ToString()); 154 | return FormatValue(value.ToString()); 155 | } 156 | 157 | private static JsonValue FormatValue(Int64 value) { 158 | return new LongJsonValue(value); 159 | } 160 | 161 | private static JsonValue FormatValue(Double value) { 162 | return new DoubleJsonValue(value); 163 | } 164 | 165 | private static JsonValue FormatValue(String value) { 166 | return new StringJsonValue(value); 167 | } 168 | 169 | private static readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 170 | 171 | private static long ToUnixTime(DateTime datetime) { 172 | return Convert.ToInt64((datetime.ToUniversalTime() - unixEpoch).TotalSeconds); 173 | } 174 | 175 | #endregion 176 | 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbUdpWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using Metrics.InfluxDB.Model; 6 | 7 | namespace Metrics.InfluxDB.Adapters 8 | { 9 | /// 10 | /// This class writes s formatted in the LineProtocol to the InfluxDB server using the UDP transport. 11 | /// 12 | public class InfluxdbUdpWriter : InfluxdbLineWriter 13 | { 14 | 15 | /// 16 | /// Creates a new with the specified URI. 17 | /// 18 | /// The UDP URI of the InfluxDB server. Should be in the format: net.udp//{host}:{port}/ 19 | public InfluxdbUdpWriter(Uri influxDbUri) 20 | : this(new InfluxConfig(influxDbUri)) { 21 | } 22 | 23 | /// 24 | /// Creates a new with the specified configuration and batch size. 25 | /// 26 | /// The InfluxDB configuration. 27 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 28 | public InfluxdbUdpWriter(InfluxConfig config, Int32 batchSize = 0) 29 | : base(config, batchSize) { 30 | 31 | if (String.IsNullOrEmpty(config.Hostname)) 32 | throw new ArgumentNullException(nameof(config.Hostname)); 33 | if ((config.Port ?? 0) == 0) 34 | throw new ArgumentNullException(nameof(config.Port), "Port is required for UDP connections."); 35 | if ((config.Precision ?? InfluxPrecision.Nanoseconds) != InfluxPrecision.Nanoseconds) 36 | throw new ArgumentException($"Timestamp precision for UDP connections must be Nanoseconds. Actual: {config.Precision}", nameof(config.Precision)); 37 | } 38 | 39 | 40 | /// 41 | /// Gets the byte representation of the in LineProtocol syntax using UTF8 encoding. 42 | /// 43 | /// The batch to get the bytes for. 44 | /// The byte representation of the batch. 45 | protected override Byte[] GetBatchBytes(InfluxBatch batch) { 46 | // UDP only supports ns precision 47 | var strBatch = batch.ToLineProtocol(InfluxPrecision.Nanoseconds); 48 | var bytes = Encoding.UTF8.GetBytes(strBatch); 49 | return bytes; 50 | } 51 | 52 | /// 53 | /// Writes the byte array to the InfluxDB server in a single UDP send operation. 54 | /// 55 | /// The bytes to write to the InfluxDB server. 56 | /// The HTTP response from the server after writing the message. 57 | protected override Byte[] WriteToTransport(Byte[] bytes) { 58 | try { 59 | using (var client = new UdpClient()) { 60 | int result = client.Send(bytes, bytes.Length, config.Hostname, config.Port.Value); 61 | return Encoding.UTF8.GetBytes(result.ToString()); 62 | } 63 | } catch (Exception ex) { 64 | String firstNLines = "\n" + String.Join("\n", Encoding.UTF8.GetString(bytes).Split('\n').Take(5)) + "\n"; 65 | MetricsErrorHandler.Handle(ex, $"Error while uploading {Batch.Count} measurements ({formatSize(bytes.Length)}) to InfluxDB over UDP [net.udp://{config.Hostname}:{config.Port.Value}/] - Ensure that the message size is less than the UDP send buffer size (usually 8-64KB), and reduce the BatchSize on the InfluxdbWriter if necessary. - First 5 lines: {firstNLines}"); 66 | return Encoding.UTF8.GetBytes(0.ToString()); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Adapters/InfluxdbWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Metrics.InfluxDB.Model; 6 | 7 | namespace Metrics.InfluxDB.Adapters 8 | { 9 | /// 10 | /// The is responsible for writing s to the InfluxDB server. 11 | /// Derived classes can implement various methods and protocols for writing the data (ie. HTTP API, UDP, etc). 12 | /// 13 | public abstract class InfluxdbWriter : IDisposable { 14 | 15 | /// This function formats bytes into a string with units either in bytes or KiB. 16 | protected static readonly Func formatSize = bytes => bytes < (1 << 12) ? $"{bytes:n0} bytes" : $"{bytes / 1024.0:n2} KiB"; 17 | 18 | 19 | private readonly InfluxBatch batch; 20 | private Int32 batchSize; 21 | 22 | 23 | #region Public Data Members 24 | 25 | /// 26 | /// The currently buffered that has not yet been flushed to the underlying writer. 27 | /// 28 | public InfluxBatch Batch { 29 | get { return batch; } 30 | } 31 | 32 | /// 33 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 34 | /// 35 | public Int32 BatchSize { 36 | get { return batchSize; } 37 | set { 38 | if (value < 0) 39 | throw new ArgumentOutOfRangeException(nameof(value), "Batch size cannot be negative."); 40 | batchSize = value; 41 | } 42 | } 43 | 44 | #endregion 45 | 46 | 47 | /// 48 | /// Creates a new instance of a . 49 | /// 50 | public InfluxdbWriter() 51 | : this(0) { 52 | } 53 | 54 | /// 55 | /// Creates a new instance of a . 56 | /// 57 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 58 | public InfluxdbWriter(Int32 batchSize) { 59 | if (batchSize < 0) 60 | throw new ArgumentOutOfRangeException(nameof(batchSize), "Batch size cannot be negative."); 61 | 62 | this.batch = new InfluxBatch(); 63 | this.batchSize = batchSize; 64 | } 65 | 66 | 67 | #region Abstract Methods 68 | 69 | /// 70 | /// Gets the byte representation of the . This will convert the 71 | /// batch to the correct string syntax and then get the byte array for it in UTF8 encoding. 72 | /// 73 | /// The batch to get the bytes for. 74 | /// The byte representation of the batch. 75 | protected abstract Byte[] GetBatchBytes(InfluxBatch batch); 76 | 77 | /// 78 | /// Writes the byte array to the InfluxDB server using the underlying transport. 79 | /// 80 | /// The bytes to write to the InfluxDB server. 81 | /// The response from the server after writing the message, or null if there is no response (like for UDP). 82 | protected abstract Byte[] WriteToTransport(Byte[] bytes); 83 | 84 | #endregion 85 | 86 | #region Public Methods 87 | 88 | /// 89 | /// Flushes all buffered records in the batch by writing them to the server in a single write operation. 90 | /// 91 | public virtual void Flush() { 92 | if (Batch.Count == 0) return; 93 | Byte[] bytes = new Byte[0]; 94 | 95 | try { 96 | bytes = GetBatchBytes(Batch); 97 | WriteToTransport(bytes); 98 | } catch (Exception ex) { 99 | String firstNLines = "\n" + String.Join("\n", Encoding.UTF8.GetString(bytes).Split('\n').Take(5)) + "\n"; 100 | MetricsErrorHandler.Handle(ex, $"Error while flushing {Batch.Count} measurements to InfluxDB. Bytes: {formatSize(bytes.Length)} - First 5 lines: {firstNLines}"); 101 | } finally { 102 | // clear always, regardless if it was successful or not 103 | Batch.Clear(); 104 | } 105 | } 106 | 107 | /// 108 | /// Writes the record to the InfluxDB server. If batching is used, the record will be added to the 109 | /// batch buffer but will not immediately be written to the server. If the number of buffered records 110 | /// is greater than or equal to the BatchSize, then the batch will be flushed to the underlying writer. 111 | /// 112 | /// The record to write. 113 | public virtual void Write(InfluxRecord record) { 114 | if (record == null) throw new ArgumentNullException(nameof(record)); 115 | batch.Add(record); 116 | if (batchSize > 0 && batch.Count >= batchSize) 117 | Flush(); // flush if batch is full 118 | } 119 | 120 | /// 121 | /// Writes the records to the InfluxDB server. Flushing will occur in increments of the defined BatchSize. 122 | /// 123 | /// The records to write. 124 | public virtual void Write(IEnumerable records) { 125 | if (records == null) throw new ArgumentNullException(nameof(records)); 126 | foreach (var r in records) 127 | Write(r); 128 | } 129 | 130 | /// 131 | /// Flushes all buffered records and clears the batch. 132 | /// 133 | public virtual void Dispose() { 134 | try { 135 | Flush(); 136 | } finally { 137 | batch.Clear(); 138 | } 139 | } 140 | 141 | #endregion 142 | 143 | } 144 | 145 | /// 146 | /// This class writes s formatted in the LineProtocol to the InfluxDB server. 147 | /// 148 | public abstract class InfluxdbLineWriter : InfluxdbWriter 149 | { 150 | /// 151 | /// The InfluxDB configuration. 152 | /// 153 | protected readonly InfluxConfig config; 154 | 155 | /// 156 | /// Creates a new with the specified configuration and batch size. 157 | /// 158 | /// The InfluxDB configuration. 159 | /// The maximum number of records to write per flush. Set to zero to write all records in a single flush. Negative numbers are not allowed. 160 | public InfluxdbLineWriter(InfluxConfig config, Int32 batchSize = 0) 161 | : base(batchSize) { 162 | this.config = config; 163 | if (config == null) 164 | throw new ArgumentNullException(nameof(config)); 165 | } 166 | 167 | /// 168 | /// Gets the byte representation of the in LineProtocol syntax using UTF8 encoding. 169 | /// 170 | /// The batch to get the bytes for. 171 | /// The byte representation of the batch. 172 | protected override Byte[] GetBatchBytes(InfluxBatch batch) { 173 | var strBatch = batch.ToLineProtocol(config.Precision); 174 | var bytes = Encoding.UTF8.GetBytes(strBatch); 175 | return bytes; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/InfluxdbBaseReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Metrics.MetricData; 5 | using Metrics.Logging; 6 | using Metrics.Reporters; 7 | using Metrics.InfluxDB.Model; 8 | using Metrics.InfluxDB.Adapters; 9 | 10 | namespace Metrics.InfluxDB 11 | { 12 | /// 13 | /// A metrics report that sends data to an InfluxDB server. 14 | /// This sends the data using the InfluxDB LineProtocol syntax over HTTP. 15 | /// 16 | public abstract class InfluxdbBaseReport : BaseReport 17 | { 18 | // TODO: loggers are internal in Metrics.NET 19 | //private static readonly ILog log = LogProvider.GetCurrentClassLogger(); 20 | 21 | private readonly InfluxConfig config; 22 | private readonly InfluxdbWriter writer; 23 | private readonly InfluxdbConverter converter; 24 | private readonly InfluxdbFormatter formatter; 25 | 26 | 27 | /// 28 | /// The configuration settings. 29 | /// 30 | public InfluxConfig Config { get { return config; } } 31 | 32 | /// 33 | /// Gets the used by this 34 | /// instance to convert Metrics.NET metrics into s. 35 | /// 36 | public InfluxdbConverter Converter { get { return converter; } } 37 | 38 | /// 39 | /// Gets the used by this 40 | /// instance to format identifier names. 41 | /// 42 | public InfluxdbFormatter Formatter { get { return formatter; } } 43 | 44 | /// 45 | /// Gets the used by this 46 | /// instance to write s to the InfluxDB server. 47 | /// 48 | public InfluxdbWriter Writer { get { return writer; } } 49 | 50 | 51 | /// 52 | /// Gets a new instance with the default values. 53 | /// 54 | public static InfluxdbBaseReport Default { get { return new DefaultInfluxdbReport(); } } 55 | 56 | 57 | 58 | /// 59 | /// Creates a new InfluxDB report that uses the LineProtocol syntax over the protocol specified by the URI schema. 60 | /// 61 | /// The URI of the InfluxDB server, including any query string parameters. 62 | public InfluxdbBaseReport(Uri influxDbUri) 63 | : this (new InfluxConfig(influxDbUri)) { 64 | } 65 | 66 | /// 67 | /// Creates a new InfluxDB report that uses the Line Protocol syntax. 68 | /// 69 | /// The InfluxDB configuration object. 70 | public InfluxdbBaseReport(InfluxConfig config = null) { 71 | this.config = GetDefaultConfig(config) ?? new InfluxConfig(); 72 | this.converter = config.Converter; 73 | this.formatter = config.Formatter; 74 | this.writer = config.Writer; 75 | ValidateConfig(this.config); 76 | } 77 | 78 | 79 | /// 80 | /// Returns an instance with the default configuration for the derived type's implementation. 81 | /// If is specified, the default converter, formatter, and 82 | /// writer are applied to it if any are not set; otherwise creates and returns a new instance. 83 | /// 84 | /// The configuration instance to apply the default converter, formatter, and writer to. 85 | /// A default instance for the derived type's implementation. 86 | protected virtual InfluxConfig GetDefaultConfig(InfluxConfig defaultConfig) { 87 | return defaultConfig; 88 | } 89 | 90 | /// 91 | /// Validates the configuration to ensure the configuration is valid and throws an exception on invalid settings. 92 | /// 93 | /// The configuration to validate. 94 | protected virtual void ValidateConfig(InfluxConfig config) { 95 | if (config.Converter == null) 96 | throw new ArgumentNullException(nameof(config.Converter), $"InfluxDB configuration invalid: {nameof(config.Converter)} cannot be null"); 97 | if (config.Formatter == null) 98 | throw new ArgumentNullException(nameof(config.Formatter), $"InfluxDB configuration invalid: {nameof(config.Formatter)} cannot be null"); 99 | if (config.Writer == null) 100 | throw new ArgumentNullException(nameof(config.Writer), $"InfluxDB configuration invalid: {nameof(config.Writer)} cannot be null"); 101 | 102 | //log.Debug($"Initialized InfluxDB reporter. Writer: {config.Writer.GetType().Name} Host: {config.Hostname}:{config.Port} Database: {config.Database}"); 103 | } 104 | 105 | 106 | /// 107 | protected override void StartReport(String contextName) { 108 | converter.Timestamp = ReportTimestamp; 109 | base.StartReport(contextName); 110 | } 111 | 112 | /// 113 | protected override void StartContext(String contextName) { 114 | converter.Timestamp = CurrentContextTimestamp; 115 | base.StartContext(contextName); 116 | } 117 | 118 | /// 119 | protected override void EndReport(String contextName) { 120 | base.EndReport(contextName); 121 | writer.Flush(); 122 | } 123 | 124 | 125 | 126 | /// 127 | protected override String FormatContextName(IEnumerable contextStack, String contextName) { 128 | return formatter?.FormatContextName(contextStack, contextName) ?? base.FormatContextName(contextStack, contextName); 129 | } 130 | 131 | /// 132 | protected override String FormatMetricName(String context, MetricValueSource metric) { 133 | return formatter?.FormatMetricName(context, metric.Name, metric.Unit, metric.Tags) ?? base.FormatMetricName(context, metric); 134 | } 135 | 136 | 137 | 138 | /// 139 | protected override void ReportGauge(String name, Double value, Unit unit, MetricTags tags) { 140 | writer.Write(converter.GetRecords(name, tags, unit, value).Select(r => formatter?.FormatRecord(r) ?? r)); 141 | } 142 | 143 | /// 144 | protected override void ReportCounter(String name, CounterValue value, Unit unit, MetricTags tags) { 145 | writer.Write(converter.GetRecords(name, tags, unit, value).Select(r => formatter?.FormatRecord(r) ?? r)); 146 | } 147 | 148 | /// 149 | protected override void ReportMeter(String name, MeterValue value, Unit unit, TimeUnit rateUnit, MetricTags tags) { 150 | writer.Write(converter.GetRecords(name, tags, unit, value).Select(r => formatter?.FormatRecord(r) ?? r)); 151 | } 152 | 153 | /// 154 | protected override void ReportHistogram(String name, HistogramValue value, Unit unit, MetricTags tags) { 155 | writer.Write(converter.GetRecords(name, tags, unit, value).Select(r => formatter?.FormatRecord(r) ?? r)); 156 | } 157 | 158 | /// 159 | protected override void ReportTimer(String name, TimerValue value, Unit unit, TimeUnit rateUnit, TimeUnit durationUnit, MetricTags tags) { 160 | writer.Write(converter.GetRecords(name, tags, unit, value).Select(r => formatter?.FormatRecord(r) ?? r)); 161 | } 162 | 163 | /// 164 | protected override void ReportHealth(HealthStatus status) { 165 | writer.Write(converter.GetRecords(status).Select(r => formatter?.FormatRecord(r) ?? r)); 166 | } 167 | 168 | } 169 | 170 | /// 171 | /// The default implementation of the using default settings. 172 | /// 173 | public class DefaultInfluxdbReport : InfluxdbBaseReport 174 | { 175 | /// 176 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over HTTP. 177 | /// 178 | /// The URI of the InfluxDB server, including any query string parameters. 179 | public DefaultInfluxdbReport(Uri influxDbUri) 180 | : base(influxDbUri) { 181 | } 182 | 183 | /// 184 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over HTTP. 185 | /// 186 | /// The InfluxDB configuration object. 187 | public DefaultInfluxdbReport(InfluxConfig config = null) 188 | : base(config) { 189 | } 190 | 191 | /// 192 | /// Gets the default configuration by setting the and 193 | /// properties to and if either is not set. 194 | /// 195 | /// The configuration to apply the settings to. If null, creates a new instance. 196 | /// A default instance for the derived type's implementation. 197 | protected override InfluxConfig GetDefaultConfig(InfluxConfig defaultConfig) { 198 | var config = base.GetDefaultConfig(defaultConfig) ?? new InfluxConfig(); 199 | config.Converter = config.Converter ?? new DefaultConverter(); 200 | config.Formatter = config.Formatter ?? new DefaultFormatter(); 201 | //config.Writer = config.Writer ?? new InfluxdbHttpWriter(config); 202 | return config; 203 | 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/InfluxdbHttpReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Metrics.InfluxDB.Model; 3 | using Metrics.InfluxDB.Adapters; 4 | 5 | namespace Metrics.InfluxDB 6 | { 7 | /// 8 | /// A metrics report that sends data to an InfluxDB server. 9 | /// This sends the data using the InfluxDB LineProtocol syntax over HTTP. 10 | /// 11 | public class InfluxdbHttpReport : DefaultInfluxdbReport 12 | { 13 | /// 14 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over HTTP. 15 | /// 16 | /// The URI of the InfluxDB server, including any query string parameters. 17 | public InfluxdbHttpReport(Uri influxDbUri) 18 | : base(influxDbUri) { 19 | } 20 | 21 | /// 22 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over HTTP. 23 | /// 24 | /// The InfluxDB configuration object. 25 | public InfluxdbHttpReport(InfluxConfig config = null) 26 | : base(config) { 27 | } 28 | 29 | /// 30 | /// Gets the default configuration by setting the 31 | /// property to a new instance if it is not set. 32 | /// 33 | /// The configuration to apply the settings to. If null, creates a new instance. 34 | /// A default instance for the derived type's implementation. 35 | protected override InfluxConfig GetDefaultConfig(InfluxConfig defaultConfig) { 36 | var config = base.GetDefaultConfig(defaultConfig) ?? new InfluxConfig(); 37 | config.Writer = config.Writer ?? new InfluxdbHttpWriter(config); 38 | return config; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/InfluxdbJsonReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Metrics.InfluxDB.Adapters; 3 | using Metrics.InfluxDB.Model; 4 | 5 | namespace Metrics.InfluxDB 6 | { 7 | /// 8 | /// A metrics report that sends data to an InfluxDB server. This sends the data using the InfluxDB JSON protocol over HTTP. 9 | /// NOTE: It is recommended to NOT use the JSON reporter because of performance issues, and support for JSON has been 10 | /// removed from InfluxDB in versions later than v0.9.1. It is recommended to use the HTTP or UDP reporters instead. 11 | /// See for more information: https://docs.influxdata.com/influxdb/v0.13/write_protocols/json/ 12 | /// 13 | public class InfluxdbJsonReport : InfluxdbBaseReport 14 | { 15 | /// 16 | /// Creates a new InfluxDB report that uses the JSON protocol over HTTP. 17 | /// NOTE: It is recommended to NOT use the JSON reporter because of performance issues, and support for JSON has been 18 | /// removed from InfluxDB in versions later than v0.9.1. It is recommended to use the HTTP or UDP reporters instead. 19 | /// See for more information: https://docs.influxdata.com/influxdb/v0.13/write_protocols/json/ 20 | /// 21 | /// The URI of the InfluxDB server, including any query string parameters. 22 | public InfluxdbJsonReport(Uri influxDbUri) 23 | : base(influxDbUri) { 24 | } 25 | 26 | /// 27 | /// Creates a new InfluxDB report that uses the JSON protocol over HTTP. 28 | /// NOTE: It is recommended to NOT use the JSON reporter because of performance issues, and support for JSON has been 29 | /// removed from InfluxDB in versions later than v0.9.1. It is recommended to use the HTTP or UDP reporters instead. 30 | /// See for more information: https://docs.influxdata.com/influxdb/v0.13/write_protocols/json/ 31 | /// 32 | /// The InfluxDB configuration object. 33 | public InfluxdbJsonReport(InfluxConfig config = null) 34 | : base(config) { 35 | } 36 | 37 | /// 38 | /// Gets the default configuration by setting the 39 | /// property to a new instance if it is not set. 40 | /// The is set to a no-op default formatter that does not modify identifier names, if the formatter is not set. 41 | /// The is set to because it is the only precision supported by the JSON protocol. 42 | /// 43 | /// The configuration to apply the settings to. If null, creates a new instance. 44 | /// A default instance for the derived type's implementation. 45 | protected override InfluxConfig GetDefaultConfig(InfluxConfig defaultConfig) { 46 | var config = base.GetDefaultConfig(defaultConfig) ?? new InfluxConfig(); 47 | config.Precision = InfluxPrecision.Seconds; // JSON only supports seconds 48 | config.Formatter = config.Formatter ?? GetDefaultFormatter(); 49 | config.Writer = config.Writer ?? new InfluxdbJsonWriter(config); 50 | return config; 51 | 52 | } 53 | 54 | private InfluxdbFormatter GetDefaultFormatter() { 55 | var formatter = new DefaultFormatter(false, null); 56 | formatter.ContextNameFormatter = null; 57 | formatter.MetricNameFormatter = null; 58 | formatter.TagKeyFormatter = null; 59 | formatter.FieldKeyFormatter = null; 60 | return formatter; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/InfluxdbReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using Metrics.Json; 6 | using Metrics.Reporters; 7 | using Metrics.Utils; 8 | 9 | namespace Metrics.InfluxDB 10 | { 11 | /// 12 | /// The old JSON based InfluxDB reporter. 13 | /// 14 | public class InfluxdbReport : BaseReport 15 | { 16 | private static readonly string[] GaugeColumns = new[] { "Value" }; 17 | private static readonly string[] CounterColumns = new[] { "Count" }; 18 | private static readonly string[] MeterColumns = new[] { "Total Count", "Mean Rate", "1 Min Rate", "5 Min Rate", "15 Min Rate", }; 19 | private static readonly string[] HistogramColumns = new[] 20 | { 21 | "Total Count", "Last", "Last User Value", "Min", "Min User Value", "Mean", "Max", "Max User Value", 22 | "StdDev", "Median", "Percentile 75%", "Percentile 95%", "Percentile 98%", "Percentile 99%", "Percentile 99.9%" , "Sample Size" }; 23 | 24 | private static readonly string[] TimerColumns = new[] 25 | { 26 | "Total Count", "Active Sessions", 27 | "Mean Rate", "1 Min Rate", "5 Min Rate", "15 Min Rate", 28 | "Last", "Last User Value", "Min", "Min User Value", "Mean", "Max", "Max User Value", 29 | "StdDev", "Median", "Percentile 75%", "Percentile 95%", "Percentile 98%", "Percentile 99%", "Percentile 99.9%" , "Sample Size" 30 | }; 31 | 32 | private readonly Uri influxdb; 33 | 34 | private List data; 35 | 36 | /// 37 | /// Creates a new InfluxDB reporter using the JSON protocol. 38 | /// 39 | /// The InfluxDB URI. 40 | public InfluxdbReport(Uri influxdb) 41 | { 42 | this.influxdb = influxdb; 43 | } 44 | 45 | private class InfluxRecord 46 | { 47 | public InfluxRecord(string name, long timestamp, IEnumerable columns, IEnumerable data) 48 | { 49 | var points = new[] { new LongJsonValue(timestamp) }.Concat(data); 50 | 51 | this.Json = new JsonObject(new[] { 52 | new JsonProperty("name",name), 53 | new JsonProperty("columns", new []{"time"}.Concat(columns)), 54 | new JsonProperty("points", new JsonValueArray( new [] { new JsonValueArray( points)})) 55 | }); 56 | } 57 | 58 | public JsonObject Json { get; } 59 | } 60 | 61 | private void Pack(string name, IEnumerable columns, JsonValue value) 62 | { 63 | Pack(name, columns, Enumerable.Repeat(value, 1)); 64 | } 65 | 66 | private void Pack(string name, IEnumerable columns, IEnumerable values) 67 | { 68 | this.data.Add(new InfluxRecord(name, CurrentContextTimestamp.ToUnixTime(), columns, values)); 69 | } 70 | 71 | private JsonValue Value(long value) 72 | { 73 | return new LongJsonValue(value); 74 | } 75 | 76 | private JsonValue Value(double value) 77 | { 78 | return new DoubleJsonValue(value); 79 | } 80 | 81 | private JsonValue Value(string value) 82 | { 83 | return new StringJsonValue(value); 84 | } 85 | 86 | /// 87 | protected override void StartReport(string contextName) 88 | { 89 | this.data = new List(); 90 | base.StartReport(contextName); 91 | } 92 | 93 | /// 94 | protected override void EndReport(string contextName) 95 | { 96 | base.EndReport(contextName); 97 | 98 | using (var client = new WebClient()) 99 | { 100 | var json = new CollectionJsonValue(data.Select(d => d.Json)).AsJson(); 101 | client.UploadString(this.influxdb, json); 102 | } 103 | this.data = null; 104 | } 105 | 106 | /// 107 | protected override void ReportGauge(string name, double value, Unit unit, MetricTags tags) 108 | { 109 | if (!double.IsNaN(value) && !double.IsInfinity(value)) 110 | { 111 | Pack(name, GaugeColumns, Value(value)); 112 | } 113 | } 114 | 115 | /// 116 | protected override void ReportCounter(string name, MetricData.CounterValue value, Unit unit, MetricTags tags) 117 | { 118 | var itemColumns = value.Items.SelectMany(i => new[] { i.Item + " - Count", i.Item + " - Percent" }); 119 | var columns = CounterColumns.Concat(itemColumns); 120 | 121 | var itemValues = value.Items.SelectMany(i => new[] { Value(i.Count), Value(i.Percent) }); 122 | var data = new[] { Value(value.Count) }.Concat(itemValues); 123 | 124 | Pack(name, columns, data); 125 | } 126 | 127 | /// 128 | protected override void ReportMeter(string name, MetricData.MeterValue value, Unit unit, TimeUnit rateUnit, MetricTags tags) 129 | { 130 | var itemColumns = value.Items.SelectMany(i => new[] 131 | { 132 | i.Item + " - Count", 133 | i.Item + " - Percent", 134 | i.Item + " - Mean Rate", 135 | i.Item + " - 1 Min Rate", 136 | i.Item + " - 5 Min Rate", 137 | i.Item + " - 15 Min Rate" 138 | }); 139 | var columns = MeterColumns.Concat(itemColumns); 140 | 141 | var itemValues = value.Items.SelectMany(i => new[] 142 | { 143 | Value(i.Value.Count), 144 | Value(i.Percent), 145 | Value(i.Value.MeanRate), 146 | Value(i.Value.OneMinuteRate), 147 | Value(i.Value.FiveMinuteRate), 148 | Value(i.Value.FifteenMinuteRate) 149 | }); 150 | 151 | var data = new[] 152 | { 153 | Value(value.Count), 154 | Value (value.MeanRate), 155 | Value (value.OneMinuteRate), 156 | Value (value.FiveMinuteRate), 157 | Value (value.FifteenMinuteRate) 158 | }.Concat(itemValues); 159 | 160 | Pack(name, columns, data); 161 | } 162 | 163 | /// 164 | protected override void ReportHistogram(string name, MetricData.HistogramValue value, Unit unit, MetricTags tags) 165 | { 166 | Pack(name, HistogramColumns, new[]{ 167 | Value(value.Count), 168 | Value(value.LastValue), 169 | Value(value.LastUserValue), 170 | Value(value.Min), 171 | Value(value.MinUserValue), 172 | Value(value.Mean), 173 | Value(value.Max), 174 | Value(value.MaxUserValue), 175 | Value(value.StdDev), 176 | Value(value.Median), 177 | Value(value.Percentile75), 178 | Value(value.Percentile95), 179 | Value(value.Percentile98), 180 | Value(value.Percentile99), 181 | Value(value.Percentile999), 182 | Value(value.SampleSize) 183 | }); 184 | } 185 | 186 | /// 187 | protected override void ReportTimer(string name, MetricData.TimerValue value, Unit unit, TimeUnit rateUnit, TimeUnit durationUnit, MetricTags tags) 188 | { 189 | Pack(name, TimerColumns, new[]{ 190 | 191 | Value(value.Rate.Count), 192 | Value(value.ActiveSessions), 193 | Value(value.Rate.MeanRate), 194 | Value(value.Rate.OneMinuteRate), 195 | Value(value.Rate.FiveMinuteRate), 196 | Value(value.Rate.FifteenMinuteRate), 197 | Value(value.Histogram.LastValue), 198 | Value(value.Histogram.LastUserValue), 199 | Value(value.Histogram.Min), 200 | Value(value.Histogram.MinUserValue), 201 | Value(value.Histogram.Mean), 202 | Value(value.Histogram.Max), 203 | Value(value.Histogram.MaxUserValue), 204 | Value(value.Histogram.StdDev), 205 | Value(value.Histogram.Median), 206 | Value(value.Histogram.Percentile75), 207 | Value(value.Histogram.Percentile95), 208 | Value(value.Histogram.Percentile98), 209 | Value(value.Histogram.Percentile99), 210 | Value(value.Histogram.Percentile999), 211 | Value(value.Histogram.SampleSize) 212 | }); 213 | } 214 | 215 | /// 216 | protected override void ReportHealth(HealthStatus status) 217 | { 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/InfluxdbUdpReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Metrics.InfluxDB.Model; 3 | using Metrics.InfluxDB.Adapters; 4 | 5 | namespace Metrics.InfluxDB 6 | { 7 | /// 8 | /// A metrics report that sends data to an InfluxDB server. 9 | /// This sends the data using the InfluxDB LineProtocol syntax over UDP. 10 | /// 11 | public class InfluxdbUdpReport : DefaultInfluxdbReport 12 | { 13 | /// 14 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over UDP. 15 | /// 16 | /// The UDP URI of the InfluxDB server. 17 | public InfluxdbUdpReport(Uri influxDbUri) 18 | : base(influxDbUri) { 19 | } 20 | 21 | /// 22 | /// Creates a new InfluxDB report that uses the Line Protocol syntax over UDP. 23 | /// 24 | /// The InfluxDB configuration object. 25 | public InfluxdbUdpReport(InfluxConfig config = null) 26 | : base(config) { 27 | } 28 | 29 | /// 30 | /// Gets the default configuration by setting the 31 | /// property to a new instance if it is not set. 32 | /// 33 | /// The configuration to apply the settings to. If null, creates a new instance. 34 | /// A default instance for the derived type's implementation. 35 | protected override InfluxConfig GetDefaultConfig(InfluxConfig defaultConfig) { 36 | var config = base.GetDefaultConfig(defaultConfig) ?? new InfluxConfig(); 37 | config.Writer = config.Writer ?? new InfluxdbUdpWriter(config); 38 | return config; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Metrics.InfluxDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1B6C2147-30DB-4C58-AD92-5FD34937F9A4} 8 | Library 9 | Properties 10 | Metrics.InfluxDB 11 | Metrics.InfluxDB 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\..\bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | ..\..\bin\Debug\Metrics.InfluxDB.xml 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | ..\..\bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | ..\..\bin\Release\Metrics.InfluxDB.xml 35 | true 36 | 37 | 38 | 39 | ..\..\packages\Metrics.NET.0.5.0-pre\lib\net45\Metrics.dll 40 | True 41 | 42 | 43 | 44 | 45 | 46 | Properties\SharedAssemblyInfo.cs 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 | Designer 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Metrics.InfluxDB.Model 5 | { 6 | /// 7 | /// A collection of elements that exposes helper methods to 8 | /// genereate a batch insert query string formatted in the InfluxDB line protocol syntax. 9 | /// 10 | public class InfluxBatch : List 11 | { 12 | /// 13 | /// Creates a new that is empty and has the default values. 14 | /// 15 | public InfluxBatch() 16 | : base() { 17 | } 18 | 19 | /// 20 | /// Creates a new that is empty and has the specified initial capacity. 21 | /// 22 | /// The number of elements that the collection can initially store. 23 | public InfluxBatch(Int32 capacity) 24 | : base(capacity) { 25 | } 26 | 27 | /// 28 | /// Creates a new that contains elements copied from the specified collection. 29 | /// 30 | /// The collection whose elements are copied to the new batch. 31 | public InfluxBatch(IEnumerable collection) 32 | : base(collection) { 33 | } 34 | 35 | 36 | /// 37 | /// Converts the to a string in the line protocol syntax. 38 | /// Each record is separated by a newline character, but the complete output does not end in one. 39 | /// 40 | /// A string representing all records in the batch formatted in the line protocol format. 41 | public override String ToString() { 42 | return this.ToLineProtocol(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Metrics.InfluxDB.Adapters; 4 | 5 | namespace Metrics.InfluxDB.Model 6 | { 7 | /// 8 | /// Configuration for the InfluxDB reporter. 9 | /// 10 | public class InfluxConfig 11 | { 12 | 13 | #region Default Values 14 | 15 | /// 16 | /// Default InfluxDB configuration settings. 17 | /// 18 | public static class Default 19 | { 20 | /// 21 | /// The default port when using the HTTP protocol to connect to the InfluxDB server. This value is: 8086 22 | /// 23 | public static UInt16 PortHttp { get; } 24 | 25 | /// 26 | /// The default specifier value. This value is: 27 | /// 28 | public static InfluxPrecision Precision { get; } 29 | 30 | static Default() { 31 | PortHttp = 8086; 32 | Precision = InfluxPrecision.Seconds; 33 | } 34 | } 35 | 36 | #endregion 37 | 38 | #region InfluxDB Server Connection Settings 39 | 40 | /// 41 | /// The hostname of the InfluxDB server. 42 | /// 43 | public String Hostname { get; set; } 44 | 45 | /// 46 | /// The port of the InfluxDB server. Set to null to use the default 47 | /// port for the chosen transport and protocol, if there is one. 48 | /// 49 | public UInt16? Port { get; set; } 50 | 51 | /// 52 | /// The name of the database on the InfluxDB server to write the datapoints to. 53 | /// 54 | public String Database { get; set; } 55 | 56 | /// 57 | /// The username used to authenticate with the InfluxDB server, if authentication is required. 58 | /// 59 | public String Username { get; set; } 60 | 61 | /// 62 | /// The password used to authenticate with the InfluxDB server, if authentication is required. 63 | /// 64 | public String Password { get; set; } 65 | 66 | /// 67 | /// The retention policy to use when writing datapoints to the InfluxDB database. 68 | /// If this is null, the database's default retention policy is used. 69 | /// 70 | public String RetentionPolicy { get; set; } 71 | 72 | /// 73 | /// The precision of the timestamp value in the line protocol syntax. 74 | /// It is recommended to use as large a precision as possible to improve compression and bandwidth usage. 75 | /// 76 | public InfluxPrecision? Precision { get; set; } 77 | 78 | #endregion 79 | 80 | #region InfluxDB Adapter Instances 81 | 82 | /// 83 | /// The is used to convert metric values into objects. 84 | /// 85 | public InfluxdbConverter Converter { get; set; } 86 | 87 | /// 88 | /// The is used to format the context and metric names into strings which are 89 | /// used as the table name to insert s into. This also optionally formats the tag 90 | /// and field keys into column names by converting the case and replacing spaces with another character. 91 | /// 92 | public InfluxdbFormatter Formatter { get; set; } 93 | 94 | /// 95 | /// The used to write formatted s to the database. 96 | /// 97 | public InfluxdbWriter Writer { get; set; } 98 | 99 | #endregion 100 | 101 | #region Constructors 102 | 103 | /// 104 | /// Creates a new InfluxDB configuration object with default values. 105 | /// 106 | public InfluxConfig() 107 | : this(null, null) { 108 | } 109 | 110 | /// 111 | /// Creates a new InfluxDB configuration object with the specified hostname and database. 112 | /// 113 | /// The hostname or IP address of the InfluxDB server. 114 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 115 | public InfluxConfig(String host, String database) 116 | : this(host, null, database) { 117 | } 118 | 119 | /// 120 | /// Creates a new InfluxDB configuration object with the specified hostname, database, and precision. 121 | /// 122 | /// The hostname or IP address of the InfluxDB server. 123 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 124 | /// The precision of the timestamp value in the line protocol syntax. 125 | public InfluxConfig(String host, String database, InfluxPrecision? precision) 126 | : this(host, null, database, null, precision) { 127 | } 128 | 129 | /// 130 | /// Creates a new InfluxDB configuration object with the specified hostname, database, retention policy, and precision. 131 | /// 132 | /// The hostname or IP address of the InfluxDB server. 133 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 134 | /// The retention policy to use when writing datapoints to the InfluxDB database, or null to use the database's default retention policy. 135 | /// The precision of the timestamp value in the line protocol syntax. 136 | public InfluxConfig(String host, String database, String retentionPolicy, InfluxPrecision? precision) 137 | : this(host, null, database, retentionPolicy, precision) { 138 | } 139 | 140 | /// 141 | /// Creates a new InfluxDB configuration object with the specified hostname, port, and database. 142 | /// 143 | /// The hostname or IP address of the InfluxDB server. 144 | /// The port number to connect to on the InfluxDB server, or null to use the default port number. 145 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 146 | public InfluxConfig(String host, UInt16? port, String database) 147 | : this(host, port, database, null, null) { 148 | } 149 | 150 | /// 151 | /// Creates a new InfluxDB configuration object with the specified hostname, port, database, retention policy, and precision. 152 | /// 153 | /// The hostname or IP address of the InfluxDB server. 154 | /// The port number to connect to on the InfluxDB server, or null to use the default port number. 155 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 156 | /// The retention policy to use when writing datapoints to the InfluxDB database, or null to use the database's default retention policy. 157 | /// The precision of the timestamp value in the line protocol syntax. 158 | public InfluxConfig(String host, UInt16? port, String database, String retentionPolicy, InfluxPrecision? precision) 159 | : this(host, port, database, null, null, retentionPolicy, precision) { 160 | } 161 | 162 | /// 163 | /// Creates a new InfluxDB configuration object with the specified hostname, port, database, retention policy, precision, and credentials. 164 | /// 165 | /// The hostname or IP address of the InfluxDB server. 166 | /// The port number to connect to on the InfluxDB server, or null to use the default port number. 167 | /// The database name to write values to. This should be null if using UDP since the database is defined in the UDP endpoint configuration on the InfluxDB server. 168 | /// The username to use to connect to the InfluxDB server, or null if authentication is not used. 169 | /// The password to use to connect to the InfluxDB server, or null if authentication is not used. 170 | /// The retention policy to use when writing datapoints to the InfluxDB database, or null to use the database's default retention policy. 171 | /// The precision of the timestamp value in the line protocol syntax. 172 | public InfluxConfig(String host, UInt16? port, String database, String username, String password, String retentionPolicy, InfluxPrecision? precision) { 173 | this.Hostname = host; 174 | this.Port = port; 175 | this.Database = database; 176 | this.Username = username; 177 | this.Password = password; 178 | this.RetentionPolicy = retentionPolicy; 179 | this.Precision = precision; 180 | } 181 | 182 | /// 183 | /// Creates a new InfluxDB configuration object with values initialized from the URI. 184 | /// 185 | /// The URI of the InfluxDB server, including any query string parameters. 186 | public InfluxConfig(Uri influxDbUri) { 187 | if (influxDbUri == null) 188 | throw new ArgumentNullException(nameof(influxDbUri)); 189 | Hostname = influxDbUri.Host; 190 | Port = (UInt16)influxDbUri.Port; 191 | var queryKvps = influxDbUri.ParseQueryString(); 192 | foreach (var kvp in queryKvps.Where(k => !String.IsNullOrEmpty(k.Value))) { 193 | if (kvp.Key.ToLower() == "db") Database = kvp.Value; 194 | if (kvp.Key.ToLower() == "rp") RetentionPolicy = kvp.Value; 195 | if (kvp.Key.ToLower() == "u") Username = kvp.Value; 196 | if (kvp.Key.ToLower() == "p") Password = kvp.Value; 197 | if (kvp.Key.ToLower() == "precision") Precision = InfluxUtils.FromShortName(kvp.Value); 198 | } 199 | } 200 | 201 | #endregion 202 | 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Metrics.InfluxDB.Model 4 | { 5 | /// 6 | /// An InfluxDB field key/value pair which can be added to an . 7 | /// 8 | public struct InfluxField : IEquatable 9 | { 10 | /// 11 | /// The field key. 12 | /// 13 | public String Key { get; } 14 | 15 | /// 16 | /// The field value. 17 | /// 18 | public Object Value { get; } 19 | 20 | /// 21 | /// Returns true if this instance is equal to the Empty instance. 22 | /// 23 | public Boolean IsEmpty { get { return Empty.Equals(this); } } 24 | 25 | /// 26 | /// An empty . 27 | /// 28 | public static readonly InfluxField Empty = new InfluxField { }; 29 | 30 | 31 | /// 32 | /// Creates a new with the specified key and value. 33 | /// 34 | /// The field key name. 35 | /// The field value. 36 | public InfluxField(String key, Object value) { 37 | if (String.IsNullOrWhiteSpace(key)) 38 | throw new ArgumentNullException(nameof(key)); 39 | if (value == null || (value is String && String.IsNullOrWhiteSpace((String)value))) 40 | throw new ArgumentNullException(nameof(value)); 41 | 42 | this.Key = key; 43 | this.Value = value; 44 | } 45 | 46 | /// 47 | /// Converts the to a string in the line protocol format. 48 | /// 49 | /// A string representing the field in the line protocol format. 50 | public override String ToString() { 51 | return this.ToLineProtocol(); 52 | } 53 | 54 | 55 | #region Equality Methods 56 | 57 | /// 58 | /// Returns true if the specified object is an InfluxTag object and both the key and value are equal. 59 | /// 60 | /// The object to compare. 61 | /// true if the two objects are equal; false otherwise. 62 | public override Boolean Equals(Object obj) { 63 | return obj is InfluxField && Equals((InfluxField)obj); 64 | } 65 | 66 | /// 67 | /// Returns true if both the key and value are equal. 68 | /// 69 | /// The to compare. 70 | /// true if the two objects are equal; false otherwise. 71 | public Boolean Equals(InfluxField other) { 72 | return other.Key == this.Key && other.Value == this.Value; 73 | } 74 | 75 | /// 76 | /// Gets the hash code of the key. 77 | /// 78 | /// The hash code of the key. 79 | public override Int32 GetHashCode() { 80 | return Key.GetHashCode(); 81 | } 82 | 83 | /// 84 | /// Returns true if both the key and value are equal. 85 | /// 86 | /// The first field. 87 | /// The second field. 88 | /// true if the two objects are equal; false otherwise. 89 | public static bool operator ==(InfluxField f1, InfluxField f2) { 90 | return f1.Equals(f2); 91 | } 92 | 93 | /// 94 | /// Returns true if either the keys or values are not equal. 95 | /// 96 | /// The first field. 97 | /// The second field. 98 | /// true if the two objects are not equal; false otherwise. 99 | public static bool operator !=(InfluxField f1, InfluxField f2) { 100 | return !f1.Equals(f2); 101 | } 102 | 103 | #endregion 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxLineProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Metrics.InfluxDB.Model 7 | { 8 | /// 9 | /// Defines static methods to convert influx tags, fields, records, and batches to a string in the line protocol format. 10 | /// 11 | public static class InfluxLineProtocol 12 | { 13 | 14 | #region Convert InfluxDB model objects to LineProtocol syntax 15 | 16 | /// 17 | /// Converts the to a string in the line protocol format. 18 | /// 19 | /// The to generate the line protocol string for. 20 | /// A string representing the tag in the line protocol format. 21 | public static String ToLineProtocol(this InfluxTag tag) { 22 | if (tag.IsEmpty) throw new ArgumentNullException(nameof(tag)); 23 | String key = EscapeValue(tag.Key); 24 | String val = EscapeValue(tag.Value); 25 | return $"{key}={val}"; 26 | } 27 | 28 | /// 29 | /// Converts the to a string in the line protocol format. 30 | /// 31 | /// The to generate the line protocol string for. 32 | /// A string representing the field in the line protocol format. 33 | public static String ToLineProtocol(this InfluxField field) { 34 | if (field.IsEmpty) throw new ArgumentNullException(nameof(field)); 35 | String key = EscapeValue(field.Key); 36 | String val = FormatValue(field.Value); 37 | return $"{key}={val}"; 38 | } 39 | 40 | /// 41 | /// Converts the to a string in the line protocol format. 42 | /// The returned string does not end in a newline character. 43 | /// 44 | /// The to generate the line protocol string for. 45 | /// The timestamp precision to use in the LineProtocol syntax. If null, the default precision is used. 46 | /// A string representing the record in the line protocol format. 47 | /// Creates output in line protocol syntax (tags and timestamp are optional): 48 | /// measurement[,tag_key1=tag_value1...] field_key=field_value[,field_key2=field_value2] [timestamp] 49 | /// According to the InfluxDB docs, sorted tags get a performance boost when inserting data, see: 50 | /// https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/#key 51 | /// 52 | public static String ToLineProtocol(this InfluxRecord record, InfluxPrecision? precision = null) { 53 | if (record == null) 54 | throw new ArgumentNullException(nameof(record)); 55 | if (String.IsNullOrWhiteSpace(record.Name)) 56 | throw new ArgumentNullException(nameof(record.Name), "The measurement name must be specified."); 57 | if (record.Fields.Count == 0) 58 | throw new ArgumentNullException(nameof(record.Fields), $"Must specify at least one field. Metric name: {record.Name}"); 59 | 60 | StringBuilder sb = new StringBuilder(); 61 | sb.Append(EscapeValue(record.Name)); 62 | var sortedTags = record.Tags.OrderBy(t => t.Key); // sort for better insert performance, recommended by influxdb docs 63 | foreach (var tag in sortedTags) sb.AppendFormat(",{0}", tag.ToLineProtocol()); 64 | sb.AppendFormat(" {0}", String.Join(",", record.Fields.Select(f => f.ToLineProtocol()))); 65 | if (record.Timestamp.HasValue) sb.AppendFormat(" {0}", FormatTimestamp(record.Timestamp.Value, precision ?? InfluxConfig.Default.Precision)); 66 | return sb.ToString(); 67 | } 68 | 69 | /// 70 | /// Converts the to a string in the line protocol syntax. 71 | /// Each record is separated by a newline character, but the complete output does not end in a newline. 72 | /// 73 | /// The to generate the line protocol string for. 74 | /// The timestamp precision to use in the LineProtocol syntax. If null, the default precision is used. 75 | /// A string representing all records in the batch formatted in the line protocol format. 76 | public static String ToLineProtocol(this InfluxBatch batch, InfluxPrecision? precision = null) { 77 | if (batch == null) throw new ArgumentNullException(nameof(batch)); 78 | return String.Join("\n", batch.Select(r => r.ToLineProtocol(precision))); 79 | } 80 | 81 | #endregion 82 | 83 | #region Format Field Values 84 | 85 | /// 86 | /// Formats the field value in the appropriate line protocol format based on the type of the value object. 87 | /// The value type must be a string, boolean, or integral or floating-point type. 88 | /// 89 | /// The field value to format. 90 | /// The field value formatted as a string used in the line protocol format. 91 | public static String FormatValue(Object value) { 92 | Type type = value?.GetType(); 93 | if (value == null) 94 | throw new ArgumentNullException(nameof(value)); 95 | if (!InfluxUtils.IsValidValueType(type)) 96 | throw new ArgumentException(nameof(value), $"Value is not one of the supported types: {type} - Valid types: {String.Join(", ", InfluxUtils.ValidValueTypes.Select(t => t.Name))}"); 97 | 98 | if (InfluxUtils.IsIntegralType(type)) 99 | return FormatValue(Convert.ToInt64(value)); 100 | if (InfluxUtils.IsFloatingPointType(type)) 101 | return FormatValue(Convert.ToDouble(value)); 102 | if (value is Boolean) 103 | return FormatValue((Boolean)value); 104 | if (value is String) 105 | return FormatValue((String)value); 106 | if (value is Char) 107 | return FormatValue(value.ToString()); 108 | return FormatValue(value.ToString()); 109 | } 110 | 111 | /// 112 | /// Formats the integral value to a string used in the line protocol format. 113 | /// 114 | /// The integral value to format. 115 | /// The value formatted as a string used in the line protocol format. 116 | public static String FormatValue(Int64 value) { 117 | return String.Format(CultureInfo.InvariantCulture, "{0:d}i", value); 118 | } 119 | 120 | /// 121 | /// Formats the floating-point value to a string used in the line protocol format. 122 | /// 123 | /// The floating-point value to format. 124 | /// The value formatted as a string used in the line protocol format. 125 | public static String FormatValue(Double value) { 126 | return String.Format(CultureInfo.InvariantCulture, "{0:r}", value); // use 'r' round-trip format specifier to avoid rounding/truncating in output 127 | } 128 | 129 | /// 130 | /// Formats the boolean value to a True or False string used in the line protocol format. 131 | /// 132 | /// The boolean value to format. 133 | /// The value formatted as a string used in the line protocol format. 134 | public static String FormatValue(Boolean value) { 135 | // returns Boolean.TrueString or Boolean.FalseString (ie. "True" or "False") 136 | return value.ToString(); 137 | } 138 | 139 | /// 140 | /// Formats the string by enclosing it in quotes and escaping any existing quotes within the string. 141 | /// This method should only be used when formatting string field values, but not tag values. 142 | /// 143 | /// The string field value to format. 144 | /// The specified string field value properly escaped and enclosed in quotes. 145 | public static String FormatValue(String value) { 146 | if (String.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value)); 147 | String escapeQuote = value.Replace("\"", @"\"""); 148 | return String.Format("\"{0}\"", escapeQuote); 149 | } 150 | 151 | /// 152 | /// Escapes the value by replacing spaces, commas, and equal signs with a leading backslash. 153 | /// 154 | /// The value to escape. 155 | /// The escaped value. 156 | public static String EscapeValue(String value) { 157 | if (String.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value)); 158 | return value.Replace(" ", @"\ ").Replace(",", @"\,").Replace("=", @"\="); 159 | } 160 | 161 | 162 | private static readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 163 | 164 | /// 165 | /// Formats the timestamp to an integer string with the specified precision. 166 | /// 167 | /// The timestamp to format. 168 | /// The precision to format the timestamp to. 169 | /// The timestamp formatted to a string with the specified precision. 170 | public static String FormatTimestamp(DateTime timestamp, InfluxPrecision precision) { 171 | if (timestamp < unixEpoch) 172 | throw new ArgumentOutOfRangeException(nameof(timestamp), "The timestamp cannot be earlier than the UNIX epoch (1970/1/1)."); 173 | 174 | Int64 longTime = 0L; 175 | TimeSpan sinceEpoch = timestamp - unixEpoch; 176 | switch (precision) { 177 | case InfluxPrecision.Nanoseconds: longTime = sinceEpoch.Ticks * 100L; break; // 100ns per tick 178 | case InfluxPrecision.Microseconds: longTime = sinceEpoch.Ticks / 10L; break; // 10 ticks per us 179 | case InfluxPrecision.Milliseconds: longTime = sinceEpoch.Ticks / TimeSpan.TicksPerMillisecond; break; 180 | case InfluxPrecision.Seconds: longTime = sinceEpoch.Ticks / TimeSpan.TicksPerSecond; break; 181 | case InfluxPrecision.Minutes: longTime = sinceEpoch.Ticks / TimeSpan.TicksPerMinute; break; 182 | case InfluxPrecision.Hours: longTime = sinceEpoch.Ticks / TimeSpan.TicksPerHour; break; 183 | default: throw new ArgumentException(nameof(precision), $"Invalid timestamp precision: {precision}"); 184 | } 185 | 186 | return String.Format(CultureInfo.InvariantCulture, "{0:d}", longTime); 187 | } 188 | 189 | #endregion 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxPrecision.cs: -------------------------------------------------------------------------------- 1 | namespace Metrics.InfluxDB.Model 2 | { 3 | /// 4 | /// The precision to format timestamp values in the InfluxDB LineProtocol syntax. 5 | /// 6 | public enum InfluxPrecision 7 | { 8 | /// 9 | /// Nanosecond precision. This is the default precision used by InfluxDB when no precision specifier is defined. 10 | /// 11 | Nanoseconds = 0, 12 | 13 | /// 14 | /// Microsecond precision. 15 | /// 16 | Microseconds, 17 | 18 | /// 19 | /// Millisecond precision. 20 | /// 21 | Milliseconds, 22 | 23 | /// 24 | /// Second precision. 25 | /// 26 | Seconds, 27 | 28 | /// 29 | /// Minute precision. 30 | /// 31 | Minutes, 32 | 33 | /// 34 | /// Hour precision. 35 | /// 36 | Hours 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Metrics.InfluxDB.Model 6 | { 7 | /// 8 | /// A single InfluxDB record that defines the name, tags, fields, and timestamp values to insert into InfluxDB. 9 | /// 10 | public class InfluxRecord 11 | { 12 | /// 13 | /// The measurement or series name. This value is required. 14 | /// 15 | public String Name { get; set; } 16 | 17 | /// 18 | /// A list of tag key/value pairs associated with this record. This value is optional. 19 | /// 20 | public List Tags { get; } 21 | 22 | /// 23 | /// A list of field key/value pairs associated with this record. 24 | /// This field is required, at least one field must be specified. 25 | /// 26 | public List Fields { get; } 27 | 28 | /// 29 | /// The record timestamp. This value is optional. If this is null the timestamp is not included 30 | /// in the line value and the current timestamp will be used by default by the InfluxDB database. 31 | /// 32 | public DateTime? Timestamp { get; set; } 33 | 34 | 35 | /// 36 | /// Creates a new . 37 | /// 38 | /// The measurement or series name. This value is required and cannot be null or empty. 39 | /// The field values for this record. 40 | /// The optional timestamp for this record. 41 | public InfluxRecord(String name, IEnumerable fields, DateTime? timestamp = null) 42 | : this(name, null, fields, timestamp) { 43 | } 44 | 45 | /// 46 | /// Creates a new . 47 | /// 48 | /// The measurement or series name. This value is required and cannot be null or empty. 49 | /// The optional tags to associate with this record. 50 | /// The field values for this record. 51 | /// The optional timestamp for this record. 52 | public InfluxRecord(String name, IEnumerable tags, IEnumerable fields, DateTime? timestamp = null) { 53 | Name = name ?? String.Empty; 54 | Timestamp = timestamp; 55 | Tags = tags?.ToList() ?? new List(); 56 | Fields = fields?.ToList() ?? new List(); 57 | } 58 | 59 | 60 | /// 61 | /// Converts the to a string in the line protocol syntax. 62 | /// The returned string does not end in a newline character. 63 | /// 64 | /// A string representing the record in the line protocol format. 65 | public override String ToString() { 66 | return this.ToLineProtocol(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Metrics.InfluxDB.Model 4 | { 5 | /// 6 | /// An InfluxDB tag key/value pair which can be added to an . 7 | /// 8 | public struct InfluxTag : IEquatable 9 | { 10 | /// 11 | /// The tag key. 12 | /// 13 | public String Key { get; } 14 | 15 | /// 16 | /// The tag value. 17 | /// 18 | public String Value { get; } 19 | 20 | /// 21 | /// Returns true if this instance is equal to the Empty instance. 22 | /// 23 | public Boolean IsEmpty { get { return Empty.Equals(this); } } 24 | 25 | /// 26 | /// An empty . 27 | /// 28 | public static readonly InfluxTag Empty = new InfluxTag { }; 29 | 30 | 31 | /// 32 | /// Creates a new from the specified key/value pair. 33 | /// Both the key and value are required and cannot be null or empty. 34 | /// 35 | /// The tag key name. 36 | /// The tag value. 37 | public InfluxTag(String key, String value) { 38 | if (String.IsNullOrWhiteSpace(key)) 39 | throw new ArgumentNullException(nameof(key)); 40 | if (String.IsNullOrWhiteSpace(value)) 41 | throw new ArgumentNullException(nameof(value)); 42 | 43 | this.Key = key; 44 | this.Value = value; 45 | } 46 | 47 | 48 | /// 49 | /// Converts the to a string in the line protocol format. 50 | /// 51 | /// A string representing the tag in the line protocol format. 52 | public override String ToString() { 53 | return this.ToLineProtocol(); 54 | } 55 | 56 | 57 | #region Equality Methods 58 | 59 | /// 60 | /// Returns true if the specified object is an object and both the key and value are equal. 61 | /// 62 | /// The object to compare. 63 | /// true if the two objects are equal; false otherwise. 64 | public override Boolean Equals(Object obj) { 65 | return obj is InfluxTag && Equals((InfluxTag)obj); 66 | } 67 | 68 | /// 69 | /// Returns true if both the key and value are equal. 70 | /// 71 | /// The to compare. 72 | /// true if the two objects are equal; false otherwise. 73 | public Boolean Equals(InfluxTag other) { 74 | return other.Key == this.Key && other.Value == this.Value; 75 | } 76 | 77 | /// 78 | /// Gets the hash code of the key. 79 | /// 80 | /// The hash code of the key. 81 | public override Int32 GetHashCode() { 82 | return Key.GetHashCode(); 83 | } 84 | 85 | 86 | /// 87 | /// Returns true if both the key and value are equal. 88 | /// 89 | /// The first tag. 90 | /// The second tag. 91 | /// true if the two objects are equal; false otherwise. 92 | public static bool operator ==(InfluxTag t1, InfluxTag t2) { 93 | return t1.Equals(t2); 94 | } 95 | 96 | /// 97 | /// Returns true if either the keys or values are not equal. 98 | /// 99 | /// The first tag. 100 | /// The second tag. 101 | /// true if the two objects are not equal; false otherwise. 102 | public static bool operator !=(InfluxTag t1, InfluxTag t2) { 103 | return !t1.Equals(t2); 104 | } 105 | 106 | #endregion 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Model/InfluxUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Metrics.InfluxDB.Model 7 | { 8 | /// 9 | /// Helper methods for InfluxDB. 10 | /// 11 | public static class InfluxUtils 12 | { 13 | 14 | private const String RegexUnescSpace = @"(? 22 | /// Converts the string to lowercase and replaces all spaces with the specified character (underscore by default). 23 | /// 24 | /// The string value to lowercase and replace spaces on. 25 | /// If true, converts the string to lowercase. 26 | /// The character(s) to replace all space characters with (underscore by default). If , removes all spaces. If null, spaces are not replaced. 27 | /// A copy of the string converted to lowercase with all spaces replaced with the specified character. 28 | public static String LowerAndReplaceSpaces(String value, Boolean lowercase = true, String replaceChars = "_") { 29 | if (value == null) throw new ArgumentNullException(nameof(value)); 30 | if (lowercase) value = value.ToLowerInvariant(); 31 | if (replaceChars != null) value = Regex.Replace(value, RegexUnescSpace, replaceChars); // doesn't replace spaces preceded by a '\' (ie. escaped spaces like\ this) 32 | return value; 33 | } 34 | 35 | /// 36 | /// Gets the short name (n,u,ms,s,m,h) for the InfluxDB precision specifier to be used in the URI query string. 37 | /// 38 | /// The precision to get the short name for. 39 | /// The short name for the value. 40 | public static String ToShortName(this InfluxPrecision precision) { 41 | switch (precision) { 42 | case InfluxPrecision.Nanoseconds: return "n"; 43 | case InfluxPrecision.Microseconds: return "u"; 44 | case InfluxPrecision.Milliseconds: return "ms"; 45 | case InfluxPrecision.Seconds: return "s"; 46 | case InfluxPrecision.Minutes: return "m"; 47 | case InfluxPrecision.Hours: return "h"; 48 | default: throw new ArgumentException(nameof(precision), $"Invalid timestamp precision: {precision}"); 49 | } 50 | } 51 | 52 | /// 53 | /// Gets the from the short name (n,u,ms,s,m,h) retrieved using . 54 | /// 55 | /// The short name of the precision specifier (n,u,ms,s,m,h). 56 | /// The for the specified short name. 57 | public static InfluxPrecision FromShortName(String precision) { 58 | switch (precision) { 59 | case "n": return InfluxPrecision.Nanoseconds; 60 | case "u": return InfluxPrecision.Microseconds; 61 | case "ms": return InfluxPrecision.Milliseconds; 62 | case "s": return InfluxPrecision.Seconds; 63 | case "m": return InfluxPrecision.Minutes; 64 | case "h": return InfluxPrecision.Hours; 65 | default: throw new ArgumentException(nameof(precision), $"Invalid precision specifier: {precision}"); 66 | } 67 | } 68 | 69 | #endregion 70 | 71 | #region Parse InfluxTags 72 | 73 | /// 74 | /// Parses the MetricTags into s. Returns an for each tag that is in the format: {key}={value}. 75 | /// 76 | /// The tags to parse into s objects. 77 | /// A sequence of s parsed from the specified . 78 | public static IEnumerable ToInfluxTags(params MetricTags[] tags) { 79 | return tags.SelectMany(t => t.Tags).Select(ToInfluxTag).Where(t => !t.IsEmpty); 80 | } 81 | 82 | /// 83 | /// Parses the specified tags into a sequence of s. 84 | /// 85 | /// The tags to parse into a sequence of s. 86 | /// The specified tags parsed into a sequence of s. 87 | public static IEnumerable ToInfluxTags(params String[] tags) { 88 | return tags.Select(ToInfluxTag).Where(t => !t.IsEmpty); 89 | } 90 | 91 | /// 92 | /// Parses the string of comma-separated tags into a sequence of s. 93 | /// 94 | /// The comma-separated tags to parse into a sequence of InfluxTags. 95 | /// The specified comma-separated tags parsed into a sequence of InfluxTags. 96 | public static IEnumerable ToInfluxTags(String commaSeparatedTags) { 97 | if (String.IsNullOrWhiteSpace(commaSeparatedTags)) return Enumerable.Empty(); 98 | String[] tags = commaSeparatedTags.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 99 | return tags.Select(ToInfluxTag).Where(t => !t.IsEmpty); 100 | } 101 | 102 | /// 103 | /// Splits the tag into a key/value pair using the equals sign. The tag should be in the format: {key}={value}. 104 | /// If the tag is in an invalid format or cannot be parsed, this returns . 105 | /// 106 | /// The tag to parse into an . 107 | /// The tag parsed into an , or if the input string is in an invalid format or could not be parsed. 108 | public static InfluxTag ToInfluxTag(String keyValuePair) { 109 | if (String.IsNullOrWhiteSpace(keyValuePair)) return InfluxTag.Empty; 110 | String[] kvp = Regex.Split(keyValuePair, RegexUnescEqual).Select(s => s.Trim()).Where(s => s.Length > 0).ToArray(); 111 | if (kvp.Length != 2) return InfluxTag.Empty; 112 | return new InfluxTag(kvp[0], kvp[1]); 113 | } 114 | 115 | /// 116 | /// Parses any tags from the and concatenates them to the end of the specified list. 117 | /// If there are multiple tags with the same key in the resulting list, tags that occur later in the sequence override earlier tags. 118 | /// The can be a single tag value or a comma-separated list of values. Any values that are not in the 119 | /// key/value pair format ({key}={value}) are ignored. One exception to this is if the only has a single value 120 | /// and that value is not a key/value pair, an will be created for it using "Name" as the key and itself as the value. 121 | /// 122 | /// The item set name, this is a comma-separated list of key/value pairs. 123 | /// The tags to add in addition to any tags in the item set name. 124 | /// A sequence of InfluxTags that contain the tags in followed by any valid tags from the item name. 125 | public static IEnumerable JoinTags(String itemName, params MetricTags[] tags) { 126 | // if there's only one item and it's not a key/value pair, alter it to use "Name" as the key and itself as the value 127 | String name = itemName ?? String.Empty; 128 | String[] split = Regex.Split(name, RegexUnescComma).Select(t => t.Trim()).Where(t => t.Length > 0).ToArray(); 129 | if (split.Length == 1 && !Regex.IsMatch(split[0], RegexUnescEqual)) split[0] = $"Name={split[0]}"; 130 | var retTags = ToInfluxTags(tags).Concat(ToInfluxTags(split)); 131 | return retTags.GroupBy(t => t.Key).Select(g => g.Last()); // this is similar to: retTags.DistinctBy(t => t.Key), but takes the last value instead so global tags get overriden by later tags 132 | } 133 | 134 | #endregion 135 | 136 | #region Type Validation 137 | 138 | /// 139 | /// The supported types that can be used for an or value. 140 | /// 141 | public static readonly Type[] ValidValueTypes = new Type[] { 142 | typeof(Byte), typeof(SByte), typeof(Int16), typeof(UInt16), typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), 143 | typeof(Single), typeof(Double), typeof(Decimal), typeof(Boolean), typeof(Char), typeof(String) 144 | }; 145 | 146 | /// 147 | /// Determines if the specified type is a valid InfluxDB value type. 148 | /// The valid types are String, Boolean, integral or floating-point type. 149 | /// 150 | /// The type to check. 151 | /// true if the type is a valid InfluxDB value type; false otherwise. 152 | public static Boolean IsValidValueType(Type type) { 153 | return 154 | type == typeof(Char) || type == typeof(String) || 155 | type == typeof(Byte) || type == typeof(SByte) || 156 | type == typeof(Int16) || type == typeof(UInt16) || 157 | type == typeof(Int32) || type == typeof(UInt32) || 158 | type == typeof(Int64) || type == typeof(UInt64) || 159 | type == typeof(Single) || type == typeof(Double) || 160 | type == typeof(Decimal) || type == typeof(Boolean); 161 | } 162 | 163 | /// 164 | /// Determines if the specified type is an integral type. 165 | /// The valid integral types are Byte, Int16, Int32, Int64, and their (un)signed counterparts. 166 | /// 167 | /// The type to check. 168 | /// true if the type is an integral type; false otherwise. 169 | public static Boolean IsIntegralType(Type type) { 170 | return 171 | type == typeof(Byte) || type == typeof(SByte) || 172 | type == typeof(Int16) || type == typeof(UInt16) || 173 | type == typeof(Int32) || type == typeof(UInt32) || 174 | type == typeof(Int64) || type == typeof(UInt64); 175 | } 176 | 177 | /// 178 | /// Determines if the specified type is a floating-point type. 179 | /// The valid floating-point types are Single, Double, and Decimal. 180 | /// 181 | /// The type to check. 182 | /// true if the type is a floating-point type; false otherwise. 183 | public static Boolean IsFloatingPointType(Type type) { 184 | return 185 | type == typeof(Single) || 186 | type == typeof(Double) || 187 | type == typeof(Decimal); 188 | } 189 | 190 | #endregion 191 | 192 | #region URI Helper Methods 193 | 194 | /// The JSON URI scheme. 195 | public const String SchemeJson = "http"; 196 | 197 | /// The HTTP URI scheme. 198 | public const String SchemeHttp = "http"; 199 | 200 | /// The HTTPS URI scheme. 201 | public const String SchemeHttps = "https"; 202 | 203 | /// The UDP URI scheme. 204 | public const String SchemeUdp = "udp"; 205 | 206 | /// 207 | /// Creates a URI for InfluxDB using the values specified in the object. 208 | /// 209 | /// The configuration object to get the relevant fields to build the URI from. 210 | /// A new InfluxDB URI using the configuration specified in the parameter. 211 | public static Uri FormatInfluxUri(this InfluxConfig config) { 212 | return FormatInfluxUri(config.Hostname, config.Port, config.Database, config.Username, config.Password, config.RetentionPolicy, config.Precision); 213 | } 214 | 215 | /// 216 | /// Creates a URI for the specified hostname and database. Uses no authentication, and optionally uses the default port (8086), retention policy (DEFAULT), and time precision (s). 217 | /// 218 | /// The hostname or IP address of the InfluxDB server. 219 | /// The name of the database to write records to. 220 | /// The retention policy to use. Leave blank to use the server default of "DEFAULT". 221 | /// The timestamp precision specifier used in the line protocol writes. Leave blank to use the default of . 222 | /// A new InfluxDB URI using the specified parameters. 223 | public static Uri FormatInfluxUri(String host, String database, String retentionPolicy = null, InfluxPrecision? precision = null) { 224 | return FormatInfluxUri(host, null, database, retentionPolicy, precision); 225 | } 226 | 227 | /// 228 | /// Creates a URI for the specified hostname and database. Uses no authentication, and optionally uses the default retention policy (DEFAULT) and time precision (s). 229 | /// 230 | /// The hostname or IP address of the InfluxDB server. 231 | /// The port number of the InfluxDB server. Set to zero to use the default of . This value is required for the UDP protocol. 232 | /// The name of the database to write records to. 233 | /// The retention policy to use. Leave blank to use the server default of "DEFAULT". 234 | /// The timestamp precision specifier used in the line protocol writes. Leave blank to use the default of . 235 | /// A new InfluxDB URI using the specified parameters. 236 | public static Uri FormatInfluxUri(String host, UInt16? port, String database, String retentionPolicy = null, InfluxPrecision? precision = null) { 237 | return FormatInfluxUri(host, port, database, null, null, retentionPolicy, precision); 238 | } 239 | 240 | /// 241 | /// Creates a URI for the specified hostname and database using authentication. Optionally uses the default retention policy (DEFAULT) and time precision (s). 242 | /// 243 | /// The hostname or IP address of the InfluxDB server. 244 | /// The port number of the InfluxDB server. Set to zero to use the default of . This value is required for the UDP protocol. 245 | /// The name of the database to write records to. 246 | /// The username to use to authenticate to the InfluxDB server. Leave blank to skip authentication. 247 | /// The password to use to authenticate to the InfluxDB server. Leave blank to skip authentication. 248 | /// The retention policy to use. Leave blank to use the server default of "DEFAULT". 249 | /// The timestamp precision specifier used in the line protocol writes. Leave blank to use the default of . 250 | /// A new InfluxDB URI using the specified parameters. 251 | public static Uri FormatInfluxUri(String host, UInt16? port, String database, String username, String password, String retentionPolicy = null, InfluxPrecision? precision = null) { 252 | return FormatInfluxUri(null, host, port, database, username, password, retentionPolicy, precision); 253 | } 254 | 255 | /// 256 | /// Creates a URI for the specified hostname and database using authentication. Optionally uses the default retention policy (DEFAULT) and time precision (s). 257 | /// 258 | /// The URI scheme type, ie. http, https, net.udp 259 | /// The hostname or IP address of the InfluxDB server. 260 | /// The port number of the InfluxDB server. Set to zero to use the default of . This value is required for the UDP protocol. 261 | /// The name of the database to write records to. 262 | /// The username to use to authenticate to the InfluxDB server. Leave blank to skip authentication. 263 | /// The password to use to authenticate to the InfluxDB server. Leave blank to skip authentication. 264 | /// The retention policy to use. Leave blank to use the server default of "DEFAULT". 265 | /// The timestamp precision specifier used in the line protocol writes. Leave blank to use the default of . 266 | /// A new InfluxDB URI using the specified parameters. 267 | public static Uri FormatInfluxUri(String scheme, String host, UInt16? port, String database, String username, String password, String retentionPolicy = null, InfluxPrecision? precision = null) { 268 | scheme = scheme ?? InfluxUtils.SchemeHttp; 269 | if ((port ?? 0) == 0 && (scheme == SchemeHttp || scheme == SchemeHttps)) port = InfluxConfig.Default.PortHttp; 270 | InfluxPrecision prec = precision ?? InfluxConfig.Default.Precision; 271 | String uriString = $@"{scheme}://{host}:{port}/write?db={database}"; 272 | if (!String.IsNullOrWhiteSpace(username)) uriString += $@"&u={username}"; 273 | if (!String.IsNullOrWhiteSpace(password)) uriString += $@"&p={password}"; 274 | if (!String.IsNullOrWhiteSpace(retentionPolicy)) uriString += $@"&rp={retentionPolicy}"; 275 | if (prec != InfluxPrecision.Nanoseconds) uriString += $@"&precision={prec.ToShortName()}"; // only need to specify precision if it's not nanoseconds (the InfluxDB default) 276 | return new Uri(uriString); 277 | //return new Uri($@"{scheme}://{host}:{port}/write?db={database}&u={username}&p={password}&rp={retentionPolicy}&precision={prec.ToShortName()}"); 278 | } 279 | 280 | 281 | private static readonly Regex _regex = new Regex(@"[?|&]([\w\.]+)=([^?|^&]+)"); 282 | 283 | /// 284 | /// Parses the URI query string into a key/value collection. 285 | /// 286 | /// The URI to parse 287 | /// A key/value collection that contains the query parameters. 288 | public static IReadOnlyDictionary ParseQueryString(this Uri uri) { 289 | var match = _regex.Match(uri.PathAndQuery); 290 | var paramaters = new Dictionary(); 291 | while (match.Success) { 292 | paramaters.Add(match.Groups[1].Value, match.Groups[2].Value); 293 | match = match.NextMatch(); 294 | } 295 | return paramaters; 296 | } 297 | 298 | #endregion 299 | 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("Metrics.InfluxDB")] 4 | [assembly: AssemblyDescription("The InfluxDB reporter for the Metrics.NET monitoring and reporting library.")] 5 | 6 | -------------------------------------------------------------------------------- /Src/Metrics.InfluxDB/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | rem Download nuget.exe if the file doesn't exist 2 | if not exist .nuget\nuget.exe powershell -Command "& { wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile .nuget\nuget.exe }" 3 | 4 | .nuget\nuget.exe restore Metrics.InfluxDB.sln 5 | 6 | set MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" 7 | set XUNIT=".\packages\xunit.runner.console.2.0.0\tools\xunit.console.exe" 8 | 9 | rd /S /Q .\bin\Debug 10 | rd /S /Q .\bin\Release 11 | 12 | %MSBUILD% Metrics.InfluxDB.Sln /p:Configuration="Debug" 13 | if %errorlevel% neq 0 exit /b %errorlevel% 14 | 15 | %MSBUILD% Metrics.InfluxDB.Sln /p:Configuration="Release" 16 | if %errorlevel% neq 0 exit /b %errorlevel% 17 | 18 | %XUNIT% .\bin\Debug\Metrics.InfluxDB.Tests.dll -maxthreads 1 19 | if %errorlevel% neq 0 exit /b %errorlevel% 20 | 21 | %XUNIT% .\bin\Release\Metrics.InfluxDB.Tests.dll -maxthreads 1 22 | if %errorlevel% neq 0 exit /b %errorlevel% -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -O .nuget/nuget.exe 2 | 3 | mono .nuget/nuget.exe restore Metrics.InfluxDB.sln 4 | 5 | xbuild Metrics.InfluxDB.sln /p:Configuration="Debug" 6 | xbuild Metrics.InfluxDB.sln /p:Configuration="Release" 7 | 8 | mono ./packages/xunit.runner.console.2.0.0/tools/xunit.console.exe ./bin/Debug/Metrics.InfluxDB.Tests.dll -parallel none 9 | mono ./packages/xunit.runner.console.2.0.0/tools/xunit.console.exe ./bin/Release/Metrics.InfluxDB.Tests.dll -parallel none -------------------------------------------------------------------------------- /create-nuget.bat: -------------------------------------------------------------------------------- 1 | rd /S /Q .\Publishing\lib 2 | 3 | call build.bat 4 | if %errorlevel% neq 0 exit /b %errorlevel% 5 | 6 | md .\Publishing\lib 7 | md .\Publishing\lib\net45 8 | 9 | copy .\bin\Release\Metrics.InfluxDB.dll .\Publishing\lib\net45\ 10 | copy .\bin\Release\Metrics.InfluxDB.xml .\Publishing\lib\net45\ 11 | copy .\bin\Release\Metrics.InfluxDB.pdb .\Publishing\lib\net45\ 12 | 13 | .nuget\nuget.exe pack .\Publishing\Metrics.NET.InfluxDB.nuspec -OutputDirectory .\Publishing 14 | if %errorlevel% neq 0 exit /b %errorlevel% 15 | --------------------------------------------------------------------------------