Skip to content
This repository was archived by the owner on Jul 18, 2023. It is now read-only.

Commit 2e8eca8

Browse files
authored
Merge pull request #50 from influxdata/dev
1.1.0 Release
2 parents b28edea + 0e27d94 commit 2e8eca8

File tree

12 files changed

+456
-17
lines changed

12 files changed

+456
-17
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Supporting the full/read API of InfluxDB is an explicit _non-goal_: this package
1414
Install the _InfluxDB.Collector_ NuGet package:
1515

1616
```powershell
17-
Install-Package InfluxDB.Collector -Pre
17+
Install-Package InfluxDB.Collector
1818
```
1919

2020
Add `using` statements where needed:
@@ -55,7 +55,7 @@ View aggregated metrics in a dashboarding interface such as [Grafana](http://gra
5555
The raw API is a very thin wrapper on InfluxDB's HTTP API, in the _InfluxDB.LineProtocol_ package.
5656

5757
```powershell
58-
Install-Package InfluxDB.LineProtocol -Pre
58+
Install-Package InfluxDB.LineProtocol
5959
```
6060

6161
To send points, create a `LineProtocolPayload` containing a batch of `LineProtocolPoint`s. Each point carries the measurement name, at least one value, an optional set of tags and an optional timestamp:

src/InfluxDB.Collector/InfluxDB.Collector.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
77
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
88
<AssemblyName>InfluxDB.Collector</AssemblyName>
9-
<VersionPrefix>1.0.0</VersionPrefix>
9+
<VersionPrefix>1.1.0</VersionPrefix>
1010
<PackageId>InfluxDB.Collector</PackageId>
1111
<PackageTags>influxdb</PackageTags>
1212
<PackageIconUrl>https://raw.githubusercontent.com/influxdata/influxdb-csharp/master/asset/influxdata.jpg</PackageIconUrl>

src/InfluxDB.Collector/MetricsCollector.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace InfluxDB.Collector
77
{
88
public abstract class MetricsCollector : IPointEmitter, IDisposable
99
{
10+
readonly Util.ITimestampSource _timestampSource = new Util.PseudoHighResTimestampSource();
11+
1012
public void Increment(string measurement, long count = 1, IReadOnlyDictionary<string, string> tags = null)
1113
{
1214
Write(measurement, new Dictionary<string, object> { { "count", count } }, tags);
@@ -38,7 +40,7 @@ public void Write(string measurement, IReadOnlyDictionary<string, object> fields
3840
{
3941
try
4042
{
41-
var point = new PointData(measurement, fields, tags, timestamp ?? DateTime.UtcNow);
43+
var point = new PointData(measurement, fields, tags, timestamp ?? _timestampSource.GetUtcNow());
4244
Emit(new[] { point });
4345
}
4446
catch (Exception ex)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace InfluxDB.Collector.Util
4+
{
5+
/// <summary>
6+
/// Supplier of timestamps for metrics
7+
/// </summary>
8+
interface ITimestampSource
9+
{
10+
DateTime GetUtcNow();
11+
}
12+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
3+
namespace InfluxDB.Collector.Util
4+
{
5+
/// <summary>
6+
/// Implements <see cref="ITimestampSource"/>
7+
/// in a way that combines the low-ish resolution DateTime.UtcNow
8+
/// with a sequence number added to the ticks to provide
9+
/// pseudo-tick precision timestamps
10+
/// </summary>
11+
/// <remarks>
12+
/// See https://github.com/influxdata/influxdb-csharp/issues/46 for why this is necessary.
13+
/// Long story short:
14+
/// a) InfluxDB has a "LastWriteWins" policy for points that have the same timestamp and tags.
15+
/// b) The normal <see cref="System.DateTime.UtcNow"/> only supplies timestamps that change not as often as you think.
16+
/// c) In a web server, it's entirely possible for more than one thread to get the same UtcNow value
17+
///
18+
/// As a remediation for this, we infuse DateTime.UtcNow with a sequence number until it ticks over.
19+
/// </remarks>
20+
class PseudoHighResTimestampSource : ITimestampSource
21+
{
22+
private long _lastUtcNowTicks = 0;
23+
private long _sequence = 0;
24+
private readonly object lockObj = new object();
25+
26+
public DateTime GetUtcNow()
27+
{
28+
DateTime utcNow = DateTime.UtcNow;
29+
30+
lock (lockObj)
31+
{
32+
if (utcNow.Ticks == _lastUtcNowTicks)
33+
{
34+
// UtcNow hasn't rolled over yet, so
35+
// add a sequence number to it
36+
_sequence++;
37+
long pseudoTicks = utcNow.Ticks + _sequence;
38+
return new DateTime(pseudoTicks, DateTimeKind.Utc);
39+
}
40+
else
41+
{
42+
// Reset as UtcNow has rolled over
43+
_sequence = 0;
44+
_lastUtcNowTicks = utcNow.Ticks;
45+
return utcNow;
46+
}
47+
}
48+
}
49+
}
50+
}

src/InfluxDB.LineProtocol/Client/LineProtocolClient.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,43 @@ protected LineProtocolClient(HttpMessageHandler handler, Uri serverBaseAddress,
3232

3333
public Task<LineProtocolWriteResult> WriteAsync(LineProtocolPayload payload, CancellationToken cancellationToken = default(CancellationToken))
3434
{
35-
var writer = new StringWriter();
35+
var stringWriter = new StringWriter();
3636

37-
payload.Format(writer);
37+
payload.Format(stringWriter);
3838

39-
return SendAsync(writer.ToString(), cancellationToken);
39+
return SendAsync(stringWriter.ToString(), Precision.Nanoseconds, cancellationToken);
4040
}
4141

42-
public Task<LineProtocolWriteResult> SendAsync(LineProtocolWriter writer, CancellationToken cancellationToken = default(CancellationToken))
42+
public Task<LineProtocolWriteResult> SendAsync(LineProtocolWriter lineProtocolWriter, CancellationToken cancellationToken = default(CancellationToken))
4343
{
44-
return SendAsync(writer.ToString(), cancellationToken);
44+
return SendAsync(lineProtocolWriter.ToString(), lineProtocolWriter.Precision, cancellationToken);
4545
}
4646

47-
private async Task<LineProtocolWriteResult> SendAsync(string payload, CancellationToken cancellationToken = default(CancellationToken))
47+
private async Task<LineProtocolWriteResult> SendAsync(string payload, Precision precision, CancellationToken cancellationToken = default(CancellationToken))
4848
{
4949
var endpoint = $"write?db={Uri.EscapeDataString(_database)}";
5050
if (!string.IsNullOrEmpty(_username))
5151
endpoint += $"&u={Uri.EscapeDataString(_username)}&p={Uri.EscapeDataString(_password)}";
5252

53+
switch (precision)
54+
{
55+
case Precision.Microseconds:
56+
endpoint += "&precision=u";
57+
break;
58+
case Precision.Milliseconds:
59+
endpoint += "&precision=ms";
60+
break;
61+
case Precision.Seconds:
62+
endpoint += "&precision=s";
63+
break;
64+
case Precision.Minutes:
65+
endpoint += "&precision=m";
66+
break;
67+
case Precision.Hours:
68+
endpoint += "&precision=h";
69+
break;
70+
}
71+
5372
var content = new StringContent(payload, Encoding.UTF8);
5473
var response = await _httpClient.PostAsync(endpoint, content, cancellationToken).ConfigureAwait(false);
5574
if (response.IsSuccessStatusCode)

src/InfluxDB.LineProtocol/InfluxDB.LineProtocol.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<Description>A .NET library for efficiently sending time series to InfluxDB</Description>
55
<Authors>influxdb-csharp Contributors</Authors>
66
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
7-
<VersionPrefix>1.0.0</VersionPrefix>
7+
<VersionPrefix>1.1.0</VersionPrefix>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
99
<AssemblyName>InfluxDB.LineProtocol</AssemblyName>
1010
<PackageId>InfluxDB.LineProtocol</PackageId>

src/InfluxDB.LineProtocol/LineProtocolWriter.cs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,33 @@ public class LineProtocolWriter
99
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
1010

1111
private readonly TextWriter textWriter;
12+
private readonly PrecisionResolutionStrategy defaultResolutionStrategy;
1213

1314
private LinePosition position = LinePosition.NothingWritten;
1415

15-
public LineProtocolWriter()
16+
public LineProtocolWriter() : this(Precision.Nanoseconds)
1617
{
18+
}
19+
20+
public LineProtocolWriter(Precision precision, PrecisionResolutionStrategy defaultResolutionStrategy = PrecisionResolutionStrategy.Error)
21+
{
22+
if (!Enum.IsDefined(typeof(Precision), precision))
23+
{
24+
throw new ArgumentOutOfRangeException(nameof(precision));
25+
}
26+
27+
if (!Enum.IsDefined(typeof(PrecisionResolutionStrategy), defaultResolutionStrategy))
28+
{
29+
throw new ArgumentOutOfRangeException(nameof(defaultResolutionStrategy));
30+
}
31+
32+
this.Precision = precision;
33+
this.defaultResolutionStrategy = defaultResolutionStrategy;
1734
this.textWriter = new StringWriter();
1835
}
1936

37+
public Precision Precision { get; }
38+
2039
public LineProtocolWriter Measurement(string name)
2140
{
2241
if (name == null)
@@ -164,8 +183,42 @@ public LineProtocolWriter Field(string name, bool value)
164183
return this;
165184
}
166185

167-
public void Timestamp(long value)
186+
public void Timestamp(long nanoseconds)
187+
{
188+
this.Timestamp(nanoseconds, defaultResolutionStrategy);
189+
}
190+
191+
public void Timestamp(long nanoseconds, PrecisionResolutionStrategy resolutionStrategy)
168192
{
193+
var nanosecondsAbovePrecision = nanoseconds % (long)Precision;
194+
195+
if (nanosecondsAbovePrecision != 0)
196+
{
197+
switch (resolutionStrategy)
198+
{
199+
case PrecisionResolutionStrategy.Error:
200+
throw new ArgumentOutOfRangeException(nameof(nanoseconds));
201+
case PrecisionResolutionStrategy.Floor:
202+
nanoseconds -= nanosecondsAbovePrecision;
203+
break;
204+
case PrecisionResolutionStrategy.Ceiling:
205+
nanoseconds += (long)Precision - nanosecondsAbovePrecision;
206+
break;
207+
case PrecisionResolutionStrategy.Round:
208+
if (nanosecondsAbovePrecision < (long)Precision / 2)
209+
{
210+
Timestamp(nanoseconds, PrecisionResolutionStrategy.Floor);
211+
}
212+
else
213+
{
214+
Timestamp(nanoseconds, PrecisionResolutionStrategy.Ceiling);
215+
}
216+
return;
217+
default:
218+
throw new ArgumentOutOfRangeException(nameof(resolutionStrategy));
219+
}
220+
}
221+
169222
switch (position)
170223
{
171224
case LinePosition.FieldWritten:
@@ -177,29 +230,45 @@ public void Timestamp(long value)
177230
throw InvalidPositionException("Cannot write timestamp as no field written for current measurement.");
178231
}
179232

180-
textWriter.Write(value.ToString(CultureInfo.InvariantCulture));
233+
var timestamp = nanoseconds / (long)Precision;
234+
textWriter.Write(timestamp.ToString(CultureInfo.InvariantCulture));
181235

182236
position = LinePosition.TimestampWritten;
183237
}
184238

185239
public void Timestamp(TimeSpan value)
186240
{
187-
Timestamp(value.Ticks * 100L);
241+
Timestamp(value, defaultResolutionStrategy);
242+
}
243+
244+
public void Timestamp(TimeSpan value, PrecisionResolutionStrategy resolutionStrategy)
245+
{
246+
Timestamp(value.Ticks * 100, resolutionStrategy);
188247
}
189248

190249
public void Timestamp(DateTimeOffset value)
191250
{
192-
Timestamp(value.UtcDateTime);
251+
Timestamp(value, defaultResolutionStrategy);
252+
}
253+
254+
public void Timestamp(DateTimeOffset value, PrecisionResolutionStrategy resolutionStrategy)
255+
{
256+
Timestamp(value.UtcDateTime, resolutionStrategy);
193257
}
194258

195259
public void Timestamp(DateTime value)
260+
{
261+
Timestamp(value, defaultResolutionStrategy);
262+
}
263+
264+
public void Timestamp(DateTime value, PrecisionResolutionStrategy resolutionStrategy)
196265
{
197266
if (value != null && value.Kind != DateTimeKind.Utc)
198267
{
199268
throw new ArgumentException("Timestamps must be specified as UTC", nameof(value));
200269
}
201270

202-
Timestamp(value - UnixEpoch);
271+
Timestamp(value - UnixEpoch, resolutionStrategy);
203272
}
204273

205274
public override string ToString()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace InfluxDB.LineProtocol
2+
{
3+
public enum Precision : long
4+
{
5+
Nanoseconds = 1,
6+
Microseconds = 1000,
7+
Milliseconds = Microseconds * 1000,
8+
Seconds = Milliseconds * 1000,
9+
Minutes = Seconds * 60,
10+
Hours = Minutes * 60
11+
}
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace InfluxDB.LineProtocol
2+
{
3+
public enum PrecisionResolutionStrategy
4+
{
5+
Error,
6+
Round,
7+
Floor,
8+
Ceiling
9+
}
10+
}

0 commit comments

Comments
 (0)