Git Product home page Git Product logo

legilimens's Introduction

Forum Travis CI build status

Legilimens

A very lightweight single-header C++17 library for runtime introspection and tracing in deeply embedded applications. Legilimens does not introduce any significant overhead and does not interefere with the application when not in use. It can be used to conduct runtime introspection and tracing of hard real-time systems.

Legilimens has to rely on low-level memory aliasing and heavy compile-time computations to ensure lack of interference with the application at runtime, but it is easy to use. In a way, you can think of it as a pico-sized remote debugging server that you can ship with production systems which lies dormant until needed.

Usage

The entirety of the library API is just the following simple collection of entities.

Before we describe them, we need to define what a "probe category" is: a probe category defines a collection of probes that share the same human-readable name and the same type of the sampled value.

  • Macro LEGILIMENS_PROBE(name, reference). Use it to define "probes" - access points for internal states. Legilimens knows what probes are available in the application statically, by constructing a linked list of them during static initialization. The name cannot be longer than 36 characters (the limit may be made configurable in the future).
  • Function Category* findCategoryByName(name). Use it to sample probes by name.
  • Function Category* findCategoryByIndex(index). Like above, but with index instead of human-readable name. Indexes are assigned in an arbitrary way during static initialization. Indexes stay constant as long as the application is running. This is useful if you want to iterate over all available probe categories.
  • Function std::size_t countCategories() returns the number of categories registered in the application.
  • Function Name findFirstNonUniqueCategoryName() returns the name of the first randomly chosen category which shares its name with another category (i.e., if there are values of different types under the same name). This is useful if you want to guarantee that the category names are unique, in which case you should invoke this function shortly after application startup and ensure that it returns an empty name (meaning that all category names are unique).
  • Class Category with its methods Name getName(), TypeDescriptor getTypeDescriptor(), and std::pair<Timestamp, SampledBytes> sample(). Use it to request metadata about sampled values and perform the actual sampling.
// Single header only.
#include <legilimens.hpp>

// Tracing a static variable. So easy.
// The tracing entity is called a "probe", and it is created using a macro as shown below.
static float g_static_value = 123.456F;
LEGILIMENS_PROBE("my_static_value",         // <-- human-readable name for this value
                 g_static_value);           // <-- reference to the value

// Tracing a member variable.
// If the class is instantiated multiple times, the probe will point to the least recently instantiated instance.
// Older instances will be unreachable for tracing until the newer ones are removed.
// Think of it as a stack where you can remove items in a random order.
struct MyClass
{
    double a = 0;                           // <-- we're going to trace this
    LEGILIMENS_PROBE("my_class.a",          // <-- human-readable name
                     a);                    // <-- reference to the member variable defined above
};

void foo()
{
    // Local variables can also be traced!
    int local = 0;
    LEGILIMENS_PROBE("foo.local", local);
}

void bar()
{
    // There may be more than one probe under the same name, and they may refer to differently-typed values.
    float local = 0;
    LEGILIMENS_PROBE("foo.local", local);   // <-- this time it's a float
}

void accessExample()
{
    // In a real application don't forget to check for nullptr.
    auto [timestamp, bytes] = legilimens::findCategoryByName("my_class.a")->sample();
    if (bytes.size())
    {
        // Okay, we have sampled the value (atomically!); its image is stored in 'bytes'.
        // Now we can send these bytes to an external system for inspection, logging, plotting, or whatever.
        // The time is sampled atomically with the image.
        send(timestamp, bytes);
    }
    else
    {
        // The value that we attempted to sample did not exist at the moment.
        // For example, its container (if it is a member variable of a class) or its context
        // (if it's a function-local variable) were nonexistent.
    }

    // You can also list all probes that exist in the application statically.
    // The list of probes is always static and never changes while the application is running.
    // For example, the following calls return valid pointers to Category instances,
    // even if their traced values don't exist at the time of calling.
    assert(legilimens::findCategoryByName("my_class.a"));  // Non-null even if there are no instances of MyClass
    assert(legilimens::findCategoryByName("foo.local"));   // Non-null even if foo() is never invoked
    assert(legilimens::findCategoryByIndex(3));            // Non-null because there are >3 probe categories
}

Looks convoluted, doesn't it? The best way to learn how to use it is to just read its source code (and the unit tests). Luckily, there is not a lot of code -- just a few hundred lines of it.

Requirements

Legilimens requires a full-featured C++17 compiler with the following standard library headers available:

  • cstdint
  • cassert
  • cstring
  • cstddef
  • type_traits
  • tuple
  • array

Legilimens requires Senoval: https://github.com/Zubax/senoval, which is a simple header-only dependency-free C++ utility library for deeply embedded systems. Think of it as a robust replacement of std::vector and stuff that does not use heap, RTTI, or exceptions.

Legilimens does not use heap, RTTI, or exceptions, thus being suitable for deeply embedded high-reliability applications.

Legilimens is time-deterministic and memory-deterministic; it does not contain variable-complexity routines.

Development

Use JetBrains CLion or whatever you're into. Use the test directory as the project root.

This is how you test: cd test && cmake . && make && ./legilimens_test

The code must follow the Zubax Coding Conventions.

Examples

Brief excerpt from a real application

class SeriousBusinessLogic
{
    // <snip>

    CurrentPIController pid_i_d_;
    CurrentPIController pid_i_q_;
    Vector<2> setpoint_unconstrained_u_dq_ = Vector<2>::Zero();
    Vector<2> setpoint_constrained_u_dq_   = Vector<2>::Zero();
    mutable Vector<3> setpoint_u_abc_      = Vector<3>::Zero();

    LEGILIMENS_PROBE("motor.u_dq_uncn_setpoint",           setpoint_unconstrained_u_dq_);
    LEGILIMENS_PROBE("motor.u_dq_cons_setpoint",           setpoint_constrained_u_dq_);
    LEGILIMENS_PROBE("motor.phase_voltage_setpoint",       setpoint_u_abc_);
    LEGILIMENS_PROBE("motor.i_d_pid.error_integral",       pid_i_d_.getIntegral());
    LEGILIMENS_PROBE("motor.i_q_pid.error_integral",       pid_i_q_.getIntegral());

public:
    // <snip>
};
void processRegisterDataRequest(const RegisterDataRequestMessage& request, ResponseSender sender)
{
    RegisterDataResponseMessage response;
    response.name = request.name;
    if (const auto probe_cat = legilimens::findCategoryByName(legilimens::Name(name)))
    {
        const auto [timestamp, sample] = probe_cat->sample();
        convertLegilimensSampleToPopcopRegister(probe_cat->getTypeDescriptor(), sample, response.value);
        response.timestamp = duration_cast<popcop::standard::Timestamp>(timestamp.time_since_epoch());
    }
    response.encode(StreamEmitter(StandardFrameTypeCode, sender).begin());
}

void processRegisterDiscoveryRequest(const RegisterDiscoveryRequestMessage& request, ResponseSender sender)
{
    RegisterDiscoveryResponseMessage response;
    response.index = request.index;
    if (const auto cat = legilimens::findCategoryByIndex(request.index))
    {
        response.name = cat->getName().toString();
    }
    response.encode(StreamEmitter(StandardFrameTypeCode, sender).begin());
}

legilimens's People

Contributors

pavel-kirienko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

legilimens's Issues

Unable to Compile Test

I failed at compiling both from the command line and in VSCode on my Mac. I'm sure I'm missing a step, but here's my output:

> Executing task: make -j10 <

Scanning dependencies of target legilimens_test
[ 33%] Building CXX object CMakeFiles/legilimens_test.dir/test.cpp.o
[ 66%] Building CXX object CMakeFiles/legilimens_test.dir/test_main.cpp.o
In file included from /Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:32:
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:395:78: error: implicit instantiation of undefined template 'std::__1::array<long long, 1000>'
using ContainerElementType = std::decay_t<decltype(*std::declval<Container>().data())>;
                                                                             ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:398:53: note: in instantiation of template type alias 'ContainerElementType' requested here
constexpr std::size_t ContainerElementSize = sizeof(ContainerElementType<Container>);
                                                    ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:450:15: note: in instantiation of variable template specialization 'legilimens::impl_::ContainerElementSize<std::__1::array<long long, 1000> >'
      requested here
static_assert(ContainerElementSize<std::array<std::int64_t, 1000>> == 8);
              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
In file included from /Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:32:
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:450:15: error: static_assert expression is not an integral constant expression
static_assert(ContainerElementSize<std::array<std::int64_t, 1000>> == 8);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:395:78: error: implicit instantiation of undefined template 'std::__1::array<unsigned short, 100>'
using ContainerElementType = std::decay_t<decltype(*std::declval<Container>().data())>;
                                                                             ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:398:53: note: in instantiation of template type alias 'ContainerElementType' requested here
constexpr std::size_t ContainerElementSize = sizeof(ContainerElementType<Container>);
                                                    ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:451:15: note: in instantiation of variable template specialization 'legilimens::impl_::ContainerElementSize<std::__1::array<unsigned short, 100> >'
      requested here
static_assert(ContainerElementSize<std::array<std::uint16_t, 100>> == 2);
              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
In file included from /Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:32:
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:451:15: error: static_assert expression is not an integral constant expression
static_assert(ContainerElementSize<std::array<std::uint16_t, 100>> == 2);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:395:78: error: implicit instantiation of undefined template 'std::__1::array<unsigned char, 10>'
using ContainerElementType = std::decay_t<decltype(*std::declval<Container>().data())>;
                                                                             ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:398:53: note: in instantiation of template type alias 'ContainerElementType' requested here
constexpr std::size_t ContainerElementSize = sizeof(ContainerElementType<Container>);
                                                    ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:452:15: note: in instantiation of variable template specialization 'legilimens::impl_::ContainerElementSize<std::__1::array<unsigned char, 10> >'
      requested here
static_assert(ContainerElementSize<std::array<std::uint8_t, 10>> == 1);
              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
In file included from /Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:32:
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:452:15: error: static_assert expression is not an integral constant expression
static_assert(ContainerElementSize<std::array<std::uint8_t, 10>> == 1);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:454:15: error: no matching function for call to 'getContainerSize'
static_assert(getContainerSize<std::array<std::int64_t, 1000>>() == 1000);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:402:89: note: candidate template ignored: substitution failure [with Container = std::__1::array<long long, 1000>]: implicit instantiation of
      undefined template 'std::__1::tuple_size<std::__1::array<long long, 1000> >'
static constexpr std::enable_if_t<(std::tuple_size<Container>::value > 0), std::size_t> getContainerSize()
                                        ~~~~~~~~~~                                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:409:84: note: candidate template ignored: substitution failure [with Container = std::__1::array<long long, 1000>]: implicit instantiation of
      undefined template 'std::__1::array<long long, 1000>'
static constexpr std::enable_if_t<(Container::SizeAtCompileTime > 0), std::size_t> getContainerSize()
                                   ~~~~~~~~~                                       ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:455:15: error: no matching function for call to 'getContainerSize'
static_assert(getContainerSize<std::array<std::uint16_t, 100>>() == 100);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:402:89: note: candidate template ignored: substitution failure [with Container = std::__1::array<unsigned short, 100>]: implicit instantiation of
      undefined template 'std::__1::tuple_size<std::__1::array<unsigned short, 100> >'
static constexpr std::enable_if_t<(std::tuple_size<Container>::value > 0), std::size_t> getContainerSize()
                                        ~~~~~~~~~~                                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:409:84: note: candidate template ignored: substitution failure [with Container = std::__1::array<unsigned short, 100>]: implicit instantiation of
      undefined template 'std::__1::array<unsigned short, 100>'
static constexpr std::enable_if_t<(Container::SizeAtCompileTime > 0), std::size_t> getContainerSize()
                                   ~~~~~~~~~                                       ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:456:15: error: no matching function for call to 'getContainerSize'
static_assert(getContainerSize<std::array<std::uint8_t, 10>>() == 10);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:402:89: note: candidate template ignored: substitution failure [with Container = std::__1::array<unsigned char, 10>]: implicit instantiation of
      undefined template 'std::__1::tuple_size<std::__1::array<unsigned char, 10> >'
static constexpr std::enable_if_t<(std::tuple_size<Container>::value > 0), std::size_t> getContainerSize()
                                        ~~~~~~~~~~                                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:409:84: note: candidate template ignored: substitution failure [with Container = std::__1::array<unsigned char, 10>]: implicit instantiation of
      undefined template 'std::__1::array<unsigned char, 10>'
static constexpr std::enable_if_t<(Container::SizeAtCompileTime > 0), std::size_t> getContainerSize()
                                   ~~~~~~~~~                                       ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:395:78: error: implicit instantiation of undefined template 'std::__1::array<short, 3>'
using ContainerElementType = std::decay_t<decltype(*std::declval<Container>().data())>;
                                                                             ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:425:69: note: in instantiation of template type alias 'ContainerElementType' requested here
        return CompileTimeTypeDescriptor<TypeDescriptor::deduceKind<ContainerElementType<D>>(),
                                                                    ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:436:55: note: in instantiation of function template specialization
      'legilimens::impl_::constructCompileTimeTypeDescriptor<std::__1::array<short, 3> >' requested here
using CompileTimeTypeDescriptorConstructor = decltype(constructCompileTimeTypeDescriptor<T>());
                                                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:482:15: note: in instantiation of template type alias 'CompileTimeTypeDescriptorConstructor' requested here
static_assert(CompileTimeTypeDescriptorConstructor<std::array<std::int16_t, 3>>::getRuntimeTypeDescriptor()
              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
In file included from /Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:32:
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:436:55: error: no matching function for call to 'constructCompileTimeTypeDescriptor'
using CompileTimeTypeDescriptorConstructor = decltype(constructCompileTimeTypeDescriptor<T>());
                                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:484:15: note: in instantiation of template type alias 'CompileTimeTypeDescriptorConstructor' requested here
static_assert(CompileTimeTypeDescriptorConstructor<std::array<std::int16_t, 3>>::getRuntimeTypeDescriptor()
              ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:416:23: note: candidate template ignored: substitution failure [with T = std::__1::array<short, 3>]
static constexpr auto constructCompileTimeTypeDescriptor()
                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:436:55: error: no matching function for call to 'constructCompileTimeTypeDescriptor'
using CompileTimeTypeDescriptorConstructor = decltype(constructCompileTimeTypeDescriptor<T>());
                                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:486:15: note: in instantiation of template type alias 'CompileTimeTypeDescriptorConstructor' requested here
static_assert(CompileTimeTypeDescriptorConstructor<std::array<std::int16_t, 3>>::getRuntimeTypeDescriptor()
              ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:416:23: note: candidate template ignored: substitution failure [with T = std::__1::array<short, 3>]
static constexpr auto constructCompileTimeTypeDescriptor()
                      ^
/Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:199:38: error: implicit instantiation of undefined template 'std::__1::array<unsigned short, 4>'
        std::array<std::uint16_t, 4> value_b{{
                                     ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tuple:223:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
/Users/bittondb/GoogleDrive/src/legilimens/test/test.cpp:206:9: error: expected a type
        LEGILIMENS_PROBE("b", value_b);
        ^
/Users/bittondb/GoogleDrive/src/legilimens/test/../legilimens.hpp:81:108: note: expanded from macro 'LEGILIMENS_PROBE'
    const ::legilimens::Probe<::legilimens::impl_::CompileTimeTypeDescriptorConstructor<decltype(variable)>, \
                                                                                                           ^
14 errors generated.
make[2]: *** [CMakeFiles/legilimens_test.dir/test.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/legilimens_test.dir/all] Error 2
make: *** [all] Error 2
The terminal process terminated with exit code: 2

I've googled the crap out of this and all I can find is pointing to C++17 but that's correctly added in the CMakeLists.txt. Ideas where I should look?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.