Why I Started Using This Tool

Once the swalive.h header was done, I wrote the first version of the heartbeat source, and it was about as simple as it gets: print "SW alive", sleep five seconds, repeat. Two things sent me back to it. First, after leaving the board running overnight, I had a screen full of identical lines and couldn't tell whether the program had been up all night or had restarted ten minutes earlier. Second, when I ran it over SSH with the output going to a log, it printed nothing for minutes at a time, even though it was running fine. A heartbeat that can't tell you how long it's been alive, or that goes quiet while it's working, isn't doing its one job.

What It Does

swalive.c runs on its own POSIX thread and prints a line every five seconds with a cycle counter and the elapsed time since it started, formatted as HH:MM:SS. SWAlive_start guards against being called twice, sets up every field before creating the thread, prints the PID, and returns the pthread_create error code if something goes wrong. SWAlive_stop flips the running flag, joins the thread, and prints a summary line.

Two new fields in the header

To track uptime and cycles, the struct from the last post gains two fields, and the header now includes <time.h> for time_t:

#include <pthread.h>
#include <time.h>
#include "types.h"

typedef struct swalive_s
{
    pthread_t           thread_id;
    volatile BOOLEAN    running;
    UINT32              interval_sec;
    UINT32              cycle_count;
    time_t              start_time;
} SWAlive;

The whole source file

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "swalive.h"

#define SWALIVE_INTERVAL_SEC   5u

static SWAlive g_swalive;

/* Seconds from a clock that only ever moves forward. */
static time_t now_sec(void)
{
    struct timespec ts;

    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec;
}

/* Turn a number of seconds into "HH:MM:SS". */
static void format_elapsed(time_t secs, char *buf, size_t len)
{
    unsigned long hours   = (unsigned long)(secs / 3600);
    unsigned int  minutes = (unsigned int)((secs % 3600) / 60);
    unsigned int  seconds = (unsigned int)(secs % 60);

    snprintf(buf, len, "%02lu:%02u:%02u", hours, minutes, seconds);
}

static void *swalive_thread(void *arg)
{
    SWAlive *task = (SWAlive *)arg;
    char     elapsed[32];
    UINT32   i;

    while (task->running)
    {
        task->cycle_count++;
        format_elapsed(now_sec() - task->start_time, elapsed, sizeof(elapsed));

        printf("[IAmAlive] heartbeat - process is running cycle counter: %u, "
               "elapsed time since started: %s\n",
               (unsigned int)task->cycle_count, elapsed);

        /* Sleep one second at a time so Stop doesn't wait a full interval. */
        for (i = 0; i < task->interval_sec && task->running; i++)
        {
            sleep(1);
        }
    }

    return NULL;
}

INT32 SWAlive_start(void)
{
    INT32 rc;

    if (g_swalive.running)
    {
        return 0;   /* already running */
    }

    g_swalive.interval_sec = SWALIVE_INTERVAL_SEC;
    g_swalive.cycle_count  = 0;
    g_swalive.start_time   = now_sec();
    g_swalive.running      = TRUE;

    printf("[IAmAlive] started, pid %d, interval %u s\n",
           (int)getpid(), (unsigned int)g_swalive.interval_sec);

    rc = pthread_create(&g_swalive.thread_id, NULL, swalive_thread, &g_swalive);
    if (rc != 0)
    {
        g_swalive.running = FALSE;
        fprintf(stderr, "[IAmAlive] pthread_create failed: %s\n", strerror(rc));
        return rc;
    }

    return 0;
}

void SWAlive_stop(void)
{
    char elapsed[32];

    if (!g_swalive.running)
    {
        return;     /* never started, or already stopped */
    }

    g_swalive.running = FALSE;
    pthread_join(g_swalive.thread_id, NULL);

    format_elapsed(now_sec() - g_swalive.start_time, elapsed, sizeof(elapsed));
    printf("[IAmAlive] stopped after %u cycles, uptime %s\n",
           (unsigned int)g_swalive.cycle_count, elapsed);
}

Why CLOCK_MONOTONIC and not time(NULL)

The start time is a time_t stored in the task's struct, but it's filled from CLOCK_MONOTONIC instead of the wall clock. The DE10-Nano has no battery-backed clock, so it boots thinking it's 1970, and when NTP sets the real date a few seconds later, the wall clock jumps forward by decades. With time(NULL), the very next heartbeat would report 490,000 hours of uptime. The monotonic clock counts from boot and only ever moves forward, so it's the right clock for any duration: timeouts, uptime, or measuring how long something took. Save the wall clock for timestamps a human will read.

Sleeping in one-second slices

A plain sleep(5) would work, but when SWAlive_stop sets running to FALSE, the thread might have just started that sleep, and pthread_join would sit there for up to five seconds. Sleeping one second at a time and checking running between slices means shutting down takes about a second instead of a full interval. That matters more when the interval grows to 30 or 60 seconds.

Start and Stop are safe to call twice

SWAlive_start returns early if the task is already running, so a second call can't create a second thread that overwrites thread_id. It also fills in every field before pthread_create, because the new thread can start running before pthread_create even returns, and it must never see a half-initialized struct. If pthread_create fails, it puts running back to FALSE and returns the error code, which strerror() turns into something readable like "Resource temporarily unavailable". SWAlive_stop does nothing if the task was never started, so calling it on a cleanup path can't hang on a join with a garbage thread ID.

Fixing the printf from my notes

The first draft of the heartbeat line had %/d and /%s in the format string. Those slashes were typos, and %/d isn't a valid conversion at all, so the output was garbled. The counter also needed %u instead of %d, because it's unsigned. Casting it to unsigned int keeps the format and the argument in agreement no matter how UINT32 is defined, and -Wall checks the pairing for you.

Why the log went quiet: stdout buffering

This was the second problem, and it had nothing to do with the thread. When stdout is a terminal, the C library flushes it at every newline. When stdout is a pipe or a file, like an SSH session redirected to a log, it switches to full buffering and holds output in a 4 KB buffer until it fills up. At about 100 bytes per heartbeat, that's roughly 40 lines, or over three minutes of silence, before anything shows up. The program was fine; the output was just sitting in memory.

One option is fflush(stdout) after every printf. I went with a single line at the top of main() instead, which switches stdout to line buffering so every \n flushes, no matter where the output is going:

#include "types.h"
#include "swalive.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    setvbuf(stdout, NULL, _IOLBF, 0);   /* line buffering, so no fflush(stdout) needed */

    if (SWAlive_start() != 0)
    {
        fprintf(stderr, "could not start SW alive task\n");
        return EXIT_FAILURE;
    }

    printf("Main running for 20 seconds.\n");
    sleep(20);

    SWAlive_stop();
    return EXIT_SUCCESS;
}

setvbuf has to be called before anything is printed, which is why it's the first line of main(), and it covers every task in the program, not just this one. For now, main() just lets the heartbeat run for 20 seconds and then stops it. The Ctrl+C handling comes in a later post.

What the output looks like

Piped through cat, so stdout isn't a terminal, every line still shows up on time:

[IAmAlive] started, pid 1234, interval 5 s
Main running for 20 seconds.
[IAmAlive] heartbeat - process is running cycle counter: 1, elapsed time since started: 00:00:00
[IAmAlive] heartbeat - process is running cycle counter: 2, elapsed time since started: 00:00:05
[IAmAlive] heartbeat - process is running cycle counter: 3, elapsed time since started: 00:00:10
[IAmAlive] heartbeat - process is running cycle counter: 4, elapsed time since started: 00:00:15
[IAmAlive] stopped after 4 cycles, uptime 00:00:20

Because the thread's fifth heartbeat and main()'s 20-second sleep end at almost the same moment, you'll sometimes see a fifth line at 00:00:20 before the stop message. Both are correct.

Final Verdict

This is still a small file, but it's the first one in the project that feels like production code instead of a demo. The two lessons I'll carry into every other task are: use the monotonic clock whenever you're measuring a duration, and remember that printf is buffered, so output you can't see isn't proof your program is dead. If you're building your own heartbeat, add the counter and elapsed time from day one. And once you're comfortable, look at naming the thread, adding timestamps and syslog, and eventually feeding a hardware watchdog. That's when a heartbeat goes from telling you something broke to fixing it for you.