Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 13 additions & 92 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,96 +1,17 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
BasedOnStyle: Mozilla
IndentWidth: 4
ColumnLimit: 120
Standard: c++20
IndentExternBlock: NoIndent
AlwaysBreakAfterDefinitionReturnType: None
BreakAfterReturnType: None
SpaceAfterTemplateKeyword: true
AllowShortFunctionsOnASingleLine: All
AlignConsecutiveAssignments: true
AlignConsecutiveBitFields: true
AlignConsecutiveDeclarations: true
AlignConsecutiveMacros: true
AlignConsecutiveShortCaseStatements: { Enabled: true }
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
AfterExternBlock: false # Keeps the contents un-indented.
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
# BreakInheritanceList: AfterColon
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ (coverity|pragma:)'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeBlocks: Preserve
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 10000 # Raised intentionally; prefer breaking all
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 10000 # Raised intentionally because it hurts readability
PointerAlignment: Left
ReflowComments: true
SortIncludes: CaseInsensitive
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeCtorInitializerColon: true
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: Never
...
SortIncludes: false
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Checks: >-
-llvm-header-guard,
-modernize-concat-nested-namespaces,
-modernize-type-traits,
-modernize-use-auto,
-modernize-use-constraints,
-modernize-use-default-member-init,
-modernize-use-nodiscard,
Expand All @@ -30,6 +31,7 @@ Checks: >-
-bugprone-suspicious-include,
-misc-include-cleaner,
-cppcoreguidelines-pro-type-vararg,
-hicpp-use-auto,
-hicpp-vararg,
CheckOptions:
- key: readability-function-cognitive-complexity.Threshold
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:

- name: Install Dependencies
run: |
sudo apt update
sudo apt install gcc-multilib g++-multilib clang-tidy
g++ --version
clang-tidy --version
Expand Down Expand Up @@ -58,7 +59,10 @@ jobs:
- uses: actions/checkout@v4

- name: Install Dependencies
run: sudo apt install gcc-multilib g++-multilib
run: |
sudo apt update
sudo apt install gcc-multilib g++-multilib
g++ --version

- name: Configure CMake
run: >
Expand All @@ -82,9 +86,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DoozyX/clang-format-lint-action@v0.18.1
- uses: DoozyX/clang-format-lint-action@v0.20
with:
source: '.'
exclude: './lib'
extensions: 'c,h,cpp,hpp'
clangFormatVersion: 18
clangFormatVersion: 20
47 changes: 47 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Repository Guidelines

## Project Structure & Module Organization
- `include/olga_scheduler/`: public single-file headers.
- `tests/`: GoogleTest suites (`test_*.cpp`, `test_*.c`) plus demos.
- `lib/cavl/`: vendored CAVL header-only dependency used by the scheduler.
- `CMakeLists.txt`: build, test, formatting, static-analysis, and coverage wiring.

## Build, Test, and Development Commands
Common CMake workflow:
```sh
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
```
Format sources (requires `clang-format`):
```sh
cmake --build build --target format
```
Static analysis is enabled by default and requires `clang-tidy`. Disable with:
```sh
cmake -S . -B build -DNO_STATIC_ANALYSIS=1
```
Coverage build (requires `gcovr` and GCC/Clang):
```sh
cmake -S . -B build-coverage -DOLGA_ENABLE_COVERAGE=ON
cmake --build build-coverage --target coverage
```
Build the C demo:
```sh
cmake --build build --target olga_scheduler_c_demo
```

## Coding Style & Naming Conventions
- C99 for C code and C++20 for C++ code.
- Formatting is defined in `.clang-format` (Mozilla base, 4-space indent, 120 column limit). Use `clang-format` via the `format` target.
- `clang-tidy` is enforced (warnings as errors). Keep code warning-free; the project builds with `-Wall -Wextra -Werror -pedantic`.
- Tests are named `test_*.c` / `test_*.cpp`. Headers stay in `include/olga_scheduler/`.

## Testing Guidelines
- Tests use GoogleTest (fetched via CMake `FetchContent`).
- Run with `ctest` from the build directory (see commands above).
- Coverage target enforces 100% line and branch coverage for `include/olga_scheduler/olga_scheduler.h` (C API only), so keep coverage updates in sync with header changes.

## Commit & Pull Request Guidelines
- Commit history on feature branches is irrelevant as we use squash merging only.
- PRs should clearly describe behavioral changes, list tests run (e.g., `ctest --test-dir build`), and link related issues/PRs when applicable.
5 changes: 0 additions & 5 deletions CHANGELOG.md

This file was deleted.

49 changes: 48 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ if (NOT clang_format)
message(STATUS "Could not locate clang-format")
else ()
file(GLOB format_files
${CMAKE_CURRENT_SOURCE_DIR}/include/*.[ch]pp
${CMAKE_CURRENT_SOURCE_DIR}/include/*/*.h*
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.[ch]*
)
message(STATUS "Using clang-format: ${clang_format}; files: ${format_files}")
add_custom_target(format COMMAND ${clang_format} -i -fallback-style=none -style=file --verbose ${format_files})
Expand All @@ -47,6 +48,36 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion -Wcast-align -Wmissing
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wtype-limits -Wnon-virtual-dtor -Woverloaded-virtual")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")

option(OLGA_ENABLE_COVERAGE "Enable coverage instrumentation and reporting" OFF)
if (OLGA_ENABLE_COVERAGE)
# Coverage builds are optimized for instrumentation rather than static analysis.
set(CMAKE_CXX_CLANG_TIDY "")

if ((CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang"))
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g --coverage -DNDEBUG")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g --coverage -DNDEBUG")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
else ()
message(WARNING "Coverage is enabled but the compiler may not support --coverage flags.")
endif ()

find_program(gcovr_path NAMES gcovr)
if (NOT gcovr_path)
message(FATAL_ERROR "gcovr not found; install it or set OLGA_ENABLE_COVERAGE=OFF.")
endif ()
add_custom_target(coverage
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
COMMAND ${gcovr_path}
-r ${CMAKE_CURRENT_SOURCE_DIR}
--filter ${CMAKE_CURRENT_SOURCE_DIR}/include/olga_scheduler/olga_scheduler\\.h
--exclude ${CMAKE_CURRENT_SOURCE_DIR}/include/olga_scheduler/olga_scheduler\\.hpp
--txt-metric branch
--fail-under-line 100
--fail-under-branch 100
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endif ()

add_executable(test_olga_scheduler_cpp20 ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_olga_scheduler.cpp)
set_target_properties(test_olga_scheduler_cpp20 PROPERTIES CXX_STANDARD 20)
target_include_directories(test_olga_scheduler_cpp20 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/olga_scheduler)
Expand All @@ -58,3 +89,19 @@ target_link_libraries(test_olga_scheduler_cpp20

include(GoogleTest)
gtest_discover_tests(test_olga_scheduler_cpp20)

add_executable(test_olga_scheduler_capi ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_olga_scheduler_c.cpp)
set_target_properties(test_olga_scheduler_capi PROPERTIES CXX_STANDARD 20)
target_include_directories(test_olga_scheduler_capi PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/olga_scheduler)
target_include_directories(test_olga_scheduler_capi SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib/cavl)
target_link_libraries(test_olga_scheduler_capi
PRIVATE
GTest::gmock_main
)
gtest_discover_tests(test_olga_scheduler_capi)

add_executable(olga_scheduler_c_demo ${CMAKE_CURRENT_SOURCE_DIR}/tests/olga_scheduler_c_demo.c)
set_target_properties(olga_scheduler_c_demo PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED YES C_EXTENSIONS OFF)
target_include_directories(olga_scheduler_c_demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/olga_scheduler)
target_include_directories(olga_scheduler_c_demo SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib/cavl)
target_compile_options(olga_scheduler_c_demo PRIVATE -Wall -Wextra -Werror -pedantic)
98 changes: 93 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![Main Workflow](https://github.com/zubax/olga_scheduler/actions/workflows/main.yml/badge.svg)](https://github.com/zubax/olga_scheduler/actions/workflows/main.yml)

Generic single-file implementation of scheduler suitable for deeply embedded systems.
Generic single-file implementation of an EDF scheduler suitable for deeply embedded systems.
"OLGa" is a reference to the fact that it has a logarithmic asymptotic complexity.
**Simply copy `olga_scheduler.hpp` into your project tree and you are ready to roll.**
The only dependency is the CAVL (`cavl.hpp`) header-only library
(>= [v3.1.0](https://github.com/pavel-kirienko/cavl/tree/3.1.0)).

**Simply copy `olga_scheduler.hpp` (C++) or `olga_scheduler.h` (C) into your project tree and you are ready to roll.**
The only dependency is the [CAVL header-only library](https://github.com/pavel-kirienko/cavl).

The usage instructions are provided in the comments.
The code is fully covered by manual tests with full state space exploration.
Expand All @@ -17,4 +17,92 @@ To release a new version, simply create a new tag.
<!--suppress CheckImageSize, HtmlDeprecatedAttribute -->
<p align="center">
<img src="/docs/St_Olga_by_Nesterov_in_1892_(cropped).jpg" alt="Olga of Kiev" width=256>
</p>
</p>

## Examples

### C

```c
#include "olga_scheduler.h"

#include <inttypes.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

static int64_t get_microseconds(olga_t* sched)
{
(void)sched;
struct timespec ts;
(void)clock_gettime(CLOCK_MONOTONIC, &ts);
return ((int64_t)ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
}

static void handler(olga_t* sched, olga_event_t* event, int64_t now)
{
uint64_t* counter = (uint64_t*)event->user;
++*counter;
printf("counter=%" PRIu64 " now=%" PRId64 "\n", *counter, now);
// Keep events triggering exactly at 1 Hz.
olga_defer(sched, event->deadline + 1000000, event->user, handler, event);
}

int main(void)
{
olga_t sched;
olga_init(&sched, NULL, get_microseconds);

uint64_t counter = 0;
olga_event_t event = OLGA_EVENT_INIT;
olga_defer(&sched, sched.now(&sched) + 1000000, &counter, handler, &event);

for (;;) {
olga_spin_result_t spin_result = olga_spin(&sched);
(void)spin_result; // Optional performance information here.
const struct timespec delay = { .tv_sec = 0, .tv_nsec = 1000 * 1000 };
(void)nanosleep(&delay, NULL); // Do something else here: IO multiplexing, update scheduler stats, etc.
if (counter > 10) {
olga_cancel(&sched, &event);
puts("Event canceled");
break;
}
}
return 0;
}
```

### C++

```cpp
#include "olga_scheduler.hpp"

#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>

int main()
{
using namespace std::chrono_literals;

olga_scheduler::EventLoop<std::chrono::steady_clock> loop;
std::uint64_t counter = 0;

auto evt = loop.repeat(1s, [&](const auto& arg) {
++counter;
std::cout << "counter=" << counter
<< " now=" << arg.approx_now.time_since_epoch().count()
<< '\n';
if (counter > 10) {
arg.event.cancel();
}
});

while (!loop.isEmpty()) {
(void)loop.spin();
std::this_thread::sleep_for(1ms); // Do something else here.
}
return 0;
}
```
Loading