articles

sysinfo: version 0.22 and FreeBSD support

The sysinfo crate aims to provide systems' information.

In this blog post, we will go through this (huge) sysinfo release and more particularly what I needed to do to add the FreeBSD support and what issues I encountered along the way.

API changes

A big change was the add of the possibility to specify what you want to refresh more specifically in the processes. You can disable computing the CPU usage and the disk usage. To do so:

Runlet mut s = System::new();

s.refresh_processes_specifics(ProcessRefreshKind::new());

The API of ProcessRefreshKind is very close to the RefreshKind one.

I created ProcessExt::run_time which returns how many seconds this process has been alive. I also updated ProcessExt::start_time which now returns the start time (in seconds as well).

ProcessExt::kill now sends Signal::Kill and doesn't take argument anymore. To do so, use ProcessExt::kill_with. It'll return None if the signal isn't supported on this system.

FreeBSD support

Adding FreeBSD support into sysinfo wasn't very difficult in itself. Getting system information is not too complicated and contrary to linux, instead of making a lot of calls to get different information, you make one call and get a lot at once. The big problem was mostly the lack of support from the libc crate which made me open a lot of pull requests:

Thanks a lot to @Amanieu and @JohnTitor for their help and their reviews!

As mentioned above, FreeBSD APIs provide all the information you need and even more. However there is a small problem: there are often multiple APIs to do the same thing. For example, when I needed to loop through processes, I had the choice between kvm, procstat and a "system" API which is very low level.

Now, the interesting part is that procstat is using kvm which is using the "system" API. So at this point, why not simply using the highest API level and be done with it? Well, procstat is using some static variables, which isn't compatible with sysinfo's multi-threaded approach. Luckily, kvm doesn't have this issue so it's the one I used.

So now, time to talk about the issues I encountered, right?

sysctl

The first one was that sysctl doesn't return an error even when it fails. I use it to query some process information like the current working directory and the root directory. I realized that it was buggy when it returned that the current working directory for the current process was p\u{5}. The only thing I could find about this was this bugzilla issue from 2018. However it's still not merged and the last comment is from 2019, so I guess it won't be merged until a long time. However, our issue is very "actual" so I needed to find another way.

The other way to find both information was to use procstat_getfiles and then checking if each "file" is one of the two I want. Now you might wonder how I can do it since I said previously that procstat isn't thread-safe right? Well, I don't run it inside a thread. I collect all newly created processes since the last time into a Vec and then iterate it to fill the missing information.

Process I/O

Just like on Windows, when sysinfo gets the I/O of a process, it doesn't know if it's network or disk usage. We just have a number of read and written bytes and that's it...

Disks

It was surprisingly complex to get any information on disks. Once I found the right approach, everything became very simple. I could even provide a disk usage now. Maybe for as later addition. Only problem remaining: I don't know if the disk is a HDD or a SSD. There isn't a nice API which can return an enum or whatever to get this information, it's actually more a conjecture of things: if a disk has TRIM enabled and no rotation (rotation per minute), then it's definitely a SSD. However, getting these two information is actually quite complicated and it's also just way too fragile so I decided to not got any further on this matter until I find a more satisfactory way.

libc and FreeBSD version

While implementing the disk retrieval, I found a very weird bug: the struct I was passing to the C function was never filled completely. I tried the same code in C and it worked as expected. Very strange right? Then, I compared the size of the C and Rust structs. In C it was over 2400 bytes whereas it was like 400 in Rust.

At this point, my reaction was a wonderful: O.O

The libc CI is checking that the size and the padding of the struct and of each of its fields is the same as its C equivalent. It's literally impossible that it went unnoticed. So after looking a bit more, I discovered that libc was always compiling like it was on FreeBSD 11 (in the build.rs). So I sent a pull request to fix it.

Everything sounds good, right? Everyone is super happy I fixed the world and everything! Or not... It was actually a disaster. It broke the build of mio and tokio (and certainly a few other crates). Thanks to that, I discovered a huge debate on how to handle ABI breaking changes that started years ago. If you're interested about it, the thread is here.

Anyway, it was reverted right away.

I guess you wonder how it can work to use FreeBSD 11 API on FreeBSD 14? The answer is that the API from older FreeBSD is provided for each new version. However, you need to link to the correct function, otherwise you end up just like me: providing an old struct to a new API. So I sent a fix for the getmntinfo function so that it links to the correct one (the old one). Now I use the old function with the old struct and everything is working as expected.

And now we can once again thank the Linux kernel for being retrocompatible (mostly) so that such issues don't happen.

htop

This one isn't an issue but a nice thing that happened when I worked on the FreeBSD support. When I'm stuck on something, I often look at how other programs do it, and of course htop is part of it. When I was reading its source code, I discovered that it was allocating too much memory when retrieving CPU information.

Extra explanations: you allocate an array of N structs (where N is the number of CPUs) and then you pass it to a function. However, you only need to allocate one element when you retrieve "global" CPU information. They were doing the same allocation for both the CPUs and the global CPU, so the more CPUs the computer has, the worst it is.

However: it is a very minor issue which doesn't trigger any security issue or anything, it was just a little "allocating too much memory" problem. I sent a fix and it was quickly merged, making me a contributor to htop which I've been using for a very long time. (I'm very proud of that!)

CI

Unfortunately, github-actions don't support FreeBSD. There was a work-around by using virtualbox with macos but it broke a few weeks ago and I didn't know how long it would remain broken. It had other issues like its slowness (I insist: it's very slow). In the end, I decided to switch to Cirrus CI and it's working very nicely.

Conclusion

Supporting FreeBSD in sysinfo took me a lot more time than I expected. Almost every time I needed to retrieve a new information, I needed to send a pull request to libc, making the implementation overall very long.

PS: I think I'll update my blog post about how sysinfo retrieves system information to add FreeBSD.

Posted on the 14/12/2021 at 18:00 by @GuillaumeGomez

Next article

rustdoc: Recent UI and UX changes in generated documentation

Previous article

sysinfo: how to extract systems' information
Back to articles list
RSS feedRSS feed