perf_counter measures cycles on a Cortex-M without stealing your SysTick
A dedicated performance counter mainly for micro-controllers. For Cortex-M processors, the Systick will be used by default. The `perf_counter` shares the SysTick with users' original SysTick function(s) without interfering with it. This library will bring new functionalities.
At a glance
- What is it?
- This is a dedicated cycle counter for microcontrollers, and its central trick is that it shares the Cortex-M SysTick with whatever SysTick handler the application already has, without interfering with it. On top of that it adds delay services, timestamps, stack usage measurement, coroutine support and a porting template, in a library that deploys by drag and drop, as a CMSIS-Pack, or as an RT-Thread package.
- Who is it for?
- perf_counter fits a Cortex-M project that needs to know where its cycles go, that already runs an RTOS, or that needs stack watermarks per task, and it does not fit a project that only wants a delay function, since the delay services come with a counter and a set of conventions.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 85 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A cycle counter that leaves your SysTick alone
perf_counter is a dedicated performance counter mainly for micro-controllers. For Cortex-M processors the SysTick will be used by default, and the key property is that the counter shares the SysTick with the user's original SysTick function or functions without interfering with it.
That is the problem it solves. On a microcontroller the SysTick is the one timer most projects already use, usually through the RTOS tick or through a delay implementation, and a performance counter that wants exclusive use of the counter will either conflict with that or force you to move your timing to a different peripheral. This library takes the opposite approach: it wraps the existing SysTick, and your handler keeps running.
The library also positions itself as bringing new functionalities beyond the counting itself, naming a performance counter, delay services in microseconds and milliseconds, and a clock service defined in time.h.
The porting story is part of the design rather than an afterthought. A dedicated template is provided to port the library to different architectures, or to use a different timer instead of SysTick on Cortex-M. That matters because the counter is a hardware peripheral binding, and a library that could only run on one timer with one compiler would be a much narrower tool than this one.
The repository layout reflects the same structure. There is the core perf_counter.c and perf_counter.h with perfc_common.h, a default port in perfc_port_default.c and perfc_port_default.h, a separate Performance Monitor Unit port in perfc_port_pmu.c and perfc_port_pmu.h, three SysTick wrappers for the GNU, the UAL and the GCC assemblers, a task layer with perfc_task_coroutine.c and perfc_task_pt.h, an os directory, a benchmark directory, a cmsis-pack directory and a template directory.
Measuring a segment, and reading the result without printf
The primary API is a macro that wraps a code segment, and the result can be read through a local variable instead of being printed.
The syntax is:
__cycleof__(<Description String for the target>, [User Code, see ref 1]) {
//! target code segment of measurement
...
}The first parameter is a description string for the target, and the second is an optional small piece of user code that reads the measurement through a local variable named __cycle_count__. If you leave the user code out, the measured result is shown with the __perf_counter_printf__ call instead.
The note attached to it matters in practice: the first parameter cannot be ignored, and if you do not want a description you have to pass an empty string.
The simple form prints the result:
__cycleof__("") {
foreach(example_lv0_t, s_tItem, ptItem) {
__perf_counter_printf__("Processing item with ID = %d\r\n", _->chID);
}
}The fuller form stores the result and lets you do your own reporting, which is what you want in a system where printing inside a measurement is itself a cost:
int64_t lCycleResult = 0;
/* measure cycles and store it in a dedicated variable without printf */
__cycleof__("delay_us(1000ul)",
/* insert code to __cycleof__ body, "{}" can be omitted */
{
lCycleResult = __cycle_count__; /*< "__cycle_count__" stores the result */
}) {
perfc_delay_us(1000ul);
}The macro is a wrapper around get_system_ticks(), so the underlying tick source is the same one the timestamp services use.
RTOS-aware measurement that removes scheduling cost
The feature that separates this from a plain counter is the set of measurement services designed for RTOS environments.
The library measures raw, or true, cycles used by a code segment inside a thread, and the README is explicit that the scheduling cost is removed. That is the whole point. In a preemptive RTOS, a naive wall-clock measurement of a thread's code segment includes the time the thread was descheduled, so the number tells you about the scheduler, not about your algorithm. The library separates the two so that what you measure is the time your code actually occupied the CPU.
There is a second measurement service for the raw or true cycles used by a data processing path across multiple threads, which is a harder problem: the work is spread over several contexts, and the scheduling cost has to be excluded from each of them.
The other feature-level services are a Coremark 1.0 addition, so you can run a known benchmark inside your own build, and a timer service provided automatically for EventRecorder, which is the standard trace recorder for embedded tooling.
On top of that there is a layer of convenience macros and functions for the everyday case: helper macros such as __cycleof__(), __super_loop_monitor__(), __cpu_usage__() and __cpu_perf__(), and helper functions such as start_cycle_counter() and stop_cycle_counter().
The practical result is that you can measure a code segment, a whole task, or a path that crosses threads, and get numbers that mean the same thing in each case.
Delay and timestamp services that also work below 1 MHz
The time-based services are more than a convenience wrapper around a busy loop, and two details in the feature list are worth knowing.
The delay functions perfc_delay_us() and perfc_delay_ms() return a 64-bit value, which matters on a microcontroller where a 32-bit microsecond counter wraps in about 71 minutes. The 64-bit return is how the caller learns how long the delay actually took rather than trusting the request.
Second, the delay functions add weak entries named perfc_delay_us_user_code_in_loop() and perfc_delay_ms_user_code_in_loop(), which users can override, for example to feed the watchdog while the delay runs. On a device with a watchdog, a long delay without servicing the watchdog is a reboot, and making the hook weak means the default behaviour is a plain delay and the override is one function.
The timestamp services are get_system_ticks(), get_system_us() and get_system_ms().
Two more conditions are documented. Passing false to perfc_init() allows the library to be used in ISRs, or where global interrupt handling is disabled, so the counter does not insist on being the only thing using the timer. And the microsecond related APIs can be called even when the system timer clock runs below 1 MHz, which is a real constraint on low-power parts.
The RTOS integration goes further: the library supports SysTick reconfiguration and a change of system frequency, so a project that changes its clock at runtime can keep the timing services honest.
Stack watermarks, coroutines and an RTOS safe mode
Several features here are about not corrupting the system rather than about measuring it, and they are the ones a firmware reviewer will ask about.
In an RTOS environment the library supports stack overflow detection through perfc_check_task_stack_canary_safe(), and it adds a __PERFC_SAFE macro to avoid blocking high priority ISRs and tasks. That macro is configured by the user, who defines the system timer priority level with __PERFC_SYSTIMER_PRIORITY__, and in Cortex-M the value 0 means the highest configurable exception level. The reasoning is straightforward: a measurement routine that runs at the wrong interrupt priority can starve the code it is measuring.
The stack measurement macros are __stack_usage__() and __stack_usage_max() for a code segment, plus an ISR() macro that measures the stack usage of a Cortex-M exception handler. The ISR variant is enabled by defining __PERFC_STACK_CHECK_IN_ISR__ in the project configuration, and the watermark value can be overridden with __PERFC_STACK_WATERMARK_U32__, where the default is 0xDEADBEEF. Architectures whose stacks grow upward are supported by defining __PERFC_STACK_GROWS_UPWARD__.
There is also C language extension work: macros to detect the compiler, such as __IS_COMPILER_ARM_COMPILER_6__ and __IS_COMPILER_LLVM__, and to detect features such as __COMPILER_HAS_GNU_EXTENSIONS__, __IS_COMPILER_SUPPORT_C99__ and __IS_COMPILER_SUPPORT_C11__. There are language helpers in the style of other languages, a VB-like with(), foreach(), dimof() and CONNECT(), a C#-like using(), a simple overload mechanism, and an __IRQ_SAFE{...} macro for atomicity, plus __perfc_sync_barrier__() for a code barrier.
Coroutines are supported with a stack watermark, and perfc_coroutine_stack_remain() returns the stack usage information, with the stack checking feature disabled by defining __PERFC_COROUTINE_NO_STACK_CHECK__ in the compilation command line. ProtoThread support comes with or without the coroutine, and adds a timeout feature to the wait functions.
Deployment, compiler coverage and deprecated names
Three things determine how easily this library lands in an existing project.
The first is deployment. There is drag-and-drop deployment for Arm Compiler 5 and Arm Compiler 6, a CMSIS-Pack is available with the package description file at the repository root, a gen_pack.sh script to generate it, and an RT-Thread package. The repository also carries a SConscript, so a SCons based build can include it, and a CI directory for the build matrix.
The second is compiler coverage, and the README claims support for most of the arm compilers: Arm Compiler 5 with armcc, Arm Compiler 6 with armclang, arm gcc, LLVM and IAR. The three SysTick wrapper files in the tree, one each for the GNU, the UAL and the GCC assemblers, are the visible evidence of that, because a library that shares a hardware timer with your code has to get in at the assembler level.
The third is the naming, and here there is a migration to plan for. A list of functions and macros is deprecated in favour of versions with the perfc_ prefix: init_cycle_counter() becomes perfc_init(), delay_us() becomes perfc_delay_us(), delay_ms() becomes perfc_delay_ms(), CONNECT() becomes PERFC_CONNECT(), using() becomes perfc_using(), with() becomes perfc_with() and foreach() becomes perfc_foreach(). You can define __PERFC_NO_DEPRECATED__ to disable the aliases entirely, which is the setting to use in a new project so that the old names cannot be used by accident.
The release history shows a steady project: v2.5.5, described as a stable release, on 2026-07-30, v2.5.4 in November 2025 and v2.5.3 before that, with the last push on 2026-07-30. The default branch is CMSIS-Pack, which matches the way the package is distributed. The licence is Apache-2.0.
Editorial conclusion
perf_counter fits a Cortex-M project that needs to know where its cycles go, that already runs an RTOS, or that needs stack watermarks per task, and it does not fit a project that only wants a delay function, since the delay services come with a counter and a set of conventions. Before you adopt it, decide how you will integrate the SysTick, since the library shares the timer with your existing handler, and read the deprecation list, because a number of the original names now have perfc-prefixed replacements. The project is Apache-2.0 licensed, release v2.5.5 was published on 2026-07-30, and the last push was on 2026-07-30.
Frequently asked questions
How does perf_counter use the Cortex-M SysTick without breaking my own handler?
The library shares the SysTick with the user's original SysTick function or functions without interfering with it, and it does so through the SysTick wrapper files for the GNU, UAL and GCC assemblers. SysTick reconfiguration is supported for projects that need to change it.
How do I measure the cycles used by a code segment with perf_counter?
Wrap the segment in the __cycleof__ macro, passing a description string as the first argument and optionally a small piece of user code that reads the local variable __cycle_count__. If you omit the user code, the result is printed with __perf_counter_printf__ instead. The first argument cannot be left out, so pass an empty string if you do not want a description.
What does perf_counter do differently in an RTOS?
It measures raw or true cycles inside a thread with the scheduling cost removed, and it can measure a data processing path across multiple threads that way too. It also supports stack overflow detection in an RTOS environment through perfc_check_task_stack_canary_safe(), and the __PERFC_SAFE macro avoids blocking high priority ISRs and tasks.
Which compilers and deployment methods does perf_counter support?
The README lists Arm Compiler 5 with armcc, Arm Compiler 6 with armclang, arm gcc, LLVM and IAR. For deployment there is drag-and-drop installation for Arm Compiler 5 and 6, a CMSIS-Pack with a package description file at the repository root, an RT-Thread package, and a SConscript for SCons based builds.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gorgonmeducer-perf-counter)Was this useful?