Overview
This project provides a REST interface to execute queries on the databuffer.
Requirements
This project requires Java 8 or greater.
Deployment
Use the instructions provided by ch.psi.daq.install to install the application on a server.
Application Properties
Following files define and describe application properties:
- Cassandra specific properties.
- Query specific properties..
- Query REST specific properties.
It is possible to overwrite properties by defining new values in ${HOME}/.config/daq/queryrest.properties
Maven
Upload jar to the Maven repository (from ch.psi.daq.buildall):
./gradlew ch.psi.daq.queryrest:uploadArchives
DropIt
Upload jar DropIt (from ch.psi.daq.buildall):
./gradlew ch.psi.daq.queryrest:dropIt -x test
Local Instance
DAQLocal provides a local instance of the DAQ system for testing purposes (allowing users/developers to verify their code before they come to PSI to do their research and interact with the DAQ cluster).
REST Interface
The REST interface is accessible through http://data-api.psi.ch/sf
.
Query Channel Names
Request
POST http://<host>:<port>/channels
Data
{"regex": "TRFCA|TRFCB","backends": ["databuffer"],"ordering":"asc","reload":true}
Explanation
- regex: Reqular expression used to filter channel names. In case this value is undefined, no filter will be applied. Filtering is done using JAVA's Pattern, more precisely Matcher.find()).
- backends: Array of backends to access (values: databuffer|archiverappliance). In case this value is undefined, all backends will be queried for their channels.
- ordering: The ordering of the channel names (values: none|asc|desc).
- reload: Forces the server to reload cached channel names (values: false|true).
Example
Command
curl -H "Content-Type: application/json" -X POST -d '{"regex": "AMPLT|PHASE"}' http://data-api.psi.ch/sf/channels | python -m json.tool
Response
[
{
"backend":"databuffer",
"channels":[
"Channel_01",
"Channel_02",
"Channel_03"
]
},
{
"backend":"archiverappliance",
"channels":[
"Channel_01",
"Channel_04",
"Channel_05"
]
}
]
Query Data
Request
GET http://<host>:<port>/query
Request body
A request is performed by sending a valid JSON object in the HTTP request body. The JSON query defines the channels to be queried, the range, and how the data should be aggregated (this is optional but highly recommended).
The following attributes can be specified:
- channels: Array of channels to be queried (see Query Range).
- range: The range of the query (see Query Range).
- ordering: The ordering of the data (see here for possible values).
- fields: The requested fields (see here for possible values).
- nrOfBins: Activates data binning. Specifies the number of bins the pulse/time range should be divided into.
- binSize: Activates data binning. Specifies the number of pulses per bin for pulse-range queries or the number of milliseconds per bin for time-range queries (using number of pulses and number of milliseconds makes this binning strategy consistent between channel with different update frequencies).
- aggregations: Activates data aggregation. Array of requested aggregations (see here for possible values). These values will be added to the data array response.
- aggregationType: Specifies the type of aggregation (see here). The default type is value aggregation (e.g., sum([1,2,3])=6). Alternatively, it is possible to define index aggregation for multiple arrays in combination with binning (e.g., sum([1,2,3], [3,2,1]) = [4,4,4]).
- responseFormat: Specifies the format the response of the requested data is in, either in JSON or CSV format, default value JSON, see all values here)
- compression: Defines the compression algorithm to use, default value is none, see all values here)
Define Channel Names
The simplest way to define channels is to use an array of channel name Strings.
"channels":[
"Channel_02",
"Channel_04"
]
The query interface will automatically select the backend which contains the channel (e.g., databuffer for Channel_02 and archiverappliance for Channel_04). In case name clashes exist, the query interface will use following order of priority: databuffer and then archiverappliance.
It is also possible to explicitly define the backend to overcome name clashes.
"channels":[
{
"name":"Channel_01",
"backend":"archiverappliance"
},
{
"name":"Channel_01",
"backend":"databuffer"
}
]
Define Query Range
Queries are applied to a range. The following types of ranges are supported.
By Pulse-Id
"range":{
"startPulseId":0,
"endPulseId":100
}
- startPulseId: The start pulse-id of the range request.
- endPulseId: The end pulse-id of the range request.
By Date
"range":{
"startDate":"2015-08-06T18:00:00.000",
"endDate":"2015-08-06T18:59:59.999",
}
- startDate: The start date of the time range in the ISO8601 format (such as 1997-07-16T19:20:30.123+02:00 or 1997-07-16T19:20:30.123456789+02:00 (omitting +02:00 falls back to the local time zone)).
- endDate: The end date of the time range.
By Time
"range":{
"startSeconds":"0.0",
"endSeconds":"1.000999999"
}
- startSeconds: The start time of the range in seconds since January 1, 1970 (the UNIX epoch) as a decimal value including fractional seconds.
- endSeconds: The end time of the range in seconds.
Response Format
The format of the response can be defined through the field responseFormat
(values: JSON|CSV). Please note that CSV does not support index
and extrema
aggregations.
Response Compression
Responses can be compressed when transferred from the server by setting the field compression
(values: none|gzip|deflate).
If compression is enabled, you have to tell curl
that the data is compressed by defining the attribute --compressed
so that it decompresses the data automatically.
Example Queries
The following examples build on waveform data (see below). They also work for scalars (consider it as a waveform of length = 1) and images (waveform of length = dimX * dimY).
[
{
"channel":"Channel_01",
"data":[
{
"iocSeconds":"0.000000000",
"pulseId":0,
"globalSeconds":"0.000000000",
"shape":[
4
],
"value":[1,2,3,4]
},
{
"iocSeconds":"0.010000000",
"pulseId":1,
"globalSeconds":"0.010000000",
"shape":[
4
],
"value":[2,3,4,5]
},
{
"iocSeconds":"0.020000000",
"pulseId":2,
"globalSeconds":"0.020000000",
"shape":[
4
],
"value":[3,4,5,6]
},
{
"iocSeconds":"0.030000000",
"pulseId":3,
"globalSeconds":"0.030000000",
"shape":[
4
],
"value":[4,5,6,7]
}
]
}
]
Query Examples
Query by Pulse-Id Range
{
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
See JSON representation of the data above.
Query by Time Range
{
"range":{
"startSeconds":"0.0",
"endSeconds":"0.030999999"
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"range":{"startSeconds":"0.0","endSeconds":"0.030999999"},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
See JSON representation of the data above.
Query by Date Range
{
"range":{
"startDate":"1970-01-01T01:00:00.000",
"endDate":"1970-01-01T01:00:00.030"
},
"channels":[
"Channel_01"
]
}
The supported date format is ISO8601 (such as 1997-07-16T19:20:30.123+02:00 or 1997-07-16T19:20:30.123456789+02:00 (omitting +02:00 falls back to the local time zone)).
Command
curl -H "Content-Type: application/json" -X POST -d '{"range":{"startDate":"1970-01-01T01:00:00.000","endDate":"1970-01-01T01:00:00.030"},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
See JSON representation of the data above.
Querying Archiver Appliance
{
"range":{
"startSeconds":"0.0",
"endSeconds":"0.030999999"
},
"channels":[
{
"name": "Channel_01",
"backend":"archiverappliance"
},
{
"name": "Channel_02",
"backend":"archiverappliance"
}
]
}
Archiver Appliance supports queries by time range and date range only (as it has no notion about pulse-id).
Command
curl -H "Content-Type: application/json" -X POST -d '{"range":{"startSeconds":"0.0","endSeconds":"0.030999999"},"channels":[{"name": "Channel_01","backend":"archiverappliance"}]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
See JSON representation of the data above.
Query using compression
{
"compression":"gzip",
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
or deflate
can be used too:
{
"compression":"deflate",
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command (gzip)
The curl
command has a --compressed
option to decompress data automatically.
curl --compressed -H "Content-Type: application/json" -X POST -d '{"compression":"gzip","range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Query setting CSV response format
{
"responseFormat":"csv",
"range":{
"startPulseId":0,
"endPulseId":4
},
"channels":[
"channel1",
"channel2"
],
"fields":[
"channel",
"pulseId",
"iocSeconds",
"globalSeconds",
"shape",
"eventCount",
"value"
]
}
It is possible to request the time in seconds (since January 1, 1970 (the UNIX epoch) as a decimal value including fractional seconds - using fields globalSeconds and iocSeconds) or in milliseconds (since January 1, 1970 (the JAVA epoch) - using fields globalMillis and iocMillis)
Command
curl -H "Content-Type: application/json" -X POST -d '{"responseFormat":"csv","range":{"startPulseId":0,"endPulseId":4},"channels":["channel1","channel2"],"fields":["channel","pulseId","iocSeconds","globalSeconds","shape","eventCount","value"]}' http://data-api.psi.ch/sf/query
Response
The response is in CSV.
channel;pulseId;iocSeconds;globalSeconds;shape;eventCount;value
testChannel1;0;0.000000000;0.000000000;[1];1;0
testChannel1;1;0.010000000;0.010000000;[1];1;1
testChannel1;2;0.020000000;0.020000000;[1];1;2
testChannel1;3;0.030000000;0.030000000;[1];1;3
testChannel1;4;0.040000000;0.040000000;[1];1;4
testChannel2;0;0.000000000;0.000000000;[1];1;0
testChannel2;1;0.010000000;0.010000000;[1];1;1
testChannel2;2;0.020000000;0.020000000;[1];1;2
testChannel2;3;0.030000000;0.030000000;[1];1;3
testChannel2;4;0.040000000;0.040000000;[1];1;4
Querying for Specific Fields
Allows for server side optimizations since not all data needs to be retrieved.
{
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"pulseId":0,
"value":[1,2,3,4]
},
{
"pulseId":1,
"value":[2,3,4,5]
},
{
"pulseId":2,
"value":[3,4,5,6]
},
{
"pulseId":3,
"value":[4,5,6,7]
}
]
}
]
Data Ordering
{
"ordering":"desc",
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Use none in case ordering does not matter (allows for server side optimizations).
Command
curl -H "Content-Type: application/json" -X POST -d '{"ordering":"desc","fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"pulseId":3,
"value":[4,5,6,7]
},
{
"pulseId":2,
"value":[3,4,5,6]
},
{
"pulseId":1,
"value":[2,3,4,5]
},
{
"pulseId":0,
"value":[1,2,3,4]
}
]
}
]
Value Aggregation
{
"aggregationType":"value",
"aggregations":["min","max","mean"],
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"aggregationType":"value","aggregations":["min","max","mean"],"fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"pulseId":0,
"value":{
"min":1.0,
"max":4.0,
"mean":2.5
}
},
{
"pulseId":1,
"value":{
"min":2.0,
"max":5.0,
"mean":3.5
}
},
{
"pulseId":2,
"value":{
"min":3.0,
"max":6.0,
"mean":4.5
}
},
{
"pulseId":3,
"value":{
"min":4.0,
"max":7.0,
"mean":5.5
}
}
]
}
]
Value Aggregation with Binning (nrOfBins)
{
"nrOfBins":2,
"aggregationType":"value",
"aggregations":["min","max","mean"],
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"nrOfBins":2,"aggregationType":"value","aggregations":["min","max","mean"],"fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"pulseId":0,
"value":{
"min":1.0,
"max":5.0,
"mean":3.0
}
},
{
"pulseId":2,
"value":{
"min":3.0,
"max":7.0,
"mean":5.0
}
}
]
}
]
Array value aggregations with additional binning:
Value Aggregation with Binning (binSize)
binSize specifies the number of pulses per bin for pulse-range queries or the number of milliseconds per bin for time-range queries (using number of pulses and number of milliseconds makes this binning strategy consistent between channel with different update frequencies).
{
"binSize":10,
"aggregationType":"value",
"aggregations":["min","max","mean"],
"fields":["globalMillis","value"],
"range":{
"startSeconds":"0.0",
"endSeconds":"0.030000000"
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"binSize":10,"aggregationType":"value","aggregations":["min","max","mean"],"fields":["globalMillis","value"],"range":{"startSeconds":"0.0","endSeconds":"0.030000000"},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"globalMillis":0,
"value":{
"min":1.0,
"max":5.0,
"mean":3.0
}
},
{
"globalMillis":20,
"value":{
"min":3.0,
"max":7.0,
"mean":5.0
}
}
]
}
]
Array value aggregations with additional binning:
Index Aggregation
{
"nrOfBins":1,
"aggregationType":"index",
"aggregations":["min","max","mean","sum"],
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"nrOfBins":1,"aggregationType":"index","aggregations":["min","max","mean","sum"],"fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":[
{
"pulseId":0,
"value":[
{
"min":1.0,
"max":4.0,
"mean":2.5,
"sum":10.0
},
{
"min":2.0,
"max":5.0,
"mean":3.5,
"sum":14.0
},
{
"min":3.0,
"max":6.0,
"mean":4.5,
"sum":18.0
},
{
"min":4.0,
"max":7.0,
"mean":5.5,
"sum":22.0
}
]
}
]
}
]
Aggregation of array indices with binning (several nrOfBins are also possible).
Extrema Search
{
"aggregationType":"extrema",
"aggregations":["min","max","sum"],
"fields":["pulseId","value"],
"range":{
"startPulseId":0,
"endPulseId":3
},
"channels":[
"Channel_01"
]
}
Command
curl -H "Content-Type: application/json" -X POST -d '{"aggregationType":"extrema","aggregations":["min","max","sum"],"fields":["pulseId","value"],"range":{"startPulseId":0,"endPulseId":3},"channels":["Channel_01"]}' http://data-api.psi.ch/sf/query | python -m json.tool
Response
[
{
"channel":"Channel_01",
"data":{
"minima":{
"min":{
"value":1.0,
"event":{
"pulseId":0,
"value":[1,2,3,4]
}
},
"sum":{
"value":10.0,
"event":{
"pulseId":0,
"value":[1,2,3,4]
}
}
},
"maxima":{
"max":{
"value":7.0,
"event":{
"pulseId":3,
"value":[4,5,6,7]
}
},
"sum":{
"value":22.0,
"event":{
"pulseId":3,
"value":[4,5,6,7]
}
}
}
}
}
]