Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (optional at compile-time) G3Frame JSON output #69

Draft
wants to merge 65 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
7d363c1
Add (optional at compile-time) G3Frame JSON output
cozzyd Dec 17, 2021
1d88104
blindly try to fix the cmake action
cozzyd Dec 17, 2021
740ac3c
move ENABLE_JSON_OUTPUT to core/CmakeLists.txt
cozzyd Dec 18, 2021
97b9413
Add (optional at compile-time) G3Frame JSON output
cozzyd Dec 17, 2021
1fa4c82
move ENABLE_JSON_OUTPUT to core/CmakeLists.txt
cozzyd Dec 18, 2021
f85006e
First attempt at abstracting GIL locking / C++ python initialization
cozzyd Jun 3, 2022
be76b17
formatting
cozzyd Jun 3, 2022
8be8d2c
make cppexample work with python things
cozzyd Jun 3, 2022
42e7064
use python3 instead of python in shebang
cozzyd Jun 3, 2022
7724616
CmakeLists for cppexample
cozzyd Jun 3, 2022
50b4273
initial commit of spt3g-json-serve
cozzyd Jun 3, 2022
4609008
failed attempts at python thread safety
cozzyd Jun 3, 2022
3abda84
merge
cozzyd Jun 3, 2022
0de19ce
Merge branch 'master' into json_output
arahlin Jun 3, 2022
fa0a597
fix name collision with CPython symbol
cozzyd Feb 27, 2024
0cb57fa
G3PythonContext class for handling the Python GIL
arahlin Feb 28, 2024
8e949a4
more words
arahlin Feb 28, 2024
1f56430
terrible merge
cozzyd Feb 28, 2024
8fb0d62
update to newest httplib
cozzyd Feb 28, 2024
0eea5ee
Hold GIL while clearing config in G3ModuleInfo
cozzyd Feb 28, 2024
2b09b89
split interpreter initialization and GIL handling into separate classes
arahlin Feb 29, 2024
17e90d5
Hold GIL while clearing config in G3ModuleInfo
cozzyd Feb 28, 2024
d5166c0
Avoid calling into the Python interpeter in the G3ModuleConfig backend
arahlin Feb 29, 2024
2c4f205
remove spurious python context
arahlin Feb 29, 2024
f4654ff
don't bother with globals
arahlin Feb 29, 2024
f89aec8
pass tests. most pipelines are too complex to exec(repr()) anyway
arahlin Feb 29, 2024
d11b4e4
merge
cozzyd Feb 29, 2024
eefb20d
mark non-G3FrameObject arguments as repr strings and avoid python con…
arahlin Feb 29, 2024
c343a26
switch to G3PythonInterpreter
cozzyd Feb 29, 2024
80080cf
Merge branch 'gil_context' into json_output
cozzyd Feb 29, 2024
8ae8b39
cleanup
arahlin Feb 29, 2024
d7b9535
remove now-pointless to_g3frameobject function
arahlin Feb 29, 2024
9681b8f
sigint handler and make python interpreter optional
cozzyd Feb 29, 2024
eaaff1b
cleanup
arahlin Feb 29, 2024
482debd
improve whitespace in printout
cozzyd Feb 29, 2024
dd682c8
monkeypatch argument getter/setter methods for G3ModuleConfig
arahlin Feb 29, 2024
c1fc23c
rename
arahlin Feb 29, 2024
3dd3638
variable name
arahlin Feb 29, 2024
865c9f9
ocd
arahlin Feb 29, 2024
b6a1ed5
limit eval namespace, values method
arahlin Feb 29, 2024
48ba193
no print
arahlin Feb 29, 2024
fb04351
Merge remote-tracking branch 'origin/master' into modconfig_refactor
arahlin Mar 1, 2024
44ad66a
refactor to hide .config attribute from python user to avoid confusion
arahlin Mar 1, 2024
98edc00
add test
arahlin Mar 1, 2024
170fdbc
cleanup
arahlin Mar 1, 2024
24d354b
merge in latest updates to modconfig_refactor branch
cozzyd Mar 1, 2024
6b46b6d
Merge branch 'master' into json_output
cozzyd Mar 1, 2024
da75530
get rid of destructor definition resulting from crappy merge on my part
cozzyd Mar 1, 2024
fae872d
merge
cozzyd Mar 4, 2024
67843cc
remove spurious space
cozzyd Mar 4, 2024
220e605
missed part of merge
cozzyd Mar 4, 2024
b4b1b3f
somehow ended up with a few extra lines in pipelineinfo.py from earli…
cozzyd Mar 4, 2024
3de2109
cleanups
cozzyd Mar 4, 2024
84db403
style
cozzyd Mar 5, 2024
c18afae
big rework of server example, including html output for dir listing
cozzyd Mar 5, 2024
7ee4dcf
increment version, fix json output bug recently introduced
cozzyd Mar 5, 2024
7b9dc86
add some docs
cozzyd Mar 5, 2024
fc6bb3d
rst formatting
cozzyd Mar 5, 2024
e0468f6
rename to more descriptive name
cozzyd Mar 5, 2024
71d1fa4
Merge branch 'master' into json_output
arahlin Nov 20, 2024
6f41707
Fix build errors
arahlin Nov 20, 2024
0ff476a
undo unnecessary whitespace changes
arahlin Nov 20, 2024
d057779
Fix build errors
arahlin Nov 20, 2024
d9f03e6
Merge branch 'master' into json_output
arahlin Nov 20, 2024
4e4cec5
Avoid extra boost libraries
arahlin Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ add_spt3g_library(core SHARED
src/int_storage.cxx src/pybindings.cxx
)


# json output option
set(ENABLE_JSON_OUTPUT "FALSE" CACHE BOOL "Enable JSON output of frames (adds to compile time and binary size)")

if (${ENABLE_JSON_OUTPUT})
target_compile_definitions(core PUBLIC -DSPT3G_ENABLE_JSON_OUTPUT)
endif()


# Link dependencies
target_link_libraries(core PUBLIC spt3g)

Expand Down Expand Up @@ -52,6 +61,7 @@ endif()
link_python_dir()

add_spt3g_program(bin/spt3g-dump)
add_spt3g_program(bin/spt3g-jsonify)
add_spt3g_program(bin/spt3g-verify)
add_spt3g_program(bin/spt3g-inspect)
add_spt3g_program(bin/gen-analysis-doc)
Expand Down
28 changes: 28 additions & 0 deletions core/bin/spt3g-jsonify
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3

import sys
from spt3g import core

if len(sys.argv) < 2:
print('Usage: %s file.g3 ' % sys.argv[0])
print('Dumps the given file to standard output as JSON (not quite indented properly, sorry)')
sys.exit(1)

#Crappy hand-crafted json wrapper to make this stream valid json
print('{')
print(' "filename": "%s",' % (sys.argv[1]))
print(' "frames":')
print(' [')

first = True
indent = 8*' '
print_indent = 7*' '

for f in core.G3File(sys.argv[1]):
if not first:
print (print_indent,',')
print(print_indent,indent.join(f.as_json().splitlines(True)))
first = False
print(' ]')
print('}')

5 changes: 5 additions & 0 deletions core/include/core/G3Frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ class G3Frame {
template <typename T> void save(T &os) const;
template <typename T> void load(T &is);

// JSON representation of a frame (if JSON output is enabled, otherwise
// outputs valid JSON telling you that JSON output isn't enabled)
template <typename T> void saveJSON(T &os) const;
std::string asJSON() const;

// Routines for handling the stored serialized copies of data.
// These are all const because they only manipulate caches and
// so change only performance rather than data contents of the frame.
Expand Down
21 changes: 21 additions & 0 deletions core/include/core/serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <dataio.h>

#include <cereal/archives/portable_binary.hpp>
#ifdef SPT3G_ENABLE_JSON_OUTPUT
#include <cereal/archives/json.hpp>
#endif

#include <cereal/types/polymorphic.hpp>
#include <cereal/types/string.hpp>
Expand All @@ -14,13 +17,31 @@
#include <pybindings.h>
#include <G3Logging.h>

#ifdef SPT3G_ENABLE_JSON_OUTPUT

#define G3_SERIALIZABLE_CODE(x) \
template void x::serialize(cereal::PortableBinaryOutputArchive &, unsigned); \
template void x::serialize(cereal::JSONOutputArchive &, unsigned); \
template void x::serialize(cereal::JSONInputArchive &, unsigned); \
template void x::serialize(cereal::PortableBinaryInputArchive &, unsigned); \

#define G3_SPLIT_SERIALIZABLE_CODE(x) \
template void x::save(cereal::PortableBinaryOutputArchive &, unsigned) const; \
template void x::save(cereal::JSONOutputArchive &, unsigned) const; \
template void x::load(cereal::PortableBinaryInputArchive &, unsigned); \
template void x::load(cereal::JSONInputArchive &, unsigned); \

#else

#define G3_SERIALIZABLE_CODE(x) \
template void x::serialize(cereal::PortableBinaryOutputArchive &, unsigned); \
template void x::serialize(cereal::PortableBinaryInputArchive &, unsigned); \

#define G3_SPLIT_SERIALIZABLE_CODE(x) \
template void x::save(cereal::PortableBinaryOutputArchive &, unsigned) const; \
template void x::load(cereal::PortableBinaryInputArchive &, unsigned); \

#endif

template <class T>
struct g3frameobject_picklesuite : boost::python::pickle_suite
Expand Down
36 changes: 36 additions & 0 deletions core/src/G3Frame.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,37 @@ void G3Frame::save(T &os) const
ar << make_nvp("crc", crc);
}

template<typename T>
void G3Frame::saveJSON(T & os) const
{
#ifdef SPT3G_ENABLE_JSON_OUTPUT
using cereal::make_nvp;
uint32_t version(1), size(map_.size());

cereal::JSONOutputArchive ar(os);
ar << make_nvp("version", version);
ar << make_nvp("size", size);
std::string typestr(1,(char) type);
ar << make_nvp("type", typestr);
for (auto i = map_.begin(); i != map_.end(); i++) {
//make sure it's deserialized so we don't just write a blob
blob_decode(i->second);
ar << make_nvp("name", i->first);
ar << make_nvp("val", i->second.frameobject);
}
#else
os << " {error: \"spt3g-software compiled without JSON support\"}" << std::endl;
#endif
}

std::string
G3Frame::asJSON() const
{
std::stringstream str;
saveJSON(str);
return str.str();
}

template <typename T>
void G3Frame::load(T &is)
{
Expand Down Expand Up @@ -406,6 +437,10 @@ template void G3Frame::save(g3_ostream &) const;
template void G3Frame::save(std::ostream &) const;
template void G3Frame::save(std::ostringstream &) const;

template void G3Frame::saveJSON(std::ostream &) const;
template void G3Frame::saveJSON(std::ostringstream &) const;
template void G3Frame::saveJSON(boost::iostreams::filtering_ostream &) const;

G3FramePtr
g3frame_char_constructor(std::string max_4_chars)
{
Expand Down Expand Up @@ -630,6 +665,7 @@ PYBINDINGS("core") {
"where those serialized copies already exist. Saves memory for "
"frames about to be written at the expense of CPU time to "
"re-decode them if they are accessed again later.")
.def("as_json", &G3Frame::asJSON, "JSON representation of frame")
.def_pickle(g3frame_picklesuite())
;
register_vector_of<G3FramePtr>("Frame");
Expand Down
41 changes: 35 additions & 6 deletions core/src/G3Timestream.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ static FLAC__StreamEncoderWriteStatus flac_encoder_write_cb(
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}


// Generic version of loadBinary for all archive types
template <typename A>
static void load_binary_fn(A * inbuf, void * buffer, size_t size)
{
inbuf->template loadBinary<1>(buffer,size);
}

#ifdef SPT3G_ENABLE_JSON_OUTPUT

// SpecializedloadBinary version for JSONInputArchive
//
// This is only needed because cereal will generate code for loading json due
// to the nature of serialize, even though this will never actually be called.
//
// I don't know if it works or not, but since it's never called, who cares? (I
// guess in the future we can have a fromJSON method in G3Frame as well if we
// wanted to support round-tripping through JSON for some reason).

template <>
void load_binary_fn<cereal::JSONInputArchive>(cereal::JSONInputArchive * inbuf, void * buffer, size_t size)
{
inbuf->loadBinaryValue(buffer,size);
}
#endif



template<typename A>
static FLAC__StreamDecoderReadStatus flac_decoder_read_cb(
const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes,
Expand All @@ -51,16 +79,16 @@ static FLAC__StreamDecoderReadStatus flac_decoder_read_cb(
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
} else if (*bytes >= size_t(bytes_left)) {
*bytes = bytes_left;
args->inbuf->template loadBinary<1>(buffer, bytes_left);
load_binary_fn(args->inbuf, buffer, bytes_left);
args->pos += bytes_left;
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
} else {
args->inbuf->template loadBinary<1>(buffer, *bytes);
load_binary_fn(args->inbuf, buffer, *bytes);
args->pos += *bytes;
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
}

template<typename A>
static FLAC__StreamDecoderWriteStatus flac_decoder_write_cb(
const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
Expand All @@ -75,7 +103,7 @@ static FLAC__StreamDecoderWriteStatus flac_decoder_write_cb(
(*args->outbuf)[oldsize + i] = buffer[0][i];
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

static void flac_decoder_error_cb(const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data)
{
Expand Down Expand Up @@ -112,7 +140,8 @@ template <class A> void G3Timestream::save(A &ar, unsigned v) const
ar & cereal::make_nvp("flac", use_flac_);

#ifdef G3_HAS_FLAC
if (use_flac_) {
//Don't try to use FLAC when outputing to JSON, even if flac is enabled.
if (use_flac_ && typeid(A) == typeid(cereal::PortableBinaryOutputArchive)) {
std::vector<int32_t> inbuf;
std::vector<uint8_t> outbuf;
const int32_t *chanmap[1];
Expand Down Expand Up @@ -1332,7 +1361,7 @@ PYBINDINGS("core") {
.def("SetFLACCompression", &G3Timestream::SetFLACCompression,
"Pass True to turn on FLAC compression when serialized. "
"FLAC compression only works if the timestream is in units of "
"counts.")
"counts. FLAC compression is ignored when outputing to JSON.")
.def_readwrite("units", &G3Timestream::units,
"Units of the data in the timestream, stored as one of the "
"members of core.G3TimestreamUnits.")
Expand Down
22 changes: 22 additions & 0 deletions serve/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

function (make_embedded_resource var_name file_name out_file)
if (${file_name} IS_NEWER_THAN ${out_file})
message(Generating ${out_file} " from " ${file_name})
file (WRITE ${out_file} "// automatically generated from ${file_name}\n")
file (READ ${file_name} filedata HEX)
string (REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," cfiledata ${filedata})
file (APPEND ${out_file} "unsigned char ${var_name}[] = { ${cfiledata} };\n")
endif()
endfunction()

make_embedded_resource(favicon favicon.ico ${CMAKE_BINARY_DIR}/serve/favicon.h)

if(NOT DEFINED ENV{CIBUILDWHEEL})
add_spt3g_executable(spt3g-json-serve-files src/spt3g-json-serve-files.cxx)

target_link_libraries(spt3g-json-serve-files core z)
target_include_directories(spt3g-json-serve-files PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
)
endif()
57 changes: 57 additions & 0 deletions serve/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
=====
serve
=====

Example web server(s) for .g3 files

The spt3g-json-serve-files program will serves .g3 files within a directory hierarchy in json format, providing
a crude way to expose a files to a web browser.


Running the file server
=======================

Command line usage::

Usage: spt3g-json-serve-files [OPTION]
-h,--help display this message
-P,--Python initialize python interpreter
-p,--port port to use (default 2726)
-b,--bind-address bind address (default 0.0.0.0)
-d,--doc-root base dir to serve (default is .)
-t,--threads number of threads to use (default 4)
-v,--verbose verbose mode

The example server doesn't support authentication and has not rigorously been
tested for security (though it does try to avoid the most obvious directory
traversal attacks). If it is to be exposed to the internet, you probably should
probably at least reverse proxy it behind a basic authentication screendoor.


Implemented endpoints
=====================

*/[?json]
---------

List a directory within basedir. By default, this uses a clunky HTML browser,
appending the ``?json`` parameter, the directory listing will be returned
in a json format more suited to be programtically used by a less clunky browser.


*.g3[.gz|.bz2][?N=0&nskip=0]
----------------------------

List a .g3 file as JSON. Optional parameters ``N`` and ``nskip`` control the
number of frames (0 means all) and the number of frames from the begining to
skip, respectively. Note that this just affects what gets transferred over the
wire, the server must still deserialize the file up to nskip + N frames each
time (in the future, caching may be implemented, though the OS file cache probably helps a little bit).

/version
--------
List a version number (in JSON format)

/favicon.ico
------------
What else, but a favicon?
Binary file added serve/favicon.ico
Binary file not shown.
Loading