Improve search api
This commit is contained in:
@@ -99,10 +99,12 @@ pub async fn channel_search_list_v1(req: Request<Body>, proxy_config: &ProxyConf
|
||||
let (head, reqbody) = req.into_parts();
|
||||
let bodybytes = hyper::body::to_bytes(reqbody).await?;
|
||||
let query: ChannelSearchQueryV1 = serde_json::from_slice(&bodybytes)?;
|
||||
match head.headers.get("accept") {
|
||||
match head.headers.get(http::header::ACCEPT) {
|
||||
Some(v) => {
|
||||
if v == APP_JSON {
|
||||
let query = ChannelSearchQuery {
|
||||
// TODO
|
||||
backend: None,
|
||||
name_regex: query.regex.map_or(String::new(), |k| k),
|
||||
source_regex: query.source_regex.map_or(String::new(), |k| k),
|
||||
description_regex: query.description_regex.map_or(String::new(), |k| k),
|
||||
@@ -190,11 +192,13 @@ pub async fn channel_search_configs_v1(
|
||||
let (head, reqbody) = req.into_parts();
|
||||
let bodybytes = hyper::body::to_bytes(reqbody).await?;
|
||||
let query: ChannelSearchQueryV1 = serde_json::from_slice(&bodybytes)?;
|
||||
match head.headers.get("accept") {
|
||||
match head.headers.get(http::header::ACCEPT) {
|
||||
Some(v) => {
|
||||
if v == APP_JSON {
|
||||
// Transform the ChannelSearchQueryV1 to ChannelSearchQuery
|
||||
let query = ChannelSearchQuery {
|
||||
// TODO
|
||||
backend: None,
|
||||
name_regex: query.regex.map_or(String::new(), |k| k),
|
||||
source_regex: query.source_regex.map_or(String::new(), |k| k),
|
||||
description_regex: query.description_regex.map_or(String::new(), |k| k),
|
||||
|
||||
@@ -9,6 +9,7 @@ use netpod::query::RawEventsQuery;
|
||||
use netpod::{get_url_query_pairs, log::*, Channel, NanoRange};
|
||||
use netpod::{NodeConfigCached, APP_JSON_LINES};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsVal;
|
||||
use url::Url;
|
||||
|
||||
fn json_lines_stream<S, I>(stream: S) -> impl Stream<Item = Result<Vec<u8>, Error>>
|
||||
@@ -158,6 +159,87 @@ impl ScanChannels {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChannelNames {}
|
||||
|
||||
impl ChannelNames {
|
||||
pub fn prefix() -> &'static str {
|
||||
"/api/4/channelarchiver/channel/names"
|
||||
}
|
||||
|
||||
pub fn name() -> &'static str {
|
||||
"ChannelNames"
|
||||
}
|
||||
|
||||
pub fn should_handle(path: &str) -> Option<Self> {
|
||||
if path.starts_with(Self::prefix()) {
|
||||
Some(Self {})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(&self, req: Request<Body>, node_config: &NodeConfigCached) -> Result<Response<Body>, Error> {
|
||||
if req.method() != Method::GET {
|
||||
return Ok(response(StatusCode::NOT_ACCEPTABLE).body(Body::empty())?);
|
||||
}
|
||||
info!("{} handle uri: {:?}", Self::name(), req.uri());
|
||||
let conf = node_config
|
||||
.node
|
||||
.channel_archiver
|
||||
.as_ref()
|
||||
.ok_or(Error::with_msg_no_trace(
|
||||
"this node is not configured as channel archiver",
|
||||
))?;
|
||||
use archapp_wrap::archapp::archeng;
|
||||
let stream = archeng::configs::ChannelNameStream::new(conf.database.clone());
|
||||
let stream = json_lines_stream(stream);
|
||||
Ok(response(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, APP_JSON_LINES)
|
||||
.body(Body::wrap_stream(stream))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScanConfigs {}
|
||||
|
||||
impl ScanConfigs {
|
||||
pub fn prefix() -> &'static str {
|
||||
"/api/4/channelarchiver/scan/configs"
|
||||
}
|
||||
|
||||
pub fn name() -> &'static str {
|
||||
"ScanConfigs"
|
||||
}
|
||||
|
||||
pub fn should_handle(path: &str) -> Option<Self> {
|
||||
if path.starts_with(Self::prefix()) {
|
||||
Some(Self {})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(&self, req: Request<Body>, node_config: &NodeConfigCached) -> Result<Response<Body>, Error> {
|
||||
if req.method() != Method::GET {
|
||||
return Ok(response(StatusCode::NOT_ACCEPTABLE).body(Body::empty())?);
|
||||
}
|
||||
info!("{} handle uri: {:?}", Self::name(), req.uri());
|
||||
let conf = node_config
|
||||
.node
|
||||
.channel_archiver
|
||||
.as_ref()
|
||||
.ok_or(Error::with_msg_no_trace(
|
||||
"this node is not configured as channel archiver",
|
||||
))?;
|
||||
use archapp_wrap::archapp::archeng;
|
||||
let stream = archeng::configs::ChannelNameStream::new(conf.database.clone());
|
||||
let stream = archeng::configs::ConfigStream::new(stream, conf.clone());
|
||||
let stream = json_lines_stream(stream);
|
||||
Ok(response(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, APP_JSON_LINES)
|
||||
.body(Body::wrap_stream(stream))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockRefStream {}
|
||||
|
||||
impl BlockRefStream {
|
||||
@@ -190,9 +272,13 @@ impl BlockRefStream {
|
||||
"this node is not configured as channel archiver",
|
||||
))?;
|
||||
let range = NanoRange { beg: 0, end: u64::MAX };
|
||||
let url = Url::parse(&format!("dummy:{}", req.uri()))?;
|
||||
let pairs = get_url_query_pairs(&url);
|
||||
let channel_name = pairs.get("channelName").map(String::from).unwrap_or("NONE".into());
|
||||
let channel = Channel {
|
||||
backend: "".into(),
|
||||
name: "ARIDI-PCT:CURRENT".into(),
|
||||
name: channel_name,
|
||||
//name: "ARIDI-PCT:CURRENT".into(),
|
||||
};
|
||||
let s = archapp_wrap::archapp::archeng::blockrefstream::blockref_stream(channel, range, conf.clone());
|
||||
let s = s.map(|item| match item {
|
||||
@@ -251,20 +337,26 @@ impl BlockStream {
|
||||
"this node is not configured as channel archiver",
|
||||
))?;
|
||||
let range = NanoRange { beg: 0, end: u64::MAX };
|
||||
let channel = Channel {
|
||||
backend: "".into(),
|
||||
name: "ARIDI-PCT:CURRENT".into(),
|
||||
};
|
||||
let url = Url::parse(&format!("dummy:{}", req.uri()))?;
|
||||
let pairs = get_url_query_pairs(&url);
|
||||
let read_queue = pairs.get("readQueue").unwrap_or(&"1".to_string()).parse()?;
|
||||
let s = archapp_wrap::archapp::archeng::blockrefstream::blockref_stream(channel, range.clone(), conf.clone());
|
||||
let channel_name = pairs.get("channelName").map(String::from).unwrap_or("NONE".into());
|
||||
let channel = Channel {
|
||||
backend: "".into(),
|
||||
name: channel_name,
|
||||
//name: "ARIDI-PCT:CURRENT".into(),
|
||||
};
|
||||
use archapp_wrap::archapp::archeng;
|
||||
let s = archeng::blockrefstream::blockref_stream(channel, range.clone(), conf.clone());
|
||||
let s = Box::pin(s);
|
||||
let s = archapp_wrap::archapp::archeng::blockstream::BlockStream::new(s, range.clone(), read_queue);
|
||||
let s = archeng::blockstream::BlockStream::new(s, range.clone(), read_queue);
|
||||
let s = s.map(|item| match item {
|
||||
Ok(item) => {
|
||||
//use archapp_wrap::archapp::archeng::blockstream::BlockItem::*;
|
||||
Ok(item)
|
||||
use archeng::blockstream::BlockItem;
|
||||
match item {
|
||||
BlockItem::EventsItem(item) => Ok(JsVal::String("EventsItem".into())),
|
||||
BlockItem::JsVal(jsval) => Ok(jsval),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
});
|
||||
|
||||
@@ -290,6 +290,10 @@ async fn http_service_try(req: Request<Body>, node_config: &NodeConfigCached) ->
|
||||
h.handle(req, &node_config).await
|
||||
} else if let Some(h) = channelarchiver::ScanChannels::should_handle(path) {
|
||||
h.handle(req, &node_config).await
|
||||
} else if let Some(h) = channelarchiver::ScanConfigs::should_handle(path) {
|
||||
h.handle(req, &node_config).await
|
||||
} else if let Some(h) = channelarchiver::ChannelNames::should_handle(path) {
|
||||
h.handle(req, &node_config).await
|
||||
} else if let Some(h) = channelarchiver::BlockRefStream::should_handle(path) {
|
||||
h.handle(req, &node_config).await
|
||||
} else if let Some(h) = channelarchiver::BlockStream::should_handle(path) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod api4;
|
||||
|
||||
use crate::api1::{channel_search_configs_v1, channel_search_list_v1, gather_json_2_v1, proxy_distribute_v1};
|
||||
use crate::gather::{gather_get_json_generic, SubRes};
|
||||
use crate::{api_1_docs, api_4_docs, response, Cont};
|
||||
@@ -78,7 +80,8 @@ async fn proxy_http_service_try(req: Request<Body>, proxy_config: &ProxyConfig)
|
||||
} else if path == "/api/4/backends" {
|
||||
Ok(backends(req, proxy_config).await?)
|
||||
} else if path == "/api/4/search/channel" {
|
||||
Ok(channel_search(req, proxy_config).await?)
|
||||
//Ok(channel_search(req, proxy_config).await?)
|
||||
Ok(api4::channel_search(req, proxy_config).await?)
|
||||
} else if path == "/api/4/events" {
|
||||
Ok(proxy_single_backend_query::<PlainEventsJsonQuery>(req, proxy_config).await?)
|
||||
} else if path == "/api/4/binned" {
|
||||
|
||||
89
httpret/src/proxy/api4.rs
Normal file
89
httpret/src/proxy/api4.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::gather::{gather_get_json_generic, SubRes};
|
||||
use crate::response;
|
||||
use err::Error;
|
||||
use futures_core::Future;
|
||||
use http::{header, Request, Response, StatusCode};
|
||||
use hyper::Body;
|
||||
use itertools::Itertools;
|
||||
use netpod::log::*;
|
||||
use netpod::{ChannelSearchQuery, ChannelSearchResult, ProxyConfig, APP_JSON};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
pub async fn channel_search(req: Request<Body>, proxy_config: &ProxyConfig) -> Result<Response<Body>, Error> {
|
||||
let (head, _body) = req.into_parts();
|
||||
let vdef = header::HeaderValue::from_static(APP_JSON);
|
||||
let v = head.headers.get(header::ACCEPT).unwrap_or(&vdef);
|
||||
if v == APP_JSON || v == "*/*" {
|
||||
let inpurl = Url::parse(&format!("dummy:{}", head.uri))?;
|
||||
let query = ChannelSearchQuery::from_url(&inpurl)?;
|
||||
let mut bodies = vec![];
|
||||
let urls = proxy_config
|
||||
.backends
|
||||
.iter()
|
||||
.filter(|k| {
|
||||
if let Some(back) = &query.backend {
|
||||
back == &k.name
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|pb| match Url::parse(&format!("{}/api/4/search/channel", pb.url)) {
|
||||
Ok(mut url) => {
|
||||
query.append_to_url(&mut url);
|
||||
Ok(url)
|
||||
}
|
||||
Err(_) => Err(Error::with_msg(format!("parse error for: {:?}", pb))),
|
||||
})
|
||||
.fold_ok(vec![], |mut a, x| {
|
||||
a.push(x);
|
||||
bodies.push(None);
|
||||
a
|
||||
})?;
|
||||
let tags = urls.iter().map(|k| k.to_string()).collect();
|
||||
let nt = |res| {
|
||||
let fut = async {
|
||||
let body = hyper::body::to_bytes(res).await?;
|
||||
//info!("got a result {:?}", body);
|
||||
let res: ChannelSearchResult = match serde_json::from_slice(&body) {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
let msg = format!("can not parse result: {}", String::from_utf8_lossy(&body));
|
||||
error!("{}", msg);
|
||||
return Err(Error::with_msg_no_trace(msg));
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
};
|
||||
Box::pin(fut) as Pin<Box<dyn Future<Output = _> + Send>>
|
||||
};
|
||||
let ft = |all: Vec<SubRes<ChannelSearchResult>>| {
|
||||
let mut res = vec![];
|
||||
for j in all {
|
||||
for k in j.val.channels {
|
||||
res.push(k);
|
||||
}
|
||||
}
|
||||
let res = ChannelSearchResult { channels: res };
|
||||
let res = response(StatusCode::OK)
|
||||
.header(http::header::CONTENT_TYPE, APP_JSON)
|
||||
.body(Body::from(serde_json::to_string(&res)?))?;
|
||||
Ok(res)
|
||||
};
|
||||
let ret = gather_get_json_generic(
|
||||
http::Method::GET,
|
||||
urls,
|
||||
bodies,
|
||||
tags,
|
||||
nt,
|
||||
ft,
|
||||
Duration::from_millis(3000),
|
||||
)
|
||||
.await?;
|
||||
Ok(ret)
|
||||
} else {
|
||||
info!("bad accept: {:?}", head.headers.get(header::ACCEPT));
|
||||
Ok(response(StatusCode::NOT_ACCEPTABLE).body(Body::from(format!("{:?}", proxy_config.name)))?)
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,19 @@ use url::Url;
|
||||
|
||||
pub async fn channel_search(req: Request<Body>, node_config: &NodeConfigCached) -> Result<Response<Body>, Error> {
|
||||
let (head, _body) = req.into_parts();
|
||||
match head.headers.get(header::ACCEPT) {
|
||||
Some(v) if v == APP_JSON => {
|
||||
let s1 = format!("dummy:{}", head.uri);
|
||||
info!("try to parse {:?}", s1);
|
||||
let url = Url::parse(&s1)?;
|
||||
let query = ChannelSearchQuery::from_url(&url)?;
|
||||
info!("search query: {:?}", query);
|
||||
let res = dbconn::search::search_channel(query, node_config).await?;
|
||||
let body = Body::from(serde_json::to_string(&res)?);
|
||||
let ret = super::response(StatusCode::OK).body(body)?;
|
||||
Ok(ret)
|
||||
}
|
||||
_ => Ok(response(StatusCode::NOT_ACCEPTABLE).body(Body::empty())?),
|
||||
let vdef = header::HeaderValue::from_static(APP_JSON);
|
||||
let v = head.headers.get(header::ACCEPT).unwrap_or(&vdef);
|
||||
if v == APP_JSON || v == "*/*" {
|
||||
let s1 = format!("dummy:{}", head.uri);
|
||||
info!("try to parse {:?}", s1);
|
||||
let url = Url::parse(&s1)?;
|
||||
let query = ChannelSearchQuery::from_url(&url)?;
|
||||
info!("search query: {:?}", query);
|
||||
let res = dbconn::search::search_channel(query, node_config).await?;
|
||||
let body = Body::from(serde_json::to_string(&res)?);
|
||||
let ret = super::response(StatusCode::OK).body(body)?;
|
||||
Ok(ret)
|
||||
} else {
|
||||
Ok(response(StatusCode::NOT_ACCEPTABLE).body(Body::empty())?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta charset="utf-8" />
|
||||
<title>Databuffer API 4 Documentation</title>
|
||||
<meta name="keywords" content="PSI, DAQ, Databuffer">
|
||||
<meta name="author" content="Dominik Werder">
|
||||
<link rel="shortcut icon" href="about:blank"/>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
<link rel="shortcut icon" href="about:blank" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>Databuffer API 4 Documentation</h1>
|
||||
<h1>Databuffer API 4 Documentation</h1>
|
||||
|
||||
<p>Documented here is the databuffer http api 4. The "original" unversioned api is documented at
|
||||
<a href="https://git.psi.ch/sf_daq/ch.psi.daq.databuffer/blob/master/ch.psi.daq.queryrest/Readme.md">this location</a>.</p>
|
||||
<p>API version 1:
|
||||
<a href="https://data-api.psi.ch/api/1/documentation/">https://data-api.psi.ch/api/1/documentation/</a></p>
|
||||
<p>In order to keep the api surface as small as possible in comparison to api 0, we add functionality on demand,
|
||||
so please feel free to create some Jira ticket!</p>
|
||||
<p>Documented here is the databuffer http api 4. The "original" unversioned api is documented at
|
||||
<a href="https://git.psi.ch/sf_daq/ch.psi.daq.databuffer/blob/master/ch.psi.daq.queryrest/Readme.md">this
|
||||
location</a>.
|
||||
</p>
|
||||
<p>API version 1:
|
||||
<a href="https://data-api.psi.ch/api/1/documentation/">https://data-api.psi.ch/api/1/documentation/</a>
|
||||
</p>
|
||||
<p>In order to keep the api surface as small as possible in comparison to api 0, we add functionality on demand,
|
||||
so please feel free to create some Jira ticket!</p>
|
||||
|
||||
|
||||
<h2>Timestamp format</h2>
|
||||
<p>The result encodes timestamps in the form:</p>
|
||||
<pre>{
|
||||
<h2>Timestamp format</h2>
|
||||
<p>The result encodes timestamps in the form:</p>
|
||||
<pre>{
|
||||
"tsAnchor": 1623909860, // Time-anchor of this result in UNIX epoch seconds.
|
||||
"tsOffMs": [173, 472, 857, ...], // Millisecond-offset to tsAnchor for each event/bin-edge.
|
||||
"tsOffNs": [422901, 422902, 422903, ...], // Nanosecond-offset to tsAnchor in addition to tsOffMs for each event/bin-edge.
|
||||
}</pre>
|
||||
<p>which results in these nanosecond-timestamps:</p>
|
||||
<pre>1623909860573422901
|
||||
<p>which results in these nanosecond-timestamps:</p>
|
||||
<pre>1623909860573422901
|
||||
1623909875671422902
|
||||
1623909897932422903</pre>
|
||||
<p>Formally: tsAbsolute = tsAnchor * 10<sup>9</sup> + tsOffMs * 10<sup>6</sup> + tsOffNs</p>
|
||||
<p>Two reasons lead to this choice of timestamp format:</p>
|
||||
<ul>
|
||||
<li>Javascript can not represent the full nanosecond-resolution timestamps in a single numeric variable.</li>
|
||||
<li>The lowest 6 digits of the nanosecond timestamp are anyway abused by the timing system to emit a pulse-id.</li>
|
||||
</ul>
|
||||
<p>Formally: tsAbsolute = tsAnchor * 10<sup>9</sup> + tsOffMs * 10<sup>6</sup> + tsOffNs</p>
|
||||
<p>Two reasons lead to this choice of timestamp format:</p>
|
||||
<ul>
|
||||
<li>Javascript can not represent the full nanosecond-resolution timestamps in a single numeric variable.</li>
|
||||
<li>The lowest 6 digits of the nanosecond timestamp are anyway abused by the timing system to emit a pulse-id.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2>API functions</h2>
|
||||
<p>Currently available functionality:</p>
|
||||
<ul>
|
||||
<li><a href="#list-backends">List available backends</a></li>
|
||||
<li><a href="#search-channel">Search channel</a></li>
|
||||
<li><a href="#query-binned">Query binned data</a></li>
|
||||
<li><a href="#query-events">Query unbinned event data</a></li>
|
||||
</ul>
|
||||
<h2>API functions</h2>
|
||||
<p>Currently available functionality:</p>
|
||||
<ul>
|
||||
<li><a href="#list-backends">List available backends</a></li>
|
||||
<li><a href="#search-channel">Search channel</a></li>
|
||||
<li><a href="#query-binned">Query binned data</a></li>
|
||||
<li><a href="#query-events">Query unbinned event data</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
<a id="list-backends"></a>
|
||||
<h2>List available backends</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/backends</p>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
<a id="list-backends"></a>
|
||||
<h2>List available backends</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/backends</p>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/backends'
|
||||
</pre>
|
||||
<h4>Example response</h4>
|
||||
<pre>{
|
||||
<h4>Example response</h4>
|
||||
<pre>{
|
||||
"backends": [
|
||||
"sf-databuffer",
|
||||
"hipa-archive",
|
||||
"gls-archive",
|
||||
"proscan-archive"
|
||||
"proscan-archive",
|
||||
"sls-archive"
|
||||
]
|
||||
}</pre>
|
||||
|
||||
|
||||
|
||||
<a id="search-channel"></a>
|
||||
<h2>Search channel</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/search/channel</p>
|
||||
<p><strong>Query parameters:</strong> (all optional)</p>
|
||||
<ul>
|
||||
<li>nameRegex (e.g. "LSCP.*6")</li>
|
||||
<li>sourceRegex (e.g. "178:9999")</li>
|
||||
<li>descriptionRegex (e.g. "celsius")</li>
|
||||
</ul>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
<p>Full channel list is long, so it's encouraged to provide a search string of some minimal length.</p>
|
||||
<a id="search-channel"></a>
|
||||
<h2>Search channel</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/search/channel</p>
|
||||
<p><strong>Query parameters:</strong> (all optional)</p>
|
||||
<ul>
|
||||
<li>backend (e.g. "sf-databuffer", "sls-archive", ... any from list-backends API)</li>
|
||||
<li>nameRegex (e.g. "LSCP.*6")</li>
|
||||
<li>sourceRegex (e.g. "178:9999")</li>
|
||||
<li>descriptionRegex (e.g. "celsius")</li>
|
||||
</ul>
|
||||
<p><strong>Request header:</strong> "Accept" should be "application/json" for forward-compatibility but can be
|
||||
omitted for e.g. a quick manual search using CURL.</p>
|
||||
<p>Full channel list is long, so it's encouraged to provide a search string of some minimal length.</p>
|
||||
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/search/channel?sourceRegex=CV.E.+37&nameRegex=120.+y2$'
|
||||
</pre>
|
||||
|
||||
<h4>Example response:</h4>
|
||||
<p><strong>Keys always present: </strong>name, backend.</p>
|
||||
<p>Other keys are optional, it depends on the data found on disk: sometimes there is an empty string on disk, sometimes
|
||||
that key is missing.</p>
|
||||
<pre>{
|
||||
<h4>Example response:</h4>
|
||||
<p><strong>Keys always present: </strong>name, backend.</p>
|
||||
<p>Other keys are optional, it depends on the data found on disk: sometimes there is an empty string on disk,
|
||||
sometimes
|
||||
that key is missing.</p>
|
||||
<pre>{
|
||||
"channels": [
|
||||
{
|
||||
"name": "S10MA01-DBPM120:Y2",
|
||||
@@ -125,36 +135,37 @@ that key is missing.</p>
|
||||
}
|
||||
]
|
||||
}</pre>
|
||||
<p>The search constraints are AND'd.</p>
|
||||
<p>The search constraints are AND'd.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
<a id="query-events"></a>
|
||||
<h2>Query event data</h2>
|
||||
<p>Returns the full event values in a given time range.</p>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/events</p>
|
||||
<p><strong>Query parameters:</strong></p>
|
||||
<ul>
|
||||
<li>channelBackend (e.g. "sf-databuffer")</li>
|
||||
<li>channelName (e.g. "S10CB02-RBOC-DCP10:FOR-AMPLT-AVG")</li>
|
||||
<li>begDate (e.g. "2021-05-26T07:10:00.000Z")</li>
|
||||
<li>endDate (e.g. "2021-05-26T07:16:00.000Z")</li>
|
||||
</ul>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
<a id="query-events"></a>
|
||||
<h2>Query event data</h2>
|
||||
<p>Returns the full event values in a given time range.</p>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/events</p>
|
||||
<p><strong>Query parameters:</strong></p>
|
||||
<ul>
|
||||
<li>channelBackend (e.g. "sf-databuffer")</li>
|
||||
<li>channelName (e.g. "S10CB02-RBOC-DCP10:FOR-AMPLT-AVG")</li>
|
||||
<li>begDate (e.g. "2021-05-26T07:10:00.000Z")</li>
|
||||
<li>endDate (e.g. "2021-05-26T07:16:00.000Z")</li>
|
||||
</ul>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
|
||||
<h4>Timeout</h4>
|
||||
<p>If the requested range takes too long to retrieve, then the flags <strong>timedOut: true</strong> will be set.</p>
|
||||
<h4>Timeout</h4>
|
||||
<p>If the requested range takes too long to retrieve, then the flags <strong>timedOut: true</strong> will be set.
|
||||
</p>
|
||||
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/events?channelBackend=sf-databuffer
|
||||
&channelName=S10CB02-RBOC-DCP10:FOR-AMPLT-AVG&begDate=2021-05-26T07:10:00.000Z&endDate=2021-05-26T07:16:00.000Z'
|
||||
</pre>
|
||||
|
||||
<p>Example response:</p>
|
||||
<pre>
|
||||
<p>Example response:</p>
|
||||
<pre>
|
||||
{
|
||||
"finalisedRange": true,
|
||||
"tsAnchor": 1623763172,
|
||||
@@ -179,51 +190,56 @@ curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/events?channel
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h4>Finalised range</h4>
|
||||
<p>If the server can determine that no more data will be added to the requested time range
|
||||
then it will add the flag <strong>finalisedRange: true</strong> to the response.</p>
|
||||
<h4>Finalised range</h4>
|
||||
<p>If the server can determine that no more data will be added to the requested time range
|
||||
then it will add the flag <strong>finalisedRange: true</strong> to the response.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
<a id="query-binned"></a>
|
||||
<h2>Query binned data</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/binned</p>
|
||||
<p><strong>Query parameters:</strong></p>
|
||||
<ul>
|
||||
<li>channelBackend (e.g. "sf-databuffer")</li>
|
||||
<li>channelName (e.g. "SLAAR-LSCP4-LAS6891:CH7:1")</li>
|
||||
<li>begDate (e.g. "2021-05-26T07:10:00.000Z")</li>
|
||||
<li>endDate (e.g. "2021-05-26T07:16:00.000Z")</li>
|
||||
<li>binCount (number of requested bins in time-dimension, e.g. "6". The actual number of returned bins depends on bin-cache-grid-resolution. The server tries to find the best match.)</li>
|
||||
<li>binningScheme (optional)</li>
|
||||
<a id="query-binned"></a>
|
||||
<h2>Query binned data</h2>
|
||||
<p><strong>Method:</strong> GET</p>
|
||||
<p><strong>URL:</strong> https://data-api.psi.ch/api/4/binned</p>
|
||||
<p><strong>Query parameters:</strong></p>
|
||||
<ul>
|
||||
<li>if not specified: default is "binningScheme=unweightedScalar".</li>
|
||||
<li>"binningScheme=unweightedScalar": non-weighted binning, waveform gets first averaged to a scalar.</li>
|
||||
<li>"binningScheme=timeWeightedScalar": time-weighted binning, waveform gets first averaged to a scalar.</li>
|
||||
<li>"binningScheme=binnedX&binnedXcount=13": waveform gets first binned to 13 bins in X-dimension (waveform-dimension).</li>
|
||||
<li>"binningScheme=binnedX&binnedXcount=0": waveform is not binned in X-dimension but kept at full length.</li>
|
||||
<li>channelBackend (e.g. "sf-databuffer")</li>
|
||||
<li>channelName (e.g. "SLAAR-LSCP4-LAS6891:CH7:1")</li>
|
||||
<li>begDate (e.g. "2021-05-26T07:10:00.000Z")</li>
|
||||
<li>endDate (e.g. "2021-05-26T07:16:00.000Z")</li>
|
||||
<li>binCount (number of requested bins in time-dimension, e.g. "6". The actual number of returned bins depends
|
||||
on
|
||||
bin-cache-grid-resolution. The server tries to find the best match.)</li>
|
||||
<li>binningScheme (optional)</li>
|
||||
<ul>
|
||||
<li>if not specified: default is "binningScheme=unweightedScalar".</li>
|
||||
<li>"binningScheme=unweightedScalar": non-weighted binning, waveform gets first averaged to a scalar.</li>
|
||||
<li>"binningScheme=timeWeightedScalar": time-weighted binning, waveform gets first averaged to a scalar.
|
||||
</li>
|
||||
<li>"binningScheme=binnedX&binnedXcount=13": waveform gets first binned to 13 bins in X-dimension
|
||||
(waveform-dimension).</li>
|
||||
<li>"binningScheme=binnedX&binnedXcount=0": waveform is not binned in X-dimension but kept at full length.
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
<p><strong>Request header:</strong> "Accept" must be "application/json"</p>
|
||||
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
<h4>CURL example:</h4>
|
||||
<pre>
|
||||
curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/binned?channelBackend=sf-databuffer
|
||||
&channelName=SLAAR-LSCP4-LAS6891:CH7:1&begDate=2021-05-25T00:00:00.000Z&endDate=2021-05-26T00:00:00.000Z&binCount=3'
|
||||
</pre>
|
||||
|
||||
<h4>Partial result</h4>
|
||||
<p>If the requested range takes longer time to retrieve, then a partial result with at least one bin is returned.
|
||||
The partial result will contain the necessary information to send another request with a range that
|
||||
starts with the first not-yet-retrieved bin.
|
||||
This information is provided by the <strong>continueAt</strong> and <strong>missingBins</strong> fields.
|
||||
This enables the user agent to start the presentation to the user while updating the user interface
|
||||
as new bins are received.</p>
|
||||
<h4>Partial result</h4>
|
||||
<p>If the requested range takes longer time to retrieve, then a partial result with at least one bin is returned.
|
||||
The partial result will contain the necessary information to send another request with a range that
|
||||
starts with the first not-yet-retrieved bin.
|
||||
This information is provided by the <strong>continueAt</strong> and <strong>missingBins</strong> fields.
|
||||
This enables the user agent to start the presentation to the user while updating the user interface
|
||||
as new bins are received.</p>
|
||||
|
||||
<h4>Example response (without usage of binningScheme):</h4>
|
||||
<pre>{
|
||||
<h4>Example response (without usage of binningScheme):</h4>
|
||||
<pre>{
|
||||
"avgs": [
|
||||
16204.087890625,
|
||||
16204.3798828125,
|
||||
@@ -285,8 +301,8 @@ curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/binned?channel
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h4>Example response (waveform channel and usage of binningScheme):</h4>
|
||||
<pre>{
|
||||
<h4>Example response (waveform channel and usage of binningScheme):</h4>
|
||||
<pre>{
|
||||
"tsAnchor": 1623769950,
|
||||
"tsMs": [
|
||||
0,
|
||||
@@ -380,20 +396,21 @@ curl -H 'Accept: application/json' 'https://data-api.psi.ch/api/4/binned?channel
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h4>Complete result</h4>
|
||||
<p>If the result does not contain a <strong>continueAt</strong> key then the result is complete.</p>
|
||||
<h4>Complete result</h4>
|
||||
<p>If the result does not contain a <strong>continueAt</strong> key then the result is complete.</p>
|
||||
|
||||
<h4>Finalised range</h4>
|
||||
<p>If the server can determine that no more data will be added to the requested time range
|
||||
then it will add the flag <strong>finalisedRange: true</strong> to the response.</p>
|
||||
<h4>Finalised range</h4>
|
||||
<p>If the server can determine that no more data will be added to the requested time range
|
||||
then it will add the flag <strong>finalisedRange: true</strong> to the response.</p>
|
||||
|
||||
|
||||
|
||||
<h2>Feedback and comments very much appreciated!</h2>
|
||||
<p>dominik.werder@psi.ch</p>
|
||||
<p>or please assign me a JIRA ticket.</p>
|
||||
<h2>Feedback and comments very much appreciated!</h2>
|
||||
<p>dominik.werder@psi.ch</p>
|
||||
<p>or please assign me a JIRA ticket.</p>
|
||||
|
||||
<div id="footer"></div>
|
||||
<div id="footer"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user