Skip to content

Commit 7346e94

Browse files
authored
Rename library (#1)
* rename library * add tests * add some backtraces, not finished yet * improve comments * rename errno to error * add backtrace creation and tests * add backtrace gathering funcs * save progress * save progress * add errno api * add CDK_ERROR_OPTIMIZE flag * add script to rename library * save progress, new readme * cleanup repo * improve optimization * add test for dumps * enable more tests * fix ci
1 parent 3948a2e commit 7346e94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1007
-2397
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
2323
- name: Build project
2424
run: |
25-
inv build --backtrace --debug --tests --examples
25+
inv build --debug --tests --examples
2626
2727
- name: Run tests
2828
run: |

README.md

Lines changed: 145 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,184 @@
1-
# c_minilib_error
1+
# ⚡ cdk_error
22

3-
**`c_minilib_error`** is a minimal and fast C library for structured error reporting. It supports lightweight stack tracing, optional formatted messages, and a circular ring buffer backend—all designed with embedded and low-overhead systems in mind.
3+
`cdk_error` is a small, modern C library that brings structured error handling into plain C code.
4+
It works much like `errno`, but adds richer context: not just an error code, but also a message and a lightweight backtrace showing where the error has been passed along. All of this comes without any heap allocations, making it safe and fast even for embedded or low-overhead systems.
45

5-
## ✨ Features
6+
What is nice about `cdk_errno` is that on fast path it is as fast as normal `errno`, so if no error occured in the function there is no penalty hit. The difference show only on slow path so when error occurs:
7+
```
8+
❯ ./build/example/bench
9+
5-lvl errno-trace avg: 32.8 ns
10+
5-lvl fmt errno-trace avg: (disabled by CDK_ERROR_OPTIMIZE)
11+
5-lvl int avg: 5.7 ns
12+
```
13+
14+
With `CDK_ERROR_OPTIMIZE` enabled `cdk_error` is like 6-8 times slower than normal errno. This performence hit is perfectly accaptable in most cases, because there will be plenty of other app critical processes wich will slow you more than this 32ns overhead.
15+
16+
---
17+
18+
## 📦 Getting started
19+
20+
The library is header-only. To use it, copy the single header file into **your own project or library**. Each project should keep its own copy to avoid sharing one global error state across unrelated code.
21+
22+
Create a small wrapper header and add one `.c` file that defines the thread-local variables:
23+
24+
```c
25+
// myerror.h
26+
#ifndef MYERROR_H
27+
#define MYERROR_H
28+
29+
#define CDK_ERROR_FSTR_MAX 512
30+
#define CDK_ERROR_BTRACE_MAX 32
31+
#include "cdk_error.h"
32+
33+
#endif
34+
```
35+
36+
```c
37+
// myerror.c
38+
#include "myerror.h"
39+
40+
_Thread_local cdk_error_t cdk_errno = NULL;
41+
_Thread_local struct cdk_Error cdk_hidden_errno = {0};
42+
```
43+
44+
Every other file in your project just includes `myerror.h`.
45+
The `.c` file is required, because it provides the actual definitions of the thread-local globals; without it you’d only have declarations, and the linker would complain.
46+
47+
🔧 If you’d like to change the prefix (for example, from `cdk_` to `my_`), there’s a helper script:
48+
49+
```bash
50+
python3 tools/change_prefix.py \
51+
--inf include/cdk_error.h \
52+
--out my_error.h \
53+
--old cdk --new my
54+
```
55+
56+
This produces a copy of the header with your own prefix, ready to drop into a project.
57+
58+
---
59+
60+
## ❓ Why copy instead of link?
61+
62+
Unlike traditional libraries, `cdk_error` is designed to be embedded into each project separately. The reason is simple: every library or program should have its **own private error state**.
663

7-
- 📌 **Structured error objects**: Includes code, message, file, function, and line
8-
- 🧵 **Inline call trace tracking**: Record stack frames using `cme_return()`
9-
- 🧠 **Formatted errors**: Optional `printf`-style formatting
10-
- ♻️ **Allocation-free runtime**: Uses a circular buffer, no dynamic allocations in hot paths
11-
- 🧪 **Dump utilities**: Export errors to file or string buffer
64+
If multiple components linked against the same global `cdk_errno`, their errors could overwrite each other, leading to confusing or incorrect traces. By copying the header into your project (and optionally renaming the prefix), you guarantee isolation: errors raised inside your library stay inside your library, and don’t clash with others.
1265

13-
## 🔧 Usage
66+
---
67+
68+
## 💡 Usage
69+
70+
Here’s the simplest way to use the errno-style API:
1471

1572
```c
16-
#include "c_minilib_error.h"
73+
#include <stdio.h>
74+
#include "myerror.h"
1775

18-
cme_error_t deep_error(void) {
19-
return cme_errorf(42, "Sensor read failure: value=%d", -1);
76+
const char *nested_failing_func(void) {
77+
if (1) {
78+
cdk_errno = cdk_errnos(ENOBUFS, "My error");
79+
return NULL;
80+
}
81+
return "All good";
2082
}
2183

22-
cme_error_t wrapped_error(void) {
23-
return cme_return(deep_error());
84+
int failing_func(void) {
85+
const char *s = nested_failing_func();
86+
if (!s) {
87+
return cdk_ereturn(-1);
88+
}
89+
return 13;
2490
}
2591

26-
int main(void) {
27-
cme_init();
28-
29-
cme_error_t err = wrapped_error();
30-
if (err) {
31-
char buf[1024];
32-
if (cme_error_dump_to_str(err, sizeof(buf), buf) == 0)
33-
fprintf(stderr, "%s", buf);
34-
cme_error_dump_to_file(err, "trace.log");
92+
void api_entry(void) {
93+
int res = failing_func();
94+
if (res < 0) {
95+
cdk_ewrap();
96+
return;
3597
}
98+
}
3699

37-
cme_destroy();
100+
int main(void) {
101+
char buf[1024];
102+
cdk_errno = 0;
103+
104+
api_entry();
105+
if (cdk_errno) {
106+
cdk_edumps(sizeof(buf), buf);
107+
printf("%s", buf);
108+
return -1;
109+
}
38110
return 0;
39111
}
40112
```
41113
42-
## 🧱 API Overview
114+
The pattern is straightforward: when something fails, set `cdk_errno` with a code or message, return an error value, and wrap it if you need to add another frame. At the top level, dump the error to a buffer or file to see the full trace.
115+
116+
---
117+
118+
### ⚡ Performance
119+
120+
One of the nice things about `cdk_errno` is that on the **fast path** it behaves just like plain `errno`: if no error occurs in a function, there’s no extra overhead at all.
121+
The difference only shows up on the **slow path**, when an error is actually created and propagated:
122+
123+
```
124+
❯ ./build/example/bench
125+
5-lvl errno-trace avg: 32.8 ns
126+
5-lvl fmt errno-trace avg: (disabled by CDK_ERROR_OPTIMIZE)
127+
5-lvl int avg: 5.7 ns
128+
```
129+
130+
With `CDK_ERROR_OPTIMIZE` enabled (which removes formatted errors), the library is about **6–8× slower than a plain `errno` write** when an error occurs.
131+
That sounds big, but remember: it’s \~32 nanoseconds to build a full 5-level trace. In practice this overhead is negligible compared to real application work (I/O, syscalls, allocations, etc.).
43132
44-
| Function | Description |
45-
| -------------------------- | ------------------------------------- |
46-
| `cme_init()` | Allocate internal ring buffer |
47-
| `cme_destroy()` | Free ring buffer |
48-
| `cme_error()` | Create literal string error |
49-
| `cme_errorf()` | Create formatted error |
50-
| `cme_return(err)` | Propagate error and add current frame |
51-
| `cme_error_dump_to_str()` | Dump trace to a buffer |
52-
| `cme_error_dump_to_file()` | Dump trace to a file |
133+
In other words: you get structured errors and backtraces “for free” in the common case, and only pay a small price when something actually goes wrong.
53134
54-
## 🛠️ Building
135+
---
55136
56-
Using [Meson](https://mesonbuild.com/):
137+
138+
## 🛠️ Building examples and tests
139+
140+
This project uses [Meson](https://mesonbuild.com/) for its build system. To build with examples and tests enabled, run:
57141
58142
```sh
59-
meson setup build
143+
meson setup build -Dtests=true -Dexamples=true
60144
meson compile -C build
61145
```
62146

63-
## ✅ Running Tests
147+
Examples will be placed in `build/example/` and test binaries in `build/test/`. You can run them directly:
148+
149+
```sh
150+
./build/example/example_1
151+
./build/example/example_2
152+
```
153+
154+
and execute the full test suite with:
64155

65156
```sh
66157
meson test -C build
67158
```
68159

69-
Unit tests are implemented using [Unity](https://www.throwtheswitch.org/unity).
160+
🧪 Tests are written using the Unity framework, pulled in automatically as a Meson subproject.
70161

71-
## ⚙️ Dev Tools
162+
---
72163

73-
Automate with [Invoke](https://www.pyinvoke.org/):
164+
## ⚙️ Development workflow
165+
166+
For convenience, there’s an [Invoke](https://www.pyinvoke.org/) setup that automates common tasks:
74167

75168
```sh
76-
inv install # Setup tools
77-
inv build # Compile project
78-
inv test # Run test suite
79-
inv format # Apply clang-format
80-
inv lint # Run clang-tidy checks
81-
inv clean # Clean build artifacts
169+
inv install # install meson, ninja, clang-format, clang-tidy, etc.
170+
inv build # configure and compile (add --debug, --tests, --examples)
171+
inv test # run test suite
172+
inv format # apply clang-format
173+
inv lint # run clang-tidy checks
174+
inv clean # remove build artifacts
82175
```
83176

177+
This makes it easy to keep the environment consistent and catch issues early.
178+
179+
---
180+
84181
## 📄 License
85182

86-
Licensed under the [MIT License](LICENSE) © 2025 Jakub Buczynski (KubaTaba1uga).
183+
Released under the [MIT License](LICENSE) © 2025 Jakub Buczynski (KubaTaba1uga).
87184

docs/backtrace_gathering_comparison/README.md

Lines changed: 0 additions & 20 deletions
This file was deleted.

docs/backtrace_gathering_comparison/compile.sh

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)