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

Precision #42

Merged
merged 8 commits into from
Sep 5, 2017
31 changes: 25 additions & 6 deletions src/InfluxDB.LineProtocol/Client/LineProtocolClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,43 @@ protected LineProtocolClient(HttpMessageHandler handler, Uri serverBaseAddress,

public Task<LineProtocolWriteResult> WriteAsync(LineProtocolPayload payload, CancellationToken cancellationToken = default(CancellationToken))
{
var writer = new StringWriter();
var stringWriter = new StringWriter();

payload.Format(writer);
payload.Format(stringWriter);

return SendAsync(writer.ToString(), cancellationToken);
return SendAsync(stringWriter.ToString(), Precision.Nanoseconds, cancellationToken);
}

public Task<LineProtocolWriteResult> SendAsync(LineProtocolWriter writer, CancellationToken cancellationToken = default(CancellationToken))
public Task<LineProtocolWriteResult> SendAsync(LineProtocolWriter lineProtocolWriter, CancellationToken cancellationToken = default(CancellationToken))
{
return SendAsync(writer.ToString(), cancellationToken);
return SendAsync(lineProtocolWriter.ToString(), lineProtocolWriter.Precision, cancellationToken);
}

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

switch (precision)
{
case Precision.Microseconds:
endpoint += "&precision=u";
break;
case Precision.Milliseconds:
endpoint += "&precision=ms";
break;
case Precision.Seconds:
endpoint += "&precision=s";
break;
case Precision.Minutes:
endpoint += "&precision=m";
break;
case Precision.Hours:
endpoint += "&precision=h";
break;
}

var content = new StringContent(payload, Encoding.UTF8);
var response = await _httpClient.PostAsync(endpoint, content, cancellationToken).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
Expand Down
81 changes: 75 additions & 6 deletions src/InfluxDB.LineProtocol/LineProtocolWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,33 @@ public class LineProtocolWriter
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

private readonly TextWriter textWriter;
private readonly PrecisionResolutionStrategy defaultResolutionStrategy;

private LinePosition position = LinePosition.NothingWritten;

public LineProtocolWriter()
public LineProtocolWriter() : this(Precision.Nanoseconds)
{
}

public LineProtocolWriter(Precision precision, PrecisionResolutionStrategy defaultResolutionStrategy = PrecisionResolutionStrategy.Error)
{
if (!Enum.IsDefined(typeof(Precision), precision))
{
throw new ArgumentOutOfRangeException(nameof(precision));
}

if (!Enum.IsDefined(typeof(PrecisionResolutionStrategy), defaultResolutionStrategy))
{
throw new ArgumentOutOfRangeException(nameof(defaultResolutionStrategy));
}

this.Precision = precision;
this.defaultResolutionStrategy = defaultResolutionStrategy;
this.textWriter = new StringWriter();
}

public Precision Precision { get; }

public LineProtocolWriter Measurement(string name)
{
if (name == null)
Expand Down Expand Up @@ -164,8 +183,42 @@ public LineProtocolWriter Field(string name, bool value)
return this;
}

public void Timestamp(long value)
public void Timestamp(long nanoseconds)
{
this.Timestamp(nanoseconds, defaultResolutionStrategy);
}

public void Timestamp(long nanoseconds, PrecisionResolutionStrategy resolutionStrategy)
{
var nanosecondsAbovePrecision = nanoseconds % (long)Precision;

if (nanosecondsAbovePrecision != 0)
{
switch (resolutionStrategy)
{
case PrecisionResolutionStrategy.Error:
throw new ArgumentOutOfRangeException(nameof(nanoseconds));
case PrecisionResolutionStrategy.Floor:
nanoseconds -= nanosecondsAbovePrecision;
break;
case PrecisionResolutionStrategy.Ceiling:
nanoseconds += (long)Precision - nanosecondsAbovePrecision;
break;
case PrecisionResolutionStrategy.Round:
if (nanosecondsAbovePrecision < (long)Precision / 2)
{
Timestamp(nanoseconds, PrecisionResolutionStrategy.Floor);
}
else
{
Timestamp(nanoseconds, PrecisionResolutionStrategy.Ceiling);
}
return;
default:
throw new ArgumentOutOfRangeException(nameof(resolutionStrategy));
}
}

switch (position)
{
case LinePosition.FieldWritten:
Expand All @@ -177,29 +230,45 @@ public void Timestamp(long value)
throw InvalidPositionException("Cannot write timestamp as no field written for current measurement.");
}

textWriter.Write(value.ToString(CultureInfo.InvariantCulture));
var timestamp = nanoseconds / (long)Precision;
textWriter.Write(timestamp.ToString(CultureInfo.InvariantCulture));

position = LinePosition.TimestampWritten;
}

public void Timestamp(TimeSpan value)
{
Timestamp(value.Ticks * 100L);
Timestamp(value, defaultResolutionStrategy);
}

public void Timestamp(TimeSpan value, PrecisionResolutionStrategy resolutionStrategy)
{
Timestamp(value.Ticks * 100, resolutionStrategy);
}

public void Timestamp(DateTimeOffset value)
{
Timestamp(value.UtcDateTime);
Timestamp(value, defaultResolutionStrategy);
}

public void Timestamp(DateTimeOffset value, PrecisionResolutionStrategy resolutionStrategy)
{
Timestamp(value.UtcDateTime, resolutionStrategy);
}

public void Timestamp(DateTime value)
{
Timestamp(value, defaultResolutionStrategy);
}

public void Timestamp(DateTime value, PrecisionResolutionStrategy resolutionStrategy)
{
if (value != null && value.Kind != DateTimeKind.Utc)
{
throw new ArgumentException("Timestamps must be specified as UTC", nameof(value));
}

Timestamp(value - UnixEpoch);
Timestamp(value - UnixEpoch, resolutionStrategy);
}

public override string ToString()
Expand Down
12 changes: 12 additions & 0 deletions src/InfluxDB.LineProtocol/Precision.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace InfluxDB.LineProtocol
{
public enum Precision : long
{
Nanoseconds = 1,
Microseconds = 1000,
Milliseconds = Microseconds * 1000,
Seconds = Milliseconds * 1000,
Minutes = Seconds * 60,
Hours = Minutes * 60
}
}
10 changes: 10 additions & 0 deletions src/InfluxDB.LineProtocol/PrecisionResolutionStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace InfluxDB.LineProtocol
{
public enum PrecisionResolutionStrategy
{
Error,
Round,
Floor,
Ceiling
}
}
112 changes: 112 additions & 0 deletions test/InfluxDB.LineProtocol.Tests/LineProtocolWriterPrecisionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using Xunit;

namespace InfluxDB.LineProtocol.Tests
{
public class LineProtocolWriterPrecisionTests
{
[Theory]
[InlineData(Precision.Nanoseconds, 1500832764070165800, "1500832764070165800")]
[InlineData(Precision.Microseconds, 1500832764070165000, "1500832764070165")]
[InlineData(Precision.Milliseconds, 1500832764070000000, "1500832764070")]
[InlineData(Precision.Seconds, 1500832764000000000, "1500832764")]
[InlineData(Precision.Hours, 1500829200000000000, "416897")]
public void Will_write_timestamps_using_precision_of_writer(Precision precision, long nanoseconds, string expectedTimestamp)
{
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

var timestamp = unixEpoch.AddTicks(nanoseconds / 100); // .net tick is 100 nanoseconds.

var writer = new LineProtocolWriter(precision);

writer.Measurement("foo").Field("bar", 1f).Timestamp(timestamp);

Assert.Equal($"foo bar=1 {expectedTimestamp}", writer.ToString());
}

[Theory]
[InlineData(Precision.Microseconds)]
[InlineData(Precision.Milliseconds)]
[InlineData(Precision.Seconds)]
[InlineData(Precision.Hours)]
public void Will_throw_if_wrong_precision_used(Precision precision)
{
var writer = new LineProtocolWriter(precision);

var timestamp = TimeSpan.FromTicks(1);

writer.Measurement("foo").Field("bar", 1f);

Assert.Throws<ArgumentOutOfRangeException>(() => writer.Timestamp(timestamp));
}

[Theory]
[InlineData(Precision.Microseconds)]
[InlineData(Precision.Milliseconds)]
[InlineData(Precision.Seconds)]
[InlineData(Precision.Hours)]
public void Can_floor_if_wrong_precision_used(Precision precision)
{
var writer = new LineProtocolWriter(precision);

var timestamp = TimeSpan.FromTicks(1);

writer.Measurement("foo").Field("bar", 1f).Timestamp(timestamp, PrecisionResolutionStrategy.Floor);

Assert.Equal("foo bar=1 0", writer.ToString());
}

[Theory]
[InlineData(Precision.Microseconds)]
[InlineData(Precision.Milliseconds)]
[InlineData(Precision.Seconds)]
[InlineData(Precision.Hours)]
public void Can_ceiling_if_wrong_precision_used(Precision precision)
{
var writer = new LineProtocolWriter(precision);

var timestamp = TimeSpan.FromTicks(1);

writer.Measurement("foo").Field("bar", 1f).Timestamp(timestamp, PrecisionResolutionStrategy.Ceiling);

Assert.Equal("foo bar=1 1", writer.ToString());
}

[Theory]
[InlineData(Precision.Microseconds)]
[InlineData(Precision.Milliseconds)]
[InlineData(Precision.Seconds)]
[InlineData(Precision.Hours)]
public void Can_round_if_wrong_precision_used(Precision precision)
{
var writer = new LineProtocolWriter(precision);

writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromTicks(1), PrecisionResolutionStrategy.Round);
writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromTicks(((long)precision / 100) - 1), PrecisionResolutionStrategy.Round);

Assert.Equal("foo bar=t 0\nfoo bar=t 1", writer.ToString());
}

[Fact]
public void Can_define_resolution_strategy_when_creating_the_writer()
{
var writer = new LineProtocolWriter(Precision.Seconds, PrecisionResolutionStrategy.Round);

writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromMilliseconds(499));
writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromMilliseconds(500));

Assert.Equal("foo bar=t 0\nfoo bar=t 1", writer.ToString());
}

[Fact]
public void Can_override_resolution_strategy_when_writing_point()
{
var writer = new LineProtocolWriter(Precision.Seconds, PrecisionResolutionStrategy.Round);

writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromMilliseconds(700));
writer.Measurement("foo").Field("bar", true).Timestamp(TimeSpan.FromMilliseconds(700), PrecisionResolutionStrategy.Floor);

Assert.Equal("foo bar=t 1\nfoo bar=t 0", writer.ToString());
}
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy