Skip to content

Very simple hwmon fan speed meter - #2107

Open
foxpy wants to merge 2 commits into
htop-dev:mainfrom
foxpy:hwmon_fan
Open

foxpy wants to merge 2 commits into
htop-dev:mainfrom
foxpy:hwmon_fan

Conversation

@foxpy

@foxpy foxpy commented Sep 13, 2026

Copy link
Copy Markdown

This PR is a replacement for #2009, which implemented the same feature, but using a vendor-specific interface, only supporting Thinkpads running Linux. The new version should support all Linux devices, because it uses hwmon.

It only reports the speed of the first fan it finds, which is not great, but hey, it's something :)

@BenBE sorry for ignoring #2009 for the entire summer, I've been busy with personal stuff and completely forgot about this :D

I remember that we talked about adding support for minimum and maximum fan speed reporting, which I also decided to keep aside for now.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
docs/styleguide.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: cb4bccfe-7e33-4e4c-8243-4987fe9e7fc5
📥 Commits

Reviewing files that changed from the base of the PR and between fbff7d2 and 12cc640.

📒 Files selected for processing (2)
  • Makefile.am
  • linux/Platform.c

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Adds Linux fan-speed discovery through /sys/class/hwmon. The platform code scans fan*_input files and returns the first nonnegative integer speed. Adds the FanMeter class, which displays the speed in rpm or N/A. Registers the meter for Linux and adds its header and source files to the build.

Priority: ⬇️ Low

Change: Feature

Merge Risk: ⚪ Minimal · up to 12cc6

The Linux fan meter is mergeable after normal checks; no demonstrated issue remains that warrants blocking it.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 12cc6

The feature reads fan telemetry from a fixed system location without adding write operations or privilege changes. A conditional compatibility mismatch could prevent builds or terminate the monitor when openat support is unavailable; exposure on supported Linux configurations remains uncertain.

Retained concerns

  • Low · reliability · inferred: The new discovery caller hardcodes integer directory descriptors, but Compat_openat and Compat_readfileat accept path strings when HAVE_OPENAT is absent. In that configuration, this optional telemetry feature can cause compilation failure or, if compiled with the mismatch, an invalid pointer access in the monitor process. Whether supported Linux builds exercise this fallback is unresolved.
Security review details

Security Blast Radius

  • inferred — The observed new exposure is local fan telemetry read with the monitor process’s existing authority. The inspected call chain adds no cross-service operation or write sink; the conditional compatibility failure would affect the monitor process itself.

Trust Boundaries and Controls

  • inferred — Path selection is constrained by the fixed /sys/class/hwmon root and names obtained from directory enumeration, rather than caller input. Opens follow normal symlink semantics, so this control relies on the tree being kernel-managed; an ordinary unprivileged actor’s ability to replace that tree is not established.

Resilience and Maintainability Implications

  • inferred — Ordinary missing, unreadable, or unparsable attributes are contained as unavailable telemetry. The configuration-dependent argument mismatch is different: it can escape that error-return behavior and disrupt the whole monitor rather than only the meter.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Hwmon paths begin to hum
Fan values find their run
Rpm text lights the view
Missing speeds show N/A too
A new meter joins the crew

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f9b41fef-98a3-450a-8cec-3864c8e4117c

📥 Commits

Reviewing files that changed from the base of the PR and between 07fbb8d and ef3210c.

📒 Files selected for processing (5)
  • FanMeter.c
  • FanMeter.h
  • Makefile.am
  • linux/Platform.c
  • linux/Platform.h

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread FanMeter.c Outdated
Comment thread linux/Platform.c Outdated
Comment thread linux/Platform.c Outdated
Comment on lines +451 to +453
if (dfd == -1) {
return -1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove braces from the three single-statement bodies.

Remove the braces at lines 451-453, 464-466, and 514-516. Keep braces around the cleanup-plus-control-flow bodies at lines 469-471 and 502-504.

The repository guidance requires omitting braces around simple single statements.

@massimomazzariol

Copy link
Copy Markdown
Contributor

Hi, just adding some context that might be useful here for this PR

While working on #2076 I also drafted a follow-up branch to make the hardware sensor code backend-independent and add direct Linux hwmon support as a fallback when libsensors is unavailable:

https://github.com/massimomazzariol/htop/tree/feature/hwmon-fallback

The draft currently discovers hwmon temperature, fan, PWM and voltage sensors and feeds them through the same generic HardwareSensor interface used by the meters. It also avoids relying on hwmonN numbering for persistent identity, since those numbers are not stable across boots.

The main hwmon backend commit is here:

massimomazzariol@fd395ce

I deliberately kept this out of #2076 because I did not want to expand the scope again after the sensor meter implementation had gone through review.

Since #2107 is now adding fan support through hwmon, I thought it was worth mentioning in case parts of that draft or the abstraction could be useful, or if the two approaches should be aligned before both implementations diverge.

If you want, feel free to adapt the draft depending on which direction you prefer

Comment thread linux/Platform.c Outdated
this->values[residueIndex] = residuePercentage;
}

static int Platform_getFanSpeedFromHwmonDevice(int parent_dfd, char const* hwmon_device) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using const char * here is fine and overall more common in the codebase.

Suggested change
static int Platform_getFanSpeedFromHwmonDevice(int parent_dfd, char const* hwmon_device) {
static int Platform_getFanSpeedFromHwmonDevice(int parent_dfd, const char *hwmon_device) {

Comment thread linux/Platform.c Outdated
DIR* dirp;
int dfd;

dfd = openat(parent_dfd, hwmon_device, O_RDONLY | O_CLOEXEC | O_DIRECTORY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cf. Compat_openat

Comment thread linux/Platform.c Outdated
continue;
}

fclose(fp);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cf. Compat_readfileat

Comment thread linux/Platform.c

int fd = openat(dfd, dir->d_name, O_RDONLY | O_CLOEXEC);
if (fd == -1)
continue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some blank line after the if statement doesn't hurt.

Suggested change
continue;
continue;

Comment thread linux/Platform.c Outdated
Comment on lines +490 to +493
/*
Traverse hwmon sensors, find first fan, read and return its speed.
Returns -1 on failure.
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
Traverse hwmon sensors, find first fan, read and return its speed.
Returns -1 on failure.
*/
/* Traverse hwmon sensors, find first fan, read and return its speed. */

Comment thread Makefile.am Outdated
FileDescriptorMeter.h \
FunctionBar.h \
GPUMeter.h \
FanMeter.h \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File order is sorted alphabetically

Comment thread Makefile.am

linux_platform_sources = \
GPUMeter.c \
FanMeter.c \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File order is sorted alphabetically

@BenBE

BenBE commented Sep 18, 2026

Copy link
Copy Markdown
Member

@foxpy No problem. Sometimes PRs take a while to ripen. One of my earliest PRs to htop took more then 6 months and it mostly cleaned up some existing code to make it be shared by all platforms. Given the long-term maintainability is important with a huge code-base, having several approaches to a problem to compare what works best is a clear plus. This applies similarly to using synergies with existing code, thus re-using existing infrastructure to query information or realize a feature is much preferred.

@coderabbitai Removing braces around single-statement bodies is not required, but (strongly) encouraged. It's totally fine to have them, e.g. if the statements itself are somewhat complicated, but for most simple cases (i.e. just an assignment), the variant without braces is preferred/recommended. Furthermore: blank openat without HAVE_OPENAT guards MUST not appear; that's what the Compat_openat helpers are for.

@massimomazzariol Your PR #2076 is still on my list of PRs to review, but this might still take some time. As noted above: Having a shared infrastructure for accessing hwmon information without duplicating logic unnecessarily sounds good.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@BenBE Thanks for the clarification. I will treat omitted braces as the preferred style for simple single-statement bodies. I will also require Compat_openat helpers instead of unguarded openat calls where HAVE_OPENAT is not guaranteed.


✏️ Learnings added
Learnt from: BenBE
URL: https://github.com/htop-dev/htop/pull/2107

Timestamp: 2026-09-18T22:26:33.395Z
Learning: In the htop C codebase, prefer no braces for simple single-statement control-flow bodies, such as a single assignment. Braces remain acceptable when the statement is complex.

Learnt from: BenBE
URL: https://github.com/htop-dev/htop/pull/2107

Timestamp: 2026-09-18T22:26:33.395Z
Learning: In the htop C codebase, do not use unguarded `openat` calls when `HAVE_OPENAT` is not guaranteed. Use the portable `Compat_openat` helpers instead.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@foxpy

foxpy commented Sep 19, 2026

Copy link
Copy Markdown
Author

@BenBE thank you for your review. I am afraid @massimomazzariol 's work in PR #2076 is more useful and we both should focus on reviewing it instead of spending extra time on what I did. What do you think?

@BenBE

BenBE commented Oct 3, 2026

Copy link
Copy Markdown
Member

As noted earlier in this thread, I prefer if we can re-use infrastructure to fetch information from the system, thus going is #2076 is the probably the best move to go forward. The comments in my previous review are for reference, but also what would need to be done if we were to use this PR instead.

Unfortunately I'm currently a bit short on spare time for timely reviews, but I try to get around to them eventually as time permits. That said: I still have #2076 marked for reviewing on my todo list …

@BenBE BenBE added Linux 🐧 Linux related issues feature request Completely new feature requested labels Oct 3, 2026
@foxpy

foxpy commented Oct 4, 2026

Copy link
Copy Markdown
Author

Alright, I fixed the issues that you mentioned. I don't know how you would like to proceed, but feel free to close this PR and move on with #2076 if you think that's a better way to go.

If you would still like to proceed with this PR, I have a question: do you want me to squash all commits here and force push, so we don't have this dangling "review fixes" commit lying around?

@foxpy

foxpy commented Oct 4, 2026

Copy link
Copy Markdown
Author

Huh… I think the implementation of Compat_openat is incorrect?
https://github.com/foxpy/htop/actions/runs/37191769898/job/111405292101

openat accepts fd as its first argument, but in linux/Compat.c:92 the first argument is defined as const char *.

@BenBE

BenBE commented Oct 4, 2026

Copy link
Copy Markdown
Member

Huh… I think the implementation of Compat_openat is incorrect? https://github.com/foxpy/htop/actions/runs/37191769898/job/111405292101

openat accepts fd as its first argument, but in linux/Compat.c:92 the first argument is defined as const char *.

The type of Compat_openat changes depending on the availability of the syscall wrapper in glibc.

In the header you will thus find this block:

#ifdef HAVE_OPENAT

typedef int openat_arg_t;

static inline void Compat_openatArgClose(openat_arg_t dirfd) {
   close(dirfd);
}

static inline int Compat_openat(openat_arg_t dirfd, const char* pathname, int flags) {
   return openat(dirfd, pathname, flags);
}

#else /* HAVE_OPENAT */

typedef const char* openat_arg_t;

static inline void Compat_openatArgClose(openat_arg_t dirpath) {
   (void)dirpath;
}

int Compat_openat(openat_arg_t dirpath, const char* pathname, int flags);

#endif /* HAVE_OPENAT */

which forwards the fd argument directly if the syscall is available, but uses the fallback implementation in Compat.c otherwise.

Thus when using Compat_openat in your code, there's this openat_arg_t procFd argument passed around, which maps to the correct type already. When subdirectories are involved, you will thus also sometimes get code like this (this one being from LinuxMachine.c):

#ifdef HAVE_OPENAT
      int cpuDirFd = openat(xDirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);
      if (cpuDirFd < 0)
         continue;
#else
      char cpuDirFd[4096];
      xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name);
#endif

Hope this helps with your confusion.

@foxpy

foxpy commented Oct 4, 2026

Copy link
Copy Markdown
Author

Hope this helps with your confusion.

Yes, it helps a lot! And also tells that I've written grossly incorrect code :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request Completely new feature requested Linux 🐧 Linux related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants