فهرست منبع

Upgrade to .NET 5.0

By moving to .NET 5.0 we can leverage the builtin build system to make a
single executable instead of Fody and ilk. We now create a self
contained executable instead of relying on the user having the .net 5.0
runtime installed (which I'm assuming will be a rarity for quite some
time). The biggest downside is the executable size is larger 10x larger
(3MB vs 30MB).
Nick Babcock 4 سال پیش
والد
کامیت
7fba47ed21

+ 29 - 0
.github/workflows/ci.yml

@@ -0,0 +1,29 @@
+name: CI
+
+on:
+  pull_request:
+  push:
+    branches: ['master']
+    tags: ['v*']
+  schedule:
+  - cron: '00 01 * * *'
+
+jobs:
+  test:
+    name: test
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: ['ubuntu-latest', 'windows-latest']
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-dotnet@v1
+      with:
+        dotnet-version: '5.0.x'
+    - run: dotnet test
+      if: matrix.os == 'ubuntu-latest'
+    - run: dotnet test --filter 'Category!=integration'
+      if: matrix.os == 'windows-latest'

+ 40 - 0
.github/workflows/release.yml

@@ -0,0 +1,40 @@
+name: Release
+
+on:
+  pull_request:
+  push:
+    branches: ['master']
+    tags: ['v*']
+  schedule:
+  - cron: '00 01 * * *'
+
+jobs:
+  release:
+    name: release
+    runs-on: windows-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-dotnet@v1
+      with:
+        dotnet-version: '5.0.x'
+    - run: dotnet publish -c Release .\OhmGraphite\
+    - name: Set artifact name
+      shell: bash
+      working-directory: OhmGraphite\bin
+      run: |
+        echo "ARTIFACT_NAME=$(echo *.zip)" >> $GITHUB_ENV
+    - uses: actions/upload-artifact@v2
+      with:
+        path: OhmGraphite\bin\*.zip
+        name: ${{ env.ARTIFACT_NAME }}
+        if-no-files-found: error
+    - name: Release
+      uses: softprops/action-gh-release@v1
+      if: startsWith(github.ref, 'refs/tags/')
+      with:
+        files: 'OhmGraphite\bin\*.zip'
+        prerelease: true
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 5
.travis.yml

@@ -1,5 +0,0 @@
-language: rust
-sudo: required
-services:
-  - docker
-script: sudo docker-compose up --abort-on-container-exit --build --exit-code-from app

+ 60 - 42
OhmGraphite.Test/GraphiteTest.cs

@@ -1,6 +1,9 @@
 using System;
 using System.Net.Http;
 using System.Threading;
+using DotNet.Testcontainers.Containers.Builders;
+using DotNet.Testcontainers.Containers.Modules;
+using DotNet.Testcontainers.Containers.WaitStrategies;
 using Xunit;
 
 namespace OhmGraphite.Test
@@ -10,66 +13,81 @@ namespace OhmGraphite.Test
         [Fact, Trait("Category", "integration")]
         public async void InsertGraphiteTest()
         {
-            using (var writer = new GraphiteWriter("graphite", 2003, "my-pc", tags: false))
-            using (var client = new HttpClient())
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage("graphiteapp/graphite-statsd")
+                .WithEnvironment("REDIS_TAGDB", "y")
+                .WithPortBinding(2003, assignRandomHostPort: true)
+                .WithPortBinding(80, assignRandomHostPort: true)
+                .WithPortBinding(8080, assignRandomHostPort: true)
+                .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8080));
+
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+            var port = container.GetMappedPublicPort(2003);
+            using var writer = new GraphiteWriter(container.Hostname, port, "my-pc", tags: false);
+            using var client = new HttpClient();
+            for (int attempts = 0; ; attempts++)
             {
-                for (int attempts = 0; ; attempts++)
+                try
                 {
-                    try
-                    {
-                        await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+                    await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
 
-                        var resp = await client.GetAsync(
-                            "http://graphite/render?format=csv&target=ohm.my-pc.intelcpu.0.temperature.cpucore.1");
-                        var content = await resp.Content.ReadAsStringAsync();
-                        Assert.Contains("ohm.my-pc.intelcpu.0.temperature.cpucore.1", content);
-                        break;
-                    }
-                    catch (Exception)
+                    var resp = await client.GetAsync(
+                        $"http://{container.Hostname}:{container.GetMappedPublicPort(80)}/render?format=csv&target=ohm.my-pc.intelcpu.0.temperature.cpucore.1");
+                    var content = await resp.Content.ReadAsStringAsync();
+                    Assert.Contains("ohm.my-pc.intelcpu.0.temperature.cpucore.1", content);
+                    break;
+                }
+                catch (Exception)
+                {
+                    if (attempts >= 10)
                     {
-                        if (attempts >= 10)
-                        {
-                            throw;
-                        }
-
-                        Thread.Sleep(TimeSpan.FromSeconds(1));
+                        throw;
                     }
+
+                    Thread.Sleep(TimeSpan.FromSeconds(1));
                 }
             }
-
         }
 
         [Fact, Trait("Category", "integration")]
         public async void InsertTagGraphiteTest()
         {
-            // Let the tag engine time to breathe
-            Thread.Sleep(TimeSpan.FromSeconds(2));
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage("graphiteapp/graphite-statsd")
+                .WithEnvironment("REDIS_TAGDB", "y")
+                .WithPortBinding(2003, assignRandomHostPort: true)
+                .WithPortBinding(80, assignRandomHostPort: true)
+                .WithPortBinding(8080, assignRandomHostPort: true)
+                                .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8080));
 
-            using (var writer = new GraphiteWriter("graphite", 2003, "my-pc", tags: true))
-            using (var client = new HttpClient())
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+            var port = container.GetMappedPublicPort(2003);
+            using var writer = new GraphiteWriter(container.Hostname, port, "my-pc", tags: true);
+            using var client = new HttpClient();
+            for (int attempts = 0;; attempts++)
             {
-                for (int attempts = 0; ; attempts++)
+                try
                 {
-                    try
-                    {
-                        await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+                    await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+                    var resp = await client.GetAsync(
+                        $"http://{container.Hostname}:{container.GetMappedPublicPort(80)}/render?format=csv&target=seriesByTag('sensor_type=Temperature','hardware_type=CPU')");
 
-                        var resp = await client.GetAsync("http://graphite/render?format=csv&target=seriesByTag('sensor_type=Temperature','hardware_type=CPU')");
-                        var content = await resp.Content.ReadAsStringAsync();
-                        Assert.Contains("host=my-pc", content);
-                        Assert.Contains("app=ohm", content);
-                        Assert.Contains("sensor_type=Temperature", content);
-                        break;
-                    }
-                    catch (Exception)
+                    var content = await resp.Content.ReadAsStringAsync();
+                    Assert.Contains("host=my-pc", content);
+                    Assert.Contains("app=ohm", content);
+                    Assert.Contains("sensor_type=Temperature", content);
+                    break;
+                }
+                catch (Exception)
+                {
+                    if (attempts >= 10)
                     {
-                        if (attempts >= 10)
-                        {
-                            throw;
-                        }
-
-                        Thread.Sleep(TimeSpan.FromSeconds(1));
+                        throw;
                     }
+
+                    Thread.Sleep(TimeSpan.FromSeconds(1));
                 }
             }
         }

+ 95 - 42
OhmGraphite.Test/InfluxTest.cs

@@ -1,6 +1,9 @@
 using System;
 using System.Net.Http;
 using System.Threading;
+using DotNet.Testcontainers.Containers.Builders;
+using DotNet.Testcontainers.Containers.Modules;
+using DotNet.Testcontainers.Containers.WaitStrategies;
 using Xunit;
 
 namespace OhmGraphite.Test
@@ -10,32 +13,41 @@ namespace OhmGraphite.Test
         [Fact, Trait("Category", "integration")]
         public async void CanInsertIntoInflux()
         {
-            var config = new InfluxConfig(new Uri("http://influx:8086"), "mydb", "my_user", "my_pass");
-            using (var writer = new InfluxWriter(config, "my-pc"))
-            using (var client = new HttpClient())
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage("influxdb:1.8-alpine")
+                .WithEnvironment("INFLUXDB_DB", "mydb")
+                .WithEnvironment("INFLUXDB_USER", "my_user")
+                .WithEnvironment("INFLUXDB_USER_PASSWORD", "my_pass")
+                .WithPortBinding(8086, assignRandomHostPort: true)
+                .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8086));
+
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+            var baseUrl = $"http://{container.Hostname}:{container.GetMappedPublicPort(8086)}";
+            var config = new InfluxConfig(new Uri(baseUrl), "mydb", "my_user", "my_pass");
+            using var writer = new InfluxWriter(config, "my-pc");
+            using var client = new HttpClient();
+            for (int attempts = 0; ; attempts++)
             {
-                for (int attempts = 0; ; attempts++)
+                try
                 {
-                    try
-                    {
-                        await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+                    await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
 
-                        var resp = await client.GetAsync(
-                            "http://influx:8086/query?pretty=true&db=mydb&q=SELECT%20*%20FROM%20Temperature");
-                        Assert.True(resp.IsSuccessStatusCode);
-                        var content = await resp.Content.ReadAsStringAsync();
-                        Assert.Contains("/intelcpu/0/temperature/0", content);
-                        break;
-                    }
-                    catch (Exception)
+                    var resp = await client.GetAsync(
+                        $"{baseUrl}/query?pretty=true&db=mydb&q=SELECT%20*%20FROM%20Temperature");
+                    Assert.True(resp.IsSuccessStatusCode);
+                    var content = await resp.Content.ReadAsStringAsync();
+                    Assert.Contains("/intelcpu/0/temperature/0", content);
+                    break;
+                }
+                catch (Exception)
+                {
+                    if (attempts >= 10)
                     {
-                        if (attempts >= 10)
-                        {
-                            throw;
-                        }
-
-                        Thread.Sleep(TimeSpan.FromSeconds(1));
+                        throw;
                     }
+
+                    Thread.Sleep(TimeSpan.FromSeconds(1));
                 }
             }
         }
@@ -43,35 +55,76 @@ namespace OhmGraphite.Test
         [Fact, Trait("Category", "integration")]
         public async void CanInsertIntoPasswordLessInfluxdb()
         {
-            var config = new InfluxConfig(new Uri("http://influx-passwordless:8086"), "mydb", "my_user", null);
-            using (var writer = new InfluxWriter(config, "my-pc"))
-            using (var client = new HttpClient())
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage("influxdb:1.8-alpine")
+                .WithEnvironment("INFLUXDB_DB", "mydb")
+                .WithEnvironment("INFLUXDB_USER", "my_user")
+                .WithEnvironment("INFLUXDB_HTTP_AUTH_ENABLED", "false")
+                .WithPortBinding(8086, assignRandomHostPort: true)
+                .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8086));
+
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+            var baseUrl = $"http://{container.Hostname}:{container.GetMappedPublicPort(8086)}";
+            var config = new InfluxConfig(new Uri(baseUrl), "mydb", "my_user", null);
+            using var writer = new InfluxWriter(config, "my-pc");
+            using var client = new HttpClient();
+            for (int attempts = 0; ; attempts++)
             {
-                for (int attempts = 0; ; attempts++)
+                try
                 {
-                    try
-                    {
-                        await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+                    await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
 
-                        var resp = await client.GetAsync(
-                            "http://influx-passwordless:8086/query?pretty=true&db=mydb&q=SELECT%20*%20FROM%20Temperature");
-                        Assert.True(resp.IsSuccessStatusCode);
-                        var content = await resp.Content.ReadAsStringAsync();
-                        Assert.Contains("/intelcpu/0/temperature/0", content);
-                        break;
-                    }
-                    catch (Exception)
+                    var resp = await client.GetAsync(
+                        $"{baseUrl}/query?pretty=true&db=mydb&q=SELECT%20*%20FROM%20Temperature");
+                    Assert.True(resp.IsSuccessStatusCode);
+                    var content = await resp.Content.ReadAsStringAsync();
+                    Assert.Contains("/intelcpu/0/temperature/0", content);
+                    break;
+                }
+                catch (Exception)
+                {
+                    if (attempts >= 10)
                     {
-                        if (attempts >= 10)
-                        {
-                            throw;
-                        }
-
-                        Thread.Sleep(TimeSpan.FromSeconds(1));
+                        throw;
                     }
+
+                    Thread.Sleep(TimeSpan.FromSeconds(1));
                 }
             }
         }
 
+        //[Fact, Trait("Category", "integration")]
+        //public async void CanInsertIntoInflux2()
+        //{
+        //    var config = new InfluxConfig(new Uri("http://influx2:8086"), "mydb", "my_user", "my_pass");
+        //    using (var writer = new InfluxWriter(config, "my-pc"))
+        //    using (var client = new HttpClient())
+        //    {
+        //        for (int attempts = 0; ; attempts++)
+        //        {
+        //            try
+        //            {
+        //                await writer.ReportMetrics(DateTime.Now, TestSensorCreator.Values());
+
+        //                var resp = await client.GetAsync(
+        //                    "http://influx2:8086/query?pretty=true&db=mydb&q=SELECT%20*%20FROM%20Temperature");
+        //                Assert.True(resp.IsSuccessStatusCode);
+        //                var content = await resp.Content.ReadAsStringAsync();
+        //                Assert.Contains("/intelcpu/0/temperature/0", content);
+        //                break;
+        //            }
+        //            catch (Exception)
+        //            {
+        //                if (attempts >= 10)
+        //                {
+        //                    throw;
+        //                }
+
+        //                Thread.Sleep(TimeSpan.FromSeconds(1));
+        //            }
+        //        }
+        //    }
+        //}
     }
 }

+ 7 - 5
OhmGraphite.Test/OhmGraphite.Test.csproj

@@ -1,10 +1,12 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net461</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="DotNet.Testcontainers" Version="1.5.0-beta.20210415.1" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
@@ -15,10 +17,6 @@
     <ProjectReference Include="..\OhmGraphite\OhmGraphite.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Reference Include="System.Configuration" />
-  </ItemGroup>
-
   <ItemGroup>
     <None Include="..\assets\timescale.config" Link="assets/timescale.config" CopyToOutputDirectory="PreserveNewest" />
     <None Include="..\assets\prometheus.config" Link="assets/prometheus.config" CopyToOutputDirectory="PreserveNewest" />
@@ -28,6 +26,10 @@
     <None Include="..\assets\static-name.config" Link="assets/static-name.config" CopyToOutputDirectory="PreserveNewest" />
     <None Include="..\assets\rename.config" Link="assets/rename.config" CopyToOutputDirectory="PreserveNewest" />
     <None Include="..\assets\hidden-sensors.config" Link="assets/hidden-sensors.config" CopyToOutputDirectory="PreserveNewest" />
+
+    <None Include="..\ci\timescale.dockerfile" Link="docker/timescale.dockerfile" CopyToOutputDirectory="PreserveNewest" />
+    <None Include="..\ci\setup-docker.sh" Link="docker/ci/setup-docker.sh" CopyToOutputDirectory="PreserveNewest" />
+    <None Include="..\assets\schema.sql" Link="docker/assets/schema.sql" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
 
 </Project>

+ 14 - 18
OhmGraphite.Test/PrometheusTest.cs

@@ -13,15 +13,13 @@ namespace OhmGraphite.Test
             var collector = new TestSensorCreator();
             var registry = PrometheusCollection.SetupDefault(collector);
             var mserver = new MetricServer("localhost", 21881, registry: registry);
-            using (var server = new PrometheusServer(mserver, collector))
-            using (var client = new HttpClient())
-            {
-                server.Start();
-                var resp = await client.GetAsync("http://localhost:21881/metrics");
-                Assert.True(resp.IsSuccessStatusCode);
-                var content = await resp.Content.ReadAsStringAsync();
-                Assert.Contains("# HELP ohm_cpu_celsius Metric reported by open hardware sensor", content);
-            }
+            using var server = new PrometheusServer(mserver, collector);
+            using var client = new HttpClient();
+            server.Start();
+            var resp = await client.GetAsync("http://localhost:21881/metrics");
+            Assert.True(resp.IsSuccessStatusCode);
+            var content = await resp.Content.ReadAsStringAsync();
+            Assert.Contains("# HELP ohm_cpu_celsius Metric reported by open hardware sensor", content);
         }
 
         [Fact]
@@ -30,15 +28,13 @@ namespace OhmGraphite.Test
             var collector = new NicGuidSensor();
             var registry = PrometheusCollection.SetupDefault(collector);
             var mserver = new MetricServer("localhost", 21882, registry: registry);
-            using (var server = new PrometheusServer(mserver, collector))
-            using (var client = new HttpClient())
-            {
-                server.Start();
-                var resp = await client.GetAsync("http://localhost:21882/metrics");
-                Assert.True(resp.IsSuccessStatusCode);
-                var content = await resp.Content.ReadAsStringAsync();
-                Assert.Contains("Bluetooth Network Connection 2", content);
-            }
+            using var server = new PrometheusServer(mserver, collector);
+            using var client = new HttpClient();
+            server.Start();
+            var resp = await client.GetAsync("http://localhost:21882/metrics");
+            Assert.True(resp.IsSuccessStatusCode);
+            var content = await resp.Content.ReadAsStringAsync();
+            Assert.Contains("Bluetooth Network Connection 2", content);
         }
 
         [Fact]

+ 25 - 26
OhmGraphite.Test/SensorCollectorTest.cs

@@ -1,4 +1,5 @@
 using System.Linq;
+using System.Runtime.InteropServices;
 using LibreHardwareMonitor.Hardware;
 using Xunit;
 
@@ -9,39 +10,37 @@ namespace OhmGraphite.Test
         [Fact]
         public void SensorsAddedWhenHardwareAdded()
         {
-            var computer = new Computer();
-            var collector = new SensorCollector(computer, MetricConfig.ParseAppSettings(new BlankConfig()));
-
-            try
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
             {
-                collector.Open();
-                var unused = collector.ReadAllSensors().Count();
-
-                computer.IsCpuEnabled = true;
-                computer.IsMotherboardEnabled = true;
-                computer.IsStorageEnabled = true;
-                computer.IsMemoryEnabled = true;
+                return;
+            }
 
-                var addedCount = collector.ReadAllSensors().Count();
+            var computer = new Computer();
+            using var collector = new SensorCollector(computer, MetricConfig.ParseAppSettings(new BlankConfig()));
+            
+            collector.Open();
+            var unused = collector.ReadAllSensors().Count();
 
-                // On CI platforms there may be no detected hardware
-                if (addedCount <= 0)
-                {
-                    return;
-                }
+            computer.IsCpuEnabled = true;
+            computer.IsMotherboardEnabled = true;
+            computer.IsStorageEnabled = true;
+            computer.IsMemoryEnabled = true;
 
-                computer.IsCpuEnabled = false;
-                computer.IsMotherboardEnabled = false;
-                computer.IsStorageEnabled = false;
-                computer.IsMemoryEnabled = false;
+            var addedCount = collector.ReadAllSensors().Count();
 
-                var removedCount = collector.ReadAllSensors().Count();
-                Assert.True(addedCount > removedCount, "addedCount > removedCount");
-            }
-            finally
+            // On CI platforms there may be no detected hardware
+            if (addedCount <= 0)
             {
-                collector.Close();
+                return;
             }
+
+            computer.IsCpuEnabled = false;
+            computer.IsMotherboardEnabled = false;
+            computer.IsStorageEnabled = false;
+            computer.IsMemoryEnabled = false;
+
+            var removedCount = collector.ReadAllSensors().Count();
+            Assert.True(addedCount > removedCount, "addedCount > removedCount");
         }
     }
 }

+ 48 - 25
OhmGraphite.Test/TimescaleTest.cs

@@ -1,4 +1,8 @@
 using System;
+using DotNet.Testcontainers.Containers.Builders;
+using DotNet.Testcontainers.Containers.Modules;
+using DotNet.Testcontainers.Containers.WaitStrategies;
+using DotNet.Testcontainers.Images.Builders;
 using Npgsql;
 using Xunit;
 
@@ -9,40 +13,59 @@ namespace OhmGraphite.Test
         [Fact, Trait("Category", "integration")]
         public async void CanSetupTimescale()
         {
-            const string connStr = "Host=timescale;Username=postgres;Password=123456";
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage("timescale/timescaledb:latest-pg12")
+                .WithEnvironment("POSTGRES_PASSWORD", "123456")
+                .WithPortBinding(5432, assignRandomHostPort: true)
+                .WithWaitStrategy(Wait.ForUnixContainer()
+                    .UntilCommandIsCompleted($"pg_isready -h 'localhost' -p '5432'"));
+
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+
+            string connStr = $"Host={container.Hostname};Username=postgres;Password=123456;Port={container.GetMappedPublicPort(5432)}";
             var epoch = new DateTime(2001, 1, 13);
 
-            using (var writer = new TimescaleWriter(connStr, true, "my-pc"))
-            using (var conn = new NpgsqlConnection(connStr))
-            {
-                await writer.ReportMetrics(epoch, TestSensorCreator.Values());
-
-                conn.Open();
-                using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM ohm_stats", conn))
-                {
-                    Assert.Equal(3, Convert.ToInt32(cmd.ExecuteScalar()));
-                }
-            }
+            using var writer = new TimescaleWriter(connStr, true, "my-pc");
+            await using var conn = new NpgsqlConnection(connStr);
+            await writer.ReportMetrics(epoch, TestSensorCreator.Values());
+
+            conn.Open();
+            await using var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM ohm_stats", conn);
+            Assert.Equal(3, Convert.ToInt32(cmd.ExecuteScalar()));
         }
 
         [Fact, Trait("Category", "integration")]
         public async void InsertOnlyTimescale()
         {
-            const string selectStr = "Host=timescale;Username=postgres;Password=123456;Database=timescale_built";
+            var image = await new ImageFromDockerfileBuilder()
+                .WithDockerfile("timescale.dockerfile")
+                .WithDockerfileDirectory("docker")
+                .WithDeleteIfExists(true)
+                .WithName("ohm-graphite-insert-only-timescale")
+                .Build();
+
+            var testContainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
+                .WithImage(image)
+                .WithEnvironment("POSTGRES_PASSWORD", "123456")
+                .WithPortBinding(5432, assignRandomHostPort: true)
+                .WithWaitStrategy(Wait.ForUnixContainer()
+                    .UntilCommandIsCompleted($"pg_isready -h 'localhost' -p '5432'"));
+
+            await using var container = testContainersBuilder.Build();
+            await container.StartAsync();
+
+            string selectStr =$"Host={container.Hostname};Username=postgres;Password=123456;Port={container.GetMappedPublicPort(5432)};Database=timescale_built";
             var epoch = new DateTime(2001, 1, 13);
 
-            const string connStr = "Host=timescale;Username=ohm;Password=itsohm;Database=timescale_built";
-            using (var writer = new TimescaleWriter(connStr, false, "my-pc"))
-            using (var conn = new NpgsqlConnection(selectStr))
-            {
-                await writer.ReportMetrics(epoch, TestSensorCreator.Values());
-
-                conn.Open();
-                using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM ohm_stats", conn))
-                {
-                    Assert.Equal(3, Convert.ToInt32(cmd.ExecuteScalar()));
-                }
-            }
+            string connStr = $"Host={container.Hostname};Username=ohm;Password=itsohm;Port={container.GetMappedPublicPort(5432)};Database=timescale_built";
+            using var writer = new TimescaleWriter(connStr, false, "my-pc");
+            await using var conn = new NpgsqlConnection(selectStr);
+            await writer.ReportMetrics(epoch, TestSensorCreator.Values());
+
+            conn.Open();
+            await using var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM ohm_stats", conn);
+            Assert.Equal(3, Convert.ToInt32(cmd.ExecuteScalar()));
         }
     }
 }

+ 0 - 11
OhmGraphite/AppConfigManager.cs

@@ -1,11 +0,0 @@
-using System.Configuration;
-  
-namespace OhmGraphite
-{
-    class AppConfigManager : IAppConfig
-    {
-        public string this[string name] => ConfigurationManager.AppSettings[name];
-
-        public string[] GetKeys() => ConfigurationManager.AppSettings.AllKeys;
-    }
-}

+ 0 - 5
OhmGraphite/FodyWeavers.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<Weavers>
-  <Costura />
-  <Resourcer/>
-</Weavers>

+ 41 - 9
OhmGraphite/InfluxWriter.cs

@@ -1,18 +1,20 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
 using System.Threading.Tasks;
-using InfluxDB.LineProtocol.Client;
 using InfluxDB.LineProtocol.Payload;
 using NLog;
-using LibreHardwareMonitor.Hardware;
 
 namespace OhmGraphite
 {
     public class InfluxWriter : IWriteMetrics
     {
         private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
-
+        private readonly HttpClient _client = new HttpClient();
         private readonly InfluxConfig _config;
         private readonly string _localHost;
 
@@ -20,23 +22,53 @@ namespace OhmGraphite
         {
             _config = config;
             _localHost = localHost;
+
+            if (!string.IsNullOrEmpty(_config.User) && !string.IsNullOrEmpty(_config.Password))
+            {
+                var raw = Encoding.UTF8.GetBytes($"{_config.User}:{_config.Password}");
+                var encoded = Convert.ToBase64String(raw);
+                _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encoded);
+            }
         }
 
         public async Task ReportMetrics(DateTime reportTime, IEnumerable<ReportedValue> sensors)
         {
             var payload = new LineProtocolPayload();
-            var password = _config.User != null ? (_config.Password ?? "") : null;
-            var client = new LineProtocolClient(_config.Address, _config.Db, _config.User, password);
-
             foreach (var point in sensors.Select(x => NewPoint(reportTime, x)))
             {
                 payload.Add(point);
             }
 
-            var result = await client.WriteAsync(payload);
-            if (!result.Success)
+            // can't use influx db client as they don't have one that is both 1.x and 2.x compatible
+            // so we implement our own compatible client
+            var formattedData = new StringWriter();
+            payload.Format(formattedData);
+            formattedData.Flush();
+            var outData = Encoding.UTF8.GetBytes(formattedData.ToString());
+            var content = new ByteArrayContent(outData);
+
+            var queries = new List<string>();
+
+            // passwordless authentication
+            if (!string.IsNullOrEmpty(_config.User) && string.IsNullOrEmpty(_config.Password))
+            {
+                queries.Add($"u={_config.User}&p=");
+            }
+
+            if (!string.IsNullOrEmpty(_config.Db))
+            {
+                queries.Add($"db={_config.Db}");
+            }
+
+            var qs = string.Join("&", queries);
+            qs = !string.IsNullOrEmpty(qs) ? $"?{qs}" : "";
+            var addrPath = new Uri(_config.Address, "/write");
+            var url = $"{addrPath}{qs}";
+            var response = await _client.PostAsync(url, content);
+            if (!response.IsSuccessStatusCode)
             {
-                Logger.Error("Influxdb encountered an error: {0}", result.ErrorMessage);
+                var err = await response.Content.ReadAsStringAsync();
+                Logger.Error("Influxdb encountered an error: {0}", err);
             }
         }
 

+ 21 - 11
OhmGraphite/OhmGraphite.csproj

@@ -2,8 +2,13 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net461</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
+    <PublishSingleFile>true</PublishSingleFile>
+    <SelfContained>true</SelfContained>
+    <PublishTrimmed>true</PublishTrimmed>
+    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
     <PlatformTarget>AnyCPU</PlatformTarget>
     <Authors>Nick Babcock</Authors>
 
@@ -24,29 +29,34 @@
     <Content Include="NLog.config">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
-    <EmbeddedResource Include="..\assets\schema.sql" />
+    <EmbeddedResource Include="..\assets\schema.sql">
+      <LogicalName>schema.sql</LogicalName>
+    </EmbeddedResource>
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Costura.Fody" Version="3.3.2" />
-    <PackageReference Include="NLog.Config" Version="4.7.9" />
     <PackageReference Include="Npgsql" Version="5.0.4" />
     <PackageReference Include="prometheus-net" Version="4.1.1" />
-    <PackageReference Include="Resourcer.Fody" Version="1.7.3" />
+    <PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
     <PackageReference Include="TopShelf" Version="4.2.1" />
     <PackageReference Include="Topshelf.NLog" Version="4.2.1" />
     <PackageReference Include="InfluxDB.LineProtocol" Version="1.1.1" />
-    <PackageReference Include="MSBuildTasks" Version="1.5.0.235" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\LibreHardwareMonitor\LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj" />
   </ItemGroup>
-  <ItemGroup>
-    <Reference Include="System.Configuration" />
-  </ItemGroup>
 
-  <Target Name="PackExecutable" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
-    <Zip Files="$(OutputPath)OhmGraphite.exe;$(OutputPath)OhmGraphite.exe.config;$(OutputPath)NLog.config" WorkingDirectory="$(OutputPath)" ZipFileName="$(BaseOutputPath)OhmGraphite-$(Major).$(Minor).$(Revision).zip" />
+  <Target Name="ZipOutputPath" AfterTargets="Publish">
+    <RemoveDir Directories="$(BaseOutputPath)tmp\" ContinueOnError="true" />
+    <MakeDir Directories="$(BaseOutputPath)tmp\"/>
+    <Copy SourceFiles="$(OutputPath)\publish\OhmGraphite.exe" DestinationFolder="$(BaseOutputPath)tmp\"/>
+    <Copy SourceFiles="$(OutputPath)\publish\OhmGraphite.dll.config" DestinationFiles="$(BaseOutputPath)tmp\OhmGraphite.exe.config"/>
+    <Copy SourceFiles="$(OutputPath)\publish\NLog.config" DestinationFolder="$(BaseOutputPath)tmp\"/>
+
+    <ZipDirectory
+      Overwrite="true"
+      SourceDirectory="$(BaseOutputPath)\tmp"
+      DestinationFile="$(BaseOutputPath)OhmGraphite-$(Major).$(Minor).$(Revision).zip" />
   </Target>
 </Project>

+ 11 - 1
OhmGraphite/Program.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Configuration;
+using System.Diagnostics;
 using System.IO;
 using NLog;
 using LibreHardwareMonitor.Hardware;
@@ -60,7 +61,16 @@ namespace OhmGraphite
         {
             if (string.IsNullOrEmpty(configPath))
             {
-                return new AppConfigManager();
+                // https://github.com/dotnet/runtime/issues/13051#issuecomment-510267727
+                var processModule = Process.GetCurrentProcess().MainModule;
+                if (processModule != null)
+                {
+                    var pt = processModule.FileName;
+                    var fn = Path.Join(Path.GetDirectoryName(pt), "OhmGraphite.exe.config");
+                    var configMap1 = new ExeConfigurationFileMap { ExeConfigFilename = fn };
+                    var config1 = ConfigurationManager.OpenMappedExeConfiguration(configMap1, ConfigurationUserLevel.None);
+                    return new CustomConfig(config1);
+                }
             }
 
             if (!File.Exists(configPath))

+ 14 - 4
OhmGraphite/TimescaleWriter.cs

@@ -1,11 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Threading.Tasks;
 using NLog;
 using Npgsql;
 using NpgsqlTypes;
-using LibreHardwareMonitor.Hardware;
 
 namespace OhmGraphite
 {
@@ -74,10 +75,19 @@ namespace OhmGraphite
 
                         if (_setupTable)
                         {
-                            var setupSql = Resourcer.Resource.AsString("schema.sql");
-                            using (var cmd = new NpgsqlCommand(setupSql, conn))
+                            var assembly = Assembly.GetExecutingAssembly();
+                            var path = assembly.GetManifestResourceNames()
+                                .Single(str => str.EndsWith("schema.sql"));
+
+                            using (var stream = assembly.GetManifestResourceStream(path))
+                            using (var reader = new StreamReader(stream))
                             {
-                                cmd.ExecuteNonQuery();
+                                var setupSql = reader.ReadToEnd();
+                                using (var cmd = new NpgsqlCommand(setupSql, conn))
+                                {
+                                    cmd.ExecuteNonQuery();
+
+                                }
                             }
                         }
                     }

+ 0 - 11
appveyor.yml

@@ -1,11 +0,0 @@
-image: 'Visual Studio 2019'
-install:
-  - git submodule update --init --recursive
-  - msbuild /t:restore
-build:
-  project: OhmGraphite.sln
-
-test:
-  categories:
-    except:
-      - integration

+ 0 - 0
ci/Dockerfile.timescale → assets/Dockerfile.timescale


+ 0 - 6
ci/Dockerfile.tests

@@ -1,6 +0,0 @@
-FROM mono:6
-COPY . /tmp/
-WORKDIR /tmp
-RUN msbuild /t:restore /p:TargetFrameworks=net461 && \
-  msbuild /t:build /p:TargetFrameworks=net461
-CMD mono ~/.nuget/packages/xunit.runner.console/2.4.0/tools/net461/xunit.console.exe OhmGraphite.Test/bin/Debug/net461/OhmGraphite.Test.dll

+ 4 - 0
ci/setup-docker.sh

@@ -0,0 +1,4 @@
+FROM timescale/timescaledb:latest-pg12
+COPY ./ci/setup-docker.sh /docker-entrypoint-initdb.d/.
+COPY ./assets/schema.sql /sql/schema.sql
+RUN chmod 777 /docker-entrypoint-initdb.d/setup-docker.sh /sql/schema.sql

+ 0 - 177
ci/wait-for-it.sh

@@ -1,177 +0,0 @@
-#!/usr/bin/env bash
-#   Use this script to test if a given TCP host/port are available
-
-cmdname=$(basename $0)
-
-echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
-
-usage()
-{
-    cat << USAGE >&2
-Usage:
-    $cmdname host:port [-s] [-t timeout] [-- command args]
-    -h HOST | --host=HOST       Host or IP under test
-    -p PORT | --port=PORT       TCP port under test
-                                Alternatively, you specify the host and port as host:port
-    -s | --strict               Only execute subcommand if the test succeeds
-    -q | --quiet                Don't output any status messages
-    -t TIMEOUT | --timeout=TIMEOUT
-                                Timeout in seconds, zero for no timeout
-    -- COMMAND ARGS             Execute command with args after the test finishes
-USAGE
-    exit 1
-}
-
-wait_for()
-{
-    if [[ $TIMEOUT -gt 0 ]]; then
-        echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
-    else
-        echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
-    fi
-    start_ts=$(date +%s)
-    while :
-    do
-        if [[ $ISBUSY -eq 1 ]]; then
-            nc -z $HOST $PORT
-            result=$?
-        else
-            (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
-            result=$?
-        fi
-        if [[ $result -eq 0 ]]; then
-            end_ts=$(date +%s)
-            echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds"
-            break
-        fi
-        sleep 1
-    done
-    return $result
-}
-
-wait_for_wrapper()
-{
-    # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
-    if [[ $QUIET -eq 1 ]]; then
-        timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
-    else
-        timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
-    fi
-    PID=$!
-    trap "kill -INT -$PID" INT
-    wait $PID
-    RESULT=$?
-    if [[ $RESULT -ne 0 ]]; then
-        echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT"
-    fi
-    return $RESULT
-}
-
-# process arguments
-while [[ $# -gt 0 ]]
-do
-    case "$1" in
-        *:* )
-        hostport=(${1//:/ })
-        HOST=${hostport[0]}
-        PORT=${hostport[1]}
-        shift 1
-        ;;
-        --child)
-        CHILD=1
-        shift 1
-        ;;
-        -q | --quiet)
-        QUIET=1
-        shift 1
-        ;;
-        -s | --strict)
-        STRICT=1
-        shift 1
-        ;;
-        -h)
-        HOST="$2"
-        if [[ $HOST == "" ]]; then break; fi
-        shift 2
-        ;;
-        --host=*)
-        HOST="${1#*=}"
-        shift 1
-        ;;
-        -p)
-        PORT="$2"
-        if [[ $PORT == "" ]]; then break; fi
-        shift 2
-        ;;
-        --port=*)
-        PORT="${1#*=}"
-        shift 1
-        ;;
-        -t)
-        TIMEOUT="$2"
-        if [[ $TIMEOUT == "" ]]; then break; fi
-        shift 2
-        ;;
-        --timeout=*)
-        TIMEOUT="${1#*=}"
-        shift 1
-        ;;
-        --)
-        shift
-        CLI=("$@")
-        break
-        ;;
-        --help)
-        usage
-        ;;
-        *)
-        echoerr "Unknown argument: $1"
-        usage
-        ;;
-    esac
-done
-
-if [[ "$HOST" == "" || "$PORT" == "" ]]; then
-    echoerr "Error: you need to provide a host and port to test."
-    usage
-fi
-
-TIMEOUT=${TIMEOUT:-15}
-STRICT=${STRICT:-0}
-CHILD=${CHILD:-0}
-QUIET=${QUIET:-0}
-
-# check to see if timeout is from busybox?
-# check to see if timeout is from busybox?
-TIMEOUT_PATH=$(realpath $(which timeout))
-if [[ $TIMEOUT_PATH =~ "busybox" ]]; then
-        ISBUSY=1
-        BUSYTIMEFLAG="-t"
-else
-        ISBUSY=0
-        BUSYTIMEFLAG=""
-fi
-
-if [[ $CHILD -gt 0 ]]; then
-    wait_for
-    RESULT=$?
-    exit $RESULT
-else
-    if [[ $TIMEOUT -gt 0 ]]; then
-        wait_for_wrapper
-        RESULT=$?
-    else
-        wait_for
-        RESULT=$?
-    fi
-fi
-
-if [[ $CLI != "" ]]; then
-    if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
-        echoerr "$cmdname: strict mode, refusing to execute subprocess"
-        exit $RESULT
-    fi
-    exec "${CLI[@]}"
-else
-    exit $RESULT
-fi

+ 0 - 34
docker-compose.yml

@@ -1,34 +0,0 @@
-version: '3'
-services:
-  influx:
-    image: influxdb:1.8-alpine
-    environment:
-      INFLUXDB_DB: "mydb"
-      INFLUXDB_USER: "my_user"
-      INFLUXDB_USER_PASSWORD: "my_pass"
-  influx-passwordless:
-    image: influxdb:1.8-alpine
-    environment:
-      INFLUXDB_DB: "mydb"
-      INFLUXDB_USER: "my_user"
-      INFLUXDB_HTTP_AUTH_ENABLED: "false"
-  graphite:
-    image: graphiteapp/graphite-statsd
-    environment:
-      REDIS_TAGDB: "y"
-  timescale:
-    build:
-      dockerfile: ci/Dockerfile.timescale
-      context: .
-    environment:
-      POSTGRES_PASSWORD: 123456
-  app:
-    build:
-      dockerfile: ci/Dockerfile.tests
-      context: .
-    privileged: true
-    depends_on:
-      - timescale
-      - graphite
-      - influx
-    command: ./ci/wait-for-it.sh timescale:5432 -- ./ci/wait-for-it.sh influx:8086 -- ./ci/wait-for-it.sh graphite:2003 -- ./ci/wait-for-it.sh graphite:8080 -- ./ci/wait-for-it.sh graphite:80 -- mono /root/.nuget/packages/xunit.runner.console/2.4.1/tools/net461/xunit.console.exe OhmGraphite.Test/bin/Debug/net461/OhmGraphite.Test.dll