libSQL Self-hosting

You might have heard of Turso, a hosted SQLite solution. Their fork of SQLite, libSQL, is open source, so anyone can play with it for free.

Trouble is, their guide on self-hosting is somewhat lacking, so I figured I’d write down how I got everything up and running for some proper testing.

NOTE: This post is written for a Debian-based Linux system, but it’s probably not that far off for a Mac user.

Read more

Logs as metrics from embedded devices

Getting metrics out of embedded devices is often useful but also challenging due to resource constraints. What I’ve found to work quite well is attaching a debugger and just dumping metrics via printf and then parsing the output.

I always dump the logs into Loki with something like this

command-to-get-logs | promtail \
  --stdin \
  --client.url http://localhost:3100/loki/api/v1/push \
  --client.external-labels="app=my-embedded-device" \
  --server.disable

Than I can just print lines in logfmt:

logfmt: rx_bytes=100 tx_bytes=100
logfmt: rx_bytes=150 tx_bytes=100
logfmt: rx_bytes=400 tx_bytes=120

and parse into a graph in Grafana via LogQL:

Read more

When using a vendor SDK or HAL, always check how callbacks are being run

At my job, I often work with embedded devices. Once, we had a particularly nasty problem with measurements from sensors sometimes returning unexpected values, debug logs getting garbled up, and then there was the occasional crash, too.

Most of our code was unit tested on a desktop. The tests were run with sanitizers, and they reported no issues.

Still, the problems kind of pointed to some kind of memory corruption. A threading issue, perhaps?

Read more

There is more than one localhost

Well, technically I lied. Of course, localhost maps to just 127.0.0.1 but that is not the only address that points to your local machine. There are actually 16.7 million (2^24) addresses, as the loopback network range is 127.0.0.0/8.

I find it particularly useful to have different loopback addresses per project, while keeping their ports consistent. For example:

Project A development environment

  • 127.0.0.2:3000 http server
  • 127.0.0.2:5432 postgres
  • 127.0.0.2:6379 redis

Project B development environment

Read more

Comparing structs in C

What do you think this C code prints?

struct foo first = { 0 };
struct foo second = { 0 };

if (memcmp(&first, &second, sizeof(first)) == 0) {
  printf("equal\n");
} else {
  printf("not equal\n");
}

The corrent answer is: “I don’t know.”

The reason is that we don’t know if the struct contains padding bytes inserted by the compiler. Because if it does, those bytes might not be initialized. The only way to set them is with memset.

Read more

Exhaustive matching for C enums

I like Rust. I like how it forces you to do exhaustive matching in match statements (well, as long as you don’t use a catch-all like _ => {}), so when I add a new enum type, the compiler helplfully reminds me to handle it.

I don’t get to work in Rust nearly as much as I’d like; I usually find myself swimming in the C, instead.

Thankfully, modern compilers like GCC and Clang have a lot of warnings you can enable to help you avoid mistakes. For example, you can get the same kind of exhaustive matching for switch statements that you can get in Rust’s match statements, provided that you don’t add a default case.

Read more