diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8381a0d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+.cproject
+.project
+*.idea/
+.pydevproject
+*.pyc
+.vscode/
+GPATH
+GRTAGS
+GTAGS
+
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
new file mode 100644
index 0000000..5a78855
--- /dev/null
+++ b/.ycm_extra_conf.py
@@ -0,0 +1,325 @@
+# This file is NOT licensed under the GPLv3, which is the license for the rest
+# of YouCompleteMe.
+#
+# Here's the license text for this file:
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# For more information, please refer to
+
+from distutils.sysconfig import get_python_inc
+import os
+import platform
+import os.path as p
+import subprocess
+import json
+import re
+import logging
+
+logger = logging.getLogger('ycm-extra-conf')
+
+DIR_OF_THIS_SCRIPT = p.abspath( p.dirname( __file__ ) )
+DIR_OF_THIRD_PARTY = p.join( DIR_OF_THIS_SCRIPT, 'third_party' )
+SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
+
+# These are the compilation flags that will be used in case there's no
+# compilation database set (by default, one is not set).
+# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
+flags = [
+'-Wall',
+'-Wextra',
+'-Werror',
+'-Wno-long-long',
+'-Wno-variadic-macros',
+'-fexceptions',
+'-DNDEBUG',
+# You 100% do NOT need -DUSE_CLANG_COMPLETER and/or -DYCM_EXPORT in your flags;
+# only the YCM source code needs it.
+'-DUSE_CLANG_COMPLETER',
+'-DYCM_EXPORT=',
+# Custome flags
+'-Iinclude',
+'-isystem',
+'cpp/pybind11',
+'-isystem',
+'cpp/whereami',
+'-isystem',
+'cpp/BoostParts',
+'-isystem',
+get_python_inc(),
+'-isystem',
+'cpp/llvm/include',
+'-isystem',
+'cpp/llvm/tools/clang/include',
+'-I',
+'cpp/ycm',
+'-I',
+'cpp/ycm/ClangCompleter',
+'-isystem',
+'cpp/ycm/tests/gmock/gtest',
+'-isystem',
+'cpp/ycm/tests/gmock/gtest/include',
+'-isystem',
+'cpp/ycm/tests/gmock',
+'-isystem',
+'cpp/ycm/tests/gmock/include',
+'-isystem',
+'cpp/ycm/benchmarks/benchmark/include',
+]
+
+
+# Set this to the absolute path to the folder (NOT the file!) containing the
+# compile_commands.json file to use that instead of 'flags'. See here for
+# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
+#
+# You can get CMake to generate this file for you by adding:
+# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 )
+# to your CMakeLists.txt file.
+#
+# Most projects will NOT need to set this to anything; you can just change the
+# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
+
+def IsHeaderFile( filename ):
+ extension = p.splitext( filename )[ 1 ]
+ return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
+
+
+def FindCorrespondingSourceFile( filename ):
+ if IsHeaderFile( filename ):
+ basename = p.splitext( filename )[ 0 ]
+ for extension in SOURCE_EXTENSIONS:
+ replacement_file = basename + extension
+ if p.exists( replacement_file ):
+ return replacement_file
+ return filename
+
+
+def PathToPythonUsedDuringBuild():
+ try:
+ filepath = p.join( DIR_OF_THIS_SCRIPT, 'PYTHON_USED_DURING_BUILDING' )
+ with open( filepath ) as f:
+ return f.read().strip()
+ except OSError:
+ return None
+
+def dirwalk_up(bottom):
+ """
+ mimic os.walk, but walk 'up'
+ instead of down the directory tree
+ """
+
+ bottom = p.realpath(bottom)
+
+ #get files in current dir
+ try:
+ names = os.listdir(bottom)
+ except Exception as e:
+ print(e)
+ return
+
+
+ dirs = []
+ files = []
+ for name in names:
+ if p.isdir(p.join(bottom, name)):
+ dirs.append(name)
+ elif p.isfile(p.join(bottom, name)):
+ files.append(name)
+
+ yield bottom, dirs, files
+
+ new_path = p.realpath(p.join(bottom, '..'))
+
+ # see if we are at the top
+ if new_path == bottom:
+ return
+
+ for x in dirwalk_up(new_path):
+ yield x
+
+def FindNearBuildPath(filename):
+ file_dir = p.dirname(filename)
+ for path, dirs, files in dirwalk_up(file_dir):
+ if "compile_comands.json" in files:
+ return path
+ if path == os.getcwd():
+ return None
+ for rdir in dirs:
+ if rdir == "build" and p.exists(rdir + "/compile_commands.json"):
+ return "{}/build".format(path)
+ return None
+
+
+def Settings( **kwargs ):
+ # Do NOT import ycm_core at module scope.
+ import ycm_core
+
+ language = kwargs[ 'language' ]
+
+ if language == 'cfamily':
+ # If the file is a header, try to find the corresponding source file and
+ # retrieve its flags from the compilation database if using one. This is
+ # necessary since compilation databases don't have entries for header files.
+ # In addition, use this source file as the translation unit. This makes it
+ # possible to jump from a declaration in the header file to its definition
+ # in the corresponding source file.
+ filename = FindCorrespondingSourceFile( kwargs[ 'filename' ] )
+
+ logger.info("Calculating flags for {}".format(filename))
+
+ # If we can't find compilation DB -> fallback to flags
+ compilation_database_folder = FindNearBuildPath(filename)
+ if not compilation_database_folder:
+ logger.info("Could not find build-path, using default falgs")
+ cpy_flags = flags
+ if any([filename.endswith(x) for x in ("c", "cc", "h", "hh")]):
+ cpy_flags += ['-x', 'c', '-std=c11']
+ elif any([filename.endswith(x) for x in ("cpp", "cxx", "hpp", "hxx")]):
+ cpy_flags += ['-x', 'c++', '-std=c++17']
+ return {
+ 'flags': cpy_flags,
+ 'include_paths_relative_to_dir': DIR_OF_THIS_SCRIPT,
+ 'override_filename': filename
+ }
+
+ try:
+ logger.info("Found build-path, opening {}/compile_commands.json".format(compilation_database_folder))
+ db_file = open(compilation_database_folder + "/compile_commands.json")
+ database = json.load(db_file)
+ entry = [e for e in database if e['file'].endswith(filename)]
+ if not entry:
+ logger.info("No entry for {} in compilation-db".format(filename))
+ raise Exception("No entry")
+ logger.info("Found entry for {} in compilation-db".format(filename))
+ entry = entry[0]
+ entry_fname = entry['file']
+ entry_command = entry['command']
+ entry_directory = entry['directory']
+
+ iterable = iter(entry_command.split())
+ final_flags = [] + RTE_INCLUDES
+ try:
+ current = next(iterable)
+
+ # Skip until flags
+ clang_compilers = ("clang")
+ c_compilers = ("gcc", "c", "cc")
+ cpp_compilers = ("g++", "c++")
+ while True:
+ # already a flag - keep it
+ if current.startswith('-'):
+ break
+ # Skip filename
+ if current == entry_fname:
+ current = next(iterable)
+ break
+ # c-compiler, use -x flag to indicate clangd we are handling C file
+ if current in c_compilers or any([current.endswith("/" + c) for c in c_compilers]):
+ final_flags += ['-x', 'c']
+ current = next(iterable)
+ break
+ # cpp-compiler, use -x flag to indicate clangd we are handling C++ file
+ elif current in cpp_compilers or any([current.endswith("/" + c) for c in cpp_compilers]):
+ final_flags += ['-x', 'c++']
+ current = next(iterable)
+ break
+ # for clang, it should already contain the -x flag
+ elif current in clang_compilers or any([current.endswith("/" + c) for c in clang_compilers]):
+ current = next(iterable)
+ break
+ current = next(iterable)
+
+
+ while True:
+ # Defines, includes can be multi-param
+ if current in ("-D", "-I"):
+ final_flags += [current.append(next(iterable))]
+ # Double-skip these flags (-c file, -o file)
+ elif current in ("-o", "-c"):
+ current = next(iterable)
+ # Normal flag
+ else:
+ final_flags += [current]
+ current = next(iterable)
+
+
+ except StopIteration:
+ pass
+
+ logger.info("Successfully loaded flags from compilation-db")
+ return {
+ 'flags': final_flags,
+ 'include_paths_relative_to_dir': relative_dir,
+ 'override_filename': filename
+ }
+ except:
+ logger.warn("Using default flags")
+ cpy_flags = flags
+ if any([filename.endswith(x) for x in ("c", "cc", "h", "hh")]):
+ cpy_flags += ['-x', 'c', '-std=c11']
+ elif any([filename.endswith(x) for x in ("cpp", "cxx", "hpp", "hxx")]):
+ cpy_flags += ['-x', 'c++', '-std=c++17']
+ return {
+ 'flags': cpy_flags,
+ 'include_paths_relative_to_dir': DIR_OF_THIS_SCRIPT,
+ 'override_filename': filename
+ }
+
+ if language == 'python':
+ return {
+ 'interpreter_path': PathToPythonUsedDuringBuild()
+ }
+
+ return {}
+
+
+def PythonSysPath( **kwargs ):
+ sys_path = kwargs[ 'sys_path' ]
+
+ interpreter_path = kwargs[ 'interpreter_path' ]
+ major_version = subprocess.check_output( [
+ interpreter_path, '-c', 'import sys; print( sys.version_info[ 0 ] )' ]
+ ).rstrip().decode( 'utf8' )
+
+ sys_path[ 0:0 ] = [ p.join( DIR_OF_THIS_SCRIPT ),
+ p.join( DIR_OF_THIRD_PARTY, 'bottle' ),
+ p.join( DIR_OF_THIRD_PARTY, 'cregex',
+ 'regex_{}'.format( major_version ) ),
+ p.join( DIR_OF_THIRD_PARTY, 'frozendict' ),
+ p.join( DIR_OF_THIRD_PARTY, 'jedi_deps', 'jedi' ),
+ p.join( DIR_OF_THIRD_PARTY, 'jedi_deps', 'parso' ),
+ p.join( DIR_OF_THIRD_PARTY, 'requests_deps', 'requests' ),
+ p.join( DIR_OF_THIRD_PARTY, 'requests_deps',
+ 'urllib3',
+ 'src' ),
+ p.join( DIR_OF_THIRD_PARTY, 'requests_deps',
+ 'chardet' ),
+ p.join( DIR_OF_THIRD_PARTY, 'requests_deps',
+ 'certifi' ),
+ p.join( DIR_OF_THIRD_PARTY, 'requests_deps',
+ 'idna' ),
+ p.join( DIR_OF_THIRD_PARTY, 'waitress' ) ]
+
+ sys_path.append( p.join( DIR_OF_THIRD_PARTY, 'jedi_deps', 'numpydoc' ) )
+ return sys_path
diff --git a/UMakefile b/UMakefile
new file mode 100644
index 0000000..c1ce64b
--- /dev/null
+++ b/UMakefile
@@ -0,0 +1,21 @@
+$nil =
+
+$cflags = -O2 -Wall -Wextra -Werror -fPIC
+$lflags = -Wl,-zdefs -fPIC
+
+[variant:debug]
+$cflags = -O0 -g -ggdb -Wall -Wextra -Werror -fPIC
+$lflags = -Wl,-zdefs -fPIC
+
+!c(includes, flags=$nil, cflags=$cflags) : gcc -c {filename} $includes $cflags $flags -o {target} > {dir}/{noext}.umake.o
+!cpp(includes, flags=$nil, cflags=$cflags) : g++ -std=c++17 -c {filename} $includes $cflags $flags -o {target} > {dir}/{noext}.umake.o
+!so(libs, flags=$nil, lflags=$lflags) : g++ -std=c++17 --shared {filename} $lflags $flags $libs -o {target}
+!a(libs) : ar rcs {target} {filename} $libs
+
+$mri_includes = -Iinclude -Ideps
+$mri_cxx_flags = -fno-exceptions
+$mri_linkage = -lhiredis
+
+:foreach **/*.c > !c($mri_includes)
+:foreach **/*.cpp > !cpp($mri_includes, $mri_cxx_flags)
+: **/*.umake.o > !so($mri_linkage) > libmri.so
diff --git a/doc/arch.md b/doc/arch.md
new file mode 100644
index 0000000..e69de29
diff --git a/include/mri.h b/include/mri.h
new file mode 100644
index 0000000..1ff10b2
--- /dev/null
+++ b/include/mri.h
@@ -0,0 +1,36 @@
+#ifndef _MRI_H_
+#define _MRI_H_
+
+#include "mri_sched.h"
+#include "mri_config.h"
+#include "mri_typing.h"
+#include "mri_logging.h"
+
+/* prototypes */
+
+/* Notes:
+ * Each instance can only be linked to one domain (! consider !)
+ * Instance is global to process having thread-oriented CB support
+ */
+
+/**
+ * mri_init_threaded()
+ * Initialize the MRI instance in threaded-mode (new thread is created)
+ * All of the events needed to be handled by MRI will be done in the thread
+ *
+ * @param config - pointer to configuration object to use (ownership transfered)
+ * @return int - C-Style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_init_threaded(struct mri_coniguration **config);
+
+/**
+ * mri_init_async()
+ * Initialize the MRI instance in async-mode (no threads are created)
+ * All of the events needed to be handled by MRI will happen in user-thread
+ *
+ * @param config - pointer to configuration object to use (ownership transfered)
+ * @param sched - information needed to schedule MRI events into user-scheduling
+ * @return int - C-Style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_init_async(struct mri_coniguration **config, mri_sched_info_t *sched);
+#endif /* _MRI_H_ */
diff --git a/include/mri.hpp b/include/mri.hpp
new file mode 100644
index 0000000..7b77353
--- /dev/null
+++ b/include/mri.hpp
@@ -0,0 +1,54 @@
+#include
+
+extern "C" {
+ #include "mri.h"
+};
+
+/**
+ * Generic template to form any iteration of given container
+ *
+ * @tparam container_t Container-type to register.
+ * @tparam iterator_t Value Iterator-type to register.
+ * @tparam value_t Value-type to register.
+ *
+ * Other parameters as defined in typedef mri_iterator_cb
+ */
+template < typename container_t,
+ typename iterator_t = typename container_t::iterator,
+ typename value_t = typename container_t::value_type >
+int generic_mri_iterator_cb(void *container, mri_iter_state_t *state, void **mem) {
+ if (! container || ! state || ! mem) return MRI_ITER_ERR;
+
+ /* We are getting the templated type as data */
+ auto const &data_view = *((container_t *) container);
+
+ /* Check for first run */
+ if (0 == state->iteration_count) {
+ /* Deep-copy since iterators don't have copy .ctor */
+ auto begin_iterator = data_view.begin();
+
+ /**
+ * Notice we disable the warning because normally one cannot copy-construct an iterator
+ * This is purely a hack we use to avoid the need for having an in-container dedicated iterator
+ */
+ #pragma GCC diagnostic push
+ #if __GNUC__ >= 8
+ #pragma GCC diagnostic ignored "-Wclass-memaccess"
+ #endif
+ std::memcpy(&state->userdata, &begin_iterator, sizeof(iterator_t));
+ #pragma GCC diagnostic pop
+ }
+
+ /* Parse private data as iterator (see above) */
+ auto &iterator = *((iterator_t *) &state->userdata);
+
+ // If we are at the last argument -> restart iterator
+ if (data_view.end() == iterator) {
+ return MRI_ITER_STOP;
+ }
+
+ /* Set row memory from iterator using the operator */
+ *mem = &(* iterator++); /* pointer to data reference */
+
+ return MRI_ITER_CONTINUE;
+}
diff --git a/include/mri_all.h b/include/mri_all.h
new file mode 100644
index 0000000..80c005e
--- /dev/null
+++ b/include/mri_all.h
@@ -0,0 +1,8 @@
+#ifndef _MRI_ALL_H_
+#define _MRI_ALL_H_
+
+#include "mri.h"
+#include "mri_consumer.h"
+#include "mri_producer.h"
+
+#endif
diff --git a/include/mri_config.h b/include/mri_config.h
new file mode 100644
index 0000000..154d132
--- /dev/null
+++ b/include/mri_config.h
@@ -0,0 +1,92 @@
+#ifndef _MRI_CONFIG_H_
+#define _MRI_CONFIG_H_
+
+#include "mri_typing.h"
+
+#define MRI_CONFIG_MAX_STRING_SIZE 128
+
+/* FWD Decleration */
+struct mri_configuration;
+
+typedef enum mri_config_value {
+ /* Producer configuration */
+ MRI_CONFIG_DOMAIN,
+
+ /* Service discovery configuration */
+ MRI_CONFIG_SERVICE_DISCOVERY_HOST,
+ MRI_CONFIG_SERVICE_DISCOVERY_PORT,
+ MRI_CONFIG_SERVICE_DISCOVERY_TO_USE,
+
+ /* Redis specific */
+ MRI_CONFIG_REDIS_SD_TIMEOUT,
+ MRI_CONFIG_REDIS_SD_USE_SENTINEL,
+ MRI_CONFIG_REDIS_SD_SENTINEL_MASTER,
+
+ /* Never go beyond this point */
+ MRI_CONFIG_MAX_VALUE
+} mri_config_value_t;
+
+/**
+ * Convert key to human-readable string (log usage)
+ * @param type - the enumeration value for the key
+ * @return cstring_t - string representing the enum-value
+ */
+cstring_t config_type_to_string(mri_config_value_t type);
+
+/**
+ * Create configuration object
+ * Input is formatted in getopt() fashion
+ * @param argc - main-like input, number of arguments
+ * @param argv - main-like inout, array of argument-strings
+ * @return mri_configuration on success, NULL on failure
+ */
+struct mri_configuration *mri_config_create(int argc, char **argv);
+
+/**
+ * Destroy configuration object
+ * @param config - mri configuration object to destroy
+ * @returns C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_config_destroy(struct mri_configuration *config);
+
+/**
+ * Get a mutable configuration-value from object (copy of internal)
+ * @param config - mri configuration object to manage
+ * @param type - type of configuration-value (enum) to manage
+ * @param output - output buffer/object to write value into
+ * @param size - size of output object, on success write-back size written
+ * @returns C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_config_get_mutable(struct mri_configuration *config, mri_config_value_t type, void *output, size_t *size);
+
+/**
+ * Get a non-mutable configuration-value from object (reference to internal)
+ * @param config - mri configuration object to manage
+ * @param type - type of configuration-value (enum) to manage
+ * @param output - pointer to data-pointer which will point to internal-data after call
+ * @param size - pointer to size which will be assigned with internal-data size after call
+ * @returns C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_config_get(struct mri_configuration *config, mri_config_value_t type, const void **output, size_t *size);
+
+/**
+ * Set a mutable configuration-value from object (copy to internal)
+ * @param config - mri configuration object to manage
+ * @param type - type of configuration-value (enum) to manage
+ * @param input - input buffer/object to write as config-value (deep-copy)
+ * @param size - size of input object
+ * @returns C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_config_set_mutable(struct mri_configuration *config, mri_config_value_t type, void *input, size_t size);
+
+/**
+ * Set a non-mutable configuration-value from object (internal reference to it)
+ * @param config - mri configuration object to manage
+ * @param type - type of configuration-value (enum) to manage
+ * @param input - buffer to set as config-value (pointer copy)
+ * @param size - size of input object
+ * @returns C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+int mri_config_set(struct mri_configuration *config, mri_config_value_t type, void *input, size_t size);
+
+#endif
diff --git a/include/mri_consumer.h b/include/mri_consumer.h
new file mode 100644
index 0000000..3906863
--- /dev/null
+++ b/include/mri_consumer.h
@@ -0,0 +1,6 @@
+#ifndef _MRI_CONSUMER_H_
+#define _MRI_CONSUMER_H_
+
+#include "mri_typing.h"
+
+#endif
diff --git a/include/mri_logging.h b/include/mri_logging.h
new file mode 100644
index 0000000..769986b
--- /dev/null
+++ b/include/mri_logging.h
@@ -0,0 +1,27 @@
+#ifndef _MRI_LOGGING_H
+#define _MRI_LOGGING_H
+
+#include
+
+/* Severity similar to syslog */
+typedef enum mri_severity {
+ MRI_DEBUG = 7,
+ MRI_NOTICE = 6,
+ MRI_INFO = 5,
+ MRI_WARNING = 4,
+ #define MRI_WARN MRI_WARNING
+ MRI_ERROR = 3,
+ #define MRI_ERR MRI_ERROR
+ MRI_CRITICAL = 2,
+ #define MRI_CRIT MRI_CRITICAL
+ MRI_ALERT = 1,
+ MRI_EMERG = 0
+ #define MRI_PANIC MRI_EMERG
+} mri_severity_t;
+
+/* Note: it is sufficient to override just one func */
+/* Weak-ref logging functions defaulted to printf() */
+extern void mri_log(mri_severity_t, const char *, ...);
+extern void mri_vlog(mri_severity_t, const char *, va_list);
+
+#endif
diff --git a/include/mri_producer.h b/include/mri_producer.h
new file mode 100644
index 0000000..a0f95c8
--- /dev/null
+++ b/include/mri_producer.h
@@ -0,0 +1,56 @@
+#ifndef _MRI_PRODUCER_H_
+#define _MRI_PRODUCER_H_
+
+#include "mri_sched.h"
+#include "mri_typing.h"
+
+/* Formatting apart from provided-formatters section */
+int _mri_create_formatter(cstring_t name, mri_formatter_cb callback);
+
+/* TODO(omer): Add shaper configuration (amount of samples, scope and more */
+
+/* Shaping apart from provided-shapers section */
+int _mri_create_shaper(cstring_t name, mri_shaper_cb callback);
+
+/* Registering types and data to MRI */
+int _mri_create_type(cstring_t, size_t);
+int _mri_type_add_slot(cstring_t, cstring_t, cstring_t, size_t, size_t, int);
+int _mri_type_add_vslot(cstring_t, cstring_t, mri_formatter_cb);
+int _mri_type_add_shaper(cstring_t, cstring_t, cstring_t, size_t, size_t);
+
+/* Current suggestion:
+ * when someone registers data on path, we capture his thread-id,
+ * this will allow for poll on an event object that is related to his thread-id.
+ * MRI will use data in to register the event-object for the user to poll on.
+ * Ultimatly this will allow user to synchronise access to registered data without (b)locking.
+ *
+ * If none are provided, thread's data will use the default MRI sched (provided in init, or mri-thread)
+ */
+int mri_set_current_thread_sched(mri_sched_info_t *sched);
+
+/* Registering data of defined types to MRI */
+int _mri_register(cstring_t path, cstring_t type, void *object, mri_iterator_cb callback);
+int mri_register_config(cstring_t path, void *context, mri_config_change_cb callback);
+int mri_unregister(cstring_t path);
+
+/* Formalize API using macros */
+#define mri_create_type(c_type) \
+ _mri_create_type(#c_type, sizeof(c_type))
+#define mri_type_add_slot(c_type, slot, format, flags) \
+ _mri_type_add_slot(#c_type, #slot, #format, (size_t) &(((c_type *) 0)->slot), sizeof(((c_type *) 0)->slot), flags)
+#define mri_type_add_vslot(c_type, slot, vslot_format) \
+ _mri_type_add_vslot(#c_type, slot, vslot_foramt)
+#define mri_register(path, c_type, object, callback) \
+ _mri_register(path, #c_type, object, callback)
+#define mri_create_formatter(c_type, callback) \
+ _mri_create_fromatter(#c_type, callback)
+#define mri_create_shaper(c_type, callback) \
+ _mri_create_shaper(#c_type, callback)
+
+#define mri_type_add_shaper_slot(c_type, slot, format, shaper) \
+ _mri_type_add_shaper(#c_type, #slot, #format, (size_t) &(((c_type *) 0)->slot), sizeof(((c_type *) 0)->slot), #shaper)
+
+#define mri_type_add_rate(c_type, slot) \
+ mri_type_add_shaper(c_type, slot, mri_number_t, mri_rate_shaper_t)
+
+#endif
diff --git a/include/mri_typing.h b/include/mri_typing.h
new file mode 100644
index 0000000..bb726ed
--- /dev/null
+++ b/include/mri_typing.h
@@ -0,0 +1,67 @@
+#ifndef _MRI_TYPING_H_
+#define _MRI_TYPING_H_
+
+#include
+#include
+#include
+
+/* defines */
+#define MRI_MAX_HOST_SIZE (128)
+#define MRI_MAX_SLOT_STR_SIZE (256)
+#define MRI_USERDATA_SIZE (32)
+
+#define MRI_SLOT_FLAG_CONST (1 << 0) /* TODO(omer): Unused for now */
+#define MRI_SLOT_FLAG_RATE (1 << 1) /* TODO(omer): Unused for now */
+#define MRI_SLOT_FLAG_PK (1 << 2)
+#define MRI_SLOT_FLAG_HIDDEN (1 << 3)
+
+typedef enum mri_service_mode {
+ MRI_PRODUCER = (1 << 0),
+ MRI_CONSUMER = (1 << 1)
+} mri_service_mode_t;
+
+typedef enum mri_iter_action {
+ MRI_ITER_ERR,
+ MRI_ITER_STOP,
+ MRI_ITER_CONTINUE
+} mri_iter_action_t;
+
+/* typedefs */
+typedef uint8_t mri_byte_t;
+typedef const char *cstring_t;
+
+typedef struct mri_iter_state {
+ size_t iteration_count; /* Iteration count (incremented by MRI) */
+ mri_byte_t userdata[MRI_USERDATA_SIZE]; /* User provided data to help iteration */
+} mri_iter_state_t;
+
+/* Reflects result of capturing a slot */
+typedef struct mri_capture_sample {
+ uint32_t ns_timestamp; /* Data timestamp (monotonic nanoseconds) */
+ mri_byte_t sample[MRI_MAX_SLOT_STR_SIZE]; /* Data captured via calls to the formatter */
+} mri_capture_sample_t;
+
+/* Provided formatters (multi-formatters) */
+typedef struct {} mri_hex_t; /* Hexadecimal representation */
+typedef struct {} mri_number_t; /* Plain normal numeric data */
+typedef struct {} mri_buffer_t; /* Memory address dump in bytes */
+typedef struct {} mri_string_t; /* ASCII encoded human string */
+typedef struct {} mri_timespec_t; /* timespec (tv_sec, tv_usec) */
+typedef struct {} mri_epoch_time_t; /* uint64_t seconds since epoch */
+typedef struct {} mri_elapsed_time_t; /* uint64_t monotonic diff */
+
+/* Provided shapers (multi-shapers) */
+typedef struct {} mri_rate_shaper_t; /* Numeric slot rate per second */
+
+/* Callbacks (TBD) */
+
+/* Input: slot-data, size-of-slot, output-string */
+typedef int (*mri_formatter_cb)(void *, size_t, char *);
+/* Input: type-data, iteration-state-data, output-memory */
+typedef mri_iter_action_t (*mri_iterator_cb)(void *, mri_iter_state_t *, void **);
+/* Input: registered-context, cli-input, size-of-input */
+typedef int (*mri_config_change_cb)(void *, cstring_t, size_t);
+/* Input: list-of-samples, amount-of-samples, output-string */
+typedef int (*mri_shaper_cb)(mri_capture_sample_t *, size_t, char *);
+
+#endif
diff --git a/src/mri_config.c b/src/mri_config.c
new file mode 100644
index 0000000..83e5848
--- /dev/null
+++ b/src/mri_config.c
@@ -0,0 +1,192 @@
+#include "mri_config.h"
+#include "mri_general.h"
+
+#include
+#include
+#include
+
+#include "mri_config_ext.h"
+
+/* Note: remember to update to have proper debugging later */
+cstring_t config_type_to_string(mri_config_value_t type) {
+ switch(type) {
+ case MRI_CONFIG_DOMAIN : return "DOMAIN";
+ case MRI_CONFIG_SERVICE_DISCOVERY_HOST : return "SERVICE_DISCOVERY_HOST";
+ case MRI_CONFIG_SERVICE_DISCOVERY_PORT : return "SERVICE_DISCOVERY_PORT";
+ case MRI_CONFIG_SERVICE_DISCOVERY_TO_USE: return "SERVICE_DISCOVERY_TO_USE";
+ case MRI_CONFIG_REDIS_SD_TIMEOUT : return "REDIS_SD_TIMEOUT";
+ case MRI_CONFIG_REDIS_SD_USE_SENTINEL : return "REDIS_SD_USE_SETINEL";
+ case MRI_CONFIG_REDIS_SD_SENTINEL_MASTER: return "REDIS_SD_SETINEL_MASTER";
+ default : return "UNKNOWN";
+ }
+}
+
+struct mri_configuration {
+ mri_config_data_t config[MRI_CONFIG_MAX_VALUE];
+};
+
+int config_to_cstring_ref(const char *input, mri_config_data_t *output) {
+ output->data = (void *) input;
+ output->length = strnlen(input, MRI_CONFIG_MAX_STRING_SIZE);
+ return 0;
+}
+
+#include
+int config_to_boolean(const char *input, mri_config_data_t *output) {
+ if (0 == strcasecmp(input, "true")) {
+ output->data = (void *) 1;
+ } else if (0 == strcasecmp(input, "false")) {
+ output->data = (void *) 0;
+ } else {
+ MRI_LOG(MRI_ERROR, "Cannot parse boolean config (%s), not false/true", input);
+ return -1;
+ }
+
+ output->length = sizeof(int);
+ return 0;
+}
+
+/* External handlers from project */
+extern int config_to_timeval (const char *, mri_config_data_t *);
+extern int config_to_service_discovery (const char *, mri_config_data_t *);
+
+/* Array of typedef int (config_handler_cb)(const char *input, mri_config_data_t *output); */
+static int (*handle_config_cb[MRI_CONFIG_MAX_VALUE])(const char *, mri_config_data_t *) = {
+ /* Note: we assume input is from main() so no need to allocate */
+ [MRI_CONFIG_DOMAIN] = config_to_cstring_ref,
+ [MRI_CONFIG_SERVICE_DISCOVERY_HOST] = config_to_cstring_ref,
+ [MRI_CONFIG_SERVICE_DISCOVERY_PORT] = config_to_cstring_ref,
+ [MRI_CONFIG_SERVICE_DISCOVERY_TO_USE] = config_to_service_discovery,
+ /* Redis related */
+ [MRI_CONFIG_REDIS_SD_TIMEOUT] = config_to_timeval,
+ [MRI_CONFIG_REDIS_SD_USE_SENTINEL] = config_to_boolean,
+ [MRI_CONFIG_REDIS_SD_SENTINEL_MASTER] = config_to_cstring_ref
+};
+
+static int parse_opts(struct mri_configuration *conf, int argc, char **argv) {
+ int option_index;
+ struct option long_options[] = {
+ { "domain", required_argument, 0, MRI_CONFIG_DOMAIN },
+ { "sd-host", required_argument, 0, MRI_CONFIG_SERVICE_DISCOVERY_HOST },
+ { "sd-port", required_argument, 0, MRI_CONFIG_SERVICE_DISCOVERY_PORT },
+ { "service-discovery", required_argument, 0, MRI_CONFIG_SERVICE_DISCOVERY_TO_USE },
+ { "redis-timeout", required_argument, 0, MRI_CONFIG_REDIS_SD_TIMEOUT },
+ { "redis-with-sentinel", required_argument, 0, MRI_CONFIG_REDIS_SD_USE_SENTINEL },
+ { "redis-sentinel-master", required_argument, 0, MRI_CONFIG_REDIS_SD_SENTINEL_MASTER },
+ { 0, 0, 0, 0 }
+ };
+
+ /* Fetch first option and loop */
+ int c = getopt_long(argc, argv, "", long_options, &option_index);
+ while (0 == c) {
+ struct option *curr_opt = &(long_options[option_index]);
+ MRI_LOG(MRI_DEBUG, "Parsing config-item %s", curr_opt->name);
+
+ int config_item = curr_opt->val;
+ if (unlikely(config_item < 0 || config_item >= MRI_CONFIG_MAX_VALUE)) {
+ MRI_LOG(MRI_ERROR, "Config-item %s (%d) cannot be parsed (out-of-range)", curr_opt->name, config_item);
+ return -1;
+ }
+
+ __auto_type callback = handle_config_cb[config_item];
+ if (unlikely(! callback)) {
+ MRI_LOG(MRI_ERROR, "Config-item %s missing callback handler");
+ return -1;
+ }
+
+ if (0 != callback(optarg, &(conf->config[config_item]))) {
+ MRI_LOG(MRI_WARN, "Config-item %s failed parsing (value = %s) | ignoring", curr_opt->name, optarg);
+ } else {
+ MRI_LOG(MRI_DEBUG, "Config-item %s parsed successfully", curr_opt->name);
+ }
+
+ /* Fetch next option */
+ c = getopt_long(argc, argv, "", long_options, &option_index);
+ }
+
+ return 0;
+}
+
+struct mri_configuration *mri_config_create(int argc, char **argv) {
+ struct mri_configuration *conf =
+ (struct mri_configuration *) calloc(1, sizeof(struct mri_configuration));
+ if (NULL == conf) {
+ MRI_LOG(MRI_ERROR, "Failed to allocate memory for configuration (OOM)");
+ return NULL;
+ }
+
+ /* Parse arguments into configuration */
+ if (argc && 0 != parse_opts(conf, argc, argv)) {
+ mri_config_destroy(conf);
+ return NULL;
+ }
+
+ return conf;
+}
+
+int mri_config_destroy(struct mri_configuration *config) {
+ if (config) free(config);
+ return 0;
+}
+
+int mri_config_get_mutable(struct mri_configuration *config, mri_config_value_t type, void *output, size_t *size) {
+ /* Sanity */
+ if (! config || type < 0 || type > MRI_CONFIG_MAX_VALUE) return -1;
+
+ mri_config_data_t *config_item = &(config->config[type]);
+ if (! config_item->data || ! config_item->length) {
+ MRI_LOG(MRI_ERROR, "Config-item (%s) has no proper data, aborting it", config_type_to_string(type));
+ return -1;
+ }
+
+ if (*size >= config_item->length) {
+ memcpy(output, config_item->data, config_item->length);
+ *size = config_item->length;
+ return 0;
+ }
+
+ MRI_LOG(MRI_WARN,
+ "Config-item (%s) will truncate (need %zu bytes, got buffer with %zu bytes)",
+ config_type_to_string(type),
+ config_item->length,
+ *size);
+ return -1;
+}
+
+int mri_config_get(struct mri_configuration *config, mri_config_value_t type, const void **output, size_t *size) {
+ /* Sanity */
+ if (! config || type < 0 || type > MRI_CONFIG_MAX_VALUE) return -1;
+
+ mri_config_data_t *config_item = &(config->config[type]);
+ *output = config_item->data;
+ *size = config_item->length;
+ return 0;
+}
+
+int mri_config_set_mutable(struct mri_configuration *config, mri_config_value_t type, void *input, size_t size) {
+ /* Sanity */
+ if (! config || type < 0 || type > MRI_CONFIG_MAX_VALUE || ! input || ! size) return -1;
+
+ mri_config_data_t *config_item = &(config->config[type]);
+
+ /* Allocate data */
+ if (! (config_item->data = malloc(size))) {
+ MRI_LOG(MRI_ERROR, "Config-item (%s) cannot allocate %zu bytes (OOM)", config_type_to_string(type), size);
+ return -1;
+ }
+
+ memcpy(config_item->data, input, size);
+ config_item->length = size;
+ return 0;
+}
+
+
+int mri_config_set(struct mri_configuration *config, mri_config_value_t type, void *input, size_t size) {
+ /* Sanity */
+ if (! config || type < 0 || type > MRI_CONFIG_MAX_VALUE) return -1;
+
+ mri_config_data_t *config_item = &(config->config[type]);
+ config_item->data = input;
+ config_item->length = size;
+ return 0;
+}
diff --git a/src/mri_config_ext.h b/src/mri_config_ext.h
new file mode 100644
index 0000000..b620355
--- /dev/null
+++ b/src/mri_config_ext.h
@@ -0,0 +1,20 @@
+#ifndef _MRI_CONFIG_EXT_H_
+#define _MRI_CONFIG_EXT_H_
+
+#include
+
+/* Note: data is never free()'d - stays as long as the program does */
+typedef struct mri_config_data {
+ void *data; /*!< config-item data buffer (allocate if needed) */
+ size_t length; /*!< Length of buffer (if dynamic sizing is needed) */
+} mri_config_data_t;
+
+/**
+ * Signature for config handlers (defined as externals inside mri_config.c)
+ * @param input - input string -> used-data from main()
+ * @param output - where to save configuration after parsing
+ * @return C-style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+typedef int (config_handler_cb)(const char *input, mri_config_data_t *output);
+
+#endif
diff --git a/src/mri_data.hpp b/src/mri_data.hpp
new file mode 100644
index 0000000..8e26f02
--- /dev/null
+++ b/src/mri_data.hpp
@@ -0,0 +1,209 @@
+#ifndef _MRI_DATA_HPP_
+#define _MRI_DATA_HPP_
+
+#include
+#include
+#include
+#include
+#include "mri_typing.hpp"
+
+/* glibc doesn't declare tid_t properly */
+#include
+typedef pid_t tid_t;
+
+/* TODO(omer): Fix this */
+#define mri_assert(cond) (void) 0
+
+/* Declarations (should move soon) */
+struct xtype;
+struct xformat;
+struct xshaper;
+struct mri_slot;
+struct xpath_node;
+struct mri_thread_sched;
+struct slot_capture_data;
+
+using mri_slot_map_t = mri_ordered_map>;
+using mri_subpath_map_t = mri_ordered_map>;
+using mri_capture_map_t = mri_unordered_map>;
+using mri_formatter_map_t = mri_unordered_map>;
+using mri_shaper_map_t = mri_unordered_map>;
+using mri_xtype_map_t = mri_unordered_map>;
+using mri_thread_sched_map_t = mri_ordered_map>;
+using mri_result_t = mri_array;
+using mri_result_set_t = mri_vector;
+using mri_node_result_set_t = mri_vector;
+
+struct xformat {
+ std::string m_name;
+ mri_formatter_cb m_formatter;
+
+ template
+ xformat(string_t &&name, mri_formatter_cb formatter)
+ : m_name(std::forward(name))
+ , m_formatter(formatter)
+ {}
+};
+
+struct xshaper {
+ std::string m_name;
+ mri_shaper_cb m_shaper;
+
+ template
+ xshaper(string_t &&name, mri_shaper_cb shaper)
+ : m_name(std::forward(name))
+ , m_shaper(shaper)
+ {}
+};
+
+struct slot_capture_data {
+ mri_vector samples;
+};
+
+/**
+ * MRI_SLOT
+ * Member of a registered user-defined data structure
+ */
+struct mri_slot {
+ std::string m_name; /*!< Name of the slot (unique per-xtype) */
+ size_t m_offset; /*!< Offset of the slot's data in xtype */
+ size_t m_size; /*!< Size of the slot (used by formatter) */
+ mri_formatter_cb m_formatter; /*!< Reference to formatter (int, str, ...) */
+ mri_shaper_cb m_shaper; /*!< Pointer to slot data-samples shaper_cb */
+ bool m_is_hidden; /*!< Indicator for external usage (to-show) */
+
+ /* TODO(omer): Find a better way (m_shaper doubles as is-slot-shaper indicator) */
+ /* Note: m_shaper will be filled externally for xpath_node::xtype::dump to use */
+ template
+ mri_slot( string_t &&name,
+ size_t offset,
+ size_t size,
+ mri_formatter_cb formatter,
+ bool hidden)
+ : m_name(std::forward(name))
+ , m_offset(offset)
+ , m_size(size)
+ , m_formatter(formatter)
+ , m_shaper(nullptr)
+ , m_is_hidden(hidden)
+ {}
+};
+
+/* TODO(omer): Consider enabling a flat-parse for an xtype (slot, slot, slot, ...) */
+
+/**
+ * XTYPE
+ * Type which is composed from multiple slots (complex type)
+ * Those types are usually being registered to one or more xpath_node
+ */
+struct xtype {
+ std::string m_name; /*!< Name of this X-Type (unique) */
+ size_t m_size; /*!< Size needed to allocate the type */
+ mri_slot_map_t m_slots; /*!< Ordered mapping of all type's slots */
+ struct mri_slot *m_private_key; /*!< Explicit pointer to the type's PK slot */
+ mri_capture_map_t m_slot_capture_map; /*!< Externalize the per-slot data captures */
+
+ template
+ xtype(string_t &&name, size_t size)
+ : m_name(std::forward(name))
+ , m_size(size)
+ , m_slots()
+ , m_private_key(nullptr)
+ , m_slot_capture_map()
+ {}
+
+ /**
+ * Create a slot (data-member)
+ * @param slot - name of the new slot
+ * @param offset - byte offset in data struct (offsetof)
+ * @param size - size in bytes of contained data (sizeof)
+ * @param formatter - format callback for the new slot (to_string)
+ * @param flags - bitwise flags of slot options (pk, rate, hidden, etc...)
+ * @return C-Style boolean (0 = OK, <0 = ERROR) (-ERRNO)
+ */
+ int add_slot(cstring_t slot, size_t offset, size_t size, mri_formatter_cb formatter, int flags);
+
+ /**
+ * Dump the data based on this type
+ * @param input - input data that coresponds to this type
+ * @param rs - result set to add into (one result per-slot)
+ * @return bool - did function work properly
+ */
+ bool xdump(void *input, mri_result_set_t &rs);
+};
+
+struct xnode_get {
+ tid_t m_tid; /*>! Thread whom registered node (origin) */
+ xtype *m_type; /*>! Type coresponding with the node's data */
+ void *m_xdata; /*>! User-defined data (node data internal) */
+ mri_iterator_cb m_xdata_iterator; /*>! User-defined data iterator (tableview) */
+};
+
+struct xnode_set {
+ tid_t m_tid; /*>! Thread whom registered node (origin) */
+ xtype *m_type; /*>! Type coresponding with the node's data */
+ void *m_xcontext; /*>! User-defined data (xcallbck's context) */
+ mri_config_change_cb m_xcallback; /*>! User-defined callback to handle data */
+};
+
+/**
+ * xpath_node
+ * Chains of nodes defining path (/ is root)
+ */
+struct xpath_node {
+ /* General definition of a node */
+ std::string m_name; /*>! Name of node (name unique per parent) */
+ mri_subpath_map_t m_sub_nodes; /*>! Mapping of sub-nodes (each is unique) */
+ xpath_node *m_parent; /*>! Reverse reference to the parent's node */
+
+ /* User-data (specified on registration) */
+ xnode_get m_get_data; /*>! Get context (used for node-querying) */
+ xnode_set m_set_data; /*>! Set context (used for node-configuring) */
+
+ /**
+ * Notes:
+ * 1. No type = empty node, path-building
+ * 2. No parent = root node, others should not do this
+ * 3. get_data/set_data - filled later by registering function
+ * 4. Name cannot contain / (except root) to allow path traversal
+ * 5. Path is defined as ///.../
+ */
+ template
+ xpath_node( string_t &&name,
+ xpath_node *parent)
+ : m_name(std::forward(name))
+ , m_sub_nodes()
+ , m_parent(parent)
+ , m_get_data()
+ , m_set_data()
+ {}
+
+ /**
+ * get_fully_qualified_path()
+ * Most likely used for logging and debugging
+ * @return std::string - full path of the node
+ */
+ std::string get_fully_qualified_path() {
+ if (! m_parent) return "";
+ return m_parent->get_fully_qualified_path() + "/" + m_name;
+ }
+
+ /**
+ * Fetch requested sub-node (get-create mode)
+ * @param name - string_view representing name of the node requested (avoid malloc)
+ * @return xpath_node const * - pointer to the newly created sub-node (nullptr on failure)
+ */
+ xpath_node const *fetch_subnode(std::string_view name);
+
+ /**
+ * Dump the node-data into the result-set
+ * @param rs - reference to result-set to output into
+ * @return bool - did we add anything into result-set
+ */
+ bool xdump(mri_node_result_set_t &rs);
+};
+
+/* Root node is available for all */
+extern xpath_node mri_root;
+
+#endif
diff --git a/src/mri_general.h b/src/mri_general.h
new file mode 100644
index 0000000..498c377
--- /dev/null
+++ b/src/mri_general.h
@@ -0,0 +1,19 @@
+#ifndef _MRI_GENERAL_H_
+#define _MRI_GENERAL_H_
+
+/* TODO(omer): Fix includes */
+#include "mri_logging.h"
+
+#ifndef likely
+ #define likely(x) __builtin_expect((x),1)
+#endif
+#ifndef unlikely
+ #define unlikely(x) __builtin_expect((x),0)
+#endif
+
+#include
+#include
+extern char *__progname;
+#define MRI_LOG(level, fmt, ...) \
+ mri_log(level, "[%s:%d:%s] [%s:%d] " fmt, __FILE__, __LINE__, __PRETTY_FUNCTION__, __progname, (int) syscall(SYS_gettid), ##__VA_ARGS__)
+#endif
diff --git a/src/mri_general.hpp b/src/mri_general.hpp
new file mode 100644
index 0000000..e7190d6
--- /dev/null
+++ b/src/mri_general.hpp
@@ -0,0 +1,20 @@
+#ifndef _MRI_GENERAL_HPP_
+#define _MRI_GENERAL_HPP_
+
+extern "C" {
+ #include "mri_general.h"
+}
+
+template
+struct c_return {};
+
+template <>
+struct c_return {
+ int value;
+
+ constexpr c_return(bool val)
+ : value(val ? 0 : -1)
+ {}
+};
+
+#endif
diff --git a/src/mri_logger.c b/src/mri_logger.c
new file mode 100644
index 0000000..b25e2dd
--- /dev/null
+++ b/src/mri_logger.c
@@ -0,0 +1,39 @@
+/* TODO(omer): Fix include paths */
+#include "../include/mri_logging.h"
+
+#include
+#include
+
+const char *sev2string(mri_severity_t sev) {
+ switch (sev) {
+ case MRI_DEBUG : return "[DEBUG ]";
+ case MRI_NOTICE : return "[NOTICE]";
+ case MRI_INFO : return "[ INFO ]";
+ case MRI_WARNING : return "[ WARN ]";
+ case MRI_ERROR : return "[ERROR ]";
+ case MRI_CRITICAL : return "[ CRIT ]";
+ case MRI_ALERT : return "[ALERT ]";
+ case MRI_EMERG : return "[EMERG ]";
+ default : return "[BUGGED]";
+ }
+}
+
+/* Weak-ref logger functions */
+
+void __attribute__((weak))
+mri_log(mri_severity_t level, const char *fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ mri_vlog(level, fmt, va);
+ va_end(va);
+}
+
+void __attribute__((weak))
+mri_vlog(mri_severity_t level, const char *fmt, va_list args) {
+ time_t time_now;
+ time(&time_now);
+ printf("%s %s ", ctime(&time_now), sev2string(level));
+ vprintf(fmt, args);
+ printf("\n");
+}
+
diff --git a/src/mri_producer.cpp b/src/mri_producer.cpp
new file mode 100644
index 0000000..d14819b
--- /dev/null
+++ b/src/mri_producer.cpp
@@ -0,0 +1,326 @@
+#include