diff --git a/AdysTech.InfluxDB.Client.Net.Test/AdysTech.InfluxDB.Client.Net.Test.csproj b/AdysTech.InfluxDB.Client.Net.Test/AdysTech.InfluxDB.Client.Net.Test.csproj index 7733172..9e42f44 100644 --- a/AdysTech.InfluxDB.Client.Net.Test/AdysTech.InfluxDB.Client.Net.Test.csproj +++ b/AdysTech.InfluxDB.Client.Net.Test/AdysTech.InfluxDB.Client.Net.Test.csproj @@ -8,7 +8,7 @@ Properties InfluxDB.Client.Test InfluxDB.Client.Test - v4.5 + v4.6 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 @@ -16,6 +16,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + true @@ -61,6 +62,11 @@ AdysTech.InfluxDB.Client.Net + + + Always + + diff --git a/AdysTech.InfluxDB.Client.Net.Test/DataGen.cs b/AdysTech.InfluxDB.Client.Net.Test/DataGen.cs index 1836c6a..fe12f2b 100644 --- a/AdysTech.InfluxDB.Client.Net.Test/DataGen.cs +++ b/AdysTech.InfluxDB.Client.Net.Test/DataGen.cs @@ -45,7 +45,7 @@ public static double RandomDouble() public static string RandomString() { - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ,=/\\"; + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ,=/"; var stringChars = new StringBuilder (16, 16); for ( int i = 0; i < stringChars.Capacity; i++ ) diff --git a/AdysTech.InfluxDB.Client.Net.Test/InfluxDBClientTest.cs b/AdysTech.InfluxDB.Client.Net.Test/InfluxDBClientTest.cs index cb3ce51..becb58e 100644 --- a/AdysTech.InfluxDB.Client.Net.Test/InfluxDBClientTest.cs +++ b/AdysTech.InfluxDB.Client.Net.Test/InfluxDBClientTest.cs @@ -32,25 +32,6 @@ public async Task TestGetInfluxDBNamesAsync_ServiceUnavailable() var r = await client.GetInfluxDBNamesAsync(); } - //[TestMethod] - //[ExpectedException(typeof(UnauthorizedAccessException))] - //public async Task TestGetInfluxDBNamesAsync_Auth() - //{ - // var client = new InfluxDBClient(influxUrl); - // var r = await client.GetInfluxDBNamesAsync(); - //} - - - //[TestMethod] - //[ExpectedException(typeof(UnauthorizedAccessException))] - //public async Task TestGetInfluxDBNamesAsync_Auth2() - //{ - // var client = new InfluxDBClient(influxUrl, invalidUName, dbpwd); - // var r = await client.GetInfluxDBNamesAsync(); - //} - - - [TestMethod] public async Task TestGetInfluxDBNamesAsync() { @@ -164,10 +145,16 @@ public async Task TestQueryAsync() } [TestMethod] - public async Task TestQueryAsync_MultiSeries() + public async Task TestQueryMultiSeriesAsync() { var client = new InfluxDBClient(influxUrl, dbUName, dbpwd); - var r = await client.QueryMultiSeriesAsync("_internal", "Show field keys"); + + Stopwatch s = new Stopwatch(); + s.Start(); + var r = await client.QueryMultiSeriesAsync("_internal", "SHOW STATS"); + + s.Stop(); + Debug.WriteLine("Elapsed{0}", s.ElapsedMilliseconds); Assert.IsTrue(r != null && r.Count > 0, "QueryMultiSeriesAsync retunred null or invalid data"); } diff --git a/AdysTech.InfluxDB.Client.Net.Test/Tests.orderedtest b/AdysTech.InfluxDB.Client.Net.Test/Tests.orderedtest new file mode 100644 index 0000000..6012508 --- /dev/null +++ b/AdysTech.InfluxDB.Client.Net.Test/Tests.orderedtest @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AdysTech.InfluxDB.Client.Net/AdysTech.InfluxDB.Client.Net.csproj b/AdysTech.InfluxDB.Client.Net/AdysTech.InfluxDB.Client.Net.csproj index 3972f10..8ce2744 100644 --- a/AdysTech.InfluxDB.Client.Net/AdysTech.InfluxDB.Client.Net.csproj +++ b/AdysTech.InfluxDB.Client.Net/AdysTech.InfluxDB.Client.Net.csproj @@ -9,8 +9,9 @@ Properties AdysTech.InfluxDB.Client.Net AdysTech.InfluxDB.Client.Net - v4.5 + v4.6 512 + true @@ -34,6 +35,7 @@ + diff --git a/AdysTech.InfluxDB.Client.Net/DataContracts/InfluxJsonTypes.cs b/AdysTech.InfluxDB.Client.Net/DataContracts/InfluxJsonTypes.cs index ef96367..d966c48 100644 --- a/AdysTech.InfluxDB.Client.Net/DataContracts/InfluxJsonTypes.cs +++ b/AdysTech.InfluxDB.Client.Net/DataContracts/InfluxJsonTypes.cs @@ -10,30 +10,31 @@ namespace AdysTech.InfluxDB.Client.Net.DataContracts [DataContract] public class Series { - [DataMember(Name="name")] - public string SeriesName { get; set; } + [DataMember(Name = "name")] + public string Name { get; set; } [DataMember(Name = "tags")] public Dictionary Tags { get; set; } - [DataMember (Name = "columns")] - public List ColumnHeaders { get; set; } + [DataMember(Name = "columns")] + public List Columns { get; set; } - [DataMember (Name = "values")] + [DataMember(Name = "values")] public List> Values { get; set; } + } [DataContract] public class Result { - [DataMember (Name = "series")] + [DataMember(Name = "series")] public List Series { get; set; } } [DataContract] public class InfluxResponse { - [DataMember (Name = "results")] + [DataMember(Name = "results")] public List Results { get; set; } } } diff --git a/AdysTech.InfluxDB.Client.Net/InfluxDBClient.cs b/AdysTech.InfluxDB.Client.Net/InfluxDBClient.cs index db4de34..e447182 100644 --- a/AdysTech.InfluxDB.Client.Net/InfluxDBClient.cs +++ b/AdysTech.InfluxDB.Client.Net/InfluxDBClient.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Dynamic; -using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -13,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using AdysTech.InfluxDB.Client.Net.DataContracts; +using System.Web.Script.Serialization; namespace AdysTech.InfluxDB.Client.Net { @@ -111,7 +111,7 @@ private async Task GetAsync(Dictionary Quer } else if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.BadGateway || (response.StatusCode == HttpStatusCode.InternalServerError && response.ReasonPhrase == "INKApi Error")) //502 Connection refused throw new UnauthorizedAccessException("InfluxDB needs authentication. Check uname, pwd parameters"); - else if(response.StatusCode==HttpStatusCode.BadRequest) + else if (response.StatusCode == HttpStatusCode.BadRequest) { var error = await response.Content.ReadAsStringAsync(); throw new ArgumentException("Invalid Query resulted in an {0}", error.Substring(10)); @@ -159,6 +159,10 @@ private async Task PostAsync(Dictionary End private async Task PostPointsAsync(string dbName, TimePrecision precision, string retention, IEnumerable points) { + Regex multiLinePattern = new Regex(@"([\P{Cc}].*?) '([\P{Cc}].*?)':([\P{Cc}].*?)\\n", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + Regex oneLinePattern = new Regex(@"{\""error"":""([9\P{Cc}]+) '([\P{Cc}]+)':([a-zA-Z0-9 ]+)", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + Regex errorLinePattern = new Regex(@"{\""error"":""([9\P{Cc}]+) '([\P{Cc}]+)':([a-zA-Z0-9 ]+)", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + var line = new StringBuilder(); foreach (var point in points) line.AppendFormat("{0}\n", point.ConvertToInfluxLineProtocol()); @@ -179,31 +183,38 @@ private async Task PostPointsAsync(string dbName, TimePrecision precision, var content = await response.Content.ReadAsStringAsync(); //regex assumes error text from https://github.com/influxdata/influxdb/blob/master/models/points.go ParsePointsWithPrecision //fmt.Sprintf("'%s': %v", string(block[start:len(block)]) - List parts; bool partialWrite; - if (content.Contains("partial write")) + List parts=null; bool partialWrite =false; string l=""; + try { - if (content.Contains("\\n")) - parts = Regex.Matches(content.Substring(content.IndexOf("partial write:\\n") + 16), @"([\P{Cc}].*?) '([\P{Cc}].*?)':([\P{Cc}].*?)\\n").ToList(); + if (content.Contains("partial write")) + { + if (content.Contains("\\n")) + parts = multiLinePattern.Matches(content.Substring(content.IndexOf("partial write:\\n") + 16)).ToList(); + else + parts = oneLinePattern.Matches(content.Substring(content.IndexOf("partial write:\\n") + 16)).ToList(); + partialWrite = true; + } else - parts = Regex.Matches(content.Substring(content.IndexOf("partial write:\\n") + 16), @"([\P{Cc}].*?) '([\P{Cc}].*?)':([\P{Cc}].*?)").ToList(); - partialWrite = true; + { + parts = errorLinePattern.Matches(content).ToList(); + partialWrite = false; + } + + if (parts[1].Contains("\\n")) + l = parts[1].Substring(0, parts[1].IndexOf("\\n")).Unescape(); + else + l = parts[1].Unescape(); } - else + catch (Exception) { - parts = Regex.Matches(content, @"{\""error"":""([9\P{Cc}]+) '([\P{Cc}]+)':([a-zA-Z0-9 ]+)").ToList(); - partialWrite = false; + } - string l; - if (parts[1].Contains("\\n")) - l = parts[1].Substring(0, parts[1].IndexOf("\\n")).Unescape(); - else - l = parts[1].Unescape(); var point = points.Where(p => p.ConvertToInfluxLineProtocol() == l).FirstOrDefault(); if (point != null) - throw new InfluxDBException(partialWrite ? "Partial Write" : "Failed to Write", String.Format("{0}: {1} due to {2}", partialWrite ? "Partial Write" : "Failed to Write", parts[0], parts[2]), point); + throw new InfluxDBException(partialWrite ? "Partial Write" : "Failed to Write", String.Format("{0}: {1} due to {2}", partialWrite ? "Partial Write" : "Failed to Write", parts?[0], parts?[2]), point); else - throw new InfluxDBException(partialWrite ? "Partial Write" : "Failed to Write", String.Format("{0}: {1} due to {2}", partialWrite ? "Partial Write" : "Failed to Write", parts[0], parts[2]), l); + throw new InfluxDBException(partialWrite ? "Partial Write" : "Failed to Write", String.Format("{0}: {1} due to {2}", partialWrite ? "Partial Write" : "Failed to Write", parts?[0], parts?[2]), l); return false; } else if (response.StatusCode == HttpStatusCode.NoContent) @@ -354,9 +365,11 @@ public async Task> QueryDBAsync(string dbName, string measurementQ var response = await GetAsync(new Dictionary() { { "db", dbName }, { "q", measurementQuery } }); if (response.StatusCode == HttpStatusCode.OK) { - var content = await response.Content.ReadAsStreamAsync(); - DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(InfluxResponse)); - var result = js.ReadObject(content) as InfluxResponse; + //var content = await response.Content.ReadAsStreamAsync(); + //DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(InfluxResponse)); + //var result = js.ReadObject(content) as InfluxResponse; + + var result = new JavaScriptSerializer().Deserialize(await response.Content.ReadAsStringAsync()); if (result.Results.Count > 1) throw new ArgumentException("The query is resulting in Multi Series respone, which is not supported by this method"); @@ -371,9 +384,9 @@ public async Task> QueryDBAsync(string dbName, string measurementQ { dynamic entry = new ExpandoObject(); results.Add(entry); - for (var col = 0; col < series.ColumnHeaders.Count; col++) + for (var col = 0; col < series.Columns.Count; col++) { - var header = char.ToUpper(series.ColumnHeaders[col][0]) + series.ColumnHeaders[col].Substring(1); + var header = char.ToUpper(series.Columns[col][0]) + series.Columns[col].Substring(1); ((IDictionary)entry).Add(header, series.Values[row][col]); } @@ -449,12 +462,20 @@ public async Task PostPointsAsync(string dbName, IEnumerable new { p.Precision, p.Retention?.Name })) { + var pointsGroup = group.AsEnumerable().Select((point, index) => new { Index = index, Point = point })//get the index of each point - .GroupBy(x => x.Index / maxBatchSize) //chunk into smaller batches - .Select(x => x.Select(v => v.Point)); //get the points + .GroupBy(x => x.Index / maxBatchSize) //chunk into smaller batches + .Select(x => x.Select(v => v.Point)); //get the points foreach (var points in pointsGroup) { - result = await PostPointsAsync(dbName, group.Key.Precision, group.Key.Name, points); + try + { + result = await PostPointsAsync(dbName, group.Key.Precision, group.Key.Name, points); + } + catch (Exception) + { + throw; + } finalResult = result && finalResult; if (result) { @@ -462,6 +483,8 @@ public async Task PostPointsAsync(string dbName, IEnumerable> QueryMultiSeriesAsync(string dbName, strin if (response.StatusCode == HttpStatusCode.OK) { var results = new List(); - var content = await response.Content.ReadAsStreamAsync(); - DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(InfluxResponse)); - var rawResult = js.ReadObject(content) as InfluxResponse; + + //var content = await response.Content.ReadAsStreamAsync(); + //DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(InfluxResponse)); + //var rawResult = js.ReadObject(content) as InfluxResponse; + + var rawResult = new JavaScriptSerializer().Deserialize (await response.Content.ReadAsStringAsync()); if (rawResult.Results.Count > 1) throw new ArgumentException("The query is resulting in a format, which is not supported by this method yet"); @@ -582,16 +608,16 @@ public async Task> QueryMultiSeriesAsync(string dbName, strin { var result = new InfluxSeries(); results.Add(result); - result.SeriesName = series.SeriesName; - //result.Tags=series.ta + result.SeriesName = series.Name; + result.Tags = series.Tags; result.Entries = new List(); for (var row = 0; row < series.Values.Count; row++) { dynamic entry = new ExpandoObject(); result.Entries.Add(entry); - for (var col = 0; col < series.ColumnHeaders.Count; col++) + for (var col = 0; col < series.Columns.Count; col++) { - var header = char.ToUpper(series.ColumnHeaders[col][0]) + series.ColumnHeaders[col].Substring(1); + var header = char.ToUpper(series.Columns[col][0]) + series.Columns[col].Substring(1); ((IDictionary)entry).Add(header, series.Values[row][col]); } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2092b12 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +## v0.5.0 [4/15/2016] + +### Release Notes +This version supports Retention policies. You can create a new retention policy, or post into existing ones, as well as query them. This version also supports querying multi series data. +There is a breaking change to `GetInfluxDBStructureAsync`, which now returns a custom InfluxDB object, which not only provides info about measurements, but also about tags and fields in each of them. + +### Features + +- [#8](https://github.com/AdysTech/InfluxDB.Client.Net/pull/8): Add Nuget Package +- [#10](https://github.com/AdysTech/InfluxDB.Client.Net/pull/10): Support for Retention Policy, query Influx Version +- [#11](https://github.com/AdysTech/InfluxDB.Client.Net/pull/11): Enable AppVeyor integration for ci tests + +### Bugfixes + +- [#7](https://github.com/AdysTech/InfluxDB.Client.Net/issues/7): InfluxDataPoint fields cannot be different types. +- [#9](https://github.com/AdysTech/InfluxDB.Client.Net/issues/9): confused by "Integer" support + +## v0.2.1.0 [12/31/2015] + +### Release Notes +Added type safe write methods via a new data structure called InfluxDataPoint. Previous write methods are marked obsolete. + +### Features + +- [#5](https://github.com/AdysTech/InfluxDB.Client.Net/pull/5): Added InfluxDataPoint, and PostPoint methods +- [#6](https://github.com/AdysTech/InfluxDB.Client.Net/pull/6): Added Partial Writes, and malformed requests + + +## v0.1.1.0 [12/19/2015] + +### Release Notes +Added the functionality to query for existing data from InfluxDB. Also unknown little quirk was Influx's need for . (dot) to treat a number as a number, so non US local code can beak Influx data writes. , now double to string conversion will work in any locale. + +### Features + +- [#4](https://github.com/AdysTech/InfluxDB.Client.Net/pull/4): Add Querying InfluxDB functionality + +### Bugfixes + +- [#3](https://github.com/AdysTech/InfluxDB.Client.Net/pull/3): Double to str conversion fix, Thanks to @spamik \ No newline at end of file diff --git a/InfluxDB.Client.Net.sln b/InfluxDB.Client.Net.sln index 2d90f83..375fb31 100644 --- a/InfluxDB.Client.Net.sln +++ b/InfluxDB.Client.Net.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.40629.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdysTech.InfluxDB.Client.Net", "AdysTech.InfluxDB.Client.Net\AdysTech.InfluxDB.Client.Net.csproj", "{31C6D4D4-86A6-4E76-9E54-F4C399140CE7}" EndProject diff --git a/README.md b/README.md index 5388e10..21d4cdc 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ It currently supports 3. Creating new database 4. Querying for the whole DB structure (hierarchical structure of all measurements, and fields) 5. Writing single, multiple values to db +6. Retention policy management +7. Post data to specific retention policies To be added are a. Query for all tags, their unique values -b. Updating retention policies -c. Deleting data +b. Deleting data, currently drop queries can be fired to delete data ####Nuget https://www.nuget.org/packages/AdysTech.InfluxDB.Client.Net @@ -37,33 +38,41 @@ Creates a new database in InfluxDB. Does not raise exceptions if teh DB already #####`InfluxDatapoint` It represents a single Point (collection of fields) in a series. In InfluxDB each point is uniquely identified by its series and timestamp. Currently this class (as well as InfluxDB as of 0.9) supports `Boolean`, `String`, `Integer` and `Double` (additionally `decimal` is supported in client, which gets stored as a double in InfluxDB) types for field values. -Multiple fields of same type are supported, as well as tags. +Multiple fields of same type are supported, as well as tags. + + ####Writing data points to DB - var valDouble = new InfluxDatapoint (); - valDouble.UtcTimestamp = DateTime.UtcNow; - valDouble.Tags.Add ("TestDate", DateTime.Now.ToShortDateString ()); - valDouble.Tags.Add ("TestTime", DateTime.Now.ToShortTimeString ()); - valDouble.Fields.Add ("Doublefield", 123.456); - valDouble.Fields.Add ("Doublefield2", 0.000124545); - valDouble.MeasurementName = "TestMeasurement"; - valDouble.Precision = TimePrecision.Seconds; - var r = await client.PostPointAsync ("TestDB", valDouble); + var valMixed = new InfluxDatapoint(); + valMixed.UtcTimestamp = DateTime.UtcNow; + valMixed.Tags.Add("TestDate", time.ToShortDateString()); + valMixed.Tags.Add("TestTime", time.ToShortTimeString()); + valMixed.Fields.Add("Doublefield", new InfluxValueField(rand.NextDouble())); + valMixed.Fields.Add("Stringfield", new InfluxValueField(DataGen.RandomString())); + valMixed.Fields.Add("Boolfield", new InfluxValueField(true)); + valMixed.Fields.Add("Int Field", new InfluxValueField(rand.Next())); + + valMixed.MeasurementName = measurementName; + valMixed.Precision = TimePrecision.Seconds; + valMixed.Retention = new InfluxRetentionPolicy() { Name = "Test2" }; + + var r = await client.PostPointAsync(dbName, valMixed); A collection of points can be posted using `await client.PostPointsAsync (dbName, points)`, where `points` can be collection of different types of `InfluxDatapoint` ####Query for data points + +#####Single series + `await client.QueryDBAsync ("", ");` This function uses dynamic object (`ExpandoObject` to be exact), so `var r = await client.QueryDBAsync ("stress", "select * from performance limit 10");` will result in list of objects, where each object has properties with its value set to measument value. So the result can be used like `r[0].time`. This also opens up a way to have an update mechanism as you can now query for data, change some values/tags etc, and write back. Since Influx uses combination of timestamp, tags as primary key, if you don't change tags, the values will be overwritten. +#####Multiple series +`await client.QueryMultiSeriesAsync("_internal", "SHOW STATS");` - -###--------------New Version - 0.1.1.0 - 12/19/2015-------------------- -Added the functionality to query for existing data from InfluxDB. Also unknown little quirk was Influx's need for . (dot) to treat a number as a number, so non US local code can beak Influx data writes. Thanks to @spamik, now double to string conversion will work in any locale. - -###--------------New Version - 0.2.1.0 - 12/31/2015-------------------- -Added type safe write methods via a new data structure called InfluxDataPoint. Previous write methods are marked obsolete. +This function returns `List`, `InfluxSeries` is a custom object which will have a series name, set of tags (e.g. columns you used in `group by` clause. For the actual values, it will use dynamic object. +This allows to get data like `r.FirstOrDefault(x=>x.SeriesName=="queryExecutor").Entries[0].QueryDurationNs` kind of queries. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..d51ca8d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,28 @@ +version: 0.{build} +pull_requests: + do_not_increment_build_number: true +branches: + only: + - master +before_build: +- set PATH="C:\Program Files (x86)\MSBuild\14.0\Bin";%PATH% +build: + verbosity: minimal + +test_script: +- ps: >- + $source = "https://github.com/mvadu/influxdb/releases/download/v0.11.0-rc1/Influxdb.v0.11.0-rc1.zip" + + $destination = "$env:Temp\influxdb-nightly_windows_amd64.zip" + + Invoke-WebRequest $source -OutFile $destination + + $x = 7z x $destination -o"$env:Temp\influxdb" + + Start-Process -FilePath "$env:Temp\influxdb\influxd.exe" + + pwd + + vstest.console /logger:Appveyor ".\AdysTech.InfluxDB.Client.Net.Test\bin\Debug\Tests.orderedtest" + +deploy: off \ No newline at end of file