Skip to content

Commit

Permalink
libpressio version 0.23.0
Browse files Browse the repository at this point in the history
Major Changes

+ Several breaking changes to the C++ API to make it more native to C++.
  The C api is uneffected.
  + new required `prefix` api added to return the prefix of the
    compressor using in options.
  + get_metrics_results(), get_metrics(), set_options(), get_options(),
    check_options(), get_configuration(), pressio_data_for_each(),
    and associated `_impl` methods now use references or values instead
    of pointers to be more consistent with C++ style.
  + New operator bool() for `pressio_compressor` to check if the pointer
    is occupied.
  + Refactored compressor plugins to use the C++ interface.
+ Changed `BUILD_TESTS` to `BUILD_TESTING` to match the CMake standard.
+ Marked all of the `std_compat.h` functions as internal.  Use them
  outside of libpressio at your own risk.
+ BREAKING CHANGE `pressio_compressor_check_options` default
  implementation now only considers options in the same prefix as
  potentially conflicting, this allows more usages than previously
  allowed.

Minor Changes

+ Documented how to write a compressor and metrics plugin.  Added test
  cases for this use.
+ Documented that libpressio now compiles on the gcc 4.8.5
+ Documented the list of builtin plugins

Bug Fixes

+ Correctly pass valgrind options to CTest.  Previously the wrong
  file name and option names were passed.
+ Fixed a memory leak in `pressio_data::operator=(pressio_data&&)`
  • Loading branch information
robertu94 committed Nov 13, 2019
1 parent 83c6dd6 commit d262244
Show file tree
Hide file tree
Showing 34 changed files with 935 additions and 345 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ before_install:
script:
- mkdir build
- cd build
- cmake .. -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -DLIBPRESSIO_HAS_SZ=OFF -DLIBPRESSIO_HAS_ZFP=OFF -DBUILD_DOCS=ON
- cmake .. -DCMAKE_BUILD_TYPE=Release -DLIBPRESSIO_HAS_SZ=OFF -DLIBPRESSIO_HAS_ZFP=OFF -DBUILD_DOCS=ON
- cmake --build .
- cmake --build . --target docs
- ctest
Expand Down
8 changes: 3 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(libpressio VERSION "0.22.0" LANGUAGES CXX C)
project(libpressio VERSION "0.23.0" LANGUAGES CXX C)

#correct was to set a default build type
# https://blog.kitware.com/cmake-and-the-default-build-type/
Expand All @@ -19,8 +19,6 @@ if(NOT LIBPRESSIO_CXX_VERSION)
set_property(CACHE LIBPRESSIO_CXX_VERSION PROPERTY STRINGS 11 14 17)
endif()

enable_testing()
include(CTestCustom.cmake)
include(CTest)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
Expand Down Expand Up @@ -250,8 +248,7 @@ install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libpressio)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/pressio_version.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libpressio)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libpressio.pc DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pkgconfig)

option(BUILD_TESTS "build the test cases and examples" OFF)
if(BUILD_TESTS)
if(BUILD_TESTING)
add_subdirectory(test)
endif()

Expand All @@ -273,6 +270,7 @@ if(BUILD_DOCS)
doxygen_add_docs(
docs
${PROJECT_SOURCE_DIR}/README.md
${PROJECT_SOURCE_DIR}/docs
${PROJECT_SOURCE_DIR}/include
COMMENT "Generate Documenation"
)
Expand Down
2 changes: 2 additions & 0 deletions CTestConfig.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=all --track-origins=yes")
set(MEMORYCHECK_SUPPRESSIONS_FILE "${CMAKE_SOURCE_DIR}/test/valgrind.supp")
2 changes: 0 additions & 2 deletions CTestCustom.cmake

This file was deleted.

25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ Libpressio unconditionally requires:

+ `cmake` version `3.13` or later (3.14 required for python bindings)
+ either:
+ `gcc-7.4` or later
+ `clang-7.0.0` or later using either `libc++` or `libstdc++`
+ `gcc-4.8.5` or later
+ `clang-7.0.0` or later using either `libc++` or `libstdc++`. Beware that system libraries may need to be recompiled with `libc++` if using `libc++`

Libpressio additionally optionally requires:

+ `zfp` commit `e8edaced12f139ddf16167987ded15e5da1b98da` or later and its dependencies to provide the SZ plugin
+ `sz` commit `7b7463411f02be4700d13aac6737a6a9662806b4` or later and its dependencies to provide the ZFP plugin
+ `sz` commit `7b7463411f02be4700d13aac6737a6a9662806b4` or later and its dependencies to provide the ZFP plugin
+ `numpy` version `1.14.5` or later and its dependencies to provide the python bindings
+ `Doxygen` version 1.8.15 or later to generate documentation
+ `HDF5` version 1.10.0 or later for HDF5 data support
+ `swig` version 3.0.12 or later for python support
+ `ImageMagick` version 6.9.7 or later for ImageMagick image support. Version 7 or later supports additional data types.
+ `blosc` version 1.14.2 for lossless compressor support via blosc
+ `boost` version 1.53 to compile on a c++14 or earlier compiler
+ `numpy` version `1.14.5` or later and its dependencies to provide the python bindings
+ `swig` version 3.0.12 or later for python support
+ `sz` commit `7b7463411f02be4700d13aac6737a6a9662806b4` or later and its dependencies to provide the SZ plugin
+ `zfp` commit `e8edaced12f139ddf16167987ded15e5da1b98da` or later and its dependencies to provide the ZFP plugin

You can also use the provided `Dockerfile`s in `./docker` to get a working libpressio install. Doing so requires installing `docker` version 17.05 or higher or other build tool that supports this version of Dockerfile syntax or later.

Expand All @@ -40,18 +39,19 @@ LibPressio uses cmake to configure build options. See CMake documentation to se

+ `CMAKE_INSTALL_PREFIX` - install the library to a local directory prefix
+ `BUILD_DOCS` - build the project documentation
+ `BUILD_TESTS` - build the test cases
+ `BUILD_TESTING` - build the test cases

## Building and Installing LibPressio

To build and install the library only.
To build and tests and install the library only.

```bash
BUILD_DIR=build
mkdir $BUILD_DIR
cd $BUILD_DIR
cmake ..
make
make test
make install
```

Expand Down Expand Up @@ -89,13 +89,13 @@ make
make install
```

To build the test cases
To disable building the test cases

```
BUILD_DIR=build
mkdir $BUILD_DIR
cd $BUILD_DIR
cmake .. -DBUILD_TESTS=ON
cmake .. -DBUILD_TESTING=OFF
make
ctest .
```
Expand Down Expand Up @@ -173,6 +173,9 @@ main(int argc, char* argv[])
More examples can be found in `test/`
For information on writing a compressor plugin see [Writing a Compressor Plugin](@ref writingacompressor)
For information on writing a metrics plugin see [Writing a Compressor Plugin](@ref writingametric)
## Option Names
LibPressio uses a key-value system to refer to configuration settings.
Expand Down
17 changes: 17 additions & 0 deletions docs/BuiltInModules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Built-in Modules {#builtins}

Libpression provides a number of builtin compressor and metrics modules.

## Compressor Plugins

+ `sz` -- the SZ error bounded lossy compressor
+ `zfp` -- the ZFP error bounded lossy compressor
+ `mgard` -- the MGARD error bounded lossy compressor
+ `blosc` -- the blosc lossless compressor
+ `magick` -- the ImageMagick image compression/decompression library

## Metrics Plugins

+ `time` -- time information on each compressor API
+ `error_stat` -- statistics on the difference between the uncompressed and decompressed values that can be computed in one pass in linear time.
+ `size` -- information on the size of the compressed and decompressed data
166 changes: 166 additions & 0 deletions docs/WritingACompressorPlugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Writing a Compressor Plugin {#writingacompressor}

Libpressio supports adding custom compressor plugins.

To create a compressor plugin, simply create a subclass of `libpressio_compressor_plugin` that implements the compressor functions that you are interested in, and register the plugin with libpressio.

For example, let's create a compressor plugin that preforms a log transform as a preprocessing step to compression, and does a exponential transformation as a post processing step to decompression. Some scientific data compresses much better in the log domain than in the normal domain.

First you will need to include several headers.

```cpp
#include <cmath> //for exp and log
#include "libpressio_ext/cpp/data.h" //for access to pressio_data structures
#include "libpressio_ext/cpp/compressor.h" //for the libpressio_compressor_plugin class
#include "libpressio_ext/cpp/options.h" // for access to pressio_options
#include "libpressio_ext/cpp/pressio.h" //for the plugin registries
```

First we are going to delegate most of the methods to the underlying compressor.
To do so, we are going to do this in one of the most naive was possible and accept the compressor as a constructor argument.
Then we are going to provide overrides that pass each of the methods to the underlying compressor.

Notice the function `check_error`.
Since we are using composition rather than inheritance, we need to propagate error messages to our wrapper.
The `check_error` function simply check for the error condition in the underlying function and propagates the error if these is one.

Also notice that we are using the `_impl` versions of the various methods.
This is because the base class is doing some work to provide metrics functionality and basic error checking that we want to use.

```cpp
class log_transform : public libpressio_compressor_plugin {
public:
log_transform(): compressor(nullptr) {}
log_transform(pressio_compressor&& comp): compressor(std::move(comp)) {}


//TODO compress and decompress, see below


//getting and setting options/configuration
pressio_options get_options_impl() const override {
auto options = compressor.plugin->get_options();
options.set("log:compressor", (void*)&compressor);
return options;
}
int set_options_impl(pressio_options const& options) override {
//if the user hasn't set the compressor yet, fail
if(!compressor) return invalid_compressor();
int rc = check_error(compressor.plugin->set_options(options));

//try to extract the compressor plugin from the option
void* tmp;
if(options.get("log:compressor", &tmp) == pressio_options_key_set) {
compressor = std::move(*(pressio_compressor*)tmp);
}

return rc;
}
pressio_options get_configuration_impl() const override {
//if the user hasn't set the compressor yet, fail
if(!compressor) return pressio_options();
return compressor.plugin->get_configuration();
}
int check_options_impl(pressio_options const& options) override {
//if the user hasn't set the compressor yet, fail
if(!compressor) return invalid_compressor();
return check_error(compressor.plugin->check_options(options));
}

//getting version information
const char* prefix() const override {
return "log";
}
const char* version() const override {
if(!compressor) return "";
return compressor.plugin->version();
}
int major_version() const override {
if(!compressor) return -1;
return compressor.plugin->major_version();
}
int minor_version() const override {
if(!compressor) return -1;
return compressor.plugin->minor_version();
}
int patch_version() const override {
if(!compressor) return -1;
return compressor.plugin->patch_version();
}

private:
int check_error(int rc) {
if(rc) {
set_error(
compressor.plugin->error_code(),
compressor.plugin->error_msg()
);
}
return rc;
}
int invalid_compressor() { return set_error(-1, "compressor must be set"); };
pressio_compressor compressor;
};
```
Now to the harder part, writing compress and decompress.


First we can write our log and exponential transform functions.

```cpp
namespace {
struct log_fn{
template <class T>
pressio_data operator()(T* begin, T* end) {
pressio_data log_data = pressio_data::clone(*input);
auto output_it = reinterpret_cast<T*>(log_data.data());
std::transform(begin, end, output_it, [](T i){ return std::log(i); });
return log_data;
}
pressio_data const* input;
};

struct exp_fn{
template <class T>
pressio_data operator()(T* begin, T* end) {
pressio_data log_data = pressio_data::clone(*output);
auto output_it = reinterpret_cast<T*>(log_data.data());
std::transform(begin, end, output_it, [](T i){ return std::exp(i); });
return log_data;
}
pressio_data const* output;
};
}
```

We will use these methods using the `pressio_data_for_each` API to preform the transform
Now, we can write compress and decompress:


```cpp
//compress and decompress
int compress_impl(pressio_data const* input, pressio_data* output) override {
if(!compressor) return invalid_compressor();
pressio_data log_input = pressio_data_for_each<pressio_data>(*input, log_fn{input});
return check_error(compressor.plugin->compress(&log_input, output));
}

int decompress_impl(pressio_data const* input, pressio_data* output) override {
if(!compressor) return invalid_compressor();
int rc = compressor.plugin->decompress(input, output);
*output = pressio_data_for_each<pressio_data>(*output, exp_fn{output});
return check_error(rc);
}
```
We finally register the library with libpressio:
```cpp
static pressio_register X(compressor_plugins(), "log", [](){ return std::make_unique<log_transform>();});
```

High quality compressor modules may be accepted into libpressio. Contributed modules should be placed in to
the `src/plugins/compressors/` directory and added to the main CMakeLists.txt. If the compressor plugin
requires an external dependency, it should be hidden behind a configuration option.

84 changes: 84 additions & 0 deletions docs/WritingAMetricsPlugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Writing a User Metrics Plugin {#writingametric}

libpressio supports adding custom metrics plugins.


To create a metrics plugin, simply create a subclass of `libpressio_metrics_plugin` that implements the check that you are interested in, and register the plugin with libpressio.

For example, let's create a plugin that counts the number of compressions of each data type that the user preforms.

First we will include a number of required headers:

```cpp
//required for all plugins
#include "libpressio_ext/cpp/metrics.h" // provides the libpressio_metrics_plugin
#include "libpressio_ext/cpp/options.h" // provides pressio_options and related methods
#include "libpressio_ext/cpp/pressio.h" // for the plugin registry

//required for this plugin
#include "libpressio_ext/cpp/data.h" // to be able to access ->dtype()
#include <memory> //for make_unique (in c++14 or later)
#include <map> //for the map that counts what data type was used

```

Next, we need to write our class. Since we want to count the number of data buffers of each type that we compress, we will hook the `begin_compress` method. Alternatively we could also hook the `end_compress` method. Once we have the counts, we report them out using the `get_metrics_results` function. We do this like so:

```cpp
class counting_metric: public libpressio_metrics_plugin {
counting_metric() {
//operator[] is non-const, so explicits instantiate each of the values we need
counts[pressio_int8_dtype] = 0;
counts[pressio_int16_dtype] = 0;
counts[pressio_int32_dtype] = 0;
counts[pressio_int64_dtype] = 0;
counts[pressio_uint8_dtype] = 0;
counts[pressio_uint16_dtype] = 0;
counts[pressio_uint32_dtype] = 0;
counts[pressio_uint64_dtype] = 0;
counts[pressio_float_dtype] = 0;
counts[pressio_double_dtype] = 0;
counts[pressio_byte_dtype] = 0;
}

//increment the count at the beginning of each compress
void begin_compress(pressio_data const* input, pressio_data const*) override {
counts[input->dtype()]++;
}

//return an options structure containing the metrics we care about
//notice how the namespaced matches the name of the plugin that is registered below
pressio_options get_metrics_results() const override {
pressio_options opts;
opts.set("counts:int8", counts.at(pressio_int8_dtype));
opts.set("counts:int16", counts.at(pressio_int16_dtype));
opts.set("counts:int32", counts.at(pressio_int32_dtype));
opts.set("counts:int64", counts.at(pressio_int64_dtype));
opts.set("counts:uint8", counts.at(pressio_uint8_dtype));
opts.set("counts:uint16", counts.at(pressio_uint16_dtype));
opts.set("counts:uint32", counts.at(pressio_uint32_dtype));
opts.set("counts:uint64", counts.at(pressio_uint64_dtype));
opts.set("counts:float", counts.at(pressio_float_dtype));
opts.set("counts:double", counts.at(pressio_double_dtype));
opts.set("counts:byte", counts.at(pressio_byte_dtype));
return opts;
}

std::map<pressio_dtype, unsigned int> counts;
};
```
Finally, we will register the plugin in the under the names "counts" in the metrics plugging registry
```cpp
static pressio_register X(metrics_plugins(), "counts", [](){ return std::make_unique<counting_metric>(); });
```

Then a user of the library can then ask libpressio to construct their new plugin as normal.
But what if our metrics modules takes arguments?
Instead of registering it directly, the user can instantiate it manually and combine it with others from the library using the `make_m_composite` method.

If your metric is suitably general and well implemented, you can contribute it back to libpressio.
Metrics should be added to the `src/plugins/metrics/` directory and to the main `CMakeLists.txt`. If the metric requires additional dependencies beyond the standard library, it should be hidden behind a configuration flag that enables its use and disabled by default.

This full example can be found in the `test/test_regsiter_metrics.cc` file, and the full documentation of the allowed hooks and their arguments can be found in the documentation for `libpressio_metrics_plugin`.
Loading

0 comments on commit d262244

Please sign in to comment.