From e4ab3467e8e7d48e5fadb13d6fa568ef4c7f659f Mon Sep 17 00:00:00 2001 From: Torsten Schroeder Date: Tue, 12 Nov 2019 09:41:24 +0100 Subject: [PATCH] Update InfluxManager.cs added specific retention policy to the write functions --- InfluxClient/InfluxManager.cs | 650 ++++++++++++++++------------------ 1 file changed, 308 insertions(+), 342 deletions(-) diff --git a/InfluxClient/InfluxManager.cs b/InfluxClient/InfluxManager.cs index 72019dc..bb6144f 100644 --- a/InfluxClient/InfluxManager.cs +++ b/InfluxClient/InfluxManager.cs @@ -10,368 +10,334 @@ using System.Threading.Tasks; using System.Web.Script.Serialization; -namespace InfluxClient -{ - public class InfluxManager - { - #region Constructor and private properties - - /// - /// The base url for API calls - /// - private string _baseUrl = ""; - - /// - /// The influxDB database to use - /// - private string _database = ""; - - /// - /// User name to use - /// - private string _username = ""; - - /// - /// Password to use - /// - private string _password = ""; - - /// - /// Action to tun when there is an exception - /// - private Action _exceptionHandler = (exception, s, values) => { }; - - /// - /// Creates a new InfluxDB manager - /// - /// The influxdb endpoint, including the port (if any) - /// The database to write to - /// Whether or not to throw any exceptions for methods called on this instance - public InfluxManager(string influxEndpoint, string database, bool throwExceptions = false) - : this(influxEndpoint, database, DetermineDefaultExceptionHandler(throwExceptions)) - { - } +namespace InfluxClient { + public class InfluxManager { + #region Constructor and private properties + + /// + /// The base url for API calls + /// + private string _baseUrl = ""; + + /// + /// The influxDB database to use + /// + private string _database = ""; + + /// + /// User name to use + /// + private string _username = ""; + + /// + /// Password to use + /// + private string _password = ""; + + /// + /// Action to tun when there is an exception + /// + private Action _exceptionHandler = (exception, s, values) => { }; + + /// + /// Creates a new InfluxDB manager + /// + /// The influxdb endpoint, including the port (if any) + /// The database to write to + /// Whether or not to throw any exceptions for methods called on this instance + public InfluxManager(string influxEndpoint, string database, bool throwExceptions = false) + : this(influxEndpoint, database, DetermineDefaultExceptionHandler(throwExceptions)) { + } - /// - /// Creates a new InfuxDB manager with authentication credentials - /// - /// The influxdb endpoint, including the port (if any) - /// The database to write to - /// The username to authenticate with - /// The password to authenticate with - /// Whether or not to throw any exceptions for methods called on this instance - public InfluxManager(string influxEndpoint, string database, string username, string password, bool throwExceptions = false) - : this(influxEndpoint, database, username, password, DetermineDefaultExceptionHandler(throwExceptions)) - { - } + /// + /// Creates a new InfuxDB manager with authentication credentials + /// + /// The influxdb endpoint, including the port (if any) + /// The database to write to + /// The username to authenticate with + /// The password to authenticate with + /// Whether or not to throw any exceptions for methods called on this instance + public InfluxManager(string influxEndpoint, string database, string username, string password, bool throwExceptions = false) + : this(influxEndpoint, database, username, password, DetermineDefaultExceptionHandler(throwExceptions)) { + } - /// - /// Creates a new InfluxDB manager - /// - /// The influxdb endpoint, including the port (if any) - /// The database to write to - /// Action to handle exceptions - public InfluxManager(string influxEndpoint, string database, Action exceptionHandler) - { - // If the endpoint has a trailing backslash, remove it: - if (influxEndpoint.EndsWith("/")) - { influxEndpoint = influxEndpoint.Remove(influxEndpoint.LastIndexOf("/")); } - - // Set the base url and database: - _baseUrl = influxEndpoint; - _database = database; - - // Set the bubble exceptions parameter: - _exceptionHandler = exceptionHandler; - } + /// + /// Creates a new InfluxDB manager + /// + /// The influxdb endpoint, including the port (if any) + /// The database to write to + /// Action to handle exceptions + public InfluxManager(string influxEndpoint, string database, Action exceptionHandler) { + // If the endpoint has a trailing backslash, remove it: + if (influxEndpoint.EndsWith("/")) { influxEndpoint = influxEndpoint.Remove(influxEndpoint.LastIndexOf("/")); } + + // Set the base url and database: + _baseUrl = influxEndpoint; + _database = database; + + // Set the bubble exceptions parameter: + _exceptionHandler = exceptionHandler; + } - /// - /// Creates a new InfuxDB manager with authentication credentials - /// - /// The influxdb endpoint, including the port (if any) - /// The database to write to - /// The username to authenticate with - /// The password to authenticate with - /// Action to handle exceptions - public InfluxManager(string influxEndpoint, string database, string username, string password, - Action exceptionHandler) : this(influxEndpoint, database, exceptionHandler) - { - // Set the username and password: - _username = username; - _password = password; - } + /// + /// Creates a new InfuxDB manager with authentication credentials + /// + /// The influxdb endpoint, including the port (if any) + /// The database to write to + /// The username to authenticate with + /// The password to authenticate with + /// Action to handle exceptions + public InfluxManager(string influxEndpoint, string database, string username, string password, + Action exceptionHandler) : this(influxEndpoint, database, exceptionHandler) { + // Set the username and password: + _username = username; + _password = password; + } - #endregion - - /// - /// Pings the InfluxDB database - /// - /// - async public Task Ping() - { - // The default response message: - HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); - - // Create our url to ping - string url = string.Format("{0}/ping", _baseUrl); - - try - { - // Make an async call to get the response - using(HttpClient client = new HttpClient()) - { - retval = await client.GetAsync(url); - } - } - catch(Exception ex) - { - Trace.TraceError("Ping {0} caused an exception: {1}", url, ex.Message); - - LogError(ex, "Ping {0} caused an exception: {1}", url, ex.Message); - } - - return retval; - } + #endregion - /// - /// Write a measurement to the InfluxDB database - /// - /// The measurement to write. It must have at least one field specified - /// An awaitable Task containing the HttpResponseMessage returned from the InfluxDB server - async public Task Write(Measurement m) - { - // The default response message: - HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); - - // Make sure the measurement has at least one field: - if(!(m.BooleanFields.Any() - || m.FloatFields.Any() - || m.IntegerFields.Any() - || m.StringFields.Any())) - { - string error = string.Format("Measurement '{0}' needs at least one field value", m.Name); - Trace.TraceError(error); - - LogError(new ArgumentException(error), error); - } - - // Create our url to post data to - string url = string.Format("{0}/write?db={1}", _baseUrl, _database); - - // Create our data to post: - HttpContent content = new StringContent(LineProtocol.Format(m)); - - try - { - // Make an async call to get the response - using(HttpClient client = new HttpClient()) - { - if(CredentialsHaveBeenSet()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); - } - retval = await client.PostAsync(url, content); - } - } - catch(Exception ex) - { - Trace.TraceError("Write {0} caused an exception: {1}", url, ex.Message); - - LogError(ex, "Write {0} caused an exception: {1}", url, ex.Message); - } - - return retval; - } + /// + /// Pings the InfluxDB database + /// + /// + async public Task Ping() { + // The default response message: + HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); - /// - /// Writes a list of measurements to the InfluxDB database - /// - /// The list of measurements to write. Each measurement must have at least one field specified - /// An awaitable Task containing the HttpResponseMessage returned from the InfluxDB server - async public Task Write(List listOfMeasurements) - { - // The default response message: - HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); - - // Create our url to post data to - string url = string.Format("{0}/write?db={1}", _baseUrl, _database); - - // Our string to build: - StringBuilder sb = new StringBuilder(); - foreach(var m in listOfMeasurements) - { - // Make sure the measurement has at least one field: - if(!(m.BooleanFields.Any() - || m.FloatFields.Any() - || m.IntegerFields.Any() - || m.StringFields.Any())) - { - string error = string.Format("Measurement '{0}' needs at least one field value", m.Name); - Trace.TraceError(error); - - LogError(new ArgumentException(error), "Measurement '{0}' needs at least one field value", m.Name); - } - - sb.AppendFormat("{0}\n", LineProtocol.Format(m)); - } - - // If we had some measurements... - if(listOfMeasurements.Any()) - { - // Remove the last trailing newline - sb.Remove(sb.Length - 1, 1); - } - - // Create our data to post: - HttpContent content = new StringContent(sb.ToString()); - - try - { - // Make an async call to get the response - using(HttpClient client = new HttpClient()) - { - if(CredentialsHaveBeenSet()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); - } - retval = await client.PostAsync(url, content); - } - } - catch(Exception ex) - { - Trace.TraceError("Write (list) {0} caused an exception: {1}", url, ex.Message); - - LogError(ex, "Write (list) {0} caused an exception: {1}", url, ex.Message); - } - - return retval; - } + // Create our url to ping + string url = string.Format("{0}/ping", _baseUrl); - /// - /// Query the InfluxDB database - /// - /// - /// - async public Task QueryJSON(string influxQL) - { - // Create our url to query data with - string url = string.Format("{0}/query?db={1}&q={2}", _baseUrl, _database, influxQL); - - // Make an async call to get the response - using(HttpClient client = new HttpClient()) - { - if(CredentialsHaveBeenSet()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); - } - return await client.GetAsync(url); - } + try { + // Make an async call to get the response + using (HttpClient client = new HttpClient()) { + retval = await client.GetAsync(url); } + } catch (Exception ex) { + Trace.TraceError("Ping {0} caused an exception: {1}", url, ex.Message); - /// - /// Query the InfluxDB database and deserialize the returned data - /// to the specified object format - /// - /// The type to deserialize to. See https://influxdb.com/docs/v0.9/guides/querying_data.html for the shape this should take - /// - /// - async public Task Query(string influxQL) - { - // The return value: - T retval = default(T); - - // Call the QueryJSON method to get the data back: - HttpResponseMessage response = await QueryJSON(influxQL); - string data = await response.Content.ReadAsStringAsync(); - - // Serialize the data to the requested object - // (it should take the shape of the returned JSON - // https://influxdb.com/docs/v0.9/guides/querying_data.html) - JavaScriptSerializer jser = new JavaScriptSerializer(); - retval = jser.Deserialize(data); - - return retval; - } + LogError(ex, "Ping {0} caused an exception: {1}", url, ex.Message); + } - /// - /// Query the InfluxDB database and deserialize the returned data - /// - /// - /// - async public Task Query(string influxQL) - { - QueryResponse retval = await Query(influxQL); - return retval; - } + return retval; + } - /// - /// Wraps _exceptionHandler Action to allow for using params - /// - /// Exception to log - /// Message template to log - /// Values to populate the message template - private void LogError(Exception exception, string message, params object[] values) - { - _exceptionHandler(exception, message, values); + /// + /// Write a measurement to the InfluxDB database + /// + /// The measurement to write. It must have at least one field specified + /// The specific retention policy name to write to. + /// An awaitable Task containing the HttpResponseMessage returned from the InfluxDB server + async public Task Write(Measurement m, String retention = "") { + // The default response message: + HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); + + // Make sure the measurement has at least one field: + if (!(m.BooleanFields.Any() + || m.FloatFields.Any() + || m.IntegerFields.Any() + || m.StringFields.Any())) { + string error = string.Format("Measurement '{0}' needs at least one field value", m.Name); + Trace.TraceError(error); + + LogError(new ArgumentException(error), error); + } + + // Create our url to post data to + string url = string.Format("{0}/write?db={1}", _baseUrl, _database); + if (retention.Length > 0) + url += string.Format("&rp={0}", retention); + + // Create our data to post: + HttpContent content = new StringContent(LineProtocol.Format(m)); + + try { + // Make an async call to get the response + using (HttpClient client = new HttpClient()) { + if (CredentialsHaveBeenSet()) { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); + } + retval = await client.PostAsync(url, content); } + } catch (Exception ex) { + Trace.TraceError("Write {0} caused an exception: {1}", url, ex.Message); - /// - /// Turn throwExceptions into an action that will throw the action if required. This is adapter for the previous constructor to new action constructor. - /// - /// True will return an action that throws the exception. False will return an action that swallows the exception. - /// - private static Action DetermineDefaultExceptionHandler(bool throwExceptions) - { - if (throwExceptions) - { - return (exception, s, values) => { ExceptionDispatchInfo.Capture(exception).Throw(); }; - } - - return (exception, s, values) => { }; + LogError(ex, "Write {0} caused an exception: {1}", url, ex.Message); + } + + return retval; + } + + /// + /// Writes a list of measurements to the InfluxDB database + /// + /// The list of measurements to write. Each measurement must have at least one field specified + /// The specific retention policy name to write to. + /// An awaitable Task containing the HttpResponseMessage returned from the InfluxDB server + async public Task Write(List listOfMeasurements, String retention = "") { + // The default response message: + HttpResponseMessage retval = new HttpResponseMessage(HttpStatusCode.InternalServerError); + + // Create our url to post data to + string url = string.Format("{0}/write?db={1}", _baseUrl, _database); + if (retention.Length > 0) + url += string.Format("&rp={0}", retention); + + // Our string to build: + StringBuilder sb = new StringBuilder(); + foreach (var m in listOfMeasurements) { + // Make sure the measurement has at least one field: + if (!(m.BooleanFields.Any() + || m.FloatFields.Any() + || m.IntegerFields.Any() + || m.StringFields.Any())) { + string error = string.Format("Measurement '{0}' needs at least one field value", m.Name); + Trace.TraceError(error); + + LogError(new ArgumentException(error), "Measurement '{0}' needs at least one field value", m.Name); } - #region API helpers - - /// - /// Gets InfluxDB credentials in HTTP basic auth format - /// if they have been set. Returns an empty string if they have not - /// - /// - private string GetHttpBasicAuthCredentials() - { - string retval = string.Empty; - - // If the username and password aren't empty ... - if(CredentialsHaveBeenSet()) - { - // ... Format the username/password string - byte[] byteArray = Encoding.ASCII.GetBytes( - string.Format("{0}:{1}", _username, _password) - ); - - // base 64 encode the string - retval = Convert.ToBase64String(byteArray); - } - - return retval; + sb.AppendFormat("{0}\n", LineProtocol.Format(m)); + } + + // If we had some measurements... + if (listOfMeasurements.Any()) { + // Remove the last trailing newline + sb.Remove(sb.Length - 1, 1); + } + + // Create our data to post: + HttpContent content = new StringContent(sb.ToString()); + + try { + // Make an async call to get the response + using (HttpClient client = new HttpClient()) { + if (CredentialsHaveBeenSet()) { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); + } + retval = await client.PostAsync(url, content); } + } catch (Exception ex) { + Trace.TraceError("Write (list) {0} caused an exception: {1}", url, ex.Message); - /// - /// Returns 'true' if credentials have been set, false if they haven't - /// - /// - private bool CredentialsHaveBeenSet() - { - bool retval = false; + LogError(ex, "Write (list) {0} caused an exception: {1}", url, ex.Message); + } - if(!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password)) - { - retval = true; - } + return retval; + } - return retval; + /// + /// Query the InfluxDB database + /// + /// + /// + async public Task QueryJSON(string influxQL) { + // Create our url to query data with + string url = string.Format("{0}/query?db={1}&q={2}", _baseUrl, _database, influxQL); + + // Make an async call to get the response + using (HttpClient client = new HttpClient()) { + if (CredentialsHaveBeenSet()) { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", GetHttpBasicAuthCredentials()); } + return await client.GetAsync(url); + } + } + + /// + /// Query the InfluxDB database and deserialize the returned data + /// to the specified object format + /// + /// The type to deserialize to. See https://influxdb.com/docs/v0.9/guides/querying_data.html for the shape this should take + /// + /// + async public Task Query(string influxQL) { + // The return value: + T retval = default(T); + + // Call the QueryJSON method to get the data back: + HttpResponseMessage response = await QueryJSON(influxQL); + string data = await response.Content.ReadAsStringAsync(); + + // Serialize the data to the requested object + // (it should take the shape of the returned JSON + // https://influxdb.com/docs/v0.9/guides/querying_data.html) + JavaScriptSerializer jser = new JavaScriptSerializer(); + retval = jser.Deserialize(data); + + return retval; + } + + /// + /// Query the InfluxDB database and deserialize the returned data + /// + /// + /// + async public Task Query(string influxQL) { + QueryResponse retval = await Query(influxQL); + return retval; + } + + /// + /// Wraps _exceptionHandler Action to allow for using params + /// + /// Exception to log + /// Message template to log + /// Values to populate the message template + private void LogError(Exception exception, string message, params object[] values) { + _exceptionHandler(exception, message, values); + } + + /// + /// Turn throwExceptions into an action that will throw the action if required. This is adapter for the previous constructor to new action constructor. + /// + /// True will return an action that throws the exception. False will return an action that swallows the exception. + /// + private static Action DetermineDefaultExceptionHandler(bool throwExceptions) { + if (throwExceptions) { + return (exception, s, values) => { ExceptionDispatchInfo.Capture(exception).Throw(); }; + } + + return (exception, s, values) => { }; + } - #endregion + #region API helpers + + /// + /// Gets InfluxDB credentials in HTTP basic auth format + /// if they have been set. Returns an empty string if they have not + /// + /// + private string GetHttpBasicAuthCredentials() { + string retval = string.Empty; + + // If the username and password aren't empty ... + if (CredentialsHaveBeenSet()) { + // ... Format the username/password string + byte[] byteArray = Encoding.ASCII.GetBytes( + string.Format("{0}:{1}", _username, _password) + ); + + // base 64 encode the string + retval = Convert.ToBase64String(byteArray); + } + + return retval; } + + /// + /// Returns 'true' if credentials have been set, false if they haven't + /// + /// + private bool CredentialsHaveBeenSet() { + bool retval = false; + + if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password)) { + retval = true; + } + + return retval; + } + + #endregion + } }