Search code examples
c#azure-timeseries-insights

On Azure TSI preview, what is the optimal way to query instance latest event value?


I currently do have a working method which is based on the currently released example code at https://github.com/Azure-Samples/Azure-Time-Series-Insights/tree/master/csharp-tsi-preview-sample

The used types within the following method are created with AutoRest as guided in the GitHub sample: https://github.com/Azure/azure-rest-api-specs/tree/master/specification/timeseriesinsights/data-plane

My initial try is the following:

 public async Task<T> GetLatestEventValue<T>(object[] timeSeriesId, string tsiPropertyName,
     DateTimeRange searchSpan)
 {
     var client = await _tsiClientFactory.GetTimeSeriesInsightsClient();
     var propertyType = GetPropertyType(typeof(T));
     if (propertyType == null) throw new InvalidOperationException($"Unsupported property type (${typeof(T)})");

     string continuationToken = null;
     do
     {
         QueryResultPage queryResponse = await client.Query.ExecuteAsync(
             new QueryRequest(
                 getEvents: new GetEvents(
                     timeSeriesId: timeSeriesId,
                     searchSpan: searchSpan,
                     filter: null,
                     projectedProperties: new List<EventProperty>()
                         {new EventProperty(tsiPropertyName, propertyType)})),
             continuationToken: continuationToken);

         var latestEventIndex = GetLatestEventPropertyIndex(queryResponse.Timestamps);
         var lastValue = queryResponse.Properties
             .FirstOrDefault()
             ?.Values[latestEventIndex];

         if (lastValue != null)
         {
             return (T)lastValue;
         }

         continuationToken = queryResponse.ContinuationToken;
     } while (continuationToken != null);

     return default;
 }

And usage of the method (timeSeriesId is the same as in Microsoft public example):

 var repository = new TsiRepository(_factory);
 object[] timeSeriesId = new object[] { "2da181d7-8346-4cf2-bd94-a17742237429" };
 var today = DateTime.Now;
 var earlierDateTime = today.AddDays(-1);
 var searchSpan = new DateTimeRange(earlierDateTime.ToUniversalTime(), today.ToUniversalTime());
 var result = await repository.GetLatestEventValue<double>(timeSeriesId, "data", searchSpan);

The approach presented above kinda works but does not feel optimal. Is there a simpler way to query the latest event and its value for a given time-series instance? Maybe to take advance of the Time Series Expression (Tsx) capabilities?


Solution

  • After some time spent searching for the answer, my approach is to take advance of the TSX query syntax for last value and add an additional parameter which decides that the query is run only on the warm storage of TSI. This seems to speed things up nicely. The default is cold storage.

    public async Task<T> RunAggregateSeriesLastValueAsync<T>(object[] timeSeriesId, DateTimeRange searchSpan)
    {
        var interval = searchSpan.To - searchSpan.FromProperty;
        string continuationToken = null;
        object lastValue;
        do
        {
            QueryResultPage queryResponse = await _tsiClient.Query.ExecuteAsync(
              new QueryRequest(
                  aggregateSeries: new AggregateSeries(
                  timeSeriesId: timeSeriesId,
                  searchSpan: searchSpan,
                  filter: null,
                  interval: interval,
                  projectedVariables: new[] { "Last_Numeric" },
                  inlineVariables: new Dictionary<string, Variable>()
                  {
                      ["Last_Numeric"] = new NumericVariable(
                      value: new Tsx("$event.value"),
                      aggregation: new Tsx("last($value)"))
                  })),
              storeType: "WarmStore", // Speeds things up since only warm storage is used
              continuationToken: continuationToken)
            lastValue = queryResponse.Properties
              .FirstOrDefault()
              ?.Values.LastOrDefault(v => v != null)
            continuationToken = queryResponse.ContinuationToken;
        } while (continuationToken != null)
        return (T)lastValue;
    }