{"id":26965602,"url":"https://github.com/zubax/legilimens","last_synced_at":"2025-08-10T03:34:24.911Z","repository":{"id":78927724,"uuid":"124239602","full_name":"Zubax/legilimens","owner":"Zubax","description":"Single-header tracing library for hard real-time embedded systems","archived":false,"fork":false,"pushed_at":"2019-03-04T20:36:29.000Z","size":145,"stargazers_count":10,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-03T07:37:02.089Z","etag":null,"topics":["cpp","cpp17","embedded","embedded-systems","hard-realtime","header-only","monitoring","real-time","single-header","single-header-lib"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Zubax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-07T13:25:19.000Z","updated_at":"2025-02-06T04:52:20.000Z","dependencies_parsed_at":"2023-02-25T08:15:17.452Z","dependency_job_id":null,"html_url":"https://github.com/Zubax/legilimens","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Zubax/legilimens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zubax%2Flegilimens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zubax%2Flegilimens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zubax%2Flegilimens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zubax%2Flegilimens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Zubax","download_url":"https://codeload.github.com/Zubax/legilimens/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zubax%2Flegilimens/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269672052,"owners_count":24457112,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp17","embedded","embedded-systems","hard-realtime","header-only","monitoring","real-time","single-header","single-header-lib"],"created_at":"2025-04-03T07:30:12.051Z","updated_at":"2025-08-10T03:34:24.903Z","avatar_url":"https://github.com/Zubax.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Forum](https://img.shields.io/discourse/https/forum.zubax.com/users.svg)](https://forum.zubax.com)\n[![Travis CI build status](https://travis-ci.org/Zubax/legilimens.svg?branch=master)](https://travis-ci.org/Zubax/legilimens)\n\n# Legilimens\n\nA very lightweight single-header C++17 library for runtime introspection and tracing in deeply embedded applications.\nLegilimens does not introduce any significant overhead and does not interefere with the application when not in use.\nIt can be used to conduct runtime introspection and tracing of hard real-time systems.\n\nLegilimens has to rely on low-level memory aliasing and heavy compile-time computations to ensure lack\nof interference with the application at runtime, but it is easy to use.\nIn a way, you can think of it as a pico-sized remote debugging server that you can ship with production systems\nwhich lies dormant until needed.\n\n## Usage\n\nThe entirety of the library API is just the following simple collection of entities.\n\nBefore we describe them, we need to define what a \"probe category\" is:\na probe category defines a collection of probes that share the same human-readable name\nand the same type of the sampled value.\n\n- Macro `LEGILIMENS_PROBE(name, reference)`. Use it to define \"probes\" - access points for internal states.\nLegilimens knows what probes are available in the application statically, by constructing a linked list of them\nduring static initialization.\nThe name cannot be longer than 36 characters (the limit may be made configurable in the future).\n- Function `Category* findCategoryByName(name)`. Use it to sample probes by name.\n- Function `Category* findCategoryByIndex(index)`. Like above, but with index instead of human-readable name.\nIndexes are assigned in an arbitrary way during static initialization.\nIndexes stay constant as long as the application is running.\nThis is useful if you want to iterate over all available probe categories.\n- Function `std::size_t countCategories()` returns the number of categories registered in the application.\n- Function `Name findFirstNonUniqueCategoryName()` returns the name of the first randomly chosen category\nwhich shares its name with another category (i.e., if there are values of different types under the same name).\nThis is useful if you want to guarantee that the category names are unique,\nin which case you should invoke this function shortly after application startup and ensure that it returns an\nempty name (meaning that all category names are unique).\n- Class `Category` with its methods `Name getName()`, `TypeDescriptor getTypeDescriptor()`, and\n`std::pair\u003cTimestamp, SampledBytes\u003e sample()`.\nUse it to request metadata about sampled values and perform the actual sampling.\n\n```c++\n// Single header only.\n#include \u003clegilimens.hpp\u003e\n\n// Tracing a static variable. So easy.\n// The tracing entity is called a \"probe\", and it is created using a macro as shown below.\nstatic float g_static_value = 123.456F;\nLEGILIMENS_PROBE(\"my_static_value\",         // \u003c-- human-readable name for this value\n                 g_static_value);           // \u003c-- reference to the value\n\n// Tracing a member variable.\n// If the class is instantiated multiple times, the probe will point to the least recently instantiated instance.\n// Older instances will be unreachable for tracing until the newer ones are removed.\n// Think of it as a stack where you can remove items in a random order.\nstruct MyClass\n{\n    double a = 0;                           // \u003c-- we're going to trace this\n    LEGILIMENS_PROBE(\"my_class.a\",          // \u003c-- human-readable name\n                     a);                    // \u003c-- reference to the member variable defined above\n};\n\nvoid foo()\n{\n    // Local variables can also be traced!\n    int local = 0;\n    LEGILIMENS_PROBE(\"foo.local\", local);\n}\n\nvoid bar()\n{\n    // There may be more than one probe under the same name, and they may refer to differently-typed values.\n    float local = 0;\n    LEGILIMENS_PROBE(\"foo.local\", local);   // \u003c-- this time it's a float\n}\n\nvoid accessExample()\n{\n    // In a real application don't forget to check for nullptr.\n    auto [timestamp, bytes] = legilimens::findCategoryByName(\"my_class.a\")-\u003esample();\n    if (bytes.size())\n    {\n        // Okay, we have sampled the value (atomically!); its image is stored in 'bytes'.\n        // Now we can send these bytes to an external system for inspection, logging, plotting, or whatever.\n        // The time is sampled atomically with the image.\n        send(timestamp, bytes);\n    }\n    else\n    {\n        // The value that we attempted to sample did not exist at the moment.\n        // For example, its container (if it is a member variable of a class) or its context\n        // (if it's a function-local variable) were nonexistent.\n    }\n\n    // You can also list all probes that exist in the application statically.\n    // The list of probes is always static and never changes while the application is running.\n    // For example, the following calls return valid pointers to Category instances,\n    // even if their traced values don't exist at the time of calling.\n    assert(legilimens::findCategoryByName(\"my_class.a\"));  // Non-null even if there are no instances of MyClass\n    assert(legilimens::findCategoryByName(\"foo.local\"));   // Non-null even if foo() is never invoked\n    assert(legilimens::findCategoryByIndex(3));            // Non-null because there are \u003e3 probe categories\n}\n```\n\nLooks convoluted, doesn't it?\nThe best way to learn how to use it is to just read its source code (and the unit tests).\nLuckily, there is not a lot of code -- just a few hundred lines of it.\n\n## Requirements\n\nLegilimens requires a full-featured C++17 compiler with the following standard library headers available:\n\n- `cstdint`\n- `cassert`\n- `cstring`\n- `cstddef`\n- `type_traits`\n- `tuple`\n- `array`\n\nLegilimens requires Senoval: \u003chttps://github.com/Zubax/senoval\u003e, which is a simple header-only dependency-free\nC++ utility library for deeply embedded systems. Think of it as a robust replacement of `std::vector`\nand stuff that does not use heap, RTTI, or exceptions.\n\nLegilimens **does not use** heap, RTTI, or exceptions, thus being suitable for deeply embedded\nhigh-reliability applications.\n\nLegilimens is time-deterministic and memory-deterministic;\nit does not contain variable-complexity routines.\n\n## Development\n\nUse JetBrains CLion or whatever you're into. Use the `test` directory as the project root.\n\nThis is how you test: `cd test \u0026\u0026 cmake . \u0026\u0026 make \u0026\u0026 ./legilimens_test`\n\nThe code must follow the [Zubax Coding Conventions](https://kb.zubax.com/x/84Ah).\n\n## Examples\n\n### Brief excerpt from a real application\n\n```c++\nclass SeriousBusinessLogic\n{\n    // \u003csnip\u003e\n\n    CurrentPIController pid_i_d_;\n    CurrentPIController pid_i_q_;\n    Vector\u003c2\u003e setpoint_unconstrained_u_dq_ = Vector\u003c2\u003e::Zero();\n    Vector\u003c2\u003e setpoint_constrained_u_dq_   = Vector\u003c2\u003e::Zero();\n    mutable Vector\u003c3\u003e setpoint_u_abc_      = Vector\u003c3\u003e::Zero();\n\n    LEGILIMENS_PROBE(\"motor.u_dq_uncn_setpoint\",           setpoint_unconstrained_u_dq_);\n    LEGILIMENS_PROBE(\"motor.u_dq_cons_setpoint\",           setpoint_constrained_u_dq_);\n    LEGILIMENS_PROBE(\"motor.phase_voltage_setpoint\",       setpoint_u_abc_);\n    LEGILIMENS_PROBE(\"motor.i_d_pid.error_integral\",       pid_i_d_.getIntegral());\n    LEGILIMENS_PROBE(\"motor.i_q_pid.error_integral\",       pid_i_q_.getIntegral());\n\npublic:\n    // \u003csnip\u003e\n};\n```\n\n```c++\nvoid processRegisterDataRequest(const RegisterDataRequestMessage\u0026 request, ResponseSender sender)\n{\n    RegisterDataResponseMessage response;\n    response.name = request.name;\n    if (const auto probe_cat = legilimens::findCategoryByName(legilimens::Name(name)))\n    {\n        const auto [timestamp, sample] = probe_cat-\u003esample();\n        convertLegilimensSampleToPopcopRegister(probe_cat-\u003egetTypeDescriptor(), sample, response.value);\n        response.timestamp = duration_cast\u003cpopcop::standard::Timestamp\u003e(timestamp.time_since_epoch());\n    }\n    response.encode(StreamEmitter(StandardFrameTypeCode, sender).begin());\n}\n\nvoid processRegisterDiscoveryRequest(const RegisterDiscoveryRequestMessage\u0026 request, ResponseSender sender)\n{\n    RegisterDiscoveryResponseMessage response;\n    response.index = request.index;\n    if (const auto cat = legilimens::findCategoryByIndex(request.index))\n    {\n        response.name = cat-\u003egetName().toString();\n    }\n    response.encode(StreamEmitter(StandardFrameTypeCode, sender).begin());\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzubax%2Flegilimens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzubax%2Flegilimens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzubax%2Flegilimens/lists"}