Time Series

The /timeSeries Query

The REST API provides a /timeSeries query by which you can retrieve Time Series data. This time series allows you to create a graph of metric data for a particular resource over a specific time range. Nearly every resource in the REST API allows you to retrieve time series data.

For example, you can retrieve the time series data of a single server request with, say, ID 39012 ( server request time series data):
/api/v1/requests/39012/timeSeries?aggregationPeriod=15&since=2016-01-25T22:00:00.000Z&until=2016-01-26T22:28:00.000Z
Or the time series data of a set of browser pages with known IDs ( multiple pages time series data):
/api/v1/pages/timeSeries?idList=2026,2029,30134&aggregationPeriod=15&since=2016-01-25T22:00:00.000Z&until=2016-01-26T22:28:00.000Z
Or the time series data of a thread pool in an application server with known ID 37125 ( application server thread pool time series data):
api/v1/appservers/37125/threadPoolSummary/timeSeries?aggregationPeriod=15&since=2016-01-25T22:00:00.000Z&until=2016-01-26T22:28:00.000Z
These are just a few examples.

Time Range and Aggregation Period Parameters

When you retrieve time series data you specify a time range you are interested in using the since and until parameters, such as the last 48 hours:
timeSeries?since=2016-01-25T22:00:00.000Z&until=2016-01-27T22:00:00.000Z
And an aggregation period, such as 1 hour:
&aggregationPeriod=60

The time range is much like the time range in any other REST API operation, described in Since and Until Time Range.

The aggregation period splits the returned metric data in the specified time range into buckets, each bucket representing one of those periods. The units of time in the aggregation period are 1-minute, therefore, if you use aggregationPeriod=60, as in the above example, you are requesting that the data be split up into 1-hour bucket intervals. For time series data collected over a longer time range, you may choose to use a longer aggregation period, and for a shorter time range you can use a shorter aggregation period. You can refine the granularity of the time series data by adjusting the aggregation period up or down. If you do not specify an aggregation period for time series data, the default is 60 minutes.

The full time range is divided into buckets whose duration is the aggregation period. The last bucket may be a partial period. For example, if you specified an aggregation period of 60 minutes, but the time range was 24 hours and 20 minutes, the last bucket will contain data from the last 20 minutes in the time range.

Note that since the server agents and browser agents do not collect data during idle periods, the REST API operation will return null time series data for those same periods. This allows you to determine if there was no data for a specific aggregation period within your time range. In some cases no data will be interpreted differently to zero data. Also note that even if the requested resource object does not exist, the time series returned will be an empty series, full of null data.

Sometimes the time range you specify in your query may be internally adjusted to fit better with the aggregation period you use. This internal adjustment is explained later.

Time Series JSON Response Object

Depending on the type of resource for which you are retrieving time series data, the system returns different sets of metric data. For example, if you retrieve time series data for server requests, the system returns data for metrics such as:
  • request error percentage
  • average database access time
  • failure count
  • maximum response time
  • average response time
and more. Each of these measurements is split up into the buckets reflected by the aggregation period. So, if you retrieve the time series data for the last 24 hours with 1-hour buckets, you would find that the first bucket of average response time metrics would contain the data for the first hour of average response times.
Another example may retrieve time series data for a browser page using the same time range and aggregation period as above. In this example the system would return data for metrics such as:
  • maximum interactive time
  • maximum time to receive the first byte
  • average time to receive the first byte
and more. Thus, depending on your context, you would process and interpret that time series metric data differently.

As explained earlier, when the application servers and browser pages are idle, the monitoring agents are not collecting any data, and your retrieved time series data may be null. It is quite common for the JSON time series response to be quite sparse, with many null entries. Getting at least one non-null entry in your data set means you actually did collect a metric during your time range.

The following example was extracted from the APMCS UI which gathers time series data to display on its graphs. The time range chosen was 24 hours, so, the APMCS UI chose to increase the aggregation period from the default of 60 minutes to 120 minutes. The query issued (which can be viewed, say, from a web browser's developer tools as described in From UI to REST API) was:
/api/v1/requests/timeSeries?since=2016-02-07T18:00:00.000Z&until=2016-02-08T18:06:00.000Z&idList=101849,101247&aggregationPeriod=120
The actual idList parameter has been shortened for this example.

The returned time series JSON response data for the first server request was the following. Note that since the time range used was a relatively idle day on the servers involved, many buckets are null, and a few are zero:

{
    "id" : 101849,
    "maxSelfTime" : [ null, null, null, null, null, 5007, 5000, null, null, null, null, null, null ],
    "minSelfTime" : [ null, null, null, null, null, 5000, 5000, null, null, null, null, null, null ],
    "errorPercentage" : [ null, null, null, null, null, 0, 0, null, null, null, null, null, null ],
    "avgAppServerTime" : [ null, null, null, null, null, 9411.15, 8208.5, null, null, null, null, null, null ],
    "avgDatabaseTime" : [ null, null, null, null, null, 0, 0, null, null, null, null, null, null ],
    "avgExternalTime" : [ null, null, null, null, null, 0, 0, null, null, null, null, null, null ],
    "avgSelfTime" : [ null, null, null, null, null, 5001.54, 5000, null, null, null, null, null, null ],
    "completedCount" : [ null, null, null, null, null, 13, 2, null, null, null, null, null, null ],
    "failureCount" : [ null, null, null, null, null, 0, 0, null, null, null, null, null, null ],
    "totalTime" : [ null, null, null, null, null, 122345, 16417, null, null, null, null, null, null ],
    "maxResponseTime" : [ null, null, null, null, null, 15629, 10601, null, null, null, null, null, null ],
    "minResponseTime" : [ null, null, null, null, null, 5630, 5816, null, null, null, null, null, null ],
    "averageResponseTime" : [ null, null, null, null, null, 9411.15, 8208.50, null, null, null, null, null, null ],
    "count" : 13,
    "bucket" : [ 13593, 13594, 13595, 13596, 13597, 13598, 13599, 13600, 13601, 13602, 13603, 13604, 13605 ],
    "lengthMin" : [ 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 6 ],
    "time" : [ 1454868000000, 1454875200000, 1454882400000, 1454889600000, 1454896800000, 1454904000000, 1454911200000, 1454918400000, 1454925600000, 1454932800000, 1454940000000, 1454947200000, 1454954400000 ],
    "formattedTime" : [ "2016-02-07T18:00:00.000Z", "2016-02-07T20:00:00.000Z", "2016-02-07T22:00:00.000Z", "2016-02-08T00:00:00.000Z", "2016-02-08T02:00:00.000Z", "2016-02-08T04:00:00.000Z", "2016-02-08T06:00:00.000Z", "2016-02-08T08:00:00.000Z", "2016-02-08T10:00:00.000Z", "2016-02-08T12:00:00.000Z", "2016-02-08T14:00:00.000Z", "2016-02-08T16:00:00.000Z", "2016-02-08T18:00:00.000Z" ],
} 
        

Other than the sparse values and the metric data pertaining to the resource object for which you are retrieving time series data, there are a few elements in the time series response object that need explaining:

Element Description
count Count of the number of buckets in your data. The number of buckets can generally be calculated as:
time-range/aggregation-period
bucket An array of internal bucket IDs.
lengthMin An array of the aggregation period length in minutes. Generally, each bucket should be equal to your aggregation period. The last bucket may be less than the aggregation period. In our example, the last bucket was just 6 minutes (a remainder from the 120 minute aggregation period).
time & formattedTime As mentioned in Since and Until Time Range, the system stores time time stamps and data time intervals as long values representing the Unix epoch in milliseconds. Each time bucket value is the precise data point reflecting the starting time of the metrics contained in the corresponding bucket. The formattedTime is the UTC string representation of these same time values.

As noted earlier, sometimes the time range you specify may be adjusted to fit better with the aggregation period you use. This internal adjustment is explained next.

Aggregation Period Adjustments to Time Range

When you use a time range in a non-time series query the default aggregation period is 1-minute. Thus, whatever your since time specification is, the data point at which metrics will be retrieved is the 1-minute boundary that is equal to or earlier to the since value. If you always keep the since time value on the 1-minute boundary (without any extra seconds or milliseconds), then your data will be retrieved starting exactly at the since time value you specified.

The 1-minute until time specification is adjusted a little differently. It is important to note that your until specification is not inclusive - that is, your time range ends at that time and not at the minute beginning at that time. How is the until specification adjusted? First, any seconds and milliseconds are subtracted from the until value. Then the 1-minute before that value is considered the last minute whose metrics are included.

For example, if your time range is the following (note the aggregation period is 1-minute):
/api/v1/requests/39012/timeSeries?aggregationPeriod=1&since=2016-01-25T22:15:12.345Z&until=2016-01-25T22:45:67.890Z
then the time stamp of the first element in the result set is 2016-01-25T22:15:00.000Z - the 12.345 seconds and millseconds have been removed from the since value. And the starting time stamp of the last element in the result set is 2016-01-25T22:44:00.000Z - like with since value, the seconds and millseconds have been removed, and the last value begins at the 1-minute boundary that ends with your adjusted until time.

It is important to understand the above when looking at the timestamps of a time series whose aggregation period is greater than 1-minute. In fact, it is quite possible that the since value specified in a time series query may differ significantly from the start time of the first bucket of time series data. Why is this?

When computing aggregation periods that are greater than 1-minute the time series data must be aggregated into aggregation buckets. Aggregation bucket boundaries are fixed relative to a zero point, and occur at multiples of the aggregation period, which is provided in the time series query. For example, if your aggregation period is aggregationPeriod=30, then the first bucket of time series data will be on a half-hour mark. If your since value is on the half-hour mark, then the data's starting time and your time range's starting time will match. If, however, your since value is not on the half-hour mark, then the data's starting time will be the half-hour preceding your since value. This is quite similar to what was explained earlier for 1-minute data, where the since value started on a clean 1-minute boundary.

The maximum difference between the actual time series data's starting time and the specified since time value is aggregationPeriod-1 millisecond. For example, if your aggregation period is aggregationPeriod=30, and you specify a since value of 2016-01-25T22:29:29.999Z, your effective starting time will be 2016-01-25T22:00:00.000Z. Because the default aggregation period is 60 minutes, even in the default case, if you are not precise about setting your time range, you could be nearly an hour off in retrieved measurements as compared with expected measurements.

Internally, the system adjusts the since parameter so that it aligns with the first aggregation bucket on or before the value passed in the query-specified since parameter.

As part of this adjustment the system also aligns the last bucket on or before the value provided in the until parameter, so that the last bucket ends with the until value (adjusted to a 1-minute boundary). As mentioned earlier, the last aggregation bucket may be a partial aggregation period. This is done so that a REST API client will have up-to-date data for the end of the time period, which is often the current time, and which likely includes the seconds and millseconds for that time.

Figure - Time Series Adjustment

Time Series Adjustment

Notice that the first bucket's start time is adjusted to the aggregation period alignment boundary and will always include the specified since value. The start of last bucket will also be on an aggregation period alignment boundary and will end at the specified until value, adjusted back to a 1-minute boundary. As shown, the size of the last bucket is not necessarily a full aggregation period.

To avoid internal adjustment ensure that the aggregation period and the time range's starting time match up. For example, if your aggregation period is 60 minutes (the default), then make sure your since value is on a 1-hour boundary. Similarly, you can adjust your until value so that you need not deal with partial aggregation buckets. Lastly, though not advised, you may choose an aggregation period of 1 minute, but that would greatly increase the amount of data you need to process.