Run first time with scalar, refactor value extraction

This commit is contained in:
Dominik Werder
2021-05-12 17:45:12 +02:00
parent 3df25e7a75
commit 34217967e9
14 changed files with 363 additions and 128 deletions

View File

@@ -3,6 +3,6 @@ members = ["retrieval"]
[profile.release]
debug = 1
#opt-level = 1
opt-level = 1
#overflow-checks = true
#debug-assertions = true

View File

@@ -49,3 +49,40 @@ pub async fn database_size(node_config: &NodeConfigCached) -> Result<u64, Error>
let size = size as u64;
Ok(size)
}
pub struct TableSizes {
pub sizes: Vec<(String, String)>,
}
pub async fn table_sizes(node_config: &NodeConfigCached) -> Result<TableSizes, Error> {
let sql = format!(
"{} {} {} {} {} {} {}",
"SELECT nspname || '.' || relname AS relation, pg_size_pretty(pg_total_relation_size(C.oid)) AS total_size",
"FROM pg_class C",
"LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)",
"WHERE nspname NOT IN ('pg_catalog', 'information_schema')",
"AND C.relkind <> 'i'",
"AND nspname !~ '^pg_toast'",
"ORDER BY pg_total_relation_size(C.oid) DESC LIMIT 20",
);
let sql = sql.as_str();
let cl = create_connection(node_config).await?;
let rows = cl.query(sql, &[]).await?;
let mut sizes = TableSizes { sizes: vec![] };
for row in rows {
sizes.sizes.push((row.get(0), row.get(1)));
}
sizes.sizes.push((format!("dummy0"), format!("A")));
sizes.sizes.push((format!("dummy1"), format!("B")));
Ok(sizes)
}
pub async fn random_channel(node_config: &NodeConfigCached) -> Result<String, Error> {
let sql = "select name from channels order by rowid limit 1 offset (random() * (select count(rowid) from channels))::bigint";
let cl = create_connection(node_config).await?;
let rows = cl.query(sql, &[]).await?;
if rows.len() == 0 {
Err(Error::with_msg("can not get random channel"))?;
}
Ok(rows[0].get(0))
}

View File

@@ -6,6 +6,7 @@ use super::eventchunker::EventFull;
use crate::agg::binnedt::AggregatableTdim;
use crate::agg::eventbatch::{MinMaxAvgScalarEventBatch, MinMaxAvgScalarEventBatchStreamItem};
use crate::eventchunker::EventChunkerItem;
use bytes::BytesMut;
use err::Error;
use futures_core::Stream;
use futures_util::StreamExt;
@@ -307,6 +308,61 @@ pub struct Dim1F32Stream<S> {
completed: bool,
}
trait NumEx {
const BY: usize;
}
struct NumF32;
impl NumEx for NumF32 {
const BY: usize = 4;
}
macro_rules! make_get_values {
($n:ident, $TY:ident, $FROM_BYTES:ident, $BY:expr) => {
fn $n(decomp: &BytesMut, ty: &ScalarType) -> Result<Vec<f32>, Error> {
let n1 = decomp.len();
if ty.bytes() as usize != $BY {
Err(Error::with_msg(format!(
"ty.bytes() != BY {} vs {}",
ty.bytes(),
$BY
)))?;
}
if n1 % ty.bytes() as usize != 0 {
Err(Error::with_msg(format!(
"n1 % ty.bytes() as usize != 0 {} vs {}",
n1,
ty.bytes()
)))?;
}
let ele_count = n1 / ty.bytes() as usize;
let mut j = Vec::with_capacity(ele_count);
let mut p2 = j.as_mut_ptr();
let mut p1 = 0;
for _ in 0..ele_count {
unsafe {
let mut r = [0u8; $BY];
std::ptr::copy_nonoverlapping(&decomp[p1], r.as_mut_ptr(), $BY);
*p2 = $TY::$FROM_BYTES(r) as f32;
p1 += $BY;
p2 = p2.add(1);
};
}
unsafe {
j.set_len(ele_count);
}
Ok(j)
}
};
}
make_get_values!(get_values_u16_le, u16, from_le_bytes, 2);
make_get_values!(get_values_f32_le, f32, from_le_bytes, 4);
make_get_values!(get_values_f64_le, f64, from_le_bytes, 8);
make_get_values!(get_values_u16_be, u16, from_be_bytes, 2);
make_get_values!(get_values_f32_be, f32, from_be_bytes, 4);
make_get_values!(get_values_f64_be, f64, from_be_bytes, 8);
impl<S> Dim1F32Stream<S> {
pub fn new(inp: S) -> Self {
Self {
@@ -322,51 +378,38 @@ impl<S> Dim1F32Stream<S> {
for i1 in 0..k.tss.len() {
// TODO iterate sibling arrays after single bounds check
let ty = &k.scalar_types[i1];
let be = k.be[i1];
let decomp = k.decomps[i1].as_ref().unwrap();
match ty {
U16 => {
const BY: usize = 2;
// do the conversion
let n1 = decomp.len();
assert!(n1 % ty.bytes() as usize == 0);
let ele_count = n1 / ty.bytes() as usize;
let mut j = Vec::with_capacity(ele_count);
let mut p1 = 0;
for _ in 0..ele_count {
let u = unsafe {
let mut r = [0u8; BY];
std::ptr::copy_nonoverlapping(&decomp[p1], r.as_mut_ptr(), BY);
u16::from_be_bytes(r)
};
j.push(u as f32);
p1 += BY;
}
U16 if be => {
let value = get_values_u16_be(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(j);
ret.values.push(value);
}
U16 => {
let value = get_values_u16_le(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(value);
}
F32 if be => {
let value = get_values_f32_be(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(value);
}
F32 => {
let value = get_values_f32_le(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(value);
}
F64 if be => {
let value = get_values_f64_be(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(value);
}
F64 => {
const BY: usize = 8;
// do the conversion
let n1 = decomp.len();
assert!(n1 % ty.bytes() as usize == 0);
let ele_count = n1 / ty.bytes() as usize;
let mut j = Vec::with_capacity(ele_count);
unsafe {
j.set_len(ele_count);
}
let mut p1 = 0;
for i1 in 0..ele_count {
let u = unsafe {
let mut r = [0u8; BY];
std::ptr::copy_nonoverlapping(&decomp[p1], r.as_mut_ptr(), BY);
f64::from_be_bytes(r)
//f64::from_be_bytes(std::mem::transmute::<_, [u8; 8]>(&decomp[p1]))
};
j[i1] = u as f32;
p1 += BY;
}
let value = get_values_f64_le(decomp, ty)?;
ret.tss.push(k.tss[i1]);
ret.values.push(j);
ret.values.push(value);
}
_ => {
let e = Error::with_msg(format!("Dim1F32Stream unhandled scalar type: {:?}", ty));

View File

@@ -48,7 +48,7 @@ impl BinnedStreamFromPreBinnedPatches {
match PreBinnedValueFetchedStream::new(&query, &node_config) {
Ok(k) => Box::pin(k),
Err(e) => {
error!("see error {:?}", e);
error!("error from PreBinnedValueFetchedStream::new {:?}", e);
Box::pin(futures_util::stream::iter(vec![Err(e)]))
}
};
@@ -73,7 +73,6 @@ impl BinnedStreamFromPreBinnedPatches {
}
Ok(PreBinnedItem::RangeComplete) => Some(Ok(MinMaxAvgScalarBinBatchStreamItem::RangeComplete)),
Ok(PreBinnedItem::EventDataReadStats(stats)) => {
//info!("BinnedStream ''''''''''''''''''' observes stats {:?}", stats);
Some(Ok(MinMaxAvgScalarBinBatchStreamItem::EventDataReadStats(stats)))
}
Ok(PreBinnedItem::Log(item)) => Some(Ok(MinMaxAvgScalarBinBatchStreamItem::Log(item))),

View File

@@ -254,7 +254,7 @@ impl Stream for PreBinnedValueStream {
match self.query.cache_usage {
super::CacheUsage::Use | super::CacheUsage::Recreate => {
let msg = format!(
"Write cache file query: {:?} bin count: {}",
"write cache file query: {:?} bin count: {}",
self.query.patch,
self.values.ts1s.len()
);

View File

@@ -5,7 +5,6 @@ use futures_util::StreamExt;
use netpod::log::*;
use netpod::timeunits::MS;
use netpod::{ChannelConfig, NanoRange, Nanos, Node};
use std::mem::size_of;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncReadExt, AsyncSeekExt, ErrorKind, SeekFrom};
@@ -38,7 +37,6 @@ async fn open_files_inner(
node: Node,
) -> Result<(), Error> {
let channel_config = channel_config.clone();
// TODO reduce usage of `query` and see what we actually need.
let mut timebins = vec![];
{
let rd = tokio::fs::read_dir(paths::channel_timebins_dir_path(&channel_config, &node)?).await?;
@@ -74,7 +72,6 @@ async fn open_files_inner(
debug!("opening path {:?}", &path);
let mut file = OpenOptions::new().read(true).open(&path).await?;
debug!("opened file {:?} {:?}", &path, &file);
{
let index_path = paths::index_path(ts_bin, &channel_config, &node)?;
match OpenOptions::new().read(true).open(&index_path).await {
@@ -105,7 +102,7 @@ async fn open_files_inner(
buf.resize(buf.capacity(), 0);
debug!("read exact index file {} {}", buf.len(), buf.len() % 16);
index_file.read_exact(&mut buf).await?;
match find_ge(range.beg, &buf[2..])? {
match super::index::find_ge(range.beg, &buf[2..])? {
Some(o) => {
debug!("FOUND ts IN INDEX: {:?}", o);
file.seek(SeekFrom::Start(o.1)).await?;
@@ -118,65 +115,15 @@ async fn open_files_inner(
}
Err(e) => match e.kind() {
ErrorKind::NotFound => {
// TODO Read first 1k, assume that channel header fits.
// TODO Seek via binary search. Can not read whole file into memory!
error!("TODO Seek directly in scalar file");
todo!("Seek directly in scalar file");
file = super::index::position_file(file, range.beg).await?;
}
_ => Err(e)?,
},
}
}
// TODO Since I want to seek into the data file, the consumer of this channel must not expect a file channel name header.
chtx.send(Ok(file)).await?;
}
// TODO keep track of number of running
debug!("open_files_inner done");
Ok(())
}
fn find_ge(h: u64, buf: &[u8]) -> Result<Option<(u64, u64)>, Error> {
trace!("find_ge {}", h);
const N: usize = 2 * size_of::<u64>();
let n1 = buf.len();
if n1 % N != 0 {
return Err(Error::with_msg(format!("find_ge bad len {}", n1)));
}
if n1 == 0 {
warn!("Empty index data");
return Ok(None);
}
let n1 = n1 / N;
let a = unsafe {
let ptr = &buf[0] as *const u8 as *const ([u8; 8], [u8; 8]);
std::slice::from_raw_parts(ptr, n1)
};
let mut j = 0;
let mut k = n1 - 1;
let x = u64::from_be_bytes(a[j].0);
let y = u64::from_be_bytes(a[k].0);
trace!("first/last ts: {} {}", x, y);
if x >= h {
return Ok(Some((u64::from_be_bytes(a[j].0), u64::from_be_bytes(a[j].1))));
}
if y < h {
return Ok(None);
}
loop {
if k - j < 2 {
let ret = (u64::from_be_bytes(a[k].0), u64::from_be_bytes(a[k].1));
trace!("FOUND {:?}", ret);
return Ok(Some(ret));
}
let m = (k + j) / 2;
let x = u64::from_be_bytes(a[m].0);
trace!("CHECK NEW M: {}", x);
if x < h {
j = m;
} else {
k = m;
}
}
}

View File

@@ -160,7 +160,7 @@ impl EventChunker {
use super::dtflags::*;
let is_compressed = type_flags & COMPRESSION != 0;
let is_array = type_flags & ARRAY != 0;
let _is_big_endian = type_flags & BIG_ENDIAN != 0;
let is_big_endian = type_flags & BIG_ENDIAN != 0;
let is_shaped = type_flags & SHAPE != 0;
if let Shape::Wave(_) = self.channel_config.shape {
assert!(is_array);
@@ -228,6 +228,7 @@ impl EventChunker {
pulse,
Some(decomp),
ScalarType::from_dtype_index(type_index)?,
is_big_endian,
);
}
}
@@ -236,9 +237,22 @@ impl EventChunker {
}
};
} else {
Err(Error::with_msg(format!(
"TODO uncompressed event parsing not yet implemented"
)))?;
let p1 = sl.position();
//info!("len: {} p1: {}", len, p1);
if len < p1 as u32 + 4 {
let msg = format!("uncomp len: {} p1: {}", len, p1);
Err(Error::with_msg(msg))?;
}
let vlen = len - p1 as u32 - 4;
//info!("vlen: {}", vlen);
let decomp = BytesMut::from(&buf[p1 as usize..(p1 as u32 + vlen) as usize]);
ret.add_event(
ts,
pulse,
Some(decomp),
ScalarType::from_dtype_index(type_index)?,
is_big_endian,
);
}
buf.advance(len as usize);
parsed_bytes += len as u64;
@@ -259,6 +273,7 @@ pub struct EventFull {
pub pulses: Vec<u64>,
pub decomps: Vec<Option<BytesMut>>,
pub scalar_types: Vec<ScalarType>,
pub be: Vec<bool>,
}
impl EventFull {
@@ -268,14 +283,16 @@ impl EventFull {
pulses: vec![],
decomps: vec![],
scalar_types: vec![],
be: vec![],
}
}
fn add_event(&mut self, ts: u64, pulse: u64, decomp: Option<BytesMut>, scalar_type: ScalarType) {
fn add_event(&mut self, ts: u64, pulse: u64, decomp: Option<BytesMut>, scalar_type: ScalarType, be: bool) {
self.tss.push(ts);
self.pulses.push(pulse);
self.decomps.push(decomp);
self.scalar_types.push(scalar_type);
self.be.push(be);
}
}

View File

@@ -109,15 +109,16 @@ where
let tyid = u32::from_le_bytes(*arrayref::array_ref![buf, 8, 4]);
let len = u32::from_le_bytes(*arrayref::array_ref![buf, 12, 4]);
if magic != INMEM_FRAME_MAGIC {
error!("InMemoryFrameAsyncReadStream tryparse incorrect magic: {}", magic);
return (
Some(Some(Err(Error::with_msg(format!(
"InMemoryFrameAsyncReadStream tryparse incorrect magic: {}",
magic
))))),
buf,
wp,
let z = nb.min(32);
let u = String::from_utf8_lossy(&buf[0..z]);
let e = Error::with_msg("INCORRECT MAGIC");
error!("incorrect magic buf as utf8: {:?} error: {:?}", u, e);
let msg = format!(
"InMemoryFrameAsyncReadStream tryparse incorrect magic: {} buf as utf8: {:?}",
magic, u
);
error!("{}", msg);
return (Some(Some(Err(Error::with_msg(format!("{}", msg))))), buf, wp);
}
if len == 0 {
if nb != INMEM_FRAME_HEAD + INMEM_FRAME_FOOT {

View File

@@ -1,8 +1,10 @@
use arrayref::array_ref;
use err::Error;
use netpod::log::*;
use netpod::{ChannelConfig, Nanos, Node};
use tokio::fs::OpenOptions;
use tokio::io::ErrorKind;
use std::mem::size_of;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncReadExt, AsyncSeekExt, ErrorKind, SeekFrom};
pub async fn find_start_pos_for_config(
ts: Nanos,
@@ -24,3 +26,169 @@ pub async fn find_start_pos_for_config(
};
Ok(ret)
}
pub fn find_ge(h: u64, buf: &[u8]) -> Result<Option<(u64, u64)>, Error> {
const N: usize = 2 * size_of::<u64>();
let n1 = buf.len();
if n1 % N != 0 {
return Err(Error::with_msg(format!("find_ge bad len {}", n1)));
}
if n1 == 0 {
warn!("Empty index data");
return Ok(None);
}
let n1 = n1 / N;
let a = unsafe {
let ptr = &buf[0] as *const u8 as *const ([u8; 8], [u8; 8]);
std::slice::from_raw_parts(ptr, n1)
};
let mut j = 0;
let mut k = n1 - 1;
let x = u64::from_be_bytes(a[j].0);
let y = u64::from_be_bytes(a[k].0);
if x >= h {
return Ok(Some((u64::from_be_bytes(a[j].0), u64::from_be_bytes(a[j].1))));
}
if y < h {
return Ok(None);
}
loop {
if k - j < 2 {
let ret = (u64::from_be_bytes(a[k].0), u64::from_be_bytes(a[k].1));
return Ok(Some(ret));
}
let m = (k + j) / 2;
let x = u64::from_be_bytes(a[m].0);
if x < h {
j = m;
} else {
k = m;
}
}
}
async fn read(buf: &mut [u8], file: &mut File) -> Result<usize, Error> {
let mut wp = 0;
loop {
let n1 = file.read(&mut buf[wp..]).await?;
if n1 == 0 {
break;
} else {
wp += n1;
}
if wp >= buf.len() {
break;
}
}
Ok(wp)
}
pub fn parse_channel_header(buf: &[u8]) -> Result<(u32,), Error> {
if buf.len() < 6 {
return Err(Error::with_msg(format!("parse_channel_header buf len: {}", buf.len())));
}
let ver = i16::from_be_bytes(*array_ref![buf, 0, 2]);
if ver != 0 {
return Err(Error::with_msg(format!("unknown file version: {}", ver)));
}
let len1 = u32::from_be_bytes(*array_ref![buf, 2, 4]);
if len1 < 9 || len1 > 256 {
return Err(Error::with_msg(format!("unexpected data file header len1: {}", len1)));
}
if buf.len() < 2 + len1 as usize {
return Err(Error::with_msg(format!(
"data file header not contained in buffer len1: {} vs {}",
len1,
buf.len()
)));
}
let len2 = u32::from_be_bytes(*array_ref![buf, 2 + len1 as usize - 4, 4]);
if len1 != len2 {
return Err(Error::with_msg(format!("len mismatch len1: {} len2: {}", len1, len2)));
}
Ok((len1 as u32,))
}
pub fn parse_event(buf: &[u8]) -> Result<(u32, Nanos), Error> {
if buf.len() < 4 {
return Err(Error::with_msg(format!("parse_event buf len: {}", buf.len())));
}
let len1 = u32::from_be_bytes(*array_ref![buf, 0, 4]);
if len1 < 9 || len1 > 512 {
return Err(Error::with_msg(format!("unexpected event len1: {}", len1)));
}
if buf.len() < len1 as usize {
return Err(Error::with_msg(format!(
"event not contained in buffer len1: {} vs {}",
len1,
buf.len()
)));
}
let len2 = u32::from_be_bytes(*array_ref![buf, len1 as usize - 4, 4]);
if len1 != len2 {
return Err(Error::with_msg(format!("len mismatch len1: {} len2: {}", len1, len2)));
}
let ts = u64::from_be_bytes(*array_ref![buf, 12, 8]);
Ok((len1 as u32, Nanos { ns: ts }))
}
pub async fn read_event_at(pos: u64, file: &mut File) -> Result<(u32, Nanos), Error> {
file.seek(SeekFrom::Start(pos)).await?;
let mut buf = vec![0; 1024];
let _n1 = read(&mut buf, file).await?;
let ev = parse_event(&buf)?;
Ok(ev)
}
pub async fn position_file(mut file: File, beg: u64) -> Result<File, Error> {
// Read first chunk which should include channel name packet, and a first event.
// It can be that file is empty.
// It can be that there is a a channel header but zero events.
let flen = file.seek(SeekFrom::End(0)).await?;
file.seek(SeekFrom::Start(0)).await?;
let mut buf = vec![0; 1024];
let n1 = read(&mut buf, &mut file).await?;
if n1 < buf.len() {
// file has less content than our buffer
} else {
//
}
let hres = parse_channel_header(&buf)?;
info!("hres: {:?}", hres);
let headoff = 2 + hres.0 as u64;
let ev = parse_event(&buf[headoff as usize..])?;
info!("ev: {:?}", ev);
let evlen = ev.0 as u64;
info!("flen: {} flen - headoff mod evlen: {}", flen, (flen - headoff) % evlen);
let mut j = headoff;
let mut k = ((flen - headoff) / evlen - 1) * evlen + headoff;
info!("j {} k {}", j, k);
let x = ev.1.ns;
let y = read_event_at(k, &mut file).await?.1.ns;
info!("x {} y {}", x, y);
if x >= beg {
info!("found A");
file.seek(SeekFrom::Start(j)).await?;
return Ok(file);
}
if y < beg {
info!("found B");
file.seek(SeekFrom::Start(j)).await?;
return Ok(file);
}
loop {
if k - j < 2 * evlen {
info!("found C");
file.seek(SeekFrom::Start(k)).await?;
return Ok(file);
}
let m = j + (k - j) / 2 / evlen * evlen;
let x = read_event_at(m, &mut file).await?.1.ns;
info!("event at m: {} ts: {}", m, x);
if x < beg {
j = m;
} else {
k = m;
}
}
}

View File

@@ -6,6 +6,7 @@ use err::Error;
use futures_core::Stream;
use futures_util::future::FusedFuture;
use futures_util::{pin_mut, select, FutureExt, StreamExt};
use netpod::log::*;
use netpod::{ChannelConfig, NanoRange, Node, Shape};
use std::future::Future;
use std::path::PathBuf;
@@ -14,8 +15,6 @@ use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use tokio::fs::{File, OpenOptions};
use tokio::io::AsyncRead;
#[allow(unused_imports)]
use tracing::{debug, error, info, span, trace, warn, Level};
pub mod agg;
#[cfg(test)]

View File

@@ -173,18 +173,7 @@ async fn raw_conn_handler_inner_try(
event_chunker_conf,
)
.into_dim_1_f32_stream()
.into_binned_x_bins_1()
.map(|k| {
if false {
match &k {
Ok(MinMaxAvgScalarEventBatchStreamItem::EventDataReadStats(stats)) => {
info!("raw::conn ✑ ✑ ✑ ✑ ✑ ✑ seeing stats: {:?}", stats);
}
_ => {}
}
}
k
});
.into_binned_x_bins_1();
let mut e = 0;
while let Some(item) = s1.next().await {
match &item {

View File

@@ -48,7 +48,10 @@ fn fmt_backtrace(trace: &backtrace::Backtrace) -> String {
for sy in fr.symbols() {
let is_ours = match sy.filename() {
None => false,
Some(s) => s.to_str().unwrap().contains("dev/daqbuffer"),
Some(s) => {
let s = s.to_str().unwrap();
s.contains("dev/daqbuffer/") || s.contains("/retrsbld/")
}
};
let name = match sy.name() {
Some(k) => k.to_string(),

View File

@@ -92,6 +92,18 @@ async fn data_api_proxy_try(req: Request<Body>, node_config: &NodeConfigCached)
} else {
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(Body::empty())?)
}
} else if path == "/api/1/table_sizes" {
if req.method() == Method::GET {
Ok(table_sizes(req, &node_config).await?)
} else {
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(Body::empty())?)
}
} else if path == "/api/1/random_channel" {
if req.method() == Method::GET {
Ok(random_channel(req, &node_config).await?)
} else {
Ok(response(StatusCode::METHOD_NOT_ALLOWED).body(Body::empty())?)
}
} else if path == "/api/1/parsed_raw" {
if req.method() == Method::POST {
Ok(parsed_raw(req, &node_config.node).await?)
@@ -278,3 +290,22 @@ async fn node_status(req: Request<Body>, node_config: &NodeConfigCached) -> Resu
let ret = response(StatusCode::OK).body(Body::from(ret))?;
Ok(ret)
}
async fn table_sizes(req: Request<Body>, node_config: &NodeConfigCached) -> Result<Response<Body>, Error> {
let (_head, _body) = req.into_parts();
let sizes = dbconn::table_sizes(node_config).await?;
let mut ret = String::new();
for size in sizes.sizes {
use std::fmt::Write;
write!(ret, "{:60} {:20}\n", size.0, size.1)?;
}
let ret = response(StatusCode::OK).body(Body::from(ret))?;
Ok(ret)
}
pub async fn random_channel(req: Request<Body>, node_config: &NodeConfigCached) -> Result<Response<Body>, Error> {
let (_head, _body) = req.into_parts();
let ret = dbconn::random_channel(node_config).await?;
let ret = response(StatusCode::OK).body(Body::from(ret))?;
Ok(ret)
}

View File

@@ -62,6 +62,7 @@ pub async fn get_binned(
let req = hyper::Request::builder()
.method(http::Method::GET)
.uri(uri)
.header("aCCepT", "application/octet-stream")
.body(Body::empty())?;
let client = hyper::Client::new();
let res = client.request(req).await?;