├── Examples ├── SimplestExample │ ├── App.config │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Program.cs │ └── SimplestExample.csproj ├── Nabbix.Console │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── AdvancedCounters.cs │ ├── App.config │ ├── SimpleCounters.cs │ ├── RandomGenerator.cs │ ├── Program.cs │ └── Nabbix.ConsoleApp.csproj ├── SimplestExample.NetCore │ └── SimplestExample.NetCore.csproj ├── Nabbix.ConsoleApp.NetCore │ └── Nabbix.ConsoleApp.NetCore.csproj └── Examples.sln ├── Nabbix.Tests ├── Nabbix.Tests.csproj ├── WindowsPerformanceCountersTests.cs ├── NabbixItemAttributeTests.cs └── NabbixAgentTests.cs ├── Nabbix ├── Items │ ├── NabbixDiskSpace.cs │ ├── NabbixFileCountItemAttribute.cs │ ├── NabbixFileCount.cs │ ├── PropertyHelper.cs │ ├── NabbixItemAttribute.cs │ ├── NabbixDiskSpaceItemAttribute.cs │ └── BaseTypeHelper.cs ├── Item.cs ├── Nabbix.csproj ├── ItemRegistry.cs ├── WindowsPerformanceCounters.cs ├── QueryHandler.cs └── NabbixAgent.cs ├── LICENSE.txt ├── Nabbix.sln ├── README.md └── .gitignore /Examples/SimplestExample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/Nabbix.Console/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/SimplestExample.NetCore/SimplestExample.NetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Examples/SimplestExample/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Nabbix.Tests/Nabbix.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Nabbix/Items/NabbixDiskSpace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Nabbix.Items 5 | { 6 | public class NabbixDiskSpace 7 | { 8 | private readonly string _drive; 9 | 10 | public NabbixDiskSpace(string drive) 11 | { 12 | if (string.IsNullOrWhiteSpace(drive)) 13 | throw new ArgumentException("Argument is null or whitespace", nameof(drive)); 14 | 15 | _drive = drive; 16 | } 17 | 18 | internal DriveInfo GetDriveInfo() 19 | { 20 | return new DriveInfo(_drive); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Examples/Nabbix.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Nabbix.Console")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("Nabbix.Console")] 9 | [assembly: AssemblyCopyright("Copyright © 2016")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("04307925-07fa-42d0-a1a4-81f5ab643f64")] 14 | [assembly: AssemblyVersion("1.0.0.0")] 15 | [assembly: AssemblyFileVersion("1.0.0.0")] 16 | -------------------------------------------------------------------------------- /Examples/SimplestExample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("SimplestExample")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("SimplestExample")] 9 | [assembly: AssemblyCopyright("Copyright © 2016")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("f055c4fa-1232-406b-a98e-acf3002e17e2")] 14 | [assembly: AssemblyVersion("1.0.0.0")] 15 | [assembly: AssemblyFileVersion("1.0.0.0")] 16 | -------------------------------------------------------------------------------- /Examples/Nabbix.Console/AdvancedCounters.cs: -------------------------------------------------------------------------------- 1 | using Nabbix.Items; 2 | 3 | namespace Nabbix.ConsoleApp 4 | { 5 | public class AdvancedCounters 6 | { 7 | [NabbixDiskSpaceItem("c_available_free", "c_total_free", "c_total_size", "c_volume_label")] 8 | public NabbixDiskSpace DiskSpace { get; } = new NabbixDiskSpace(@"C"); 9 | 10 | [NabbixFileCountItem("all_files")] 11 | public NabbixFileCount NabbixAllFiles { get; } = new NabbixFileCount(@"C:\git\nabbix"); 12 | 13 | [NabbixFileCountItem("csharp_files")] 14 | public NabbixFileCount NabbixFiles { get; } = new NabbixFileCount(@"C:\git\nabbix", "*.cs"); 15 | } 16 | } -------------------------------------------------------------------------------- /Nabbix/Items/NabbixFileCountItemAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Nabbix.Items 5 | { 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public class NabbixFileCountItemAttribute : NabbixItemAttribute 8 | { 9 | public NabbixFileCountItemAttribute(string zabbixItemKey) : base(zabbixItemKey) 10 | { 11 | } 12 | 13 | protected override string GetPropertyValue(string key, object propertyValue) 14 | { 15 | var fileCount = PropertyHelper.GetType(propertyValue); 16 | return fileCount?.GetFileCount().ToString(CultureInfo.InvariantCulture) ?? Item.NotSupported; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Nabbix.Tests/WindowsPerformanceCountersTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Xunit; 3 | 4 | namespace Nabbix.Tests 5 | { 6 | public class WindowsPerformanceCountersTests 7 | { 8 | [Fact] 9 | public void ParseCounter_HappyPath_NonNullReturn() 10 | { 11 | PerformanceCounter counterA = WindowsPerformanceCounters.ParseCounter( 12 | @"perf_counter[""\Processor Information(_Total)\% Processor Time""]"); 13 | PerformanceCounter counterB = WindowsPerformanceCounters.ParseCounter( 14 | @"perf_counter[""\Memory\Available Bytes""]"); 15 | 16 | Assert.True(counterA != null); 17 | Assert.True(counterB != null); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/Nabbix.ConsoleApp.NetCore/Nabbix.ConsoleApp.NetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/Nabbix.Console/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Nabbix/Items/NabbixFileCount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Nabbix.Items 5 | { 6 | public class NabbixFileCount 7 | { 8 | private readonly string _folder; 9 | private readonly string _searchPattern; 10 | private readonly SearchOption _searchOption; 11 | 12 | public NabbixFileCount(string folder, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories) 13 | { 14 | if (string.IsNullOrWhiteSpace(folder)) 15 | throw new ArgumentException("Argument is null or whitespace", nameof(folder)); 16 | 17 | _folder = folder; 18 | _searchPattern = searchPattern; 19 | _searchOption = searchOption; 20 | } 21 | 22 | internal int GetFileCount() => Directory.GetFiles(_folder, _searchPattern, _searchOption).Length; 23 | } 24 | } -------------------------------------------------------------------------------- /Nabbix/Item.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Nabbix.Items; 4 | 5 | namespace Nabbix 6 | { 7 | public class Item 8 | { 9 | public const string NotSupported = "ZBX_NOTSUPPORTED"; 10 | 11 | private readonly PropertyInfo _property; 12 | private readonly NabbixItemAttribute _attribute; 13 | private readonly object _instance; 14 | 15 | public Item(PropertyInfo property, NabbixItemAttribute attribute, object instance) 16 | { 17 | if (property == null) throw new ArgumentNullException(nameof(property)); 18 | if (attribute == null) throw new ArgumentNullException(nameof(attribute)); 19 | if (instance == null) throw new ArgumentNullException(nameof(instance)); 20 | 21 | _property = property; 22 | _attribute = attribute; 23 | _instance = instance; 24 | } 25 | 26 | public string GetValue(string key) 27 | { 28 | return _attribute.GetValue(key, _instance, _property); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Nabbix/Nabbix.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net461 4 | $(PackageVersion) 5 | $(PackageVersion) 6 | https://github.com/nolstoy/nabbix/blob/master/LICENSE.txt 7 | https://github.com/nolstoy/nabbix 8 | This package contains a lightweight, embeddable C# Zabbix Agent that responds to Zabbix Passive Checks. 9 | https://github.com/nolstoy/nabbix 10 | $(PackageVersion) 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Examples/SimplestExample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Nabbix; 4 | using Nabbix.Items; 5 | 6 | namespace SimplestExample 7 | { 8 | internal class Program 9 | { 10 | private class MyCounter 11 | { 12 | private long _incrementing; 13 | internal void Increment() 14 | { 15 | Interlocked.Increment(ref _incrementing); 16 | } 17 | 18 | [NabbixItem("long_example")] 19 | public long Incrementing => Interlocked.Read(ref _incrementing); 20 | } 21 | 22 | private static void Main() 23 | { 24 | // Create the instance of our counter 25 | var counters = new MyCounter(); 26 | 27 | // Start the agent. 28 | var agent = new NabbixAgent(10052, counters); 29 | 30 | // Increment the counter. Normally done on API or method call. 31 | counters.Increment(); 32 | 33 | // Shutdown 34 | Console.ReadKey(); 35 | agent.Stop(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Nabbix/Items/PropertyHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Common.Logging; 3 | 4 | namespace Nabbix.Items 5 | { 6 | public static class PropertyHelper 7 | { 8 | private static readonly ILog Log = LogManager.GetLogger(typeof(PropertyHelper)); 9 | 10 | public static T GetType(object propertyValue) where T : class 11 | { 12 | var propertyType = propertyValue as T; 13 | if (propertyType == null) 14 | { 15 | Log.ErrorFormat( 16 | "propertyValue is of the wrong type. It must be associated with a {0} property, but it's associated with: {1}", 17 | typeof(T).Name, 18 | propertyValue.GetType().Name); 19 | } 20 | 21 | return propertyType; 22 | } 23 | 24 | 25 | internal static string GetZabbixItem(string prefix, string value) 26 | { 27 | if (string.IsNullOrWhiteSpace(prefix)) 28 | throw new ArgumentException("Argument is null or whitespace", nameof(prefix)); 29 | if (string.IsNullOrWhiteSpace(value)) 30 | throw new ArgumentException("Argument is null or whitespace", nameof(value)); 31 | 32 | return prefix + "_" + value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Nabbix.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.0.0 5 | MinimumVisualStudioVersion = 10.0.0.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nabbix", "Nabbix\Nabbix.csproj", "{DA811772-A05C-41A3-81DC-FEEAC4E08DA4}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nabbix.Tests", "Nabbix.Tests\Nabbix.Tests.csproj", "{17F29A0E-352F-4608-A515-7A916E2AE490}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {DA811772-A05C-41A3-81DC-FEEAC4E08DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {DA811772-A05C-41A3-81DC-FEEAC4E08DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {DA811772-A05C-41A3-81DC-FEEAC4E08DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {DA811772-A05C-41A3-81DC-FEEAC4E08DA4}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {17F29A0E-352F-4608-A515-7A916E2AE490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {17F29A0E-352F-4608-A515-7A916E2AE490}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {17F29A0E-352F-4608-A515-7A916E2AE490}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {17F29A0E-352F-4608-A515-7A916E2AE490}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Nabbix/Items/NabbixItemAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Fasterflect; 6 | 7 | namespace Nabbix.Items 8 | { 9 | [AttributeUsage(AttributeTargets.Property)] 10 | public class NabbixItemAttribute : Attribute 11 | { 12 | public NabbixItemAttribute(string zabbixItemKey) 13 | { 14 | if (string.IsNullOrWhiteSpace(zabbixItemKey)) throw new ArgumentNullException(nameof(zabbixItemKey)); 15 | 16 | ZabbixItemKeys = new List { zabbixItemKey}; 17 | } 18 | 19 | public NabbixItemAttribute(ICollection itemKeys) 20 | { 21 | if (itemKeys == null || itemKeys.Count == 0) throw new ArgumentException("Argument is empty collection", nameof(itemKeys)); 22 | 23 | ZabbixItemKeys = itemKeys; 24 | } 25 | 26 | public ICollection ZabbixItemKeys { get; } 27 | 28 | protected virtual string GetPropertyValue(string key, object propertyValue) 29 | { 30 | return BaseTypeHelper.GetPropertyValue(propertyValue); 31 | } 32 | 33 | internal string GetValue(string key, object instance, PropertyInfo propertyInfo) 34 | { 35 | if (ZabbixItemKeys.Count == 1) 36 | { 37 | string first = ZabbixItemKeys.First(); 38 | if (key != first) 39 | { 40 | throw new ArgumentException($"key is invalid. {key} != {first}"); 41 | } 42 | } 43 | 44 | object propertyValue = propertyInfo.Get(instance); 45 | if (propertyValue == null) 46 | return Item.NotSupported; 47 | 48 | return GetPropertyValue(key, propertyValue); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Examples/Nabbix.Console/SimpleCounters.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Nabbix.Items; 3 | 4 | namespace Nabbix.ConsoleApp 5 | { 6 | public class SimpleCounters 7 | { 8 | private readonly object _floatLockObj = new object(); 9 | private float _float; 10 | private long _incrementing; 11 | 12 | private readonly object _doubleLockObj = new object(); 13 | private double _double; 14 | 15 | private readonly object _decimalLockObj = new object(); 16 | private decimal _decimal; 17 | 18 | private readonly object _stringLockObj = new object(); 19 | private string _string; 20 | 21 | internal void Increment() 22 | { 23 | Interlocked.Increment(ref _incrementing); 24 | } 25 | 26 | [NabbixItem("long_example")] 27 | public long Incrementing => Interlocked.Read(ref _incrementing); 28 | 29 | [NabbixItem("float_example")] 30 | public float FloatExample 31 | { 32 | get { lock (_floatLockObj) return _float; } 33 | set { lock (_floatLockObj) _float = value; } 34 | } 35 | 36 | [NabbixItem("double_example")] 37 | public double DoubleExample 38 | { 39 | get { lock (_doubleLockObj) return _double; } 40 | set { lock (_doubleLockObj) _double = value; } 41 | } 42 | 43 | [NabbixItem("decimal_example")] 44 | public decimal DecimalExample 45 | { 46 | get { lock (_decimalLockObj) return _decimal; } 47 | set { lock (_decimalLockObj) _decimal = value; } 48 | } 49 | 50 | [NabbixItem("string_example")] 51 | public string StringExample 52 | { 53 | get { lock (_stringLockObj) return _string; } 54 | set { lock (_stringLockObj) _string = value; } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![nolstoy-packages MyGet Build Status](https://www.myget.org/BuildSource/Badge/nolstoy-packages?identifier=96e0f973-0095-4da0-914d-7c8ccebfde05)](https://www.myget.org/feed/nolstoy-packages/package/nuget/Nabbix)[![Nuget download](https://img.shields.io/nuget/v/Nabbix.svg)](https://www.nuget.org/packages/Nabbix/) [![Nuget download](https://img.shields.io/nuget/dt/Nabbix.svg)](https://www.nuget.org/packages/Nabbix/) 2 | # What? 3 | 4 | A lightweight Zabbix Agent that can be embedded in any .Net program ~ ASP.Net, Windows Service or Console Applications. 5 | 6 | # Why? 7 | 8 | Zabbix does have a Windows Agent. It's largely limited to Performance Counters, Log Files and the Windows Event Log. When monitoring a .Net application, it's a non trivial task to install the agent and register custom performance counters. 9 | 10 | With Nabbix, monitoring a .Net program only requires referencing a NuGet package and adding a few lines of code. 11 | 12 | # How? 13 | 14 | ## 1. Add nabbix NuGet Package 15 | 16 | ``` 17 | Install-Package Nabbix 18 | ``` 19 | 20 | ## 2. Create class(es) with the counter(s) 21 | 22 | ``` 23 | 24 | // Class containing a single Zabbix Item 'long_example' 25 | private class MyCounter 26 | { 27 | private long _incrementing; 28 | internal void Increment() 29 | { 30 | Interlocked.Increment(ref _incrementing); 31 | } 32 | 33 | [NabbixItem("long_example")] 34 | public long Incrementing => Interlocked.Read(ref _incrementing); 35 | } 36 | ``` 37 | 38 | ## 3. Create a Nabbix Agent and register instances of the counter classes. 39 | 40 | ``` 41 | private static void Main() 42 | { 43 | // Create the instance of the counter class with a single Zabbix Item 44 | var counters = new MyCounter(); 45 | 46 | // Start the agent. 47 | var agent = new NabbixAgent(10052, counters); 48 | 49 | // Increment the counter. Normally done on API or method call. 50 | counters.Increment(); 51 | 52 | // Shutdown 53 | Console.ReadKey(); 54 | agent.Stop(); 55 | } 56 | ``` 57 | 58 | # More information? 59 | 60 | https://github.com/nolstoy/nabbix/wiki 61 | -------------------------------------------------------------------------------- /Examples/Nabbix.Console/RandomGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Nabbix.ConsoleApp 5 | { 6 | public class RandomGenerator 7 | { 8 | private readonly Random _rand = new Random(); 9 | 10 | public int NextInt(int min, int max) 11 | { 12 | return _rand.Next(min, max); 13 | } 14 | 15 | internal long NextLong() 16 | { 17 | return _rand.Next(1000, 10000); 18 | } 19 | 20 | internal double NextDouble() 21 | { 22 | return _rand.NextDouble(); 23 | } 24 | 25 | // http://stackoverflow.com/questions/3365337/best-way-to-generate-a-random-float-in-c-sharp 26 | internal float NextFloat() 27 | { 28 | double range = (double)float.MaxValue - (double)float.MinValue; 29 | double sample = _rand.NextDouble(); 30 | double scaled = (sample * range) + float.MinValue; 31 | return (float)scaled; 32 | } 33 | 34 | private int NextInt32() 35 | { 36 | unchecked 37 | { 38 | int firstBits = _rand.Next(0, 1 << 4) << 28; 39 | int lastBits = _rand.Next(0, 1 << 28); 40 | return firstBits | lastBits; 41 | } 42 | } 43 | 44 | // http://stackoverflow.com/questions/609501/generating-a-random-decimal-in-c-sharp 45 | internal decimal NextDecimal() 46 | { 47 | byte scale = (byte) _rand.Next(29); 48 | bool sign = _rand.Next(2) == 1; 49 | return new decimal(NextInt32(), 50 | NextInt32(), 51 | NextInt32(), 52 | sign, 53 | scale); 54 | } 55 | 56 | public string NextString() 57 | { 58 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 59 | var random = new Random(); 60 | return new string(Enumerable.Repeat(chars, _rand.Next(10, 50)) 61 | .Select(s => s[random.Next(s.Length)]).ToArray()); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Nabbix/Items/NabbixDiskSpaceItemAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using static Nabbix.Items.PropertyHelper; 6 | 7 | namespace Nabbix.Items 8 | { 9 | public class NabbixDiskSpaceItemAttribute : NabbixItemAttribute 10 | { 11 | public NabbixDiskSpaceItemAttribute(string zabbixItemPrefix) 12 | : this(GetZabbixItem(zabbixItemPrefix, "available_freespace"), 13 | GetZabbixItem(zabbixItemPrefix, "total_freespace"), 14 | GetZabbixItem(zabbixItemPrefix, "total_size"), 15 | GetZabbixItem(zabbixItemPrefix, "volume_label")) 16 | { 17 | } 18 | 19 | public NabbixDiskSpaceItemAttribute(string availableFreeSpaceZabbixItem, 20 | string totalFreeSpaceZabbixItem, 21 | string totalSizeZabbixItem, 22 | string volumeLabelZabbixItem) 23 | : base(new[] {availableFreeSpaceZabbixItem, totalFreeSpaceZabbixItem, totalSizeZabbixItem, volumeLabelZabbixItem}) 24 | { 25 | _itemMapping = new Dictionary>(); 26 | _itemMapping.Add(availableFreeSpaceZabbixItem, d => d.AvailableFreeSpace.ToString(CultureInfo.InvariantCulture)); 27 | _itemMapping.Add(totalFreeSpaceZabbixItem, d => d.TotalFreeSpace.ToString(CultureInfo.InvariantCulture)); 28 | _itemMapping.Add(totalSizeZabbixItem, d => d.TotalSize.ToString(CultureInfo.InvariantCulture)); 29 | _itemMapping.Add(volumeLabelZabbixItem, d => d.VolumeLabel); 30 | } 31 | 32 | private readonly Dictionary> _itemMapping; 33 | 34 | protected override string GetPropertyValue(string key, object propertyValue) 35 | { 36 | NabbixDiskSpace diskSpace = GetType(propertyValue); 37 | DriveInfo info = diskSpace?.GetDriveInfo(); 38 | if (info == null) 39 | return Item.NotSupported; 40 | 41 | Func item; 42 | if (_itemMapping.TryGetValue(key, out item)) 43 | { 44 | return item(info); 45 | } 46 | 47 | return Item.NotSupported; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Nabbix/ItemRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | using Common.Logging; 5 | using Nabbix.Items; 6 | 7 | namespace Nabbix 8 | { 9 | public class ItemRegistry 10 | { 11 | private static readonly ILog Log = LogManager.GetLogger(typeof (ItemRegistry)); 12 | 13 | public ConcurrentDictionary RegisteredProperties { get; } 14 | 15 | public ItemRegistry() 16 | { 17 | RegisteredProperties = new ConcurrentDictionary(); 18 | } 19 | 20 | public void RegisterInstance(object instance) 21 | { 22 | foreach (PropertyInfo property in instance.GetType().GetProperties()) 23 | { 24 | var attribute = property.GetCustomAttribute(); 25 | if (attribute != null) 26 | { 27 | foreach (var key in attribute.ZabbixItemKeys) 28 | 29 | if (key != null) 30 | { 31 | RegisteredProperties.TryAdd(key, new Item(property, attribute, instance)); 32 | } 33 | else 34 | { 35 | Log.WarnFormat("NabbixItemAttribute - Missing key for object {0}, {1}", 36 | instance.GetType(), property.Name); 37 | } 38 | } 39 | } 40 | } 41 | 42 | public string GetItemValue(string key) 43 | { 44 | key = key.Trim(); 45 | 46 | if (key == "agent.ping") 47 | return "1"; 48 | 49 | try 50 | { 51 | if (WindowsPerformanceCounters.IsCounter(key)) 52 | { 53 | return WindowsPerformanceCounters.GetNextValue(key); 54 | } 55 | 56 | Item item; 57 | if (RegisteredProperties.TryGetValue(key, out item)) 58 | { 59 | return item.GetValue(key); 60 | } 61 | } 62 | catch (Exception e) 63 | { 64 | Log.ErrorFormat("Exception occurred querying key {0}", e, key); 65 | } 66 | 67 | 68 | 69 | return Item.NotSupported; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Nabbix/Items/BaseTypeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Nabbix.Items 4 | { 5 | public static class BaseTypeHelper 6 | { 7 | public static string GetPropertyValue(object propertyValue) 8 | { 9 | if (propertyValue is float) 10 | return GetFloatValue((float) propertyValue); 11 | if (propertyValue is double) 12 | return GetDoubleValue((double) propertyValue); 13 | if (propertyValue is decimal) 14 | return GetDecimalValue((decimal) propertyValue); 15 | return propertyValue.ToString(); 16 | } 17 | 18 | public static string GetPropertyValue(ValueType propertyValue) 19 | { 20 | if (propertyValue is float) 21 | return GetFloatValue((float)propertyValue); 22 | if (propertyValue is double) 23 | return GetDoubleValue((double)propertyValue); 24 | if (propertyValue is decimal) 25 | return GetDecimalValue((decimal)propertyValue); 26 | return propertyValue.ToString(); 27 | } 28 | 29 | // Not perfect, but it's close to the maximum values of 30 | // https://www.zabbix.com/documentation/2.0/manual/config/items/item 31 | 32 | public const double MinDoubleValue = -999000000000.0D; 33 | public const double MaxDoubleValue = 999000000000.0D; 34 | 35 | public static string GetDoubleValue(double value) 36 | { 37 | value = Math.Min(MaxDoubleValue, value); 38 | value = Math.Max(MinDoubleValue, value); 39 | 40 | return value.ToString("0.0000"); 41 | } 42 | 43 | public const decimal MinDecimalValue = -999000000000.0m; 44 | public const decimal MaxDecimalValue = 999000000000.0m; 45 | 46 | public static string GetDecimalValue(decimal value) 47 | { 48 | value = Math.Min(MaxDecimalValue, value); 49 | value = Math.Max(MinDecimalValue, value); 50 | 51 | return value.ToString("0.0000"); 52 | } 53 | 54 | public const float MinFloatValue = -990000000000.0f; 55 | public const float MaxFloatValue = 990000000000.0f; 56 | 57 | public static string GetFloatValue(float value) 58 | { 59 | value = Math.Min(MaxFloatValue, value); 60 | value = Math.Max(MinFloatValue, value); 61 | 62 | return value.ToString("0.0000"); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Examples/Nabbix.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | 5 | namespace Nabbix.ConsoleApp 6 | { 7 | internal class Program 8 | { 9 | private static volatile bool _stopped; 10 | 11 | private static void IncrementCounters(RandomGenerator random, int millisecondsTimeout, SimpleCounters counters, AdvancedCounters moreCounters) 12 | { 13 | while (_stopped == false) 14 | { 15 | counters.Increment(); 16 | counters.FloatExample = random.NextFloat(); 17 | counters.DoubleExample = random.NextDouble(); 18 | counters.DecimalExample = random.NextDecimal(); 19 | counters.StringExample = random.NextString(); 20 | 21 | if (millisecondsTimeout == -1) 22 | { 23 | millisecondsTimeout = random.NextInt(1, 10); 24 | } 25 | else 26 | { 27 | Thread.Sleep(millisecondsTimeout); 28 | } 29 | 30 | } 31 | } 32 | 33 | // ReSharper disable once UseObjectOrCollectionInitializer 34 | private static Thread IncrementCountersOnBackgroundThread(int millisecondsTimeout, SimpleCounters counters, AdvancedCounters moreCounters) 35 | { 36 | var increaseCounters = new Thread(() => IncrementCounters(new RandomGenerator(), millisecondsTimeout, counters, moreCounters)); 37 | increaseCounters.IsBackground = true; 38 | increaseCounters.Start(); 39 | 40 | return increaseCounters; 41 | } 42 | 43 | private static void Main(string[] args) 44 | { 45 | var counters = new SimpleCounters(); 46 | var moreCounters = new AdvancedCounters(); 47 | INabbixAgent agent = new NabbixAgent(10052, counters, moreCounters); 48 | 49 | // 100,000 requests/s for an extended period of time will run out of memory. 50 | 51 | const int numThreads = 16; 52 | Thread[] threads = new Thread[numThreads]; 53 | 54 | for (int i = 0; i < numThreads; i++) 55 | { 56 | threads[i] = IncrementCountersOnBackgroundThread(10, counters, moreCounters); 57 | } 58 | 59 | Console.ReadKey(); 60 | _stopped = true; 61 | foreach (var thread in threads) 62 | { 63 | thread.Join(); 64 | } 65 | 66 | agent.Stop(); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Nabbix/WindowsPerformanceCounters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Nabbix.Items; 5 | 6 | namespace Nabbix 7 | { 8 | public class WindowsPerformanceCounters 9 | { 10 | private static readonly Dictionary Counters = new Dictionary(); 11 | 12 | public static bool IsCounter(string key) 13 | { 14 | return IsWindows() && key.StartsWith("perf_counter"); 15 | } 16 | 17 | public static string GetNextValue(string key) 18 | { 19 | if (!IsWindows()) throw new NotSupportedException(); 20 | 21 | PerformanceCounter counter; 22 | if (!Counters.TryGetValue(key, out counter)) 23 | { 24 | counter = ParseCounter(key); 25 | Counters.Add(key, counter); 26 | } 27 | 28 | float value = counter.NextValue(); 29 | return BaseTypeHelper.GetFloatValue(value); 30 | } 31 | 32 | // https://www.zabbix.com/documentation/1.8/manual/config/windows_performance_counters 33 | public static PerformanceCounter ParseCounter(string key) 34 | { 35 | if (!IsWindows()) throw new NotSupportedException(); 36 | 37 | int start = key.IndexOf('"'); 38 | int end = key.LastIndexOf('"'); 39 | int middle = key.LastIndexOf('\\'); 40 | if (start == -1 || middle == -1 || end == -1) 41 | { 42 | throw new FormatException($"Performance Counter format is invalid {key}"); 43 | } 44 | 45 | int instanceStart = key.IndexOf('('); 46 | int instanceEnd = key.IndexOf(')'); 47 | 48 | int categoryLength = instanceStart == -1 49 | ? middle - start 50 | : instanceStart - start; 51 | 52 | const bool readOnly = true; 53 | string category = key.Substring(start + 2, categoryLength - 2); 54 | string counterName = key.Substring(middle + 1, end - middle - 1); 55 | 56 | if (instanceStart == -1 || instanceEnd == -1) 57 | { 58 | return new PerformanceCounter(category, counterName, readOnly); 59 | } 60 | 61 | string instance = key.Substring(instanceStart + 1, instanceEnd - instanceStart - 1); 62 | return new PerformanceCounter(category, counterName, instance, readOnly); 63 | } 64 | 65 | private static bool IsWindows() 66 | { 67 | OperatingSystem os = Environment.OSVersion; 68 | PlatformID pid = os.Platform; 69 | 70 | return pid == PlatformID.Win32S || 71 | pid == PlatformID.Win32Windows || 72 | pid == PlatformID.Win32NT || 73 | pid == PlatformID.WinCE; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Examples/Examples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nabbix.ConsoleApp", "Nabbix.Console\Nabbix.ConsoleApp.csproj", "{04307925-07FA-42D0-A1A4-81F5AB643F64}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nabbix", "..\Nabbix\Nabbix.csproj", "{7E4C9B6B-E0A7-4909-B97C-D24D3EE1C477}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplestExample", "SimplestExample\SimplestExample.csproj", "{F055C4FA-1232-406B-A98E-ACF3002E17E2}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nabbix.Tests", "..\Nabbix.Tests\Nabbix.Tests.csproj", "{4FCDCBEB-68D6-43BD-BE7B-71099765AD2E}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nabbix.ConsoleApp.NetCore", "Nabbix.ConsoleApp.NetCore\Nabbix.ConsoleApp.NetCore.csproj", "{6C270573-402F-43B4-AFF0-623FA77895B9}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplestExample.NetCore", "SimplestExample.NetCore\SimplestExample.NetCore.csproj", "{93C6BF7A-B2A0-483D-8322-05FDAE1DC194}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {04307925-07FA-42D0-A1A4-81F5AB643F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {04307925-07FA-42D0-A1A4-81F5AB643F64}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {04307925-07FA-42D0-A1A4-81F5AB643F64}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {04307925-07FA-42D0-A1A4-81F5AB643F64}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {7E4C9B6B-E0A7-4909-B97C-D24D3EE1C477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {7E4C9B6B-E0A7-4909-B97C-D24D3EE1C477}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {7E4C9B6B-E0A7-4909-B97C-D24D3EE1C477}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {7E4C9B6B-E0A7-4909-B97C-D24D3EE1C477}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {F055C4FA-1232-406B-A98E-ACF3002E17E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {F055C4FA-1232-406B-A98E-ACF3002E17E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {F055C4FA-1232-406B-A98E-ACF3002E17E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {F055C4FA-1232-406B-A98E-ACF3002E17E2}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {4FCDCBEB-68D6-43BD-BE7B-71099765AD2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {4FCDCBEB-68D6-43BD-BE7B-71099765AD2E}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {4FCDCBEB-68D6-43BD-BE7B-71099765AD2E}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {4FCDCBEB-68D6-43BD-BE7B-71099765AD2E}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {6C270573-402F-43B4-AFF0-623FA77895B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {6C270573-402F-43B4-AFF0-623FA77895B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {6C270573-402F-43B4-AFF0-623FA77895B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {6C270573-402F-43B4-AFF0-623FA77895B9}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {93C6BF7A-B2A0-483D-8322-05FDAE1DC194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {93C6BF7A-B2A0-483D-8322-05FDAE1DC194}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {93C6BF7A-B2A0-483D-8322-05FDAE1DC194}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {93C6BF7A-B2A0-483D-8322-05FDAE1DC194}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {BEF4CE05-8104-4768-A3BC-B1686FDFFAD7} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /Examples/SimplestExample/SimplestExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F055C4FA-1232-406B-A98E-ACF3002E17E2} 8 | Exe 9 | Properties 10 | SimplestExample 11 | SimplestExample 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll 38 | 39 | 40 | ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll 41 | 42 | 43 | ..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll 44 | True 45 | 46 | 47 | ..\packages\Fasterflect.Netstandard.1.0.8\lib\net45\Fasterflect.Netstandard.dll 48 | 49 | 50 | 51 | ..\packages\Nabbix.0.2.4\lib\net45\Nabbix.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Designer 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /Examples/Nabbix.Console/Nabbix.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {04307925-07FA-42D0-A1A4-81F5AB643F64} 8 | Exe 9 | Properties 10 | Nabbix.ConsoleApp 11 | Nabbix.ConsoleApp 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll 38 | 39 | 40 | ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll 41 | 42 | 43 | ..\packages\Fasterflect.Netstandard.1.0.8\lib\net45\Fasterflect.Netstandard.dll 44 | 45 | 46 | ..\packages\Nabbix.0.2.4\lib\net45\Nabbix.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /Nabbix/QueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using Common.Logging; 7 | 8 | namespace Nabbix 9 | { 10 | internal class QueryHandler 11 | { 12 | private static readonly ILog Log = LogManager.GetLogger(typeof(QueryHandler)); 13 | 14 | private static byte[] GetResponseLength(string response) 15 | { 16 | var responseLength = new byte[8]; 17 | long length = response.Length; 18 | for (int i = 0; i < 8; i++) 19 | { 20 | responseLength[i] = (byte)(int)(length & 0xFF); 21 | length >>= 8; 22 | } 23 | return responseLength; 24 | } 25 | 26 | private static string GetRequest(NetworkStream stream) 27 | { 28 | const int lengthSize = 4; 29 | const int reservedSize = 4; 30 | 31 | var dataLength = 0; 32 | 33 | var oldProtocol = false; 34 | var bytes = new List(); 35 | int currentByte; 36 | do 37 | { 38 | currentByte = stream.ReadByte(); 39 | bytes.Add((byte) currentByte); 40 | 41 | if (bytes.Count <= Header.Length) 42 | { 43 | if (!bytes.Take(bytes.Count).SequenceEqual(Header.Take(bytes.Count))) 44 | oldProtocol = true; 45 | } 46 | else if (bytes.Count == Header.Length + lengthSize) 47 | { 48 | var lengthBytes = bytes.Skip(Header.Length).Take(lengthSize).ToArray(); 49 | dataLength = GetLittleEndianIntegerFromByteArray(lengthBytes, 0); 50 | } 51 | else if (bytes.Count == Header.Length + lengthSize + reservedSize + dataLength) 52 | { 53 | var dataBytes = bytes.Skip(Header.Length + lengthSize + reservedSize).Take(dataLength).ToArray(); 54 | var dataString = Encoding.ASCII.GetString(dataBytes); 55 | 56 | return dataString; 57 | } 58 | 59 | if(oldProtocol && currentByte == 10) 60 | return Encoding.ASCII.GetString(bytes.ToArray()); 61 | } 62 | while (currentByte != -1); 63 | 64 | return "ZBX_INVALID_DATA_ERR"; // never occurs i think 65 | } 66 | 67 | private static readonly byte[] Header = {(byte)'Z', (byte)'B', (byte)'X', (byte)'D', 1}; 68 | private static void SendResponse(NetworkStream stream, string response) 69 | { 70 | byte[] responseLength = GetResponseLength(response); 71 | byte[] responseBytes = Encoding.ASCII.GetBytes(response); 72 | 73 | byte[] buffer = new byte[Header.Length + responseLength.Length + responseBytes.Length]; 74 | 75 | // https://www.zabbix.com/documentation/1.8/protocols 76 | Array.Copy(Header, 0, buffer, 0, Header.Length); 77 | Array.Copy(responseLength, 0, buffer, Header.Length, responseLength.Length); 78 | Array.Copy(responseBytes, 0, buffer, Header.Length + responseLength.Length, responseBytes.Length); 79 | 80 | Log.Debug("Response sending..."); 81 | stream.Write(buffer, 0, buffer.Length); 82 | stream.Flush(); 83 | Log.Debug("Response sent."); 84 | } 85 | 86 | internal static void Run(TcpClient client, ItemRegistry registry) 87 | { 88 | Log.Debug("Run... ."); 89 | 90 | var stream = client.GetStream(); 91 | do 92 | { 93 | Log.Debug("Request recieving..."); 94 | var request = GetRequest(stream); 95 | Log.DebugFormat("Request received: {0}", request); 96 | 97 | var response = registry.GetItemValue(request); 98 | Log.DebugFormat("Response: {0}", response); 99 | SendResponse(stream, response); 100 | } while (stream.DataAvailable); 101 | 102 | Log.Debug("Run. Ended."); 103 | } 104 | 105 | static int GetLittleEndianIntegerFromByteArray(byte[] data, int startIndex) 106 | { 107 | return (data[startIndex + 3] << 24) 108 | | (data[startIndex + 2] << 16) 109 | | (data[startIndex + 1] << 8) 110 | | data[startIndex]; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Nabbix.Tests/NabbixItemAttributeTests.cs: -------------------------------------------------------------------------------- 1 | using Nabbix.Items; 2 | using Xunit; 3 | 4 | namespace Nabbix.Tests 5 | { 6 | public class BaseTypeHelperTests 7 | { 8 | [Fact] 9 | public void GetFloatValue_SizeExceedsMax_ReturnsMaxAllowedValue() 10 | { 11 | const string maxZabbixMySqlValue = "990000000000.0000"; 12 | const float exceedsMaxSize = 999999999999.9999f; 13 | 14 | string result = BaseTypeHelper.GetFloatValue(exceedsMaxSize); 15 | Assert.Equal(maxZabbixMySqlValue, result); 16 | } 17 | 18 | [Fact] 19 | public void GetFloatValue_SizeExceedsMin_ReturnsMinAllowedValue() 20 | { 21 | const string minZabbixMySqlValue = "-990000000000.0000"; 22 | const float exceedsMinSize = -999999999999.9999f; 23 | 24 | string result = BaseTypeHelper.GetFloatValue(exceedsMinSize); 25 | Assert.Equal(minZabbixMySqlValue, result); 26 | } 27 | 28 | [Fact] 29 | public void GetFloatValue_SizeIsAcceptable_ReturnsSize() 30 | { 31 | const float validSize = 124.99f; 32 | 33 | string result = BaseTypeHelper.GetFloatValue(validSize); 34 | Assert.Equal("124.9900", result); 35 | } 36 | 37 | [Fact] 38 | public void GetFloatValue_TooManyDecimals_DecimalIsTrimmed() 39 | { 40 | const float validSize = 124.99543f; 41 | 42 | string result = BaseTypeHelper.GetFloatValue(validSize); 43 | Assert.Equal("124.9954", result); 44 | } 45 | 46 | [Fact] 47 | public void GetDoubleValue_SizeExceedsMax_ReturnsMaxAllowedValue() 48 | { 49 | const string maxZabbixMySqlValue = "999000000000.0000"; 50 | const double exceedsMaxSize = 999999999999.9999D; 51 | 52 | string result = BaseTypeHelper.GetDoubleValue(exceedsMaxSize); 53 | Assert.Equal(maxZabbixMySqlValue, result); 54 | } 55 | 56 | [Fact] 57 | public void GetDoubleValue_SizeExceedsMin_ReturnsMinAllowedValue() 58 | { 59 | const string minZabbixMySqlValue = "-999000000000.0000"; 60 | const double exceedsMinSize = -999999999999.9999D; 61 | 62 | string result = BaseTypeHelper.GetDoubleValue(exceedsMinSize); 63 | Assert.Equal(minZabbixMySqlValue, result); 64 | } 65 | 66 | [Fact] 67 | public void GetDoubleValue_SizeIsAcceptable_ReturnsSize() 68 | { 69 | const double validSize = 124.99D; 70 | 71 | string result = BaseTypeHelper.GetDoubleValue(validSize); 72 | Assert.Equal("124.9900", result); 73 | } 74 | 75 | [Fact] 76 | public void GetDoubleValue_TooManyDecimals_DecimalIsTrimmed() 77 | { 78 | const double validSize = 124.99543D; 79 | 80 | string result = BaseTypeHelper.GetDoubleValue(validSize); 81 | Assert.Equal("124.9954", result); 82 | } 83 | 84 | [Fact] 85 | public void GetDecimalValue_SizeExceedsMax_ReturnsMaxAllowedValue() 86 | { 87 | const string maxZabbixMySqlValue = "999000000000.0000"; 88 | const decimal exceedsMaxSize = 999999999999.9999m; 89 | 90 | string result = BaseTypeHelper.GetDecimalValue(exceedsMaxSize); 91 | Assert.Equal(maxZabbixMySqlValue, result); 92 | } 93 | 94 | [Fact] 95 | public void GetDecimalValue_SizeExceedsMin_ReturnsMinAllowedValue() 96 | { 97 | const string minZabbixMySqlValue = "-999000000000.0000"; 98 | const decimal exceedsMinSize = -999999999999.9999m; 99 | 100 | string result = BaseTypeHelper.GetDecimalValue(exceedsMinSize); 101 | Assert.Equal(minZabbixMySqlValue, result); 102 | } 103 | 104 | [Fact] 105 | public void GetDecimalValue_SizeIsAcceptable_ReturnsSize() 106 | { 107 | const decimal validSize = 124.99m; 108 | 109 | string result = BaseTypeHelper.GetDecimalValue(validSize); 110 | Assert.Equal("124.9900", result); 111 | } 112 | 113 | [Fact] 114 | public void GetDecimalValue_TooManyDecimals_DecimalIsTrimmed() 115 | { 116 | const decimal validSize = 124.99543m; 117 | 118 | string result = BaseTypeHelper.GetDecimalValue(validSize); 119 | Assert.Equal("124.9954", result); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /Nabbix/NabbixAgent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Threading; 5 | using Common.Logging; 6 | 7 | namespace Nabbix 8 | { 9 | public interface INabbixAgent 10 | { 11 | void Start(); 12 | void Stop(); 13 | void RegisterInstance(object instance); 14 | } 15 | 16 | public class NabbixAgent : INabbixAgent 17 | { 18 | private static readonly ILog Log = LogManager.GetLogger(typeof(NabbixAgent)); 19 | 20 | private readonly ItemRegistry _registry; 21 | private readonly IPAddress _address; 22 | private readonly int _port; 23 | 24 | private TcpListener _listener; 25 | private CancellationTokenSource _source; 26 | 27 | public int LocalEndpointPort 28 | { 29 | get 30 | { 31 | return ((IPEndPoint)_listener.LocalEndpoint).Port; 32 | } 33 | } 34 | 35 | public NabbixAgent(ItemRegistry registry, string address, int port, bool startImmediately = true) 36 | { 37 | if (registry == null) throw new ArgumentNullException(nameof(registry)); 38 | 39 | _registry = registry; 40 | _address = address == null 41 | ? IPAddress.Any 42 | : IPAddress.Parse(address); 43 | _port = port; 44 | 45 | if (startImmediately) 46 | { 47 | Start(); 48 | } 49 | } 50 | 51 | public NabbixAgent(string address, int port, bool startImmediately = true, params object[] instances) 52 | : this(new ItemRegistry(), address, port, startImmediately) 53 | { 54 | if (instances == null) 55 | { 56 | return; 57 | } 58 | 59 | foreach (object instance in instances) 60 | { 61 | RegisterInstance(instance); 62 | } 63 | } 64 | 65 | public NabbixAgent(int port, params object[] instances) 66 | : this(null, port, true, instances) 67 | { 68 | } 69 | 70 | public NabbixAgent(string address, int port, params object[] instances) 71 | : this(address, port, true, instances) 72 | { 73 | } 74 | 75 | public void Start() 76 | { 77 | Log.Info("Starting NabbixAgent."); 78 | _source = new CancellationTokenSource(); 79 | 80 | _listener = new TcpListener(_address, _port); 81 | _listener.Start(); 82 | 83 | _listener.BeginAcceptTcpClient(ProcessRequest, _listener); 84 | } 85 | 86 | private void ProcessRequest(IAsyncResult ar) 87 | { 88 | if (_source.Token.IsCancellationRequested) 89 | { 90 | return; 91 | } 92 | 93 | if (!(ar.AsyncState is TcpListener listener)) 94 | { 95 | Log.Debug("TcpLister is Null. This is impossible"); 96 | return; 97 | } 98 | 99 | if (_source.Token.IsCancellationRequested) 100 | { 101 | Log.Debug("Token is already cancelled"); 102 | return; 103 | } 104 | 105 | listener.BeginAcceptTcpClient(ProcessRequest, listener); 106 | 107 | try 108 | { 109 | using (TcpClient client = listener.EndAcceptTcpClient(ar)) 110 | { 111 | QueryHandler.Run(client, _registry); 112 | } 113 | } 114 | catch (Exception e) 115 | { 116 | Log.Error("Error in QueryHandler Run", e); 117 | } 118 | } 119 | 120 | public void Stop() 121 | { 122 | if (_source.Token.IsCancellationRequested) 123 | { 124 | Log.Debug("Cancellation has already been requested."); 125 | return; 126 | } 127 | 128 | Log.Info("Stopping TCP connections."); 129 | _source.Cancel(); 130 | 131 | Thread.Sleep(100); 132 | Log.Info("Stopping TCP Listener."); 133 | _listener.Stop(); 134 | 135 | Log.Info("Stopped successfully."); 136 | } 137 | 138 | public void RegisterInstance(object instance) 139 | { 140 | if (instance == null) 141 | { 142 | Log.WarnFormat("instance is null"); 143 | return; 144 | } 145 | 146 | Log.InfoFormat("Registering instance {0}.", instance.GetType()); 147 | 148 | _registry.RegisterInstance(instance); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | *.idea 14 | *.DS_Store 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nupkg 150 | # The packages folder can be ignored because of Package Restore 151 | **/packages/* 152 | # except build/, which is used as an MSBuild target. 153 | !**/packages/build/ 154 | # Uncomment if necessary however generally it will be regenerated when needed 155 | #!**/packages/repositories.config 156 | # NuGet v3's project.json files produces more ignoreable files 157 | *.nuget.props 158 | *.nuget.targets 159 | 160 | # Microsoft Azure Build Output 161 | csx/ 162 | *.build.csdef 163 | 164 | # Microsoft Azure Emulator 165 | ecf/ 166 | rcf/ 167 | 168 | # Microsoft Azure ApplicationInsights config file 169 | ApplicationInsights.config 170 | 171 | # Windows Store app package directory 172 | AppPackages/ 173 | BundleArtifacts/ 174 | 175 | # Visual Studio cache files 176 | # files ending in .cache can be ignored 177 | *.[Cc]ache 178 | # but keep track of directories ending in .cache 179 | !*.[Cc]ache/ 180 | 181 | # Others 182 | ClientBin/ 183 | ~$* 184 | *~ 185 | *.dbmdl 186 | *.dbproj.schemaview 187 | *.pfx 188 | *.publishsettings 189 | node_modules/ 190 | orleans.codegen.cs 191 | 192 | # RIA/Silverlight projects 193 | Generated_Code/ 194 | 195 | # Backup & report files from converting an old project file 196 | # to a newer Visual Studio version. Backup files are not needed, 197 | # because we have git ;-) 198 | _UpgradeReport_Files/ 199 | Backup*/ 200 | UpgradeLog*.XML 201 | UpgradeLog*.htm 202 | 203 | # SQL Server files 204 | *.mdf 205 | *.ldf 206 | 207 | # Business Intelligence projects 208 | *.rdl.data 209 | *.bim.layout 210 | *.bim_*.settings 211 | 212 | # Microsoft Fakes 213 | FakesAssemblies/ 214 | 215 | # GhostDoc plugin setting file 216 | *.GhostDoc.xml 217 | 218 | # Node.js Tools for Visual Studio 219 | .ntvs_analysis.dat 220 | 221 | # Visual Studio 6 build log 222 | *.plg 223 | 224 | # Visual Studio 6 workspace options file 225 | *.opt 226 | 227 | # Visual Studio LightSwitch build output 228 | **/*.HTMLClient/GeneratedArtifacts 229 | **/*.DesktopClient/GeneratedArtifacts 230 | **/*.DesktopClient/ModelManifest.xml 231 | **/*.Server/GeneratedArtifacts 232 | **/*.Server/ModelManifest.xml 233 | _Pvt_Extensions 234 | 235 | # Paket dependency manager 236 | .paket/paket.exe 237 | 238 | # FAKE - F# Make 239 | .fake/ 240 | -------------------------------------------------------------------------------- /Nabbix.Tests/NabbixAgentTests.cs: -------------------------------------------------------------------------------- 1 | using Nabbix.Items; 2 | using System; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace Nabbix.Tests 8 | { 9 | public class NabbixAgentTests : IDisposable 10 | { 11 | private readonly NabbixAgent _agent; 12 | 13 | private const string OneCharacterKey = "h"; 14 | private const string TwoCharacterKey = "he"; 15 | private const string ThreeCharacterKey = "hel"; 16 | private const string FourCharacterKey = "ruok"; 17 | private const string FiveCharacterKey = "hello"; 18 | private const string FakeZabbixHeader = "ZBXD1"; 19 | private const string MediumSizedKey = "hello world"; 20 | 21 | private const string LongKey = 22 | "In cyberspace where lines of code do dwell, /A timeless phrase in bytes and bits unfurled, /It whispers softly, breaking coder's spell, /The universal hymn: \"Hello, World!\"/From languages diverse, its voice resounds, /In Python's embrace or C's stern gaze, /In Java's tortuous syntax, or HTML's bounds, /In every tongue, its message finds its ways. /In but a single line, its tale is told, /A greeting, a beginning, and a sign, /In zeroes and ones, its truth behold, /A spark ignites, a code divine. //So let it ring in every realm unfurled, /The programmer's anthem: \"Hello, World!\""; 23 | 24 | public NabbixAgentTests() 25 | { 26 | _agent = new NabbixAgent("127.0.0.1", 0, new MyCounter()); 27 | _agent.Start(); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | _agent.Stop(); 33 | } 34 | 35 | private class MyCounter 36 | { 37 | [NabbixItem(OneCharacterKey)] 38 | // ReSharper disable UnusedMember.Local 39 | public long OneCharacterReturn => 1234; 40 | 41 | [NabbixItem(TwoCharacterKey)] 42 | public long ReturnConstant => 1234; 43 | 44 | [NabbixItem(ThreeCharacterKey)] 45 | public long ThreeCharacterReturn => 1234; 46 | 47 | [NabbixItem(FourCharacterKey)] 48 | public long FourCharacterReturn => 1234; 49 | 50 | [NabbixItem(FiveCharacterKey)] 51 | public long FiveCharacterReturn => 1234; 52 | 53 | [NabbixItem(FakeZabbixHeader)] 54 | public long FakeZabbixHeaderReturn => 1234; 55 | 56 | [NabbixItem(MediumSizedKey)] 57 | public long MediumSizedKeyReturn => 1234; 58 | 59 | [NabbixItem(LongKey)] 60 | public long LongSizedKeyPattern => 1234; 61 | // ReSharper enable UnusedMember.Local 62 | 63 | } 64 | 65 | private static byte[] CreateOldProtocolRequest(string key) 66 | { 67 | var sb = new StringBuilder(); 68 | sb.Append(key); 69 | sb.Append((char)10); 70 | 71 | return Encoding.ASCII.GetBytes(sb.ToString()); 72 | } 73 | 74 | private static byte[] CreateNewProtocolRequest(string key) 75 | { 76 | byte[] header = Encoding.ASCII.GetBytes("ZBXD\x01"); 77 | byte[] dataLen = BitConverter.GetBytes((long)key.Length); 78 | byte[] content = Encoding.ASCII.GetBytes(key); 79 | byte[] message = new byte[header.Length + dataLen.Length + content.Length]; 80 | Buffer.BlockCopy(header, 0, message, 0, header.Length); 81 | Buffer.BlockCopy(dataLen, 0, message, header.Length, dataLen.Length); 82 | Buffer.BlockCopy(content, 0, message, header.Length + dataLen.Length, content.Length); 83 | 84 | return message; 85 | } 86 | 87 | private static string ReadResponse(NetworkStream stream) 88 | { 89 | byte[] data = new byte[1024]; 90 | int totalRead = 0; 91 | 92 | int read; 93 | while ((read = stream.Read(data, totalRead, data.Length - totalRead)) > 0) 94 | { 95 | totalRead += read; 96 | 97 | if (!stream.DataAvailable) 98 | break; 99 | } 100 | 101 | return Encoding.ASCII.GetString(data, 0, totalRead); 102 | } 103 | 104 | [Theory] 105 | [InlineData(true, MediumSizedKey)] 106 | [InlineData(true, OneCharacterKey)] 107 | [InlineData(true, TwoCharacterKey)] 108 | [InlineData(true, ThreeCharacterKey)] 109 | [InlineData(true, FourCharacterKey)] 110 | [InlineData(true, FiveCharacterKey)] 111 | [InlineData(true, FakeZabbixHeader)] 112 | [InlineData(true, LongKey)] 113 | [InlineData(false, MediumSizedKey)] 114 | [InlineData(false, OneCharacterKey)] 115 | [InlineData(false, TwoCharacterKey)] 116 | [InlineData(false, ThreeCharacterKey)] 117 | [InlineData(false, FourCharacterKey)] 118 | [InlineData(false, FiveCharacterKey)] 119 | [InlineData(false, FakeZabbixHeader)] 120 | [InlineData(false, LongKey)] 121 | public void Integration_OneConnection_SendReceiveResults(bool useOldProtocol, string key) 122 | { 123 | var port = _agent.LocalEndpointPort; 124 | 125 | for (int i = 0; i < 3; i++) 126 | { 127 | var client = new TcpClient("127.0.0.1", port); 128 | using (NetworkStream stream = client.GetStream()) 129 | { 130 | byte[] request = useOldProtocol ? CreateOldProtocolRequest(key) : CreateNewProtocolRequest(key); 131 | stream.Write(request, 0, request.Length); 132 | stream.Flush(); 133 | 134 | string results = ReadResponse(stream); 135 | Assert.EndsWith("1234", results); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | --------------------------------------------------------------------------------