Showing preview only (787K chars total). Download the full file or copy to clipboard to get everything.
Repository: dkorunic/iSMC
Branch: master
Commit: 241fa80697e2
Files: 90
Total size: 753.5 KB
Directory structure:
gitextract_zre7wo39/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── camo-purge.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── AGENTS.md
├── CLAUDE.md
├── LICENSE
├── README.md
├── Taskfile.yml
├── cmd/
│ ├── all.go
│ ├── batt.go
│ ├── curr.go
│ ├── fans.go
│ ├── guess.go
│ ├── guess_test.go
│ ├── hw.go
│ ├── power.go
│ ├── raw.go
│ ├── root.go
│ ├── temp.go
│ ├── version.go
│ └── volt.go
├── go.mod
├── go.sum
├── gosmc/
│ ├── .clang-format
│ ├── LICENSE
│ ├── README.md
│ ├── go.mod
│ ├── gosmc.go
│ ├── smc.c
│ ├── smc.h
│ └── values.go
├── hid/
│ ├── get.go
│ └── hid.go
├── main.go
├── output/
│ ├── influxoutput.go
│ ├── influxoutput_test.go
│ ├── jsonoutput.go
│ ├── jsonoutput_test.go
│ ├── output.go
│ ├── output_test.go
│ ├── outputfactory.go
│ ├── outputfactory_test.go
│ ├── tableoutput.go
│ └── tableoutput_test.go
├── platform/
│ ├── get.go
│ ├── mapping.go
│ └── mapping_test.go
├── reports/
│ ├── report-a18.txt
│ ├── report-intel-t2.txt
│ ├── report-m1-max.txt
│ ├── report-m1-pro-2.txt
│ ├── report-m1-pro.txt
│ ├── report-m1-ultra.txt
│ ├── report-m2.txt
│ ├── report-m3-max-2.txt
│ ├── report-m3-max.txt
│ ├── report-m3-pro-2.txt
│ ├── report-m3-pro-3.txt
│ ├── report-m3-pro.txt
│ ├── report-m4-2.txt
│ ├── report-m4-3.txt
│ ├── report-m4-4.txt
│ ├── report-m4-max.txt
│ ├── report-m4-pro-2.txt
│ ├── report-m4-pro.txt
│ ├── report-m4.txt
│ ├── report-m5-max-2.txt
│ ├── report-m5-max.txt
│ ├── report-m5-pro.txt
│ ├── reports.go
│ └── reports_test.go
├── smc/
│ ├── conv.go
│ ├── conv_test.go
│ ├── gen-sensors.sh
│ ├── get.go
│ ├── mapping_test.go
│ ├── raw.go
│ ├── rawtemp.go
│ ├── rawtemp_test.go
│ ├── sensors.go
│ ├── smc.go
│ └── smc_test.go
├── src/
│ ├── current.txt
│ ├── fans.txt
│ ├── power.txt
│ ├── temp.txt
│ └── voltage.txt
└── stress/
└── stress_darwin.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
time: "22:00"
open-pull-requests-limit: 10
================================================
FILE: .github/workflows/camo-purge.yml
================================================
name: Purge camo cache
on:
workflow_dispatch:
push:
tags:
- "*"
schedule:
- cron: "0 7 * * *"
jobs:
purge-camo-cache:
runs-on: ubuntu-latest
steps:
- name: Purge camo cache
uses: kevincobain2000/action-camo-purge@v1
================================================
FILE: .gitignore
================================================
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
iSMC
dist/
vendor/
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Unit tests and coverage reports
profile.cov
report.xml
### Intellij ###
# User-specific stuff:
.idea/
# CMake
cmake-build-debug/
## File-based project format:
*.iws
# IntelliJ
/out/
### VisualStudioCode ###
.vscode/
## Usual macOS cruft ###
.DS_Store
## Claude Code
.claude
docs/
================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
default: all
disable:
- cyclop
- depguard
- dupl
- exhaustruct
- forbidigo
- funlen
- gochecknoglobals
- gocognit
- lll
- mnd
- varnamelen
- wrapcheck
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: .goreleaser.yml
================================================
before:
hooks:
- go mod tidy
builds:
- flags:
- -trimpath
env:
- CGO_ENABLED=1
ldflags: |
-s -w -X "github.com/dkorunic/iSMC/cmd.GitTag={{.Tag}}" -X "github.com/dkorunic/iSMC/cmd.GitCommit={{.ShortCommit}}" -X "github.com/dkorunic/iSMC/cmd.GitDirty= " -X "github.com/dkorunic/iSMC/cmd.BuildTime={{.Date}}"
goos:
- darwin
goarch:
- amd64
- arm64
universal_binaries:
- replace: true
changelog:
sort: asc
archives:
- name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides:
- goos: windows
format: zip
files:
- README.md
- LICENSE
- src: dist/CHANGELOG.md
dst: ""
strip_parent: true
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ .Tag }}-next"
brews:
- name: ismc
repository:
owner: dkorunic
name: homebrew-tap
homepage: https://github.com/dkorunic/iSMC
description: Apple SMC CLI tool that can decode and display temperature, fans, battery, power, voltage and current information
license: GPL-3.0
test: |
system "#{bin}/iSMC", "help"
install: |
bin.install "iSMC"
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
This repository already contains `CLAUDE.md` with detailed build, architecture, and workflow guidance.
## Non-obvious constraints
- **Darwin-only:** Every source file has `//go:build darwin`. Builds/tests fail on Linux/Windows regardless of `CGO_ENABLED=1`. The C source (`smc.h`, `smc.c`) and IOKit/HID dependencies are macOS-only.
- **Two Go modules:** Root (`github.com/dkorunic/iSMC`) and nested (`github.com/dkorunic/iSMC/gosmc`). When updating gosmc dependencies, `cd` into that directory first.
- **`smc/sensors.go` is code-generated.** Do not edit it manually — run `task generate` (reads data from `src/{temp,fans,power,voltage,current}.txt`).
- **`task build` has a fixed order:** generate → fmt → build. Breaking this order produces stale sensor data.
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Development Commands
This project uses [Task](https://taskfile.dev) (`task` CLI) as the build runner. `CGO_ENABLED=1` is required for all builds since the code wraps Apple IOKit via CGo.
```sh
task build # generate sensors, format, build optimized binary (PGO enabled)
task build-debug # update deps, format, build with race detector + no optimizations
task lint # format + golangci-lint
task fmt # go mod tidy, gci, gofumpt, betteralign
task generate # regenerate smc/sensors.go from src/*.txt data files
task update # go get -u && go mod tidy
task check # gomajor list — check for available major version upgrades
task release # goreleaser release (requires git tag + GITHUB_TOKEN)
```
To build manually without Task:
```sh
CGO_ENABLED=1 go build -trimpath -pgo=auto -o iSMC
```
To run tests:
```sh
CGO_ENABLED=1 go test ./...
# Run a single test package:
CGO_ENABLED=1 go test ./output/...
```
## Architecture
### Hardware Abstraction
The tool supports two distinct hardware paths that are merged at the output layer:
- **`gosmc/`** — nested Go module (`github.com/dkorunic/iSMC/gosmc`). CGo bindings to Apple IOKit's SMC interface via `smc.h`. Handles Intel/PPC Macs. Contains IOKit constants (`values.go`) and wrapper functions for `SMCOpen`/`SMCClose`/`SMCReadKey`/`SMCCall`/`SMCWriteKey`.
- **`smc/`** — uses `gosmc` to query named SMC keys. `sensors.go` is **code-generated** (do not edit directly) — regenerate with `task generate` or `go generate ./smc`. The generator reads colon-delimited data from `../src/{temp,fans,power,voltage,current}.txt`. Supports wildcard keys (`%`) expanded to indices 0–9.
- **`hid/`** — Apple Silicon (M-series) sensor support via the HID sensor hub. Uses embedded C (`get.go` has CGo). Reads temperature, current, voltage from IOKit HID services.
### Data Flow
```
cmd/ (Cobra subcommands)
→ output.Factory(OutputFlag) [selects output format]
→ output.Output interface methods (All, Temperature, Fans, etc.)
→ merge(smc.Get*(), hid.Get*())
→ gosmc.SMCOpen/ReadKey (Intel)
→ CGo HID calls (Apple Silicon)
```
All sensor data is returned as `map[string]any` where each entry contains `"key"`, `"value"`, and `"type"` fields.
### Output System
`output/` implements the `Output` interface with four backends selected via `-o` flag:
- `table` — pretty table (default, uses `go-pretty`)
- `ascii` — plain ASCII table
- `json` — JSON
- `influx` — InfluxDB line protocol
`output/outputfactory.go` is the factory. The `GetAll`, `GetTemperature`, etc. vars in `output/output.go` are function variables to enable monkey-patching in tests.
### Module Structure
This repo contains **two Go modules**:
- Root: `github.com/dkorunic/iSMC` (`go.mod` at root)
- Nested: `github.com/dkorunic/iSMC/gosmc` (`gosmc/go.mod`)
When updating dependencies in `gosmc/`, cd into that directory first.
## Code Style & Linting
All files have a `//go:build darwin` constraint — this is macOS-only code. The linter config is in `.golangci.yml` (golangci-lint v2, `default: all` with specific linters disabled). Formatters used: `gci`, `gofmt`, `gofumpt`, `goimports`. Run `task fmt` before committing.
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
# iSMC
[](https://github.com/dkorunic/iSMC/blob/master/LICENSE)
[](https://github.com/dkorunic/iSMC/releases/latest)
## About
`iSMC` is a macOS command-line tool for querying the Apple System Management Controller (SMC). It reads a broad set of well-known SMC keys, determines their type and value, and classifies the results into temperature, power, current, voltage, fan, and battery readings. Each key is accompanied by a human-readable description.
In addition to standard SMC support for Intel Mac hardware, `iSMC` supports Apple Silicon (M1–M5 and later, including the Neo family), where temperature, voltage, current, and power sensors are exposed through a HID sensor hub rather than the SMC directly.

## Installation
`iSMC` runs on macOS only.
### Homebrew
You can install iSMC using [Homebrew](https://brew.sh/) by adding the `homebrew-tap` tap and installing the `ismc` package:
```shell
brew tap dkorunic/tap
brew install ismc
```
### Manual
Download the appropriate iSMC binary for your platform from [the releases page](https://github.com/dkorunic/iSMC/releases/latest) and install it manually.
### Using go install
You can also install iSMC using the `go install` command:
```shell
CGO_ENABLED=1 go install github.com/dkorunic/iSMC@latest
```
## Usage
```shell
Apple SMC CLI tool that can decode and display temperature, fans, battery, power, voltage and current
information for various hardware in your Apple Mac hardware.
Usage:
iSMC [flags]
iSMC [command]
Available Commands:
all Display all known sensors, fans and battery status
batt Display battery status
completion Generate the autocompletion script for the specified shell
curr Display current sensors
fans Display fans status
guess Map SMC temperature sensors to CPU cores by thermal correlation
help Help about any command
hw Display hardware information
power Display power sensors
raw Display all raw SMC keys and their byte values
temp Display temperature sensors
version Print the version number of iSMC
volt Display voltage sensors
Flags:
-h, --help help for iSMC
-o, --output string Output format (ascii, table, json, influx) (default "table")
Use "iSMC [command] --help" for more information about a command.
```
Each command also accepts short and long aliases: `bat`/`batt`/`battery`, `cur`/`curr`/`current`, `fan`/`fans`, `pow`/`power`, `tmp`/`temp`/`temperature`, `vol`/`volt`/`voltage`, `everything`/`all`.
### Output formats
| Format | Description |
| -------- | --------------------------------- |
| `table` | Coloured terminal table (default) |
| `ascii` | Plain ASCII table |
| `json` | JSON |
| `influx` | InfluxDB line protocol |
## Related work
This tool was inspired by several Apple SMC-related projects:
- **SMCKit** — Apple SMC library and tool in Swift: [github.com/beltex/SMCKit](https://github.com/beltex/SMCKit)
- **libsmc** — SMC API in pure C: [github.com/beltex/libsmc](https://github.com/beltex/libsmc)
- **iStats** — Ruby gem for Mac stats: [github.com/Chris911/iStats](https://github.com/Chris911/iStats)
- **smcFanControl** — Fan control tool in Objective-C, includes `smc-command` for raw SMC key queries: [github.com/hholtmann/smcFanControl](https://github.com/hholtmann/smcFanControl)
- **FakeSMC** — Hackintosh kext: [github.com/RehabMan/OS-X-FakeSMC-kozlek](https://github.com/RehabMan/OS-X-FakeSMC-kozlek)
- **VirtualSMC** — Hackintosh kext: [github.com/acidanthera/VirtualSMC](https://github.com/acidanthera/VirtualSMC)
- **osx-cpu-temp** — CPU temperature display in pure C: [github.com/lavoiesl/osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp)
- **applesmc.c** — Linux kernel Apple SMC driver: [github.com/torvalds/linux](https://github.com/torvalds/linux/blob/master/drivers/hwmon/applesmc.c)
- **gosmc** — Low-level Go SMC bindings: [github.com/panotza/gosmc](https://github.com/panotza/gosmc)
- **sensors** — Koan-Sin Tan's M1 IOKit demo: [github.com/freedomtan/sensors](https://github.com/freedomtan/sensors)
- **Stats** — Serhiy Mytrovtsiy's macOS Stats app: [github.com/exelban/stats](https://github.com/exelban/stats)
## Todo
Planned features:
- fetch and decode SMC key descriptions from the SMC itself,
- generate and probe random SMC keys,
- persist discovered SMC keys to a configuration file,
- add support for missing data types (`si*`, `hex_`, `pwm`, etc.).
## Bugs, feature requests, etc.
Please open an issue or submit a pull request.
## Star history
[](https://star-history.com/#dkorunic/iSMC&Date)
================================================
FILE: Taskfile.yml
================================================
version: "3"
vars:
TARGET: iSMC
GIT_LAST_TAG:
sh: git describe --abbrev=0 --tags 2>/dev/null || echo latest
GIT_HEAD_COMMIT:
sh: git rev-parse --short HEAD 2>/dev/null || echo unknown
GIT_TAG_COMMIT:
sh: git rev-parse --short {{.GIT_LAST_TAG}} 2>/dev/null || echo unknown
GIT_MODIFIED1:
sh: git diff {{.GIT_HEAD_COMMIT}} {{.GIT_TAG_COMMIT}} --quiet 2>/dev/null || echo .dev
GIT_MODIFIED2:
sh: git diff --quiet 2>/dev/null || echo .dirty
GIT_MODIFIED:
sh: echo "{{.GIT_MODIFIED1}}{{.GIT_MODIFIED2}}"
BUILD_DATE:
sh: date -u '+%Y-%m-%dT%H:%M:%SZ'
env:
CGO_ENABLED: 1
tasks:
default:
cmds:
- task: update
- task: build
update:
cmds:
- go get -u
- go mod tidy
check:
cmds:
- gomajor list
fmt:
cmds:
- go mod tidy
- gci write .
- gofumpt -l -w .
- betteralign -apply ./...
generate:
cmds:
- go generate ./smc
build:
cmds:
- task: generate
- task: fmt
- go build -trimpath -pgo=auto -ldflags="-s -w -X github.com/dkorunic/iSMC/cmd.GitTag={{.GIT_LAST_TAG}} -X github.com/dkorunic/iSMC/cmd.GitCommit={{.GIT_HEAD_COMMIT}} -X github.com/dkorunic/iSMC/cmd.GitDirty={{.GIT_MODIFIED}} -X github.com/dkorunic/iSMC/cmd.BuildTime={{.BUILD_DATE}}" -o {{.TARGET}}
build-debug:
env:
CGO_ENABLED: 1
cmds:
- task: update
- task: fmt
- go build -ldflags="-X cmd.GitTag={{.GIT_LAST_TAG}} -X cmd.GitCommit={{.GIT_HEAD_COMMIT}} -X cmd.GitDirty={{.GIT_MODIFIED}} -X cmd.BuildTime={{.BUILD_DATE}}" -race -o {{.TARGET}}
lint:
cmds:
- task: fmt
- golangci-lint run --timeout 5m
test:
cmds:
- go test ./...
release:
cmds:
- goreleaser release --clean -p 4
================================================
FILE: cmd/all.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents all commands.
var allCmd = &cobra.Command{
Use: "all",
Aliases: []string{"everything", "*"},
Short: "Display all known sensors, fans and battery status",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).All()
},
}
// init registers the all subcommand with the root command.
func init() {
rootCmd.AddCommand(allCmd)
}
================================================
FILE: cmd/batt.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents batt command.
var battCmd = &cobra.Command{
Use: "batt",
Aliases: []string{"battery", "bat"},
Short: "Display battery status",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Battery()
},
}
// init registers the batt subcommand with the root command.
func init() {
rootCmd.AddCommand(battCmd)
}
================================================
FILE: cmd/curr.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents curr command.
var currCmd = &cobra.Command{
Use: "curr",
Aliases: []string{"current", "cur"},
Short: "Display current sensors",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Current()
},
}
// init registers the curr subcommand with the root command.
func init() {
rootCmd.AddCommand(currCmd)
}
================================================
FILE: cmd/fans.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents fans command.
var fansCmd = &cobra.Command{
Use: "fans",
Aliases: []string{"fan"},
Short: "Display fans status",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Fans()
},
}
// init registers the fans subcommand with the root command.
func init() {
rootCmd.AddCommand(fansCmd)
}
================================================
FILE: cmd/guess.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"fmt"
"math"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/dkorunic/iSMC/platform"
"github.com/dkorunic/iSMC/reports"
"github.com/dkorunic/iSMC/smc"
"github.com/dkorunic/iSMC/stress"
"github.com/spf13/cobra"
)
const (
guessStressDuration = 8 * time.Second
guessCoolDuration = 12 * time.Second
guessSampleCount = 3
guessSampleInterval = 500 * time.Millisecond
// 25 °C sits in the gap between disconnected probes (≤12) and real idle sensors (≥26).
guessTempMin = float32(25.0)
guessTempMax = float32(150.0)
// ≈10 σ above sp78 noise floor (~0.25 °C).
guessOutputThreshold = float32(1.5)
// Sensor is cluster-level if min/max phase delta ≥ (1 − this).
guessClusterRatio = float32(0.20)
)
var guessCmd = &cobra.Command{
Use: "guess",
Short: "Map SMC temperature sensors to CPU cores by thermal correlation",
Long: `guess stresses all logical CPU cores simultaneously — in two or three phases
depending on the chip's pair signature — and correlates the resulting temperature
rise in T-prefixed SMC sensors to produce a sensor mapping list in the format used
by src/temp.txt.
Phase labels and QoS hints are driven by the SKU's pair signature (from the
validate-temp-mappings family roster):
- P+E (M1–M4 family, A18 Pro): Performance + Efficiency
- S+E (M5 base): Super + Efficiency
- S+P (M5 Pro / M5 Max): Super + Performance
- 3-level perflevel hierarchy: Super + Performance + Efficiency
QOS_CLASS_USER_INTERACTIVE biases the kernel toward Super cores, USER_INITIATED
toward Performance cores, and BACKGROUND toward Efficiency cores.
Within each phase, per-core labels are derived from SMC key naming patterns:
keys sharing the same non-numeric structure (e.g. TC*c) form a series, then
stride/gap analysis within each series groups sensors that belong to the same
physical core into a single sub-group. The detected per-type counts are
cross-checked against the SKU's expected layout (e.g. M5 Pro: 5–6 Super + 10–12
Performance), and deviations are flagged inline so the operator knows which lines
need manual review before pasting into src/temp.txt.
The process takes roughly 34 seconds on 2-phase chips, or ~55 seconds on 3-phase
chips. Run on an otherwise-idle machine for best results.`,
Run: runGuess,
}
func init() {
rootCmd.AddCommand(guessCmd)
}
// rawTemps reads every T-prefixed SMC key and returns a map of key → °C for all
// sensors that report a plausible temperature value.
func rawTemps() map[string]float32 {
out := make(map[string]float32)
for _, k := range smc.GetRaw() {
if len(k.Key) == 0 || k.Key[0] != 'T' {
continue
}
v, ok := smc.RawKeyToFloat32(k)
if !ok || v < guessTempMin || v > guessTempMax {
continue
}
out[k.Key] = v
}
return out
}
// avgRawTemps returns per-key averages over n samples taken sampleInterval apart.
func avgRawTemps(n int, interval time.Duration) map[string]float32 {
sums := make(map[string]float64)
counts := make(map[string]int)
for i := range n {
if i > 0 {
time.Sleep(interval)
}
for k, v := range rawTemps() {
sums[k] += float64(v)
counts[k]++
}
}
result := make(map[string]float32, len(sums))
for k, s := range sums {
result[k] = float32(s / float64(counts[k]))
}
return result
}
// deltaTemps returns sensors in hot that exceed the corresponding baseline value
// by at least guessOutputThreshold.
func deltaTemps(base, hot map[string]float32) map[string]float32 {
d := make(map[string]float32)
for k, b := range base {
if h, ok := hot[k]; ok {
if delta := h - b; delta >= guessOutputThreshold {
d[k] = delta
}
}
}
return d
}
// seriesKey returns the SMC key with every decimal digit and hex digit (A-F)
// in the numeric index replaced by '*'. Keys sharing a series key differ only
// in their numeric index and belong to the same per-core sensor series
// (e.g. "TC0c", "TC3c" → "TC*c", "Tp0A", "Tp0C" → "Tp**").
func seriesKey(key string) string {
b := []byte(key)
indexStarted := false
for i, c := range b {
switch {
case c >= '0' && c <= '9':
indexStarted = true
b[i] = '*'
case indexStarted && c >= 'A' && c <= 'F':
b[i] = '*'
case indexStarted && (c < 'A' || c > 'F'):
indexStarted = false
}
}
return string(b)
}
// numericValue extracts the numeric index from an SMC key. The index starts at
// the first digit and includes all subsequent digits and uppercase hex digits (A-F).
// Lowercase letters are treated as series-key components and excluded. If any
// uppercase hex digits (A-F) are found in the index, parses as hexadecimal;
// otherwise parses as decimal. Returns 0 for keys with no digits.
// "TC3c" → 3, "Tp09" → 9, "Te12" → 12, "Tp0A" → 10, "TcXX" → 0.
func numericValue(key string) int {
var digits []byte
hasHexDigit := false
indexStarted := false
for _, c := range []byte(key) {
if c >= '0' && c <= '9' {
indexStarted = true
digits = append(digits, c)
} else if indexStarted {
if c >= 'A' && c <= 'F' {
digits = append(digits, c)
hasHexDigit = true
} else {
break
}
}
}
if len(digits) == 0 {
return 0
}
if hasHexDigit {
v, _ := strconv.ParseInt(string(digits), 16, 64)
return int(v)
}
v, _ := strconv.Atoi(string(digits))
return v
}
// groupBySeries groups SMC sensor keys by series key (non-numeric pattern).
// Within each group the keys are sorted ascending by numericValue so that
// sorted position 0 maps to Core 1, position 1 maps to Core 2, etc.
func groupBySeries(keys []string) map[string][]string {
groups := make(map[string][]string)
for _, k := range keys {
sk := seriesKey(k)
groups[sk] = append(groups[sk], k)
}
for sk := range groups {
sort.Slice(groups[sk], func(a, b int) bool {
return numericValue(groups[sk][a]) < numericValue(groups[sk][b])
})
}
return groups
}
// sortedSeriesKeys returns the keys of a series group map sorted lexicographically.
func sortedSeriesKeys(groups map[string][]string) []string {
keys := make([]string, 0, len(groups))
for k := range groups {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// groupByStrideWithinSeries splits a series' sorted sensor list into sub-groups
// based on gaps between consecutive numericValues.
//
// Rules:
// - If all consecutive differences are equal (uniform stride): each sensor is
// its own group. This covers M5-style single-sensor-per-core series:
// Tp00/04/08 (stride 4) → [[Tp00],[Tp04],[Tp08]] → 3 cores.
// - Otherwise (non-uniform gaps): split whenever the gap exceeds minDiff.
// This covers M1/M3 triplets: diffs [1,1,2,1,1,2,...] → minDiff=1,
// split at every 2 → [[Tp00,Tp01,Tp02],[Tp04,Tp05,Tp06],...] → N cores × 3.
// - A single-element slice is returned as one group.
func groupByStrideWithinSeries(sensors []string) [][]string {
if len(sensors) <= 1 {
result := make([][]string, len(sensors))
for i, s := range sensors {
result[i] = []string{s}
}
return result
}
diffs := make([]int, len(sensors)-1)
for i := 1; i < len(sensors); i++ {
d := numericValue(sensors[i]) - numericValue(sensors[i-1])
if d < 0 {
d = -d
}
diffs[i-1] = d
}
uniform := true
for _, d := range diffs[1:] {
if d != diffs[0] {
uniform = false
break
}
}
if uniform {
groups := make([][]string, len(sensors))
for i, s := range sensors {
groups[i] = []string{s}
}
return groups
}
minDiff := diffs[0]
for _, d := range diffs[1:] {
if d < minDiff {
minDiff = d
}
}
var groups [][]string
current := []string{sensors[0]}
for i, d := range diffs {
if d > minDiff {
groups = append(groups, current)
current = []string{sensors[i+1]}
} else {
current = append(current, sensors[i+1])
}
}
return append(groups, current)
}
// Phase label prefixes; must match descriptions emitted in src/temp.txt.
const (
labelSuperCore = "CPU Super Core"
labelPerformanceCore = "CPU Performance Core"
labelEfficiencyCore = "CPU Efficiency Core"
)
// phaseSpec describes one stress phase: its label prefix, QoS class, and expected
// physical core count from platform topology data.
type phaseSpec struct {
label string
qos int
cores int // 0 if unknown.
}
// phaseResult pairs a phase specification with the sensor deltas it produced.
type phaseResult struct {
deltas map[string]float32
spec phaseSpec
}
// phaseMidWord returns the middle word of a phase label for use in progress messages.
// "CPU Super Core" → "Super", "CPU Performance Core" → "Performance",
// "CPU Efficiency Core" → "Efficiency".
func phaseMidWord(label string) string {
parts := strings.Fields(label)
if len(parts) >= 2 {
return parts[1]
}
return label
}
// qosName returns a short human-readable name for the given macOS QoS class constant.
func qosName(qos int) string {
switch qos {
case stress.QoSUserInteractive:
return "UserInteractive"
case stress.QoSUserInitiated:
return "UserInitiated"
case stress.QoSBackground:
return "Background"
default:
return fmt.Sprintf("0x%02X", qos)
}
}
// buildPhases constructs the ordered list of stress phases from live topology data
// and the SKU's pair signature.
//
// 3-level perflevel hierarchy: Super → Performance → Efficiency (e.g. a future
// M-series chip exposing all three tiers as distinct OS perflevels).
//
// 2-level hierarchy: top + bottom labels are driven by layout.PairSignature so the
// same Tp* prefix is named correctly for the SKU. Mapping is:
// - P+E (M1–M4, A18 across all variants): Performance + Efficiency
// - S+E (M5 base): Super + Efficiency — the OS may name perflevel0 "Performance"
// but the family-roster pair signature is authoritative; per the skill,
// low-stride Tp0? slots on M5 base are Super cores, not Performance cores.
// - S+P (M5 Pro / M5 Max): Super + Performance — no E-cores on these SKUs
//
// nil/empty perflevels falls back to P+E with zero core counts (legacy behaviour
// for hardware where the perflevel sysctls are unavailable).
//
// The SKU's preferred top-tier QoS class (UserInteractive for Super, UserInitiated
// for Performance) biases the kernel scheduler toward the right tier.
func buildPhases(perfLevels []platform.PerfLevel, layout platform.SKULayout) []phaseSpec {
if len(perfLevels) == 3 {
return []phaseSpec{
{label: labelSuperCore, qos: stress.QoSUserInteractive, cores: perfLevels[0].PhysicalCPU},
{label: labelPerformanceCore, qos: stress.QoSUserInitiated, cores: perfLevels[1].PhysicalCPU},
{label: labelEfficiencyCore, qos: stress.QoSBackground, cores: perfLevels[2].PhysicalCPU},
}
}
topLabel, bottomLabel := labelPerformanceCore, labelEfficiencyCore
topQoS := stress.QoSUserInitiated
switch layout.PairSignature {
case platform.PairSignatureSE:
topLabel = labelSuperCore
topQoS = stress.QoSUserInteractive
case platform.PairSignatureSP:
topLabel, bottomLabel = labelSuperCore, labelPerformanceCore
topQoS = stress.QoSUserInteractive
}
topCores, bottomCores := 0, 0
if len(perfLevels) == 2 {
topCores = perfLevels[0].PhysicalCPU
bottomCores = perfLevels[1].PhysicalCPU
}
bottomQoS := stress.QoSBackground
if layout.PairSignature == platform.PairSignatureSP {
// S+P has no E-tier; route bottom phase to Performance cores.
bottomQoS = stress.QoSUserInitiated
}
return []phaseSpec{
{label: topLabel, qos: topQoS, cores: topCores},
{label: bottomLabel, qos: bottomQoS, cores: bottomCores},
}
}
// spinCore locks the goroutine to an OS thread, sets the QoS class to bias the OS
// scheduler toward the desired core type, sets a macOS thread-affinity tag to prefer
// a specific hardware thread within that type, then burns CPU until done is closed.
func spinCore(affinityTag int, qosClass int, done <-chan struct{}) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// QoS before affinity so placement sees the class.
stress.SetQoS(qosClass)
stress.SetAffinityTag(affinityTag)
// Four FMA+sqrt chains saturate all FP ports.
a, b, c, d := 1.0001, 1.0003, 1.0007, 1.0013
for {
for range 2_500 {
a = math.Sqrt(math.FMA(a, a, math.Pi))
b = math.Sqrt(math.FMA(b, b, math.E))
c = math.Sqrt(math.FMA(c, c, math.Sqrt2))
d = math.Sqrt(math.FMA(d, d, math.Phi))
}
// Overflow guard outside hot loop.
if a > 1e15 {
a = 1.0001
}
if b > 1e15 {
b = 1.0003
}
if c > 1e15 {
c = 1.0007
}
if d > 1e15 {
d = 1.0013
}
select {
case <-done:
return
default:
}
}
}
// runAllCoresPhase launches numCPU spinCore goroutines simultaneously under the
// given QoS class, stresses for guessStressDuration, samples temperatures at
// peak load, then returns the delta map relative to baseline.
func runAllCoresPhase(numCPU int, qosClass int, baseline map[string]float32) map[string]float32 {
done := make(chan struct{})
for i := range numCPU {
go spinCore(i+1, qosClass, done)
}
time.Sleep(guessStressDuration)
hot := avgRawTemps(guessSampleCount, guessSampleInterval)
close(done)
deltas := deltaTemps(baseline, hot)
fmt.Printf(" → %d sensor(s) responded\n", len(deltas))
return deltas
}
// runGuess implements the guess subcommand.
func runGuess(_ *cobra.Command, _ []string) {
numCPU := runtime.NumCPU()
family := platform.GetFamily()
if family == "" || family == "Unknown" {
switch runtime.GOARCH {
case "arm64":
family = "Apple"
default:
family = "Intel"
}
fmt.Printf("Warning: chip family not detected; using %q as platform tag.\n\n", family)
}
product, _ := platform.GetProduct()
layout, layoutOK := platform.GetSKULayout()
perfLevels := platform.GetPerfLevels()
phases := buildPhases(perfLevels, layout)
fmt.Printf("Platform : %s\n", family)
if product.CPU != "" {
fmt.Printf("Model : %s (%s)\n", product.Name, product.CPU)
}
if layoutOK {
fmt.Printf("SKU : %s, %d die(s)\n", layout.PairSignature, layout.Dies)
fmt.Printf("Expected : %s\n", layoutSummary(layout))
}
fmt.Printf("CPUs : %d logical\n", numCPU)
fmt.Printf("Per-phase: %v stress (×%d phases, all %d cores simultaneously)\n\n",
guessStressDuration, len(phases), numCPU)
if len(perfLevels) > 0 {
fmt.Printf("Topology : %d perf level(s)\n", len(perfLevels))
for i, pl := range perfLevels {
fmt.Printf(" perflevel%d: %d phys-CPU %q\n", i, pl.PhysicalCPU, pl.Name)
}
fmt.Println()
}
fmt.Print("Sampling baseline... ")
baseline := avgRawTemps(guessSampleCount, guessSampleInterval)
fmt.Printf("%d sensors visible.\n\n", len(baseline))
results := make([]phaseResult, 0, len(phases))
for i, phase := range phases {
fmt.Printf("── Phase %d: %s sweep (%s QoS) ──\n",
i+1, phaseMidWord(phase.label), qosName(phase.qos))
fmt.Printf(" Stressing all %d cores...", numCPU)
deltas := runAllCoresPhase(numCPU, phase.qos, baseline)
results = append(results, phaseResult{spec: phase, deltas: deltas})
if i < len(phases)-1 {
fmt.Printf("\n Inter-phase cooldown (%v)...\n\n", guessCoolDuration)
time.Sleep(guessCoolDuration)
baseline = avgRawTemps(guessSampleCount, guessSampleInterval)
}
}
printMapping(family, numCPU, perfLevels, layout, layoutOK, product, results)
}
// layoutSummary returns a one-line human-readable description of the SKU's
// expected core composition, e.g. "8–10P + 4E, 16–20 GPU cores" or
// "5–6S + 10–12P, 16–20 GPU cores". A single value (e.g. "4P") is shown when
// the min and max bounds match. Used in the guess command's preamble.
func layoutSummary(l platform.SKULayout) string {
parts := make([]string, 0, 4)
if l.SCoresMax > 0 {
parts = append(parts, formatRange(l.SCoresMin, l.SCoresMax)+"S")
}
if l.PCoresMax > 0 {
parts = append(parts, formatRange(l.PCoresMin, l.PCoresMax)+"P")
}
if l.ECoresMax > 0 {
parts = append(parts, formatRange(l.ECoresMin, l.ECoresMax)+"E")
}
cpu := strings.Join(parts, " + ")
if l.GPUCoresMax > 0 {
return fmt.Sprintf("%s, %s GPU cores", cpu, formatRange(l.GPUCoresMin, l.GPUCoresMax))
}
return cpu
}
// formatRange returns "n" if min == max, else "min–max" using an en-dash.
func formatRange(minN, maxN int) string {
if minN == maxN {
return strconv.Itoa(minN)
}
return fmt.Sprintf("%d–%d", minN, maxN)
}
// expectedCores returns the SKU-expected (min, max) core count for a phase label.
// Returns (0, 0) when the phase's core type is absent on this SKU (legitimate
// case for S+E and S+P pair signatures), letting the caller skip range checks.
func expectedCores(l platform.SKULayout, phaseLabel string) (int, int) {
switch phaseLabel {
case labelSuperCore:
return l.SCoresMin, l.SCoresMax
case labelPerformanceCore:
return l.PCoresMin, l.PCoresMax
case labelEfficiencyCore:
return l.ECoresMin, l.ECoresMax
default:
return 0, 0
}
}
// printPhase emits the comment header, per-series mapping rows, and SKU-aware
// validation footer for one phase of guess output. Extracted from printMapping to
// keep that function's cyclomatic complexity below the project lint threshold.
func printPhase(family string, i int, r phaseResult, keys []string,
layout platform.SKULayout, layoutOK bool,
) {
groups := groupBySeries(keys)
seriesKeys := sortedSeriesKeys(groups)
if len(seriesKeys) == 0 {
fmt.Printf("// WARNING: Phase %d (%s) produced no sensor responses above threshold.\n",
i+1, phaseMidWord(r.spec.label))
fmt.Println("// Run on an idle machine or increase stress duration.")
if layoutOK {
if expMin, expMax := expectedCores(layout, r.spec.label); expMax > 0 {
fmt.Printf("// SKU expects %s for this phase; verify the chip's actual layout.\n",
formatRange(expMin, expMax))
}
}
return
}
coreIdx := 1
for _, sk := range seriesKeys {
coreIdx = printSeries(family, r.spec.label, sk, groups[sk], coreIdx)
}
detected := coreIdx - 1
if layoutOK {
printDetectionVerdict(layout, r.spec.label, detected)
}
}
// printSeries emits one series block (header comment + per-core sensor lines) and
// returns the next coreIdx. Pulled out of printPhase to flatten nesting. Each
// line is annotated with the canonical description from src/temp.txt (via
// smc.LookupTempDesc) so the operator can see at a glance whether the guessed
// label matches the existing mapping or proposes a new/conflicting one.
func printSeries(family, label, sk string, sensors []string, coreIdx int) int {
subGroups := groupByStrideWithinSeries(sensors)
fmt.Printf("// Series %-6s → %d sensor(s) in %d group(s) → %s %d..%d\n",
sk, len(sensors), len(subGroups), label, coreIdx, coreIdx+len(subGroups)-1)
if len(subGroups) == 1 && len(subGroups[0]) > 3 {
fmt.Printf("// NOTE: %d-sensor group; manual review recommended (check src/temp.txt)\n",
len(subGroups[0]))
}
for _, sg := range subGroups {
for _, k := range sg {
line := fmt.Sprintf("%s %d:%s:%s", label, coreIdx, k, family)
fmt.Println(annotateLine(line, label, k, family, coreIdx))
}
coreIdx++
}
return coreIdx
}
// annotateLine appends a comment to a temp.txt-style mapping line indicating
// whether the key already has a canonical description in the AppleTemp table:
//
// ✓ when src/temp.txt's description matches the guessed label exactly
// ⚠ when the key is mapped but to a different description (likely mis-label)
// ★ when the key is not yet mapped for this family
//
// The fixed alignment column (45) keeps annotations readable even with
// 16-core SKUs whose lines reach ~36 characters.
func annotateLine(line, label, key, family string, coreIdx int) string {
const annotationCol = 45
guessed := fmt.Sprintf("%s %d", label, coreIdx)
existing, ok := smc.LookupTempDesc(key, family)
switch {
case !ok:
return fmt.Sprintf("%-*s // ★ NEW for %s — not yet in src/temp.txt",
annotationCol, line, family)
case existing == guessed:
return fmt.Sprintf("%-*s // ✓ matches src/temp.txt", annotationCol, line)
default:
return fmt.Sprintf("%-*s // ⚠ src/temp.txt has %q",
annotationCol, line, existing)
}
}
// printDetectionVerdict compares the count of detected core groups against the
// SKU's expected range and emits a one-line OK / WARNING comment. Skips silently
// when the SKU has no cores of this label's type (the absence is handled by
// warnAbsentTypes after the per-phase loop completes).
func printDetectionVerdict(layout platform.SKULayout, label string, detected int) {
expMin, expMax := expectedCores(layout, label)
if expMax == 0 {
return
}
mid := phaseMidWord(label)
expectedStr := formatRange(expMin, expMax)
switch {
case detected < expMin:
fmt.Printf("// WARNING: detected %d %s group(s) but SKU expects %s — "+
"some cores may have failed to heat up; rerun on an idle machine.\n",
detected, mid, expectedStr)
case detected > expMax:
fmt.Printf("// WARNING: detected %d %s group(s) but SKU expects %s — "+
"likely a cluster-aggregate sensor mis-classified as a per-core; review.\n",
detected, mid, expectedStr)
default:
fmt.Printf("// OK: detected %d %s group(s), within SKU expectation %s.\n",
detected, mid, expectedStr)
}
}
// warnAbsentTypes emits a warning whenever the resolved per-phase results contain
// core groupings for a type the SKU is not supposed to have (e.g. P-cores reported
// on an M5 base, which is S+E only). Mirrors the validate-temp-mappings skill's
// Phase 4a pair-signature validation.
func warnAbsentTypes(l platform.SKULayout, results []phaseResult) {
for _, r := range results {
if len(r.deltas) == 0 {
continue
}
_, expMax := expectedCores(l, r.spec.label)
if expMax > 0 {
continue
}
// Sensors heated for a core type the SKU lacks; kernel routed elsewhere.
fmt.Printf("// WARNING: phase %q produced sensors but SKU pair signature %s "+
"has no cores of this type — relabel before pasting into src/temp.txt.\n",
r.spec.label, l.PairSignature)
}
}
// printMapping analyses N-phase delta results and emits a src/temp.txt-style mapping.
// Sensors are classified by dominant phase, then grouped by SMC key series and
// further split by stride/gap analysis to assign correct per-core indices.
//
// When layoutOK is true, the SKU's per-type expected counts (S/P/E) are checked
// against the detected per-phase core groups; deviations are emitted as inline
// warnings so the operator knows to inspect the output before copy-pasting it
// into src/temp.txt.
func printMapping(family string, numCPU int, perfLevels []platform.PerfLevel,
layout platform.SKULayout, layoutOK bool, product platform.Product, results []phaseResult,
) {
allKeys := make(map[string]struct{})
for _, r := range results {
for k := range r.deltas {
allKeys[k] = struct{}{}
}
}
phaseKeys := make([][]string, len(results))
var clusterKeys []string
for key := range allKeys {
dominantIdx := -1
dominantDelta := float32(0)
for i, r := range results {
d := r.deltas[key]
if d >= guessOutputThreshold && d > dominantDelta {
dominantDelta = d
dominantIdx = i
}
}
if dominantIdx < 0 {
continue
}
secondDelta := float32(0)
for i, r := range results {
if i == dominantIdx {
continue
}
d := r.deltas[key]
if d >= guessOutputThreshold && d > secondDelta {
secondDelta = d
}
}
if secondDelta > 0 {
lo, hi := secondDelta, dominantDelta
if lo > hi {
lo, hi = hi, lo
}
if lo/hi >= (1 - guessClusterRatio) {
clusterKeys = append(clusterKeys, key)
continue
}
}
phaseKeys[dominantIdx] = append(phaseKeys[dominantIdx], key)
}
sort.Strings(clusterKeys)
// Header.
fmt.Println()
fmt.Printf("// %s (%d logical CPUs) – guessed sensor mappings\n", family, numCPU)
if product.CPU != "" {
fmt.Printf("// SKU: %s (%s)\n", product.CPU, product.Name)
}
if layoutOK {
fmt.Printf("// Pair signature: %s | dies: %d | expected: %s\n",
layout.PairSignature, layout.Dies, layoutSummary(layout))
}
if len(perfLevels) > 0 {
fmt.Printf("// Topology: %d perf level(s)", len(perfLevels))
for i, pl := range perfLevels {
if i == 0 {
fmt.Printf(" | perflevel%d %d phys-CPU %q\n", i, pl.PhysicalCPU, pl.Name)
} else {
fmt.Printf("// %s| perflevel%d %d phys-CPU %q\n",
strings.Repeat(" ", 10), i, pl.PhysicalCPU, pl.Name)
}
}
}
fmt.Println("// WARNING: automated correlation is approximate; always verify on real hardware.")
fmt.Printf("// %s\n", strings.Repeat("─", 72))
// Per-phase sensor sections.
for i, r := range results {
printPhase(family, i, r, phaseKeys[i], layout, layoutOK)
}
if layoutOK {
warnAbsentTypes(layout, results)
}
// Cluster / package sensors.
if len(clusterKeys) > 0 {
fmt.Println()
fmt.Printf("// %s\n", strings.Repeat("─", 72))
fmt.Printf("// Cluster/package sensors (top two phases within %.0f%% of each other):\n",
guessClusterRatio*100)
for _, k := range clusterKeys {
parts := make([]string, 0, len(results))
for _, r := range results {
if d := r.deltas[k]; d > 0 {
parts = append(parts, fmt.Sprintf("%s +%.1f°C", phaseMidWord(r.spec.label), d))
}
}
line := fmt.Sprintf("// %-6s %s", k, strings.Join(parts, " "))
if existing, ok := smc.LookupTempDesc(k, family); ok {
fmt.Printf("%-50s ← src/temp.txt: %q\n", line, existing)
} else {
fmt.Println(line)
}
}
}
detected := allDetectedKeys(results, clusterKeys)
printTempTxtCrosscheck(family, detected)
printReportsCrosscheck(family, detected)
}
// allDetectedKeys returns the union of detected sensor keys across every phase
// plus the cluster/package keys. Used by the temp.txt and reports/ cross-checks
// to compute matched / novel / silent sets without re-walking the per-phase
// data structures.
func allDetectedKeys(results []phaseResult, clusterKeys []string) map[string]struct{} {
all := make(map[string]struct{})
for _, r := range results {
for k := range r.deltas {
all[k] = struct{}{}
}
}
for _, k := range clusterKeys {
all[k] = struct{}{}
}
return all
}
// printTempTxtCrosscheck emits a summary of the diff between detected SMC keys
// and the set of keys already mapped in src/temp.txt for the family. The
// summary line reports four cardinalities:
//
// matched: in temp.txt AND detected this run
// novel: detected this run but NOT in temp.txt → candidates to add
// silent: in temp.txt but NOT detected this run → mapping may be stale,
// OR sensor failed to heat under stress
//
// When silent keys exist, the first few are listed with their canonical
// descriptions so the operator can spot-check whether they belong to a
// no-longer-present sensor or simply did not fire in this run.
func printTempTxtCrosscheck(family string, detected map[string]struct{}) {
mapped := smc.MappedTempKeys(family)
if len(mapped) == 0 {
return
}
matched := 0
for k := range detected {
if _, ok := mapped[k]; ok {
matched++
}
}
novel := len(detected) - matched
silent := make([]string, 0)
for k := range mapped {
if _, ok := detected[k]; !ok {
silent = append(silent, k)
}
}
sort.Strings(silent)
fmt.Println()
fmt.Printf("// %s\n", strings.Repeat("─", 72))
fmt.Printf("// Cross-check vs src/temp.txt for %q:\n", family)
fmt.Printf("// detected: %d | mapped: %d | matched: %d | novel: %d | silent: %d\n",
len(detected), len(mapped), matched, novel, len(silent))
if len(silent) == 0 {
return
}
const maxSilent = 25
shown := min(len(silent), maxSilent)
fmt.Printf("// Mapped in src/temp.txt but silent in this run (showing %d of %d):\n",
shown, len(silent))
for _, k := range silent[:shown] {
fmt.Printf("// %-6s %s\n", k, mapped[k])
}
}
// printReportsCrosscheck compares the detected keys against the union of keys
// observed in reports/ for the same family. The reports represent ground-truth
// sensor presence captured from real machines, so missing detections often
// reflect runtime conditions (e.g. GPU/SSD sensors that don't heat from a
// CPU stress phase) rather than mapping bugs. Truly novel keys (detected this
// run but absent from every prior dump) are worth investigating — they may be
// new firmware additions or evidence the chip is a sub-variant we haven't
// captured in the reports/ set yet.
func printReportsCrosscheck(family string, detected map[string]struct{}) {
observed := reports.Keys(family)
if len(observed) == 0 {
return
}
matched := 0
for k := range detected {
if _, ok := observed[k]; ok {
matched++
}
}
silent := make([]string, 0)
for k := range observed {
if _, ok := detected[k]; !ok {
silent = append(silent, k)
}
}
sort.Strings(silent)
novel := make([]string, 0)
for k := range detected {
if _, ok := observed[k]; !ok {
novel = append(novel, k)
}
}
sort.Strings(novel)
fmt.Println()
fmt.Printf("// %s\n", strings.Repeat("─", 72))
fmt.Printf("// Cross-check vs reports/ observations for %q:\n", family)
fmt.Printf("// detected: %d | observed: %d | matched: %d | novel: %d | silent: %d\n",
len(detected), len(observed), matched, len(novel), len(silent))
if len(novel) > 0 {
const maxNovel = 20
shown := min(len(novel), maxNovel)
fmt.Printf("// Detected but never seen in any %s report (showing %d of %d):\n",
family, shown, len(novel))
for _, k := range novel[:shown] {
fmt.Printf("// %s\n", k)
}
}
}
================================================
FILE: cmd/guess_test.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"strings"
"testing"
"github.com/dkorunic/iSMC/platform"
"github.com/dkorunic/iSMC/stress"
)
func TestSeriesKey(t *testing.T) {
t.Parallel()
tests := []struct {
key string
want string
}{
{"TC0c", "TC*c"},
{"TC3c", "TC*c"},
{"TC9c", "TC*c"},
{"Te0T", "Te*T"},
{"Te1T", "Te*T"},
{"Tp01", "Tp**"},
{"Tp09", "Tp**"},
{"Tf0c", "Tf*c"},
{"TcXX", "TcXX"},
{"Tp0A", "Tp**"},
{"Tp0C", "Tp**"},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
t.Parallel()
got := seriesKey(tt.key)
if got != tt.want {
t.Errorf("seriesKey(%q) = %q, want %q", tt.key, got, tt.want)
}
})
}
}
func TestNumericValue(t *testing.T) {
t.Parallel()
tests := []struct {
key string
want int
}{
{"TC0c", 0},
{"TC3c", 3},
{"TC9c", 9},
{"Tp01", 1},
{"Tp09", 9},
{"Te12", 12},
{"TcXX", 0},
{"Tp0A", 10},
{"Tp0C", 12},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
t.Parallel()
got := numericValue(tt.key)
if got != tt.want {
t.Errorf("numericValue(%q) = %d, want %d", tt.key, got, tt.want)
}
})
}
}
func TestGroupBySeries(t *testing.T) {
t.Parallel()
keys := []string{"TC2c", "TC0c", "Te1T", "TC1c", "Te0T"}
got := groupBySeries(keys)
if len(got) != 2 {
t.Fatalf("groupBySeries: got %d series, want 2", len(got))
}
tcSeries, ok := got["TC*c"]
if !ok {
t.Fatal("groupBySeries: missing series TC*c")
}
wantTC := []string{"TC0c", "TC1c", "TC2c"}
if len(tcSeries) != len(wantTC) {
t.Fatalf("TC*c len: got %d, want %d; keys: %v", len(tcSeries), len(wantTC), tcSeries)
}
for i, k := range wantTC {
if tcSeries[i] != k {
t.Errorf("TC*c[%d] = %q, want %q", i, tcSeries[i], k)
}
}
teSeries, ok := got["Te*T"]
if !ok {
t.Fatal("groupBySeries: missing series Te*T")
}
wantTe := []string{"Te0T", "Te1T"}
if len(teSeries) != len(wantTe) {
t.Fatalf("Te*T len: got %d, want %d; keys: %v", len(teSeries), len(wantTe), teSeries)
}
for i, k := range wantTe {
if teSeries[i] != k {
t.Errorf("Te*T[%d] = %q, want %q", i, teSeries[i], k)
}
}
}
func TestGroupByStrideWithinSeries(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sensors []string
want [][]string
}{
{
name: "single sensor",
sensors: []string{"Tp00"},
want: [][]string{{"Tp00"}},
},
{
name: "two sensors uniform stride",
sensors: []string{"Tp00", "Tp04"},
want: [][]string{{"Tp00"}, {"Tp04"}},
},
{
name: "M1 triplets stride-4 gap",
sensors: []string{"Tp00", "Tp01", "Tp02", "Tp04", "Tp05", "Tp06", "Tp08", "Tp09", "Tp0A"},
want: [][]string{
{"Tp00", "Tp01", "Tp02"},
{"Tp04", "Tp05", "Tp06"},
{"Tp08", "Tp09", "Tp0A"},
},
},
{
name: "M5 uniform stride-4",
sensors: []string{"Tp00", "Tp04", "Tp08"},
want: [][]string{{"Tp00"}, {"Tp04"}, {"Tp08"}},
},
{
name: "M4 irregular large gap",
sensors: []string{"Tp00", "Tp01", "Tp02", "Tp04", "Tp05", "Tp06", "Tp08", "Tp09", "Tp21"},
want: [][]string{
{"Tp00", "Tp01", "Tp02"},
{"Tp04", "Tp05", "Tp06"},
{"Tp08", "Tp09"},
{"Tp21"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := groupByStrideWithinSeries(tt.sensors)
if len(got) != len(tt.want) {
t.Fatalf("groupByStrideWithinSeries(%v): got %d groups, want %d; got=%v",
tt.sensors, len(got), len(tt.want), got)
}
for i, wantGroup := range tt.want {
if len(got[i]) != len(wantGroup) {
t.Errorf("group[%d]: got %v (len %d), want %v (len %d)",
i, got[i], len(got[i]), wantGroup, len(wantGroup))
continue
}
for j, wantKey := range wantGroup {
if got[i][j] != wantKey {
t.Errorf("group[%d][%d] = %q, want %q", i, j, got[i][j], wantKey)
}
}
}
})
}
}
func TestDeltaTemps(t *testing.T) {
t.Parallel()
tests := []struct {
name string
base map[string]float32
hot map[string]float32
want map[string]float32
}{
{
name: "one above one below threshold",
base: map[string]float32{"TC0c": 30.0, "TC1c": 35.0},
hot: map[string]float32{"TC0c": 45.0, "TC1c": 36.0},
want: map[string]float32{"TC0c": 15.0},
},
{
name: "exactly at threshold",
base: map[string]float32{"TC0c": 30.0},
hot: map[string]float32{"TC0c": 31.5},
want: map[string]float32{"TC0c": 1.5},
},
{
name: "key missing from hot",
base: map[string]float32{"TC0c": 30.0},
hot: map[string]float32{},
want: map[string]float32{},
},
{
name: "key missing from base",
base: map[string]float32{},
hot: map[string]float32{"TC0c": 45.0},
want: map[string]float32{},
},
{
name: "empty inputs",
base: map[string]float32{},
hot: map[string]float32{},
want: map[string]float32{},
},
{
name: "multiple sensors all above threshold",
base: map[string]float32{"TC0c": 30.0, "TC1c": 32.0, "TC2c": 28.0},
hot: map[string]float32{"TC0c": 50.0, "TC1c": 45.0, "TC2c": 35.0},
want: map[string]float32{"TC0c": 20.0, "TC1c": 13.0, "TC2c": 7.0},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := deltaTemps(tt.base, tt.hot)
if len(got) != len(tt.want) {
t.Errorf("deltaTemps: got %d entries %v, want %d entries %v",
len(got), got, len(tt.want), tt.want)
return
}
for k, wantV := range tt.want {
gotV, ok := got[k]
if !ok {
t.Errorf("deltaTemps: missing key %q in result", k)
continue
}
if gotV != wantV {
t.Errorf("deltaTemps[%q] = %g, want %g", k, gotV, wantV)
}
}
})
}
}
func TestPhaseMidWord(t *testing.T) {
t.Parallel()
tests := []struct {
label string
want string
}{
{"CPU Super Core", "Super"},
{"CPU Performance Core", "Performance"},
{"CPU Efficiency Core", "Efficiency"},
{"Single", "Single"},
{"", ""},
{"Two Words", "Words"},
}
for _, tt := range tests {
t.Run(tt.label, func(t *testing.T) {
t.Parallel()
got := phaseMidWord(tt.label)
if got != tt.want {
t.Errorf("phaseMidWord(%q) = %q, want %q", tt.label, got, tt.want)
}
})
}
}
// TestBuildPhases verifies the phase specification construction across the chip
// topologies the validate-temp-mappings family roster covers:
// - nil / empty perfLevels → 2 phases with zero core counts (legacy fallback)
// - 2-level P+E (M1–M4) → Performance + Efficiency, UserInitiated + Background QoS
// - 2-level S+E (M5 base) → Super + Efficiency, UserInteractive + Background QoS
// - 2-level S+P (M5 Pro/Max) → Super + Performance, UserInteractive + UserInitiated QoS
// - 3-level → Super + Performance + Efficiency (hypothetical future tri-tier chip)
func TestBuildPhases(t *testing.T) {
t.Parallel()
twoLevelPE := []platform.PerfLevel{
{Name: "Performance", PhysicalCPU: 10, LogicalCPU: 10},
{Name: "Efficiency", PhysicalCPU: 4, LogicalCPU: 4},
}
twoLevelSE := []platform.PerfLevel{
{Name: "Performance", PhysicalCPU: 4, LogicalCPU: 4},
{Name: "Efficiency", PhysicalCPU: 6, LogicalCPU: 6},
}
twoLevelSP := []platform.PerfLevel{
{Name: "Performance", PhysicalCPU: 6, LogicalCPU: 6},
{Name: "Performance", PhysicalCPU: 12, LogicalCPU: 12},
}
threeLevel := []platform.PerfLevel{
{Name: "Super", PhysicalCPU: 4, LogicalCPU: 4},
{Name: "Performance", PhysicalCPU: 8, LogicalCPU: 8},
{Name: "Efficiency", PhysicalCPU: 4, LogicalCPU: 4},
}
m4Pro, _ := platform.LookupSKULayout("M4 Pro")
m5Base, _ := platform.LookupSKULayout("M5")
m5Pro, _ := platform.LookupSKULayout("M5 Pro")
tests := []struct {
name string
levels []platform.PerfLevel
layout platform.SKULayout
wantLabels []string
wantCores []int
wantQoS []int
}{
{
name: "nil levels → 2 phases with zero cores",
levels: nil,
layout: platform.SKULayout{},
wantLabels: []string{"CPU Performance Core", "CPU Efficiency Core"},
wantCores: []int{0, 0},
wantQoS: []int{stress.QoSUserInitiated, stress.QoSBackground},
},
{
name: "2-level P+E (M4 Pro)",
levels: twoLevelPE,
layout: m4Pro,
wantLabels: []string{"CPU Performance Core", "CPU Efficiency Core"},
wantCores: []int{10, 4},
wantQoS: []int{stress.QoSUserInitiated, stress.QoSBackground},
},
{
name: "2-level S+E (M5 base) — top label is Super, not Performance",
levels: twoLevelSE,
layout: m5Base,
wantLabels: []string{"CPU Super Core", "CPU Efficiency Core"},
wantCores: []int{4, 6},
wantQoS: []int{stress.QoSUserInteractive, stress.QoSBackground},
},
{
name: "2-level S+P (M5 Pro) — Super + Performance, no Efficiency",
levels: twoLevelSP,
layout: m5Pro,
wantLabels: []string{"CPU Super Core", "CPU Performance Core"},
wantCores: []int{6, 12},
wantQoS: []int{stress.QoSUserInteractive, stress.QoSUserInitiated},
},
{
name: "3-level chip (hypothetical Super+Performance+Efficiency)",
levels: threeLevel,
layout: platform.SKULayout{},
wantLabels: []string{"CPU Super Core", "CPU Performance Core", "CPU Efficiency Core"},
wantCores: []int{4, 8, 4},
wantQoS: []int{stress.QoSUserInteractive, stress.QoSUserInitiated, stress.QoSBackground},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := buildPhases(tt.levels, tt.layout)
if len(got) != len(tt.wantLabels) {
t.Fatalf("buildPhases: got %d phases, want %d; phases=%v",
len(got), len(tt.wantLabels), got)
}
for i, phase := range got {
if phase.label != tt.wantLabels[i] {
t.Errorf("buildPhases[%d].label = %q, want %q", i, phase.label, tt.wantLabels[i])
}
if phase.cores != tt.wantCores[i] {
t.Errorf("buildPhases[%d].cores = %d, want %d", i, phase.cores, tt.wantCores[i])
}
if phase.qos != tt.wantQoS[i] {
t.Errorf("buildPhases[%d].qos = %d, want %d", i, phase.qos, tt.wantQoS[i])
}
}
})
}
}
func TestFormatRange(t *testing.T) {
t.Parallel()
tests := []struct {
name string
min int
max int
want string
}{
{"equal bounds collapse", 8, 8, "8"},
{"distinct bounds use en-dash", 8, 10, "8–10"},
{"zero is a valid singleton", 0, 0, "0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := formatRange(tt.min, tt.max)
if got != tt.want {
t.Errorf("formatRange(%d, %d) = %q, want %q", tt.min, tt.max, got, tt.want)
}
})
}
}
// TestExpectedCores covers the per-phase-label lookup for SKU expectations,
// including the "absent core type" case that the Phase 4a pair-signature
// validation depends on (returning 0,0 to mean "skip range check").
func TestExpectedCores(t *testing.T) {
t.Parallel()
m4Pro, _ := platform.LookupSKULayout("M4 Pro")
m5Pro, _ := platform.LookupSKULayout("M5 Pro")
m5Base, _ := platform.LookupSKULayout("M5")
tests := []struct {
name string
layout platform.SKULayout
label string
wantMin, wantMax int
}{
{"M4 Pro Performance: 8–10P", m4Pro, labelPerformanceCore, 8, 10},
{"M4 Pro Efficiency: 4E", m4Pro, labelEfficiencyCore, 4, 4},
{"M4 Pro has no Super tier → 0,0", m4Pro, labelSuperCore, 0, 0},
{"M5 Pro Super: 5–6S", m5Pro, labelSuperCore, 5, 6},
{"M5 Pro Performance: 10–12P", m5Pro, labelPerformanceCore, 10, 12},
{"M5 Pro has no Efficiency tier → 0,0", m5Pro, labelEfficiencyCore, 0, 0},
{"M5 base has no Performance tier → 0,0", m5Base, labelPerformanceCore, 0, 0},
{"unknown label → 0,0", m4Pro, "CPU Bogus Core", 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotMin, gotMax := expectedCores(tt.layout, tt.label)
if gotMin != tt.wantMin || gotMax != tt.wantMax {
t.Errorf("expectedCores(%q): got (%d, %d), want (%d, %d)",
tt.label, gotMin, gotMax, tt.wantMin, tt.wantMax)
}
})
}
}
// TestLayoutSummary verifies the human-readable preamble line, including the
// "S + P" composition (no Efficiency on M5 Pro/Max) and the "S + E" composition
// (no Performance on M5 base) which are the cases the existing buildPhases
// could otherwise mis-label.
func TestLayoutSummary(t *testing.T) {
t.Parallel()
tests := []struct {
cpu string
want string
}{
{"M1", "4P + 4E, 7–8 GPU cores"},
{"M4 Pro", "8–10P + 4E, 16–20 GPU cores"},
{"M5", "3–4S + 6E, 8–10 GPU cores"},
{"M5 Pro", "5–6S + 10–12P, 16–20 GPU cores"},
{"M5 Max", "6S + 12P, 32–40 GPU cores"},
{"A18 Pro", "2P + 4E, 6 GPU cores"},
}
for _, tt := range tests {
t.Run(tt.cpu, func(t *testing.T) {
t.Parallel()
layout, ok := platform.LookupSKULayout(tt.cpu)
if !ok {
t.Fatalf("LookupSKULayout(%q) returned !ok", tt.cpu)
}
got := layoutSummary(layout)
if got != tt.want {
t.Errorf("layoutSummary(%q) = %q, want %q", tt.cpu, got, tt.want)
}
})
}
}
func TestSortedSeriesKeys(t *testing.T) {
t.Parallel()
tests := []struct {
name string
groups map[string][]string
want []string
}{
{
name: "three series sorted lexicographically",
groups: map[string][]string{
"Tp**": {"Tp00", "Tp01"},
"TC*c": {"TC0c", "TC1c"},
"Te*T": {"Te0T"},
},
want: []string{"TC*c", "Te*T", "Tp**"},
},
{
name: "empty map",
groups: map[string][]string{},
want: []string{},
},
{
name: "single entry",
groups: map[string][]string{
"TC*c": {"TC0c"},
},
want: []string{"TC*c"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := sortedSeriesKeys(tt.groups)
if len(got) != len(tt.want) {
t.Fatalf("sortedSeriesKeys: got %v (len %d), want %v (len %d)",
got, len(got), tt.want, len(tt.want))
}
for i, k := range tt.want {
if got[i] != k {
t.Errorf("sortedSeriesKeys[%d] = %q, want %q", i, got[i], k)
}
}
})
}
}
// TestAnnotateLine covers the three branches of the per-line annotator:
// canonical match (✓), key mapped under a different description (⚠), and
// not-yet-mapped key (★). Uses real AppleTemp entries so the test would catch
// a regression in the smc.LookupTempDesc filter logic too.
func TestAnnotateLine(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
label string
key string
family string
coreIdx int
contains string
}{
{
name: "M5 Tp00 matches Super Core 1 exactly",
line: "CPU Super Core 1:Tp00:M5",
label: "CPU Super Core",
key: "Tp00",
family: "M5",
coreIdx: 1,
contains: "✓ matches src/temp.txt",
},
{
name: "M5 Tp00 mis-labelled as Performance shows mismatch",
line: "CPU Performance Core 1:Tp00:M5",
label: "CPU Performance Core",
key: "Tp00",
family: "M5",
coreIdx: 1,
contains: `⚠ src/temp.txt has "CPU Super Core 1"`,
},
{
name: "Unknown key flagged as NEW",
line: "CPU Performance Core 1:TZZZ:M5",
label: "CPU Performance Core",
key: "TZZZ",
family: "M5",
coreIdx: 1,
contains: "★ NEW for M5",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := annotateLine(tt.line, tt.label, tt.key, tt.family, tt.coreIdx)
if !strings.Contains(got, tt.contains) {
t.Errorf("annotateLine(...) = %q\n must contain %q", got, tt.contains)
}
if !strings.Contains(got, tt.line) {
t.Errorf("annotateLine(...) = %q\n must contain original line %q",
got, tt.line)
}
})
}
}
// TestAllDetectedKeys verifies the union semantics: keys appear regardless of
// which phase produced them, and cluster keys are merged in. Duplicate keys
// across phases are de-duplicated.
func TestAllDetectedKeys(t *testing.T) {
t.Parallel()
results := []phaseResult{
{
spec: phaseSpec{label: labelPerformanceCore},
deltas: map[string]float32{"Tp00": 5.0, "Tp01": 4.5},
},
{
// Tp01 cross-phase; must dedup.
spec: phaseSpec{label: labelEfficiencyCore},
deltas: map[string]float32{"Tp01": 2.0, "Te04": 7.0},
},
}
cluster := []string{"Tex0", "Tpx0"}
got := allDetectedKeys(results, cluster)
want := []string{"Tp00", "Tp01", "Te04", "Tex0", "Tpx0"}
if len(got) != len(want) {
t.Fatalf("allDetectedKeys returned %d keys, want %d: %v", len(got), len(want), got)
}
for _, k := range want {
if _, ok := got[k]; !ok {
t.Errorf("allDetectedKeys missing %q (got %v)", k, got)
}
}
}
================================================
FILE: cmd/hw.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// hwCmd represents the hw command.
var hwCmd = &cobra.Command{
Use: "hw",
Aliases: []string{"hardware", "info"},
Short: "Display hardware information",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Hardware()
},
}
// init registers the hw subcommand with the root command.
func init() {
rootCmd.AddCommand(hwCmd)
}
================================================
FILE: cmd/power.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents power command.
var powerCmd = &cobra.Command{
Use: "power",
Aliases: []string{"pow"},
Short: "Display power sensors",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Power()
},
}
// init registers the power subcommand with the root command.
func init() {
rootCmd.AddCommand(powerCmd)
}
================================================
FILE: cmd/raw.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"fmt"
"strings"
"github.com/dkorunic/iSMC/smc"
"github.com/spf13/cobra"
)
// Hex alphabet indexed by nibble; avoids per-byte fmt.Sprintf.
const hexDigits = "0123456789abcdef"
// formatBytesHex renders size bytes from b as space-separated lowercase
// two-digit hex pairs (e.g. "01 0a ff").
func formatBytesHex(b [32]byte, size uint32) string {
if size == 0 {
return ""
}
if size > 32 {
size = 32
}
var sb strings.Builder
sb.Grow(int(size)*3 - 1)
for i := uint32(0); i < size; i++ {
if i > 0 {
sb.WriteByte(' ')
}
sb.WriteByte(hexDigits[b[i]>>4])
sb.WriteByte(hexDigits[b[i]&0x0f])
}
return sb.String()
}
// rawCmd dumps every SMC key with its raw type and bytes in the same format used by report files.
var rawCmd = &cobra.Command{
Use: "raw",
Short: "Display all raw SMC keys and their byte values",
Run: func(_ *cobra.Command, _ []string) {
for _, k := range smc.GetRaw() {
byteStr := formatBytesHex([32]byte(k.Bytes), k.DataSize)
decoded := smc.DecodeValue(k.DataType, k.Bytes, k.DataSize)
if decoded != "" {
fmt.Printf(" %s [%-4s] %s (bytes %s)\n", k.Key, k.DataType, decoded, byteStr)
} else {
fmt.Printf(" %s [%-4s] (bytes %s)\n", k.Key, k.DataType, byteStr)
}
}
},
}
// init registers the raw subcommand with the root command.
func init() {
rootCmd.AddCommand(rawCmd)
}
================================================
FILE: cmd/root.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var OutputFlag string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "iSMC",
Short: "Apple SMC information tool",
Long: `Apple SMC CLI tool that can decode and display temperature, fans, battery, power, voltage and current
information for various hardware in your Apple Mac hardware.`,
Run: func(cmd *cobra.Command, args []string) {
allCmd.Run(cmd, args)
},
}
// Execute runs the root Cobra command and exits with a non-zero status on error.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// init registers the persistent --output flag on the root command.
func init() {
rootCmd.PersistentFlags().StringVarP(&OutputFlag, "output", "o", "table", "Output format (ascii, table, json, influx)")
}
================================================
FILE: cmd/temp.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents temp command.
var tempCmd = &cobra.Command{
Use: "temp",
Aliases: []string{"temperature", "tmp"},
Short: "Display temperature sensors",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Temperature()
},
}
// init registers the temp subcommand with the root command.
func init() {
rootCmd.AddCommand(tempCmd)
}
================================================
FILE: cmd/version.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"fmt"
"runtime"
"strings"
"github.com/spf13/cobra"
)
var (
GitTag = ""
GitCommit = ""
GitDirty = ""
BuildTime = ""
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of iSMC",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("iSMC %v %v%v, built on %v, with %v\n", GitTag, GitCommit, GitDirty,
BuildTime, runtime.Version())
},
}
func init() {
GitTag = strings.TrimSpace(GitTag)
GitCommit = strings.TrimSpace(GitCommit)
GitDirty = strings.TrimSpace(GitDirty)
BuildTime = strings.TrimSpace(BuildTime)
rootCmd.AddCommand(versionCmd)
}
================================================
FILE: cmd/volt.go
================================================
// Copyright (C) 2019 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package cmd
import (
"github.com/dkorunic/iSMC/output"
"github.com/spf13/cobra"
)
// rootCmd represents volt command.
var voltCmd = &cobra.Command{
Use: "volt",
Aliases: []string{"voltage", "vol"},
Short: "Display voltage sensors",
Run: func(_ *cobra.Command, args []string) {
output.Factory(OutputFlag).Voltage()
},
}
// init registers the volt subcommand with the root command.
func init() {
rootCmd.AddCommand(voltCmd)
}
================================================
FILE: go.mod
================================================
module github.com/dkorunic/iSMC
go 1.26
replace github.com/dkorunic/iSMC/gosmc => ./gosmc
require (
github.com/dkorunic/iSMC/gosmc v0.0.0-20260413130435-fb3be841d2e6
github.com/fvbommel/sortorder v1.1.0
github.com/spf13/cobra v1.10.2
)
require (
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.37.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jedib0t/go-pretty/v6 v6.7.10
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/testify v1.11.1
golang.org/x/sys v0.44.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.7.10 h1:B/2qW2Bkv2L6n14PP8o1kx75kWzHOQ3YTluWzg9icac=
github.com/jedib0t/go-pretty/v6 v6.7.10/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: gosmc/.clang-format
================================================
---
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseArrows: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseExpressionOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
BreakTemplateDeclarations: MultiLine
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: true
AtStartOfFile: true
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MainIncludeChar: Quote
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
ExceptDoubleParentheses: false
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TableGenBreakInsideDAGArg: DontBreak
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...
================================================
FILE: gosmc/LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: gosmc/README.md
================================================
# gosmc
A Go package providing CGo bindings to the Apple System Management Controller (SMC) via macOS IOKit. It wraps the C-level SMC interface — open/close, key-info lookup, key read, and key write — into idiomatic Go types.
**macOS only.** All source files carry `//go:build darwin`. The package is a nested Go module (`github.com/dkorunic/iSMC/gosmc`) and requires `CGO_ENABLED=1`.
## API
### Connection management
```go
// Open a connection to the named IOKit service (typically "AppleSMC").
// Returns (connection handle, IOReturn result code).
connection, result := gosmc.SMCOpen("AppleSMC")
if result != gosmc.IOReturnSuccess {
// handle error
}
defer gosmc.SMCClose(connection)
```
### Reading a key
```go
val, result := gosmc.SMCReadKey(connection, "TC0P")
if result != gosmc.IOReturnSuccess {
// handle error
}
fmt.Printf("key=%s type=%s size=%d bytes=%v\n",
val.Key.ToString(), val.DataType.ToString(), val.DataSize, val.Bytes[:val.DataSize])
```
`SMCReadKey` internally uses a process-wide sorted-array cache (up to 2048 entries, binary-searched) so that key-info lookups hit IOKit only on first access. Subsequent reads for the same key avoid the blocking `SMCGetKeyInfo` round-trip entirely. Keys confirmed to be absent or restricted are negatively cached, so repeated lookups for unavailable keys also skip IOKit.
### Low-level call
```go
input := &gosmc.SMCKeyData{
Data8: gosmc.CMDReadIndex,
Data32: index,
}
output, result := gosmc.SMCCall(connection, gosmc.KernelIndexSMC, input)
```
`SMCCall` maps directly to `IOConnectCallStructMethod`. After a successful transport call it validates that the driver wrote enough bytes to cover at least the SMC result field; if the response is truncated it returns `kIOReturnUnderrun`. Use it when you need raw index-based enumeration (e.g. iterating all keys via `CMDReadIndex`).
### Writing a key
```go
val := &gosmc.SMCVal{DataSize: 1}
val.Key[0], val.Key[1], val.Key[2], val.Key[3] = 'F', '0', 'T', 'g'
val.DataType[0], val.DataType[1], val.DataType[2], val.DataType[3] = 'f', 'p', '2', 'e'
val.Bytes[0], val.Bytes[1] = 0x0C, 0x00
result := gosmc.SMCWriteKey(connection, val) // validates DataSize against current value
result = gosmc.SMCWriteKeyUnsafe(connection, val) // skips the pre-read size check
```
`SMCWriteKey` performs a pre-read to verify that `val.DataSize` matches the key's current size before writing. `SMCWriteKeyUnsafe` skips that check.
## Types
| Go type | Description |
|---------|-------------|
| `SMCVal` | Key name, data type, data size, and raw bytes for one SMC key |
| `SMCKeyData` | Full IOKit `SMCKeyData_t` struct used by `SMCCall` |
| `SMCBytes` | `[32]byte` — capped at the kernel's `SMC_MAX_DATA_SIZE` per-read limit |
| `UInt32Char` | `[5]byte` — null-terminated 4-character ASCII key or type name |
| `KeyInfo` | Data size, data type, and attributes for one key (cache entry value) |
| `DataVers` | SMC firmware version struct |
| `PLimitData` | SMC power-limit struct |
## Constants
### IOReturn result codes (`values.go`)
```go
gosmc.IOReturnSuccess // 0x000 — operation succeeded
gosmc.IOReturnError // 0x2bc — general error
gosmc.IOReturnNoDevice // 0x2c0 — SMC not found
gosmc.IOReturnBusy // 0x2d5 — device busy
gosmc.IOReturnTimeout // 0x2d6 — I/O timeout
// … full list in values.go
```
### SMC command codes
```go
gosmc.CMDReadBytes // 5 — read key value bytes
gosmc.CMDWriteBytes // 6 — write key value bytes
gosmc.CMDReadIndex // 8 — read key at numeric index
gosmc.CMDReadKeyinfo // 9 — read key metadata
gosmc.CMDReadPlimit // 11 — read power-limit data
gosmc.CMDReadVers // 12 — read SMC firmware version
```
### SMC data types
```go
gosmc.TypeSP78 // "sp78" — signed 7.8 fixed-point (most temperature sensors)
gosmc.TypeFLT // "flt" — 32-bit IEEE 754 float
gosmc.TypeUI8 // "ui8" — unsigned 8-bit integer
gosmc.TypeUI16 // "ui16" — unsigned 16-bit integer
gosmc.TypeUI32 // "ui32" — unsigned 32-bit integer
gosmc.TypeFLAG // "flag" — single boolean byte
// … full list in values.go
```
## Internal implementation notes
- **`smc.c`** — the C layer. Key functions: `SMCOpen`, `SMCClose`, `SMCCall`, `SMCReadKey`, `SMCWriteKey`, `SMCGetKeyInfo`. Internal static helpers `smcPackKeyBytes` (4-char key string → big-endian `UInt32`) and `smcUnpackKeyBytes` (`UInt32` → 4-char null-terminated string) are not part of the public API. `SMCOpen` returns `kIOReturnNoMemory` if `IOServiceMatching` fails before any IOKit call is made.
- **Key-info cache** — `SMCGetKeyInfo` maintains a process-global sorted array of up to 2048 `SMCKeyInfoCacheEntry_t` structs. Each entry carries a `negative` flag: positive entries store the key's metadata; negative entries record that the key is known to be absent or restricted, preventing repeated IOKit round-trips for unavailable keys. Lookups use binary search (O(log N)) and return +1 (hit), -1 (negative hit — skip IOKit), or 0 (miss). Inserts use binary search to find the insertion point and `memmove` to shift existing entries, keeping the array sorted at all times. A full cache blocks new insertions but never blocks in-place upgrades of negative entries to positive — the upgrade check runs before the capacity guard. `SMCClose` acquires the lock and flushes the cache before closing the port, preventing Mach port number recycling from causing a new connection to receive stale cached key-info from the old one. The `os_unfair_lock` is held only for the short cache-lookup and cache-insert sections; the blocking `SMCCall` for a cache miss runs outside the lock. After re-acquiring the lock to insert a result, the code re-checks that `g_cachedConn == conn` to discard results that belong to a connection that was switched by another thread while `SMCCall` was in progress.
- **`gosmc.go`** — the Go layer. Converts between Go structs and their `C.*` equivalents for each API call.
- **`values.go`** — IOReturn codes, SMC command codes, SMC type name constants, and SMC type size constants.
## Building
```sh
CGO_ENABLED=1 go build ./...
```
The package links against `-framework IOKit` (declared via `#cgo LDFLAGS` in `gosmc.go`).
## References
- [Apple IOKit documentation](https://developer.apple.com/documentation/iokit)
- [OS-X-FakeSMC-kozlek](https://github.com/RehabMan/OS-X-FakeSMC-kozlek) — original C SMC library this package wraps
- [osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp) — reference implementation
================================================
FILE: gosmc/go.mod
================================================
module github.com/dkorunic/iSMC/gosmc
go 1.26
================================================
FILE: gosmc/gosmc.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package gosmc
// #cgo CFLAGS: -O2 -Wall
// #cgo LDFLAGS: -framework IOKit
// #include <stdlib.h>
// #include "smc.h"
import "C"
import (
"unsafe"
)
// DataVers is IOKit DataVers struct
type DataVers struct {
Major uint8
Minor uint8
Build uint8
Reserved [1]uint8
Release uint16
}
// PLimitData is IOKit PLimitData struct
type PLimitData struct {
Version uint16
Length uint16
CPUPLimit uint32
GPUPLimit uint32
MemPLimit uint32
}
// KeyInfo is IOKit KeyInfo struct
type KeyInfo struct {
DataSize uint32
DataType uint32
DataAttributes uint8
}
// SMCKeyData is IOKit SMCKeyData struct
type SMCKeyData struct {
Key uint32
Vers DataVers
PLimitData PLimitData
KeyInfo KeyInfo
Result uint8
Status uint8
Data8 uint8
Data32 uint32
Bytes SMCBytes
}
// SMCVal is IOKit SMCVal struct
type SMCVal struct {
Key UInt32Char
DataSize uint32
DataType UInt32Char
Bytes SMCBytes
}
// UInt32Char is IOKit UInt32Char type
type UInt32Char [5]byte
// toC converts a Go UInt32Char to the equivalent C UInt32Char_t representation.
// Layout-compatible reinterpret avoids a per-byte copy loop.
func (bs UInt32Char) toC() C.UInt32Char_t {
return *(*C.UInt32Char_t)(unsafe.Pointer(&bs))
}
// ToString returns the UInt32Char as a Go string.
func (bs UInt32Char) ToString() string {
return string(bs[:])
}
// uint32CharFromC is the inverse of toC; same layout-compatible reinterpret.
func uint32CharFromC(xs C.UInt32Char_t) UInt32Char {
return *(*UInt32Char)(unsafe.Pointer(&xs))
}
// SMCBytes is IOKit UInt32Char SMCBytes (capped at the kernel's SMC_MAX_DATA_SIZE per-read limit).
type SMCBytes [32]byte
// toC converts a Go SMCBytes to the equivalent C SMCBytes_t array; see UInt32Char.toC.
func (bs SMCBytes) toC() C.SMCBytes_t {
return *(*C.SMCBytes_t)(unsafe.Pointer(&bs))
}
// smcBytesFromC is the inverse of SMCBytes.toC.
func smcBytesFromC(xs C.SMCBytes_t) SMCBytes {
return *(*SMCBytes)(unsafe.Pointer(&xs))
}
// SMCOpen wrapper for Apple IOKit SMCOpen
func SMCOpen(service string) (connection uint, result int) {
svc := C.CString(service)
defer C.free(unsafe.Pointer(svc))
var conn C.uint
result = int(C.SMCOpen(svc, &conn))
connection = uint(conn)
return connection, result
}
// SMCClose wrapper for Apple IOKit SMCClose
func SMCClose(connection uint) int {
return int(C.SMCClose(C.uint(connection)))
}
// SMCReadKey wrapper for Apple IOKit SMCReadKey
func SMCReadKey(connection uint, key string) (*SMCVal, int) {
k := C.CString(key)
defer C.free(unsafe.Pointer(k))
v := C.SMCVal_t{}
result := C.SMCReadKey(C.uint(connection), k, &v)
return &SMCVal{
Key: uint32CharFromC(v.key),
DataSize: uint32(v.dataSize),
DataType: uint32CharFromC(v.dataType),
Bytes: smcBytesFromC(v.bytes),
}, int(result)
}
// SMCCall wrapper for Apple IOKit SMCCall
func SMCCall(connection uint, index uint, inputStruct *SMCKeyData) (*SMCKeyData, int) {
in := C.SMCKeyData_t{
key: C.uint(inputStruct.Key),
vers: C.SMCKeyData_vers_t{
major: C.uchar(inputStruct.Vers.Major),
minor: C.uchar(inputStruct.Vers.Minor),
build: C.uchar(inputStruct.Vers.Build),
reserved: [1]C.uchar{C.uchar(inputStruct.Vers.Reserved[0])},
release: C.ushort(inputStruct.Vers.Release),
},
pLimitData: C.SMCKeyData_pLimitData_t{
version: C.ushort(inputStruct.PLimitData.Version),
length: C.ushort(inputStruct.PLimitData.Length),
cpuPLimit: C.uint(inputStruct.PLimitData.CPUPLimit),
gpuPLimit: C.uint(inputStruct.PLimitData.GPUPLimit),
memPLimit: C.uint(inputStruct.PLimitData.MemPLimit),
},
keyInfo: C.SMCKeyData_keyInfo_t{
dataSize: C.uint(inputStruct.KeyInfo.DataSize),
dataType: C.uint(inputStruct.KeyInfo.DataType),
dataAttributes: C.uchar(inputStruct.KeyInfo.DataAttributes),
},
result: C.uchar(inputStruct.Result),
status: C.uchar(inputStruct.Status),
data8: C.uchar(inputStruct.Data8),
data32: C.uint(inputStruct.Data32),
bytes: inputStruct.Bytes.toC(),
}
out := C.SMCKeyData_t{}
result := C.SMCCall(C.uint(connection), C.uint(index), &in, &out)
return &SMCKeyData{
Key: uint32(out.key),
Vers: DataVers{
Major: uint8(out.vers.major),
Minor: uint8(out.vers.minor),
Build: uint8(out.vers.build),
Reserved: [1]uint8{uint8(out.vers.reserved[0])},
Release: uint16(out.vers.release),
},
PLimitData: PLimitData{
Version: uint16(out.pLimitData.version),
Length: uint16(out.pLimitData.length),
CPUPLimit: uint32(out.pLimitData.cpuPLimit),
GPUPLimit: uint32(out.pLimitData.gpuPLimit),
MemPLimit: uint32(out.pLimitData.memPLimit),
},
KeyInfo: KeyInfo{
DataSize: uint32(out.keyInfo.dataSize),
DataType: uint32(out.keyInfo.dataType),
DataAttributes: uint8(out.keyInfo.dataAttributes),
},
Result: uint8(out.result),
Status: uint8(out.status),
Data8: uint8(out.data8),
Data32: uint32(out.data32),
Bytes: smcBytesFromC(out.bytes),
}, int(result)
}
// SMCWriteKey wrapper for Apple IOKit SMCWriteKey
func SMCWriteKey(connection uint, val *SMCVal) int {
result := C.SMCWriteKey(C.uint(connection), &C.SMCVal_t{
key: val.Key.toC(),
dataSize: C.uint(val.DataSize),
dataType: val.DataType.toC(),
bytes: val.Bytes.toC(),
})
return int(result)
}
// SMCWriteKeyUnsafe wrapper for Apple IOKit SMCWriteKeyUnsafe
func SMCWriteKeyUnsafe(connection uint, val *SMCVal) int {
result := C.SMCWriteKeyUnsafe(C.uint(connection), &C.SMCVal_t{
key: val.Key.toC(),
dataSize: C.uint(val.DataSize),
dataType: val.DataType.toC(),
bytes: val.Bytes.toC(),
})
return int(result)
}
================================================
FILE: gosmc/smc.c
================================================
/*
* Apple System Management Control (SMC) Tool
* Copyright (C) 2006 devnull
* Copyright (C) 2026 Dinko Korunic
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
*/
/*
cc ./smc.c -o smcutil -framework IOKit -framework CoreFoundation
-Wno-four-char-constants -Wall -g -arch arm64 -arch x86_64
*/
#include <IOKit/IOKitLib.h>
#include <os/lock.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "smc.h"
// Cache SMC key info to reduce IOKit round-trips.
// 2048 entries covers all keys on current and near-future Apple Silicon Macs
// (M4/M5 report ~1800 keys).
#define KEY_INFO_CACHE_SIZE 2048
typedef struct {
UInt32 key;
int negative; // 1 = negative cache entry (key absent/restricted), 0 = valid
SMCKeyData_keyInfo_t keyInfo; // only meaningful when negative == 0
} SMCKeyInfoCacheEntry_t;
// All cache globals are static — they are internal implementation details not
// exported to other TUs.
static SMCKeyInfoCacheEntry_t g_keyInfoCache[KEY_INFO_CACHE_SIZE];
static UInt32 g_keyInfoCacheCount = 0;
static io_connect_t g_cachedConn = IO_OBJECT_NULL;
static os_unfair_lock g_keyInfoSpinLock = OS_UNFAIR_LOCK_INIT;
// smcPackKeyBytes packs a 4-character SMC key string into a big-endian UInt32.
// size is clamped to [0, 4] to prevent shift UB (C99 §6.5.7: shifting ≥32 bits
// is undefined).
static UInt32 smcPackKeyBytes(const char *str, int size) {
UInt32 total = 0;
if (size < 0)
size = 0;
if (size > 4)
size = 4;
for (int i = 0; i < size; i++)
total |= (UInt32)(unsigned char)str[i] << ((size - 1 - i) * 8);
return total;
}
// smcUnpackKeyBytes converts a big-endian UInt32 key code back to a 4-character
// null-terminated string.
static void smcUnpackKeyBytes(char *str, UInt32 val) {
snprintf(str, 5, "%c%c%c%c", (val >> 24) & 0xFF, (val >> 16) & 0xFF,
(val >> 8) & 0xFF, val & 0xFF);
}
kern_return_t SMCOpen(const char *serviceName, io_connect_t *conn) {
kern_return_t result;
io_iterator_t iterator;
io_object_t device;
CFMutableDictionaryRef matchingDictionary = IOServiceMatching(serviceName);
if (!matchingDictionary)
return kIOReturnNoMemory;
result = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDictionary,
&iterator);
if (result != kIOReturnSuccess)
return result;
device = IOIteratorNext(iterator);
IOObjectRelease(iterator);
if (device == IO_OBJECT_NULL)
return kIOReturnNoDevice;
result = IOServiceOpen(device, mach_task_self(), 0, conn);
IOObjectRelease(device);
if (result != kIOReturnSuccess)
return result;
return result;
}
// SMCClose closes the IOKit connection and flushes the key-info cache if it was
// built for this connection, preventing a future SMCOpen from reusing a stale
// cache via Mach port recycling.
kern_return_t SMCClose(io_connect_t conn) {
os_unfair_lock_lock(&g_keyInfoSpinLock);
if (g_cachedConn == conn) {
g_keyInfoCacheCount = 0;
g_cachedConn = IO_OBJECT_NULL;
}
os_unfair_lock_unlock(&g_keyInfoSpinLock);
return IOServiceClose(conn);
}
kern_return_t SMCCall(io_connect_t conn, UInt32 index,
const SMCKeyData_t *inputStructure,
SMCKeyData_t *outputStructure) {
const size_t structureInputSize = sizeof(SMCKeyData_t);
size_t structureOutputSize = sizeof(SMCKeyData_t);
kern_return_t result =
IOConnectCallStructMethod(conn, index, inputStructure, structureInputSize,
outputStructure, &structureOutputSize);
if (result != kIOReturnSuccess)
return result;
// Guard against a truncated response: the driver must write at least enough
// bytes to cover the SMC result code. A short write would leave
// outputStructure.result at 0 (from the caller's memset), silently masking a
// bad response from the driver.
if (structureOutputSize < offsetof(SMCKeyData_t, result) + sizeof(UInt8))
return kIOReturnUnderrun;
return kIOReturnSuccess;
}
// keyInfoCacheFind performs a binary search on the always-sorted cache using a
// half-open interval [lo, hi) — safe for UInt32 indices since hi never
// underflows when count is 0. Returns 1 on positive hit (valid keyInfo written
// to *keyInfo when non-NULL),
// -1 on negative hit (key is known to be absent/restricted — do not call
// IOKit),
// 0 on miss (key not yet seen).
// Must be called with g_keyInfoSpinLock held.
static int keyInfoCacheFind(UInt32 key, SMCKeyData_keyInfo_t *keyInfo) {
UInt32 lo = 0, hi = g_keyInfoCacheCount;
while (lo < hi) {
UInt32 mid = lo + (hi - lo) / 2;
if (g_keyInfoCache[mid].key == key) {
if (g_keyInfoCache[mid].negative)
return -1;
if (keyInfo)
*keyInfo = g_keyInfoCache[mid].keyInfo;
return 1;
}
if (g_keyInfoCache[mid].key < key)
lo = mid + 1;
else
hi = mid;
}
return 0;
}
// keyInfoCacheInsert inserts a new entry in sorted order via binary search +
// memmove, keeping the array always sorted so binary-search lookups never need
// a preceding sort step.
//
// Pass keyInfo=NULL to insert a negative cache entry (key known to be absent or
// restricted). If the key already exists:
// - positive entry: skipped (SMC key metadata is stable at runtime).
// - negative entry + non-NULL keyInfo: upgraded in place to positive (key
// became available).
// - negative entry + NULL keyInfo: skipped (already negatively cached).
//
// When the cache is full, the key falls through to an IOKit round-trip on every
// subsequent lookup. Must be called with g_keyInfoSpinLock held.
static void keyInfoCacheInsert(UInt32 key,
const SMCKeyData_keyInfo_t *keyInfo) {
// Binary search for the insertion point (half-open interval [lo, hi)).
UInt32 lo = 0, hi = g_keyInfoCacheCount;
while (lo < hi) {
UInt32 mid = lo + (hi - lo) / 2;
if (g_keyInfoCache[mid].key < key)
lo = mid + 1;
else
hi = mid;
}
// The binary search lands on the leftmost position where cache[lo].key >=
// key.
if (lo < g_keyInfoCacheCount && g_keyInfoCache[lo].key == key) {
// Key exists in cache. Upgrade a negative entry to positive if we now have
// valid data. This in-place upgrade does not require a free slot, so it
// runs regardless of capacity.
if (keyInfo != NULL && g_keyInfoCache[lo].negative) {
g_keyInfoCache[lo].keyInfo = *keyInfo;
g_keyInfoCache[lo].negative = 0;
}
return;
}
// Only genuinely new entries need a free slot.
if (g_keyInfoCacheCount >= KEY_INFO_CACHE_SIZE)
return;
// Shift elements right to make room, then write at lo.
memmove(&g_keyInfoCache[lo + 1], &g_keyInfoCache[lo],
(size_t)(g_keyInfoCacheCount - lo) * sizeof(SMCKeyInfoCacheEntry_t));
g_keyInfoCache[lo].key = key;
g_keyInfoCache[lo].negative = (keyInfo == NULL) ? 1 : 0;
if (keyInfo != NULL)
g_keyInfoCache[lo].keyInfo = *keyInfo;
else
memset(&g_keyInfoCache[lo].keyInfo, 0, sizeof(SMCKeyData_keyInfo_t));
++g_keyInfoCacheCount;
}
// SMCGetKeyInfo returns key metadata, using a sorted cache to reduce IOKit
// round-trips. The lock is held only for short cache operations; the blocking
// SMCCall runs lock-free. If conn differs from the connection that populated
// the cache, the cache is flushed to prevent stale key-info from a different
// SMC service being served to the new connection. On re-insertion, the conn is
// re-validated under lock: if another thread switched connections while SMCCall
// was in progress, our result belongs to a different service and is discarded.
// Negative results (key absent or restricted) are cached to avoid repeated
// IOKit round-trips.
static kern_return_t SMCGetKeyInfo(io_connect_t conn, UInt32 key,
SMCKeyData_keyInfo_t *keyInfo) {
SMCKeyData_t inputStructure;
SMCKeyData_t outputStructure;
kern_return_t result;
// Fast path: binary-search the sorted cache under the lock (no blocking I/O).
// Flush the cache when a different connection handle is presented.
os_unfair_lock_lock(&g_keyInfoSpinLock);
if (g_cachedConn != conn) {
g_keyInfoCacheCount = 0;
g_cachedConn = conn;
}
int found = keyInfoCacheFind(key, keyInfo);
os_unfair_lock_unlock(&g_keyInfoSpinLock);
if (found > 0)
return kIOReturnSuccess;
if (found < 0)
return kIOReturnError; // negative cache: key is known absent/restricted
// Cache miss: call SMCCall outside the lock — it is a blocking IOKit call and
// must not be held across it (os_unfair_lock is not designed for blocking
// sections).
memset(&inputStructure, 0, sizeof(inputStructure));
memset(&outputStructure, 0, sizeof(outputStructure));
inputStructure.key = key;
inputStructure.data8 = SMC_CMD_READ_KEYINFO;
result = SMCCall(conn, KERNEL_INDEX_SMC, &inputStructure, &outputStructure);
if (result != kIOReturnSuccess)
return result;
// Check the SMC-layer response code — distinct from the IOKit transport
// status. Cache the negative result so subsequent lookups for this key skip
// the IOKit round-trip.
if (outputStructure.result != 0) {
os_unfair_lock_lock(&g_keyInfoSpinLock);
if (g_cachedConn == conn)
keyInfoCacheInsert(key, NULL);
os_unfair_lock_unlock(&g_keyInfoSpinLock);
return kIOReturnError;
}
*keyInfo = outputStructure.keyInfo;
// Re-acquire to insert into the sorted cache. keyInfoCacheInsert handles
// duplicates internally, so no separate find is needed. Guard against conn
// switching: if another thread changed g_cachedConn while we were in SMCCall,
// our result is for the wrong service and must not be cached under the new
// connection.
os_unfair_lock_lock(&g_keyInfoSpinLock);
if (g_cachedConn == conn)
keyInfoCacheInsert(key, &outputStructure.keyInfo);
os_unfair_lock_unlock(&g_keyInfoSpinLock);
return kIOReturnSuccess;
}
kern_return_t SMCReadKey(io_connect_t conn, const UInt32Char_t key,
SMCVal_t *val) {
kern_return_t result;
SMCKeyData_t inputStructure;
SMCKeyData_t outputStructure;
memset(&inputStructure, 0, sizeof(SMCKeyData_t));
memset(&outputStructure, 0, sizeof(SMCKeyData_t));
memset(val, 0, sizeof(SMCVal_t));
inputStructure.key = smcPackKeyBytes(key, 4);
memcpy(val->key, key, sizeof(val->key));
result = SMCGetKeyInfo(conn, inputStructure.key, &outputStructure.keyInfo);
if (result != kIOReturnSuccess)
return result;
val->dataSize = outputStructure.keyInfo.dataSize;
smcUnpackKeyBytes(val->dataType, outputStructure.keyInfo.dataType);
// Cap read size to the kernel SMC driver's hard per-read limit.
// Requesting more than SMC_MAX_DATA_SIZE bytes returns kIOReturnBadArgument.
UInt32 readSize =
(val->dataSize > SMC_MAX_DATA_SIZE) ? SMC_MAX_DATA_SIZE : val->dataSize;
inputStructure.keyInfo.dataSize = readSize;
inputStructure.data8 = SMC_CMD_READ_BYTES;
result = SMCCall(conn, KERNEL_INDEX_SMC, &inputStructure, &outputStructure);
if (result != kIOReturnSuccess)
return result;
// Check the SMC-layer response code — a non-zero value means the read was
// rejected by the SMC even though the IOKit transport call succeeded.
if (outputStructure.result != 0)
return kIOReturnError;
memcpy(val->bytes, outputStructure.bytes, readSize);
val->dataSize = readSize;
return kIOReturnSuccess;
}
kern_return_t SMCWriteKey(io_connect_t conn, const SMCVal_t *val) {
SMCVal_t readVal;
kern_return_t result = SMCReadKey(conn, val->key, &readVal);
if (result != kIOReturnSuccess)
return result;
if (readVal.dataSize != val->dataSize)
return kIOReturnError;
return SMCWriteKeyUnsafe(conn, val);
}
kern_return_t SMCWriteKeyUnsafe(io_connect_t conn, const SMCVal_t *val) {
SMCKeyData_t inputStructure;
SMCKeyData_t outputStructure;
kern_return_t result;
memset(&inputStructure, 0, sizeof(SMCKeyData_t));
memset(&outputStructure, 0, sizeof(SMCKeyData_t));
inputStructure.key = smcPackKeyBytes(val->key, 4);
inputStructure.data8 = SMC_CMD_WRITE_BYTES;
// Cap write size to SMC_MAX_DATA_SIZE to keep keyInfo.dataSize consistent
// with the data actually present in the struct; the zeroed remainder is
// ignored by the driver.
UInt32 writeSize =
(val->dataSize > SMC_MAX_DATA_SIZE) ? SMC_MAX_DATA_SIZE : val->dataSize;
inputStructure.keyInfo.dataSize = writeSize;
memcpy(inputStructure.bytes, val->bytes, writeSize);
result = SMCCall(conn, KERNEL_INDEX_SMC, &inputStructure, &outputStructure);
if (result != kIOReturnSuccess)
return result;
// Check the SMC-layer response code — a non-zero value means the write was
// rejected by the SMC even though the IOKit transport call succeeded.
return outputStructure.result != 0 ? kIOReturnError : kIOReturnSuccess;
}
================================================
FILE: gosmc/smc.h
================================================
/*
* Apple System Management Control (SMC) Tool
* Copyright (C) 2006 devnull
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <IOKit/IOKitLib.h>
#ifndef __SMC_H__
#define __SMC_H__
#ifndef kIOMainPortDefault
// kIOMainPortDefault was introduced in macOS 12; define it for older SDKs.
#define kIOMainPortDefault MACH_PORT_NULL
#endif
#define VERSION "1.0"
#define OP_NONE 0
#define OP_LIST 1
#define OP_READ 2
#define OP_READ_FAN 3
#define OP_WRITE 4
#define OP_BRUTEFORCE 5
#define KERNEL_INDEX_SMC 2
#define SMC_CMD_READ_BYTES 5
#define SMC_CMD_WRITE_BYTES 6
#define SMC_CMD_READ_INDEX 8
#define SMC_CMD_READ_KEYINFO 9
#define SMC_CMD_READ_PLIMIT 11
#define SMC_CMD_READ_VERS 12
#define SMC_TYPE_FPE2 "fpe2"
#define SMC_TYPE_FP2E "fp2e"
#define SMC_TYPE_FP4C "fp4c"
#define SMC_TYPE_CH8 "ch8*"
#define SMC_TYPE_SP78 "sp78"
#define SMC_TYPE_SP4B "sp4b"
#define SMC_TYPE_FP5B "fp5b"
#define SMC_TYPE_FP88 "fp88"
#define SMC_TYPE_UI8 "ui8"
#define SMC_TYPE_UI16 "ui16"
#define SMC_TYPE_UI32 "ui32"
#define SMC_TYPE_SI8 "si8"
#define SMC_TYPE_SI16 "si16"
#define SMC_TYPE_SI32 "si32"
#define SMC_TYPE_FLAG "flag"
#define SMC_TYPE_FDS "{fds"
#define SMC_TYPE_FLT "flt"
#define SMC_TYPE_FPXX_SIZE 2
#define SMC_TYPE_SPXX_SIZE 2
#define SMC_TYPE_UI8_SIZE 1
#define SMC_TYPE_UI16_SIZE 2
#define SMC_TYPE_UI32_SIZE 4
#define SMC_TYPE_SI8_SIZE 1
#define SMC_TYPE_SI16_SIZE 2
#define SMC_TYPE_SI32_SIZE 4
#define SMC_TYPE_FLAG_SIZE 1
typedef struct {
UInt8 major;
UInt8 minor;
UInt8 build;
UInt8 reserved[1];
UInt16 release;
} SMCKeyData_vers_t;
typedef struct {
UInt16 version;
UInt16 length;
UInt32 cpuPLimit;
UInt32 gpuPLimit;
UInt32 memPLimit;
} SMCKeyData_pLimitData_t;
typedef struct {
UInt32 dataSize;
UInt32 dataType;
UInt8 dataAttributes;
} SMCKeyData_keyInfo_t;
// SMC_MAX_DATA_SIZE is the kernel SMC driver's hard per-read byte limit.
// Requesting more bytes returns kIOReturnBadArgument regardless of buffer size.
#define SMC_MAX_DATA_SIZE 32
typedef UInt8 SMCBytes_t[SMC_MAX_DATA_SIZE];
typedef struct {
UInt32 key;
SMCKeyData_vers_t vers;
SMCKeyData_pLimitData_t pLimitData;
SMCKeyData_keyInfo_t keyInfo;
UInt8 result;
UInt8 status;
UInt8 data8;
UInt32 data32;
SMCBytes_t bytes;
} SMCKeyData_t;
typedef char UInt32Char_t[5];
typedef struct {
UInt32Char_t key;
UInt32 dataSize;
UInt32Char_t dataType;
SMCBytes_t bytes;
} SMCVal_t;
kern_return_t SMCOpen(const char *serviceName, io_connect_t *conn);
kern_return_t SMCClose(io_connect_t conn);
kern_return_t SMCCall(io_connect_t conn, UInt32 index, const SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure);
kern_return_t SMCReadKey(io_connect_t conn, const UInt32Char_t key, SMCVal_t *val);
kern_return_t SMCWriteKey(io_connect_t conn, const SMCVal_t *val);
kern_return_t SMCWriteKeyUnsafe(io_connect_t conn, const SMCVal_t *val);
#endif
================================================
FILE: gosmc/values.go
================================================
// Copyright (C) 2026 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
package gosmc
// IOReturn values
const (
IOReturnSuccess = 0x0 // OK
IOReturnError = 0x2bc // general error
IOReturnNoMemory = 0x2bd // can't allocate memory
IOReturnNoResources = 0x2be // resource shortage
IOReturnIPCError = 0x2bf // error during IPC
IOReturnNoDevice = 0x2c0 // no such device
IOReturnNotPrivileged = 0x2c1 // privilege violation
IOReturnBadArgument = 0x2c2 // invalid argument
IOReturnLockedRead = 0x2c3 // device read locked
IOReturnLockedWrite = 0x2c4 // device write locked
IOReturnExclusiveAccess = 0x2c5 // exclusive access and
// device already open
IOReturnBadMessageID = 0x2c6 // sent/received messages
// had different msg_id
IOReturnUnsupported = 0x2c7 // unsupported function
IOReturnVMError = 0x2c8 // misc. VM failure
IOReturnInternalError = 0x2c9 // internal error
IOReturnIOError = 0x2ca // General I/O error
IOReturnCannotLock = 0x2cc // can't acquire lock
IOReturnNotOpen = 0x2cd // device not open
IOReturnNotReadable = 0x2ce // read not supported
IOReturnNotWritable = 0x2cf // write not supported
IOReturnNotAligned = 0x2d0 // alignment error
IOReturnBadMedia = 0x2d1 // Media Error
IOReturnStillOpen = 0x2d2 // device(s) still open
IOReturnRLDError = 0x2d3 // rld failure
IOReturnDMAError = 0x2d4 // DMA failure
IOReturnBusy = 0x2d5 // Device Busy
IOReturnTimeout = 0x2d6 // I/O Timeout
IOReturnOffline = 0x2d7 // device offline
IOReturnNotReady = 0x2d8 // not ready
IOReturnNotAttached = 0x2d9 // device not attached
IOReturnNoChannels = 0x2da // no DMA channels left
IOReturnNoSpace = 0x2db // no space for data
IOReturnPortExists = 0x2dd // port already exists
IOReturnCannotWire = 0x2de // can't wire down
// physical memory
IOReturnNoInterrupt = 0x2df // no interrupt attached
IOReturnNoFrames = 0x2e0 // no DMA frames enqueued
IOReturnMessageTooLarge = 0x2e1 // oversized msg received
// on interrupt port
IOReturnNotPermitted = 0x2e2 // not permitted
IOReturnNoPower = 0x2e3 // no power to device
IOReturnNoMedia = 0x2e4 // media not present
IOReturnUnformattedMedia = 0x2e5 // media not formatted
IOReturnUnsupportedMode = 0x2e6 // no such mode
IOReturnUnderrun = 0x2e7 // data underrun
IOReturnOverrun = 0x2e8 // data overrun
IOReturnDeviceError = 0x2e9 // the device is not working properly!
IOReturnNoCompletion = 0x2ea // a completion routine is required
IOReturnAborted = 0x2eb // operation aborted
IOReturnNoBandwidth = 0x2ec // bus bandwidth would be exceeded
IOReturnNotResponding = 0x2ed // device not responding
IOReturnIsoTooOld = 0x2ee // isochronous I/O request for distant past!
IOReturnIsoTooNew = 0x2ef // isochronous I/O request for distant future
IOReturnNotFound = 0x2f0 // data was not found
IOReturnInvalid = 0x1 // should never be seen
)
// OP Values
const (
OPNone = iota
OPList
OPRead
OPReadFan
OPWrite
OPBruteForce
)
// Kernel values
const (
KernelIndexSMC = 2
)
// SMC CMD values
const (
CMDReadBytes = 5
CMDWriteBytes = 6
CMDReadIndex = 8
CMDReadKeyinfo = 9
CMDReadPlimit = 11
CMDReadVers = 12
)
// SMC Type values
const (
TypeFPE2 = "fpe2"
TypeFP2E = "fp2e"
TypeFP4C = "fp4c"
TypeCH8 = "ch8*"
TypeSP78 = "sp78"
TypeSP4B = "sp4b"
TypeFP5B = "fp5b"
TypeFP88 = "fp88"
TypeUI8 = "ui8"
TypeUI16 = "ui16"
TypeUI32 = "ui32"
TypeSI8 = "si8"
TypeSI16 = "si16"
TypeSI32 = "si32"
TypeFLAG = "flag"
TypeFDS = "{fds"
TypeFLT = "flt"
)
// SMC Size values
const (
TypeFPXXSize = 2
TypeSPXXSize = 2
TypeUI8Size = 1
TypeUI16Size = 2
TypeUI32Size = 4
TypeSI8Size = 1
TypeSI16Size = 2
TypeSI32Size = 4
TypeFLAGSize = 1
)
================================================
FILE: hid/get.go
================================================
// Copyright (C) 2022 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package hid
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework IOKit
#import <Foundation/Foundation.h>
#import <IOKit/hidsystem/IOHIDEventSystemClient.h>
#include <unistd.h>
typedef struct __IOHIDEvent *IOHIDEventRef;
typedef struct __IOHIDServiceClient *IOHIDServiceClientRef;
typedef double IOHIDFloat;
IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator);
int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match);
IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t, int32_t, int64_t);
CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property);
IOHIDFloat IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field);
#define IOHIDEventFieldBase(type) (type << 16)
#define kIOHIDEventTypeTemperature 15
#define kIOHIDEventTypePower 25
NSDictionary *matching(int page, int usage) {
NSDictionary *dict = @{
@"PrimaryUsagePage" : [NSNumber numberWithInt:page],
@"PrimaryUsage" : [NSNumber numberWithInt:usage],
};
return dict;
}
// getNamesFromServices extracts the "Product" property from each service in srvRef.
// The caller is responsible for releasing the returned NSArray.
static NSArray *getNamesFromServices(CFArrayRef srvRef) {
NSArray *srvs = (__bridge NSArray *)srvRef;
long count = [srvs count];
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
IOHIDServiceClientRef sc = (IOHIDServiceClientRef)srvs[i];
NSString *name = (NSString *)IOHIDServiceClientCopyProperty(sc, (__bridge CFStringRef)@"Product");
if (name) {
[array addObject:name];
[name release];
} else {
[array addObject:@"noname"];
}
}
return array;
}
// getPowerValuesFromServices reads the power event float value from each service in srvRef.
// The caller is responsible for releasing the returned NSArray.
static NSArray *getPowerValuesFromServices(CFArrayRef srvRef) {
NSArray *srvs = (__bridge NSArray *)srvRef;
long count = [srvs count];
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
IOHIDServiceClientRef sc = (IOHIDServiceClientRef)srvs[i];
IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypePower, 0, 0);
double temp = 0.0;
if (event != 0) {
temp = IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(kIOHIDEventTypePower)) / 1000.0;
CFRelease(event);
}
[array addObject:[NSNumber numberWithDouble:temp]];
}
return array;
}
// getThermalValuesFromServices reads the temperature event float value from each service in srvRef.
// The caller is responsible for releasing the returned NSArray.
static NSArray *getThermalValuesFromServices(CFArrayRef srvRef) {
NSArray *srvs = (__bridge NSArray *)srvRef;
long count = [srvs count];
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
IOHIDServiceClientRef sc = (IOHIDServiceClientRef)srvs[i];
IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0);
double temp = 0.0;
if (event != 0) {
temp = IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature));
CFRelease(event);
}
[array addObject:[NSNumber numberWithDouble:temp]];
}
return array;
}
static NSString *dumpNamesValues(NSArray *kvsN, NSArray *kvsV) {
NSMutableString *valueString = [[NSMutableString alloc] init];
int count = (int)MIN([kvsN count], [kvsV count]);
for (int i = 0; i < count; i++) {
@autoreleasepool {
NSString *name = kvsN[i];
double value = [kvsV[i] doubleValue];
if (value <= 0.0) continue;
NSString *output = [NSString stringWithFormat:@"%s:%lf\n", [name UTF8String], value];
[valueString appendString:output];
}
}
return valueString;
}
// Above 130 °C (junction limit) the tdev reading must be raw sp78, not converted °C.
#define kSP78RawThreshold 130.0
// dumpThermalNamesValues formats thermal sensor names and values, applying sp78 conversion
// for specific HID thermal sensors that use the sp78 fixed-point format (e.g., PMU tdev sensors)
static NSString *dumpThermalNamesValues(NSArray *kvsN, NSArray *kvsV) {
NSMutableString *valueString = [[NSMutableString alloc] init];
int count = (int)MIN([kvsN count], [kvsV count]);
for (int i = 0; i < count; i++) {
@autoreleasepool {
NSString *name = kvsN[i];
double value = [kvsV[i] doubleValue];
if (value <= 0.0) continue;
// PMU tdevN sensors encode values as sp78; convert when raw.
NSRange range = [name rangeOfString:@"tdev"];
if (range.location != NSNotFound) {
if (range.location + 4 < [name length]) {
unichar nextChar = [name characterAtIndex:range.location + 4];
if (value > kSP78RawThreshold && nextChar >= '1' && nextChar <= '9') {
value = value / 256.0;
}
}
}
NSString *output = [NSString stringWithFormat:@"%s:%lf\n", [name UTF8String], value];
[valueString appendString:output];
}
}
return valueString;
}
// queryHIDPowerSensors queries IOHIDEventSystem for power sensors matching the given
// HID page and usage, returning a "name:value\n" formatted string. The caller must free it.
static char *queryHIDPowerSensors(int page, int usage) {
char *finalStr = strdup("");
@autoreleasepool {
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
if (system) {
NSDictionary *sensors = matching(page, usage);
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
CFArrayRef srvRef = IOHIDEventSystemClientCopyServices(system);
if (srvRef) {
NSArray *names = getNamesFromServices(srvRef);
NSArray *values = getPowerValuesFromServices(srvRef);
NSString *result = dumpNamesValues(names, values);
CFRelease(srvRef);
const char *utf8 = result ? [result UTF8String] : "";
free(finalStr);
finalStr = strdup(utf8 ? utf8 : "");
CFRelease(names);
CFRelease(values);
CFRelease(result);
}
CFRelease(system);
}
}
return finalStr;
}
char *getCurrents() {
return queryHIDPowerSensors(0xff08, 2);
}
char *getVoltages() {
return queryHIDPowerSensors(0xff08, 3);
}
char *getThermals() {
char *finalStr = strdup("");
@autoreleasepool {
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
if (system) {
NSDictionary *sensors = matching(0xff00, 5);
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
CFArrayRef srvRef = IOHIDEventSystemClientCopyServices(system);
if (srvRef) {
NSArray *names = getNamesFromServices(srvRef);
NSArray *values = getThermalValuesFromServices(srvRef);
NSString *result = dumpThermalNamesValues(names, values);
CFRelease(srvRef);
const char *utf8 = result ? [result UTF8String] : "";
free(finalStr);
finalStr = strdup(utf8 ? utf8 : "");
CFRelease(names);
CFRelease(values);
CFRelease(result);
}
CFRelease(system);
}
}
return finalStr;
}
// HIDSensorData holds strdup'd C strings for all three sensor types. The caller
// must free each non-NULL field.
typedef struct {
char *currents;
char *voltages;
char *thermals;
} HIDSensorData;
// getAllHIDSensors opens a single HID event system client, queries all three
// sensor types by updating the matching criteria between queries, and returns
// their formatted output strings. Each field is always a valid strdup'd string
// (never NULL) that the caller must free.
HIDSensorData getAllHIDSensors(void) {
HIDSensorData result = { strdup(""), strdup(""), strdup("") };
@autoreleasepool {
IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
if (system) {
// Currents (page 0xff08, usage 2)
{
NSDictionary *sensors = matching(0xff08, 2);
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
CFArrayRef srvRef = IOHIDEventSystemClientCopyServices(system);
if (srvRef) {
NSArray *names = getNamesFromServices(srvRef);
NSArray *values = getPowerValuesFromServices(srvRef);
NSString *str = dumpNamesValues(names, values);
const char *utf8 = str ? [str UTF8String] : "";
free(result.currents);
result.currents = strdup(utf8 ? utf8 : "");
CFRelease(names);
CFRelease(values);
CFRelease(str);
CFRelease(srvRef);
}
}
// Voltages (page 0xff08, usage 3)
{
NSDictionary *sensors = matching(0xff08, 3);
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
CFArrayRef srvRef = IOHIDEventSystemClientCopyServices(system);
if (srvRef) {
NSArray *names = getNamesFromServices(srvRef);
NSArray *values = getPowerValuesFromServices(srvRef);
NSString *str = dumpNamesValues(names, values);
const char *utf8 = str ? [str UTF8String] : "";
free(result.voltages);
result.voltages = strdup(utf8 ? utf8 : "");
CFRelease(names);
CFRelease(values);
CFRelease(str);
CFRelease(srvRef);
}
}
// Thermals (page 0xff00, usage 5)
{
NSDictionary *sensors = matching(0xff00, 5);
IOHIDEventSystemClientSetMatching(system, (__bridge CFDictionaryRef)sensors);
CFArrayRef srvRef = IOHIDEventSystemClientCopyServices(system);
if (srvRef) {
NSArray *names = getNamesFromServices(srvRef);
NSArray *values = getThermalValuesFromServices(srvRef);
NSString *str = dumpThermalNamesValues(names, values);
const char *utf8 = str ? [str UTF8String] : "";
free(result.thermals);
result.thermals = strdup(utf8 ? utf8 : "");
CFRelease(names);
CFRelease(values);
CFRelease(str);
CFRelease(srvRef);
}
}
CFRelease(system);
}
}
return result;
}
*/
import "C"
import (
"unsafe"
)
// GetAll returns all detected HID sensor results using a single HID client session.
func GetAll() map[string]any {
data := C.getAllHIDSensors()
sensors := make(map[string]any)
sensors["Current"] = hidGet(data.currents, "A")
sensors["Temperature"] = hidGet(data.thermals, "°C")
sensors["Voltage"] = hidGet(data.voltages, "V")
return sensors
}
// hidGet converts a C string returned by an HID sensor function into a sensor map,
// freeing the C string before returning. Returns an empty map when cStr is nil.
func hidGet(cStr *C.char, unit string) map[string]any {
if cStr == nil {
return map[string]any{}
}
defer C.free(unsafe.Pointer(cStr)) //nolint:wsl,nlreturn
return getGeneric(unit, cStr)
}
// GetCurrent returns detected HID current sensor results.
func GetCurrent() map[string]any {
return hidGet(C.getCurrents(), "A")
}
// GetVoltage returns detected HID voltage sensor results.
func GetVoltage() map[string]any {
return hidGet(C.getVoltages(), "V")
}
// GetTemperature returns detected HID temperature sensor results.
func GetTemperature() map[string]any {
return hidGet(C.getThermals(), "°C")
}
================================================
FILE: hid/hid.go
================================================
// Copyright (C) 2022 Dinko Korunic
// SPDX-License-Identifier: GPL-3.0-only
//go:build darwin
package hid
import "C"
import (
"bufio"
"fmt"
"strconv"
"strings"
)
const (
SensorSeparator = ":"
SensorType = "hid"
)
// getGeneric returns a map of HID sensor stats.
func getGeneric(unit string, cStr *C.char) map[string]any {
goStr := C.GoString(cStr)
generic := make(map[string]any)
scanner := bufio.NewScanner(strings.NewReader(goStr))
for scanner.Scan() {
split := strings.Spl
gitextract_zre7wo39/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── camo-purge.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── AGENTS.md
├── CLAUDE.md
├── LICENSE
├── README.md
├── Taskfile.yml
├── cmd/
│ ├── all.go
│ ├── batt.go
│ ├── curr.go
│ ├── fans.go
│ ├── guess.go
│ ├── guess_test.go
│ ├── hw.go
│ ├── power.go
│ ├── raw.go
│ ├── root.go
│ ├── temp.go
│ ├── version.go
│ └── volt.go
├── go.mod
├── go.sum
├── gosmc/
│ ├── .clang-format
│ ├── LICENSE
│ ├── README.md
│ ├── go.mod
│ ├── gosmc.go
│ ├── smc.c
│ ├── smc.h
│ └── values.go
├── hid/
│ ├── get.go
│ └── hid.go
├── main.go
├── output/
│ ├── influxoutput.go
│ ├── influxoutput_test.go
│ ├── jsonoutput.go
│ ├── jsonoutput_test.go
│ ├── output.go
│ ├── output_test.go
│ ├── outputfactory.go
│ ├── outputfactory_test.go
│ ├── tableoutput.go
│ └── tableoutput_test.go
├── platform/
│ ├── get.go
│ ├── mapping.go
│ └── mapping_test.go
├── reports/
│ ├── report-a18.txt
│ ├── report-intel-t2.txt
│ ├── report-m1-max.txt
│ ├── report-m1-pro-2.txt
│ ├── report-m1-pro.txt
│ ├── report-m1-ultra.txt
│ ├── report-m2.txt
│ ├── report-m3-max-2.txt
│ ├── report-m3-max.txt
│ ├── report-m3-pro-2.txt
│ ├── report-m3-pro-3.txt
│ ├── report-m3-pro.txt
│ ├── report-m4-2.txt
│ ├── report-m4-3.txt
│ ├── report-m4-4.txt
│ ├── report-m4-max.txt
│ ├── report-m4-pro-2.txt
│ ├── report-m4-pro.txt
│ ├── report-m4.txt
│ ├── report-m5-max-2.txt
│ ├── report-m5-max.txt
│ ├── report-m5-pro.txt
│ ├── reports.go
│ └── reports_test.go
├── smc/
│ ├── conv.go
│ ├── conv_test.go
│ ├── gen-sensors.sh
│ ├── get.go
│ ├── mapping_test.go
│ ├── raw.go
│ ├── rawtemp.go
│ ├── rawtemp_test.go
│ ├── sensors.go
│ ├── smc.go
│ └── smc_test.go
├── src/
│ ├── current.txt
│ ├── fans.txt
│ ├── power.txt
│ ├── temp.txt
│ └── voltage.txt
└── stress/
└── stress_darwin.go
SYMBOL INDEX (402 symbols across 45 files)
FILE: cmd/all.go
function init (line 24) | func init() {
FILE: cmd/batt.go
function init (line 24) | func init() {
FILE: cmd/curr.go
function init (line 24) | func init() {
FILE: cmd/fans.go
function init (line 24) | func init() {
FILE: cmd/guess.go
constant guessStressDuration (line 25) | guessStressDuration = 8 * time.Second
constant guessCoolDuration (line 26) | guessCoolDuration = 12 * time.Second
constant guessSampleCount (line 27) | guessSampleCount = 3
constant guessSampleInterval (line 28) | guessSampleInterval = 500 * time.Millisecond
constant guessTempMin (line 30) | guessTempMin = float32(25.0)
constant guessTempMax (line 31) | guessTempMax = float32(150.0)
constant guessOutputThreshold (line 34) | guessOutputThreshold = float32(1.5)
constant guessClusterRatio (line 37) | guessClusterRatio = float32(0.20)
function init (line 71) | func init() {
function rawTemps (line 77) | func rawTemps() map[string]float32 {
function avgRawTemps (line 97) | func avgRawTemps(n int, interval time.Duration) map[string]float32 {
function deltaTemps (line 122) | func deltaTemps(base, hot map[string]float32) map[string]float32 {
function seriesKey (line 140) | func seriesKey(key string) string {
function numericValue (line 165) | func numericValue(key string) int {
function groupBySeries (line 204) | func groupBySeries(keys []string) map[string][]string {
function sortedSeriesKeys (line 222) | func sortedSeriesKeys(groups map[string][]string) []string {
function groupByStrideWithinSeries (line 245) | func groupByStrideWithinSeries(sensors []string) [][]string {
constant labelSuperCore (line 313) | labelSuperCore = "CPU Super Core"
constant labelPerformanceCore (line 314) | labelPerformanceCore = "CPU Performance Core"
constant labelEfficiencyCore (line 315) | labelEfficiencyCore = "CPU Efficiency Core"
type phaseSpec (line 320) | type phaseSpec struct
type phaseResult (line 327) | type phaseResult struct
function phaseMidWord (line 335) | func phaseMidWord(label string) string {
function qosName (line 345) | func qosName(qos int) string {
function buildPhases (line 377) | func buildPhases(perfLevels []platform.PerfLevel, layout platform.SKULay...
function spinCore (line 419) | func spinCore(affinityTag int, qosClass int, done <-chan struct{}) {
function runAllCoresPhase (line 467) | func runAllCoresPhase(numCPU int, qosClass int, baseline map[string]floa...
function runGuess (line 488) | func runGuess(_ *cobra.Command, _ []string) {
function layoutSummary (line 564) | func layoutSummary(l platform.SKULayout) string {
function formatRange (line 589) | func formatRange(minN, maxN int) string {
function expectedCores (line 600) | func expectedCores(l platform.SKULayout, phaseLabel string) (int, int) {
function printPhase (line 616) | func printPhase(family string, i int, r phaseResult, keys []string,
function printSeries (line 654) | func printSeries(family, label, sk string, sensors []string, coreIdx int...
function annotateLine (line 686) | func annotateLine(line, label, key, family string, coreIdx int) string {
function printDetectionVerdict (line 708) | func printDetectionVerdict(layout platform.SKULayout, label string, dete...
function warnAbsentTypes (line 736) | func warnAbsentTypes(l platform.SKULayout, results []phaseResult) {
function printMapping (line 762) | func printMapping(family string, numCPU int, perfLevels []platform.PerfL...
function allDetectedKeys (line 896) | func allDetectedKeys(results []phaseResult, clusterKeys []string) map[st...
function printTempTxtCrosscheck (line 924) | func printTempTxtCrosscheck(family string, detected map[string]struct{}) {
function printReportsCrosscheck (line 979) | func printReportsCrosscheck(family string, detected map[string]struct{}) {
FILE: cmd/guess_test.go
function TestSeriesKey (line 16) | func TestSeriesKey(t *testing.T) {
function TestNumericValue (line 48) | func TestNumericValue(t *testing.T) {
function TestGroupBySeries (line 78) | func TestGroupBySeries(t *testing.T) {
function TestGroupByStrideWithinSeries (line 121) | func TestGroupByStrideWithinSeries(t *testing.T) {
function TestDeltaTemps (line 193) | func TestDeltaTemps(t *testing.T) {
function TestPhaseMidWord (line 269) | func TestPhaseMidWord(t *testing.T) {
function TestBuildPhases (line 303) | func TestBuildPhases(t *testing.T) {
function TestFormatRange (line 409) | func TestFormatRange(t *testing.T) {
function TestExpectedCores (line 438) | func TestExpectedCores(t *testing.T) {
function TestLayoutSummary (line 478) | func TestLayoutSummary(t *testing.T) {
function TestSortedSeriesKeys (line 510) | func TestSortedSeriesKeys(t *testing.T) {
function TestAnnotateLine (line 565) | func TestAnnotateLine(t *testing.T) {
function TestAllDetectedKeys (line 626) | func TestAllDetectedKeys(t *testing.T) {
FILE: cmd/hw.go
function init (line 24) | func init() {
FILE: cmd/power.go
function init (line 24) | func init() {
FILE: cmd/raw.go
constant hexDigits (line 17) | hexDigits = "0123456789abcdef"
function formatBytesHex (line 21) | func formatBytesHex(b [32]byte, size uint32) string {
function init (line 65) | func init() {
FILE: cmd/root.go
function Execute (line 29) | func Execute() {
function init (line 37) | func init() {
FILE: cmd/temp.go
function init (line 24) | func init() {
FILE: cmd/version.go
function init (line 32) | func init() {
FILE: cmd/volt.go
function init (line 24) | func init() {
FILE: gosmc/gosmc.go
type DataVers (line 19) | type DataVers struct
type PLimitData (line 28) | type PLimitData struct
type KeyInfo (line 37) | type KeyInfo struct
type SMCKeyData (line 44) | type SMCKeyData struct
type SMCVal (line 57) | type SMCVal struct
type UInt32Char (line 65) | type UInt32Char
method toC (line 69) | func (bs UInt32Char) toC() C.UInt32Char_t {
method ToString (line 74) | func (bs UInt32Char) ToString() string {
function uint32CharFromC (line 79) | func uint32CharFromC(xs C.UInt32Char_t) UInt32Char {
type SMCBytes (line 84) | type SMCBytes
method toC (line 87) | func (bs SMCBytes) toC() C.SMCBytes_t {
function smcBytesFromC (line 92) | func smcBytesFromC(xs C.SMCBytes_t) SMCBytes {
function SMCOpen (line 97) | func SMCOpen(service string) (connection uint, result int) {
function SMCClose (line 109) | func SMCClose(connection uint) int {
function SMCReadKey (line 114) | func SMCReadKey(connection uint, key string) (*SMCVal, int) {
function SMCCall (line 130) | func SMCCall(connection uint, index uint, inputStruct *SMCKeyData) (*SMC...
function SMCWriteKey (line 192) | func SMCWriteKey(connection uint, val *SMCVal) int {
function SMCWriteKeyUnsafe (line 203) | func SMCWriteKeyUnsafe(connection uint, val *SMCVal) int {
FILE: gosmc/smc.c
type SMCKeyInfoCacheEntry_t (line 39) | typedef struct {
function UInt32 (line 55) | static UInt32 smcPackKeyBytes(const char *str, int size) {
function smcUnpackKeyBytes (line 71) | static void smcUnpackKeyBytes(char *str, UInt32 val) {
function kern_return_t (line 76) | kern_return_t SMCOpen(const char *serviceName, io_connect_t *conn) {
function kern_return_t (line 106) | kern_return_t SMCClose(io_connect_t conn) {
function kern_return_t (line 117) | kern_return_t SMCCall(io_connect_t conn, UInt32 index,
function keyInfoCacheFind (line 148) | static int keyInfoCacheFind(UInt32 key, SMCKeyData_keyInfo_t *keyInfo) {
function keyInfoCacheInsert (line 184) | static void keyInfoCacheInsert(UInt32 key,
function kern_return_t (line 234) | static kern_return_t SMCGetKeyInfo(io_connect_t conn, UInt32 key,
function kern_return_t (line 294) | kern_return_t SMCReadKey(io_connect_t conn, const UInt32Char_t key,
function kern_return_t (line 336) | kern_return_t SMCWriteKey(io_connect_t conn, const SMCVal_t *val) {
function kern_return_t (line 349) | kern_return_t SMCWriteKeyUnsafe(io_connect_t conn, const SMCVal_t *val) {
FILE: gosmc/smc.h
type SMCKeyData_vers_t (line 76) | typedef struct {
type SMCKeyData_pLimitData_t (line 84) | typedef struct {
type SMCKeyData_keyInfo_t (line 92) | typedef struct {
type UInt8 (line 102) | typedef UInt8 SMCBytes_t[SMC_MAX_DATA_SIZE];
type SMCKeyData_t (line 104) | typedef struct {
type SMCVal_t (line 118) | typedef struct {
FILE: gosmc/values.go
constant IOReturnSuccess (line 8) | IOReturnSuccess = 0x0
constant IOReturnError (line 9) | IOReturnError = 0x2bc
constant IOReturnNoMemory (line 10) | IOReturnNoMemory = 0x2bd
constant IOReturnNoResources (line 11) | IOReturnNoResources = 0x2be
constant IOReturnIPCError (line 12) | IOReturnIPCError = 0x2bf
constant IOReturnNoDevice (line 13) | IOReturnNoDevice = 0x2c0
constant IOReturnNotPrivileged (line 14) | IOReturnNotPrivileged = 0x2c1
constant IOReturnBadArgument (line 15) | IOReturnBadArgument = 0x2c2
constant IOReturnLockedRead (line 16) | IOReturnLockedRead = 0x2c3
constant IOReturnLockedWrite (line 17) | IOReturnLockedWrite = 0x2c4
constant IOReturnExclusiveAccess (line 18) | IOReturnExclusiveAccess = 0x2c5
constant IOReturnBadMessageID (line 20) | IOReturnBadMessageID = 0x2c6
constant IOReturnUnsupported (line 22) | IOReturnUnsupported = 0x2c7
constant IOReturnVMError (line 23) | IOReturnVMError = 0x2c8
constant IOReturnInternalError (line 24) | IOReturnInternalError = 0x2c9
constant IOReturnIOError (line 25) | IOReturnIOError = 0x2ca
constant IOReturnCannotLock (line 27) | IOReturnCannotLock = 0x2cc
constant IOReturnNotOpen (line 28) | IOReturnNotOpen = 0x2cd
constant IOReturnNotReadable (line 29) | IOReturnNotReadable = 0x2ce
constant IOReturnNotWritable (line 30) | IOReturnNotWritable = 0x2cf
constant IOReturnNotAligned (line 31) | IOReturnNotAligned = 0x2d0
constant IOReturnBadMedia (line 32) | IOReturnBadMedia = 0x2d1
constant IOReturnStillOpen (line 33) | IOReturnStillOpen = 0x2d2
constant IOReturnRLDError (line 34) | IOReturnRLDError = 0x2d3
constant IOReturnDMAError (line 35) | IOReturnDMAError = 0x2d4
constant IOReturnBusy (line 36) | IOReturnBusy = 0x2d5
constant IOReturnTimeout (line 37) | IOReturnTimeout = 0x2d6
constant IOReturnOffline (line 38) | IOReturnOffline = 0x2d7
constant IOReturnNotReady (line 39) | IOReturnNotReady = 0x2d8
constant IOReturnNotAttached (line 40) | IOReturnNotAttached = 0x2d9
constant IOReturnNoChannels (line 41) | IOReturnNoChannels = 0x2da
constant IOReturnNoSpace (line 42) | IOReturnNoSpace = 0x2db
constant IOReturnPortExists (line 44) | IOReturnPortExists = 0x2dd
constant IOReturnCannotWire (line 45) | IOReturnCannotWire = 0x2de
constant IOReturnNoInterrupt (line 47) | IOReturnNoInterrupt = 0x2df
constant IOReturnNoFrames (line 48) | IOReturnNoFrames = 0x2e0
constant IOReturnMessageTooLarge (line 49) | IOReturnMessageTooLarge = 0x2e1
constant IOReturnNotPermitted (line 51) | IOReturnNotPermitted = 0x2e2
constant IOReturnNoPower (line 52) | IOReturnNoPower = 0x2e3
constant IOReturnNoMedia (line 53) | IOReturnNoMedia = 0x2e4
constant IOReturnUnformattedMedia (line 54) | IOReturnUnformattedMedia = 0x2e5
constant IOReturnUnsupportedMode (line 55) | IOReturnUnsupportedMode = 0x2e6
constant IOReturnUnderrun (line 56) | IOReturnUnderrun = 0x2e7
constant IOReturnOverrun (line 57) | IOReturnOverrun = 0x2e8
constant IOReturnDeviceError (line 58) | IOReturnDeviceError = 0x2e9
constant IOReturnNoCompletion (line 59) | IOReturnNoCompletion = 0x2ea
constant IOReturnAborted (line 60) | IOReturnAborted = 0x2eb
constant IOReturnNoBandwidth (line 61) | IOReturnNoBandwidth = 0x2ec
constant IOReturnNotResponding (line 62) | IOReturnNotResponding = 0x2ed
constant IOReturnIsoTooOld (line 63) | IOReturnIsoTooOld = 0x2ee
constant IOReturnIsoTooNew (line 64) | IOReturnIsoTooNew = 0x2ef
constant IOReturnNotFound (line 65) | IOReturnNotFound = 0x2f0
constant IOReturnInvalid (line 66) | IOReturnInvalid = 0x1
constant OPNone (line 71) | OPNone = iota
constant OPList (line 72) | OPList
constant OPRead (line 73) | OPRead
constant OPReadFan (line 74) | OPReadFan
constant OPWrite (line 75) | OPWrite
constant OPBruteForce (line 76) | OPBruteForce
constant KernelIndexSMC (line 81) | KernelIndexSMC = 2
constant CMDReadBytes (line 86) | CMDReadBytes = 5
constant CMDWriteBytes (line 87) | CMDWriteBytes = 6
constant CMDReadIndex (line 88) | CMDReadIndex = 8
constant CMDReadKeyinfo (line 89) | CMDReadKeyinfo = 9
constant CMDReadPlimit (line 90) | CMDReadPlimit = 11
constant CMDReadVers (line 91) | CMDReadVers = 12
constant TypeFPE2 (line 96) | TypeFPE2 = "fpe2"
constant TypeFP2E (line 97) | TypeFP2E = "fp2e"
constant TypeFP4C (line 98) | TypeFP4C = "fp4c"
constant TypeCH8 (line 99) | TypeCH8 = "ch8*"
constant TypeSP78 (line 100) | TypeSP78 = "sp78"
constant TypeSP4B (line 101) | TypeSP4B = "sp4b"
constant TypeFP5B (line 102) | TypeFP5B = "fp5b"
constant TypeFP88 (line 103) | TypeFP88 = "fp88"
constant TypeUI8 (line 104) | TypeUI8 = "ui8"
constant TypeUI16 (line 105) | TypeUI16 = "ui16"
constant TypeUI32 (line 106) | TypeUI32 = "ui32"
constant TypeSI8 (line 107) | TypeSI8 = "si8"
constant TypeSI16 (line 108) | TypeSI16 = "si16"
constant TypeSI32 (line 109) | TypeSI32 = "si32"
constant TypeFLAG (line 110) | TypeFLAG = "flag"
constant TypeFDS (line 111) | TypeFDS = "{fds"
constant TypeFLT (line 112) | TypeFLT = "flt"
constant TypeFPXXSize (line 117) | TypeFPXXSize = 2
constant TypeSPXXSize (line 118) | TypeSPXXSize = 2
constant TypeUI8Size (line 119) | TypeUI8Size = 1
constant TypeUI16Size (line 120) | TypeUI16Size = 2
constant TypeUI32Size (line 121) | TypeUI32Size = 4
constant TypeSI8Size (line 122) | TypeSI8Size = 1
constant TypeSI16Size (line 123) | TypeSI16Size = 2
constant TypeSI32Size (line 124) | TypeSI32Size = 4
constant TypeFLAGSize (line 125) | TypeFLAGSize = 1
FILE: hid/get.go
function GetAll (line 336) | func GetAll() map[string]any {
function hidGet (line 349) | func hidGet(cStr *C.char, unit string) map[string]any {
function GetCurrent (line 360) | func GetCurrent() map[string]any {
function GetVoltage (line 365) | func GetVoltage() map[string]any {
function GetTemperature (line 370) | func GetTemperature() map[string]any {
FILE: hid/hid.go
constant SensorSeparator (line 18) | SensorSeparator = ":"
constant SensorType (line 19) | SensorType = "hid"
function getGeneric (line 23) | func getGeneric(unit string, cStr *C.char) map[string]any {
FILE: main.go
function main (line 13) | func main() {
FILE: output/influxoutput.go
type InfluxOutput (line 16) | type InfluxOutput struct
method All (line 28) | func (io InfluxOutput) All() {
method Battery (line 39) | func (io InfluxOutput) Battery() {
method Current (line 43) | func (io InfluxOutput) Current() {
method Fans (line 47) | func (io InfluxOutput) Fans() {
method Hardware (line 51) | func (io InfluxOutput) Hardware() {
method Power (line 55) | func (io InfluxOutput) Power() {
method Temperature (line 59) | func (io InfluxOutput) Temperature() {
method Voltage (line 63) | func (io InfluxOutput) Voltage() {
method print (line 123) | func (io InfluxOutput) print(name string, smcdata map[string]any) {
function NewInfluxOutput (line 21) | func NewInfluxOutput() Output {
function influxStringConvert (line 69) | func influxStringConvert(s string) string {
function influxEscape (line 78) | func influxEscape(s string) string {
function influxGetValue (line 100) | func influxGetValue(s string) string {
function influxGetUnit (line 108) | func influxGetUnit(s string) string {
FILE: output/influxoutput_test.go
function stripTimestamp (line 19) | func stripTimestamp(line string) string {
function Test_influxStringConvert (line 30) | func Test_influxStringConvert(t *testing.T) {
function Test_influxGetValue (line 50) | func Test_influxGetValue(t *testing.T) {
function Test_influxGetUnit (line 68) | func Test_influxGetUnit(t *testing.T) {
function TestInfluxOutput_methods (line 88) | func TestInfluxOutput_methods(t *testing.T) {
function TestInfluxOutput_All (line 156) | func TestInfluxOutput_All(t *testing.T) {
function TestInfluxOutput_emptyKey (line 181) | func TestInfluxOutput_emptyKey(t *testing.T) {
function TestInfluxOutput_unitExtraction (line 207) | func TestInfluxOutput_unitExtraction(t *testing.T) {
function TestInfluxOutput_empty (line 230) | func TestInfluxOutput_empty(t *testing.T) {
FILE: output/jsonoutput.go
type JSONOutput (line 18) | type JSONOutput struct
method All (line 85) | func (jo JSONOutput) All() {
method Battery (line 109) | func (jo JSONOutput) Battery() {
method Current (line 113) | func (jo JSONOutput) Current() {
method Fans (line 117) | func (jo JSONOutput) Fans() {
method Hardware (line 121) | func (jo JSONOutput) Hardware() {
method Power (line 125) | func (jo JSONOutput) Power() {
method Temperature (line 129) | func (jo JSONOutput) Temperature() {
method Voltage (line 133) | func (jo JSONOutput) Voltage() {
method print (line 139) | func (jo JSONOutput) print(v any) {
function NewJSONOutput (line 23) | func NewJSONOutput() Output {
type newstruct (line 30) | type newstruct struct
function format (line 41) | func format(d any) (any, error) {
FILE: output/jsonoutput_test.go
function Test_format_sp78 (line 20) | func Test_format_sp78(t *testing.T) {
function Test_format_nonStringValue (line 49) | func Test_format_nonStringValue(t *testing.T) {
function TestJSONOutput (line 77) | func TestJSONOutput(t *testing.T) {
FILE: output/output.go
type Output (line 31) | type Output interface
function getAll (line 51) | func getAll() map[string]any {
function getBattery (line 56) | func getBattery() map[string]any {
function getCurrent (line 61) | func getCurrent() map[string]any {
function getFans (line 66) | func getFans() map[string]any {
function getTemperature (line 71) | func getTemperature() map[string]any {
function getPower (line 76) | func getPower() map[string]any {
function getVoltage (line 81) | func getVoltage() map[string]any {
function sortedKeys (line 86) | func sortedKeys(m map[string]any) []string {
function getHardware (line 99) | func getHardware() map[string]any {
function deepCopy (line 164) | func deepCopy(dest, src map[string]any) {
function isFloatType (line 177) | func isFloatType(typ string) bool {
function merge (line 190) | func merge(a, b map[string]any) map[string]any {
FILE: output/output_test.go
function Test_deepCopy (line 16) | func Test_deepCopy(t *testing.T) {
function Test_merge (line 71) | func Test_merge(t *testing.T) {
function Test_deepCopy_isolation (line 133) | func Test_deepCopy_isolation(t *testing.T) {
function Test_merge_bOverridesA (line 158) | func Test_merge_bOverridesA(t *testing.T) {
function Test_merge_nestedBOverridesA (line 171) | func Test_merge_nestedBOverridesA(t *testing.T) {
function toJSON (line 199) | func toJSON(src map[string]any) string {
function getMapForSensor (line 206) | func getMapForSensor(sensorName string) map[string]any {
FILE: output/outputfactory.go
function Factory (line 10) | func Factory(outputType string) Output {
FILE: output/outputfactory_test.go
function TestFactory (line 13) | func TestFactory(t *testing.T) {
FILE: output/tableoutput.go
type TableOutput (line 17) | type TableOutput struct
method All (line 32) | func (to TableOutput) All() {
method Battery (line 43) | func (to TableOutput) Battery() {
method Current (line 47) | func (to TableOutput) Current() {
method Fans (line 51) | func (to TableOutput) Fans() {
method Hardware (line 55) | func (to TableOutput) Hardware() {
method Power (line 59) | func (to TableOutput) Power() {
method Temperature (line 63) | func (to TableOutput) Temperature() {
method Voltage (line 67) | func (to TableOutput) Voltage() {
method print (line 73) | func (to TableOutput) print(name string, smcdata map[string]any) {
function NewTableOutput (line 24) | func NewTableOutput(isASCII bool) Output {
FILE: output/tableoutput_test.go
function TestTableOutput_ASCII (line 35) | func TestTableOutput_ASCII(t *testing.T) {
function TestTableOutput_Table (line 165) | func TestTableOutput_Table(t *testing.T) {
function getASCIITpl (line 217) | func getASCIITpl(title ...string) string {
function getTableTpl (line 239) | func getTableTpl(title ...string) string {
FILE: platform/get.go
type PerfLevel (line 29) | type PerfLevel struct
function getModel (line 42) | func getModel() string {
function GetFamily (line 68) | func GetFamily() string {
function GetModelID (line 79) | func GetModelID() string {
function GetProduct (line 85) | func GetProduct() (Product, bool) {
function GetSKULayout (line 94) | func GetSKULayout() (SKULayout, bool) {
function LookupSKULayout (line 107) | func LookupSKULayout(cpu string) (SKULayout, bool) {
function GetTotalCPU (line 115) | func GetTotalCPU() (physical, logical int) {
function GetPerfLevels (line 131) | func GetPerfLevels() []PerfLevel {
FILE: platform/mapping.go
type Product (line 8) | type Product struct
constant PairSignaturePE (line 18) | PairSignaturePE = "P+E"
constant PairSignatureSE (line 19) | PairSignatureSE = "S+E"
constant PairSignatureSP (line 20) | PairSignatureSP = "S+P"
type SKULayout (line 26) | type SKULayout struct
FILE: platform/mapping_test.go
function Test_productsIntegrity (line 22) | func Test_productsIntegrity(t *testing.T) {
function Test_productsKnownEntries (line 59) | func Test_productsKnownEntries(t *testing.T) {
function Test_GetProduct_consistency (line 101) | func Test_GetProduct_consistency(t *testing.T) {
function Test_GetFamily_consistency (line 121) | func Test_GetFamily_consistency(t *testing.T) {
function Test_skuLayoutsIntegrity (line 151) | func Test_skuLayoutsIntegrity(t *testing.T) {
function Test_skuLayouts_coverage (line 198) | func Test_skuLayouts_coverage(t *testing.T) {
function Test_LookupSKULayout_known (line 215) | func Test_LookupSKULayout_known(t *testing.T) {
function Test_LookupSKULayout_unknown (line 263) | func Test_LookupSKULayout_unknown(t *testing.T) {
FILE: reports/reports.go
function Keys (line 39) | func Keys(family string) map[string]struct{} {
function Families (line 47) | func Families() []string {
function load (line 62) | func load() {
function familyFromFilename (line 99) | func familyFromFilename(name string) string {
function ingest (line 129) | func ingest(target map[string]map[string]struct{}, body, family string) {
FILE: reports/reports_test.go
function TestFamilyFromFilename (line 12) | func TestFamilyFromFilename(t *testing.T) {
function TestKeysIntegration (line 57) | func TestKeysIntegration(t *testing.T) {
function TestFamilies (line 100) | func TestFamilies(t *testing.T) {
function TestIngest_skipsNonTKeys (line 130) | func TestIngest_skipsNonTKeys(t *testing.T) {
FILE: smc/conv.go
type FPConv (line 18) | type FPConv struct
function fpToFloat32 (line 51) | func fpToFloat32(t string, x gosmc.SMCBytes, size uint32) (float32, erro...
function fltToFloat32 (line 69) | func fltToFloat32(x gosmc.SMCBytes, size uint32) (float32, error) {
function smcTypeToString (line 78) | func smcTypeToString(x gosmc.UInt32Char) string {
function smcBytesToUint32 (line 84) | func smcBytesToUint32(x gosmc.SMCBytes, size uint32) uint32 {
function smcBytesToFloat32 (line 98) | func smcBytesToFloat32(x gosmc.SMCBytes, size uint32) float32 {
function ioftToFloat32 (line 103) | func ioftToFloat32(x gosmc.SMCBytes, size uint32) (float32, error) {
function decodeToFloat32 (line 115) | func decodeToFloat32(dataType string, bytes gosmc.SMCBytes, size uint32)...
function DecodeValue (line 131) | func DecodeValue(dataType string, bytes gosmc.SMCBytes, size uint32) str...
FILE: smc/conv_test.go
function makeBytes (line 16) | func makeBytes(b ...byte) gosmc.SMCBytes {
function makeUInt32Char (line 24) | func makeUInt32Char(s string) gosmc.UInt32Char {
function Test_fpToFloat32 (line 31) | func Test_fpToFloat32(t *testing.T) {
function Test_fltToFloat32 (line 62) | func Test_fltToFloat32(t *testing.T) {
function Test_smcTypeToString (line 89) | func Test_smcTypeToString(t *testing.T) {
function Test_smcBytesToUint32 (line 110) | func Test_smcBytesToUint32(t *testing.T) {
function Test_smcBytesToFloat32 (line 134) | func Test_smcBytesToFloat32(t *testing.T) {
function Test_AppleFPConvTable (line 159) | func Test_AppleFPConvTable(t *testing.T) {
function Test_fpToFloat32_bigEndianAsymmetric (line 187) | func Test_fpToFloat32_bigEndianAsymmetric(t *testing.T) {
function Test_fltToFloat32_littleEndianAsymmetric (line 196) | func Test_fltToFloat32_littleEndianAsymmetric(t *testing.T) {
function Test_smcBytesToUint32_bigEndianAsymmetric (line 206) | func Test_smcBytesToUint32_bigEndianAsymmetric(t *testing.T) {
function Test_decodeToFloat32 (line 217) | func Test_decodeToFloat32(t *testing.T) {
function Test_DecodeValue (line 249) | func Test_DecodeValue(t *testing.T) {
function Test_ioftToFloat32_divisor (line 301) | func Test_ioftToFloat32_divisor(t *testing.T) {
function Test_ioftToFloat32 (line 307) | func Test_ioftToFloat32(t *testing.T) {
FILE: smc/get.go
function getKeyFloat32 (line 20) | func getKeyFloat32(c uint, key string) (float32, string, error) {
function getKeyUint32 (line 62) | func getKeyUint32(c uint, key string) (uint32, string, error) {
function getKeyBool (line 80) | func getKeyBool(c uint, key string) (bool, string, error) {
FILE: smc/mapping_test.go
function resolveM4Sensors (line 72) | func resolveM4Sensors(sensors []SensorStat, snapshot map[string]float32)...
function Test_M4Pro14CoreMapping (line 98) | func Test_M4Pro14CoreMapping(t *testing.T) {
function resolveForFamily (line 260) | func resolveForFamily(sensors []SensorStat, family string, snapshot map[...
function Test_A18ProResolvedTable (line 321) | func Test_A18ProResolvedTable(t *testing.T) {
function Test_A18ProMapping (line 367) | func Test_A18ProMapping(t *testing.T) {
function Test_M2ProMapping (line 454) | func Test_M2ProMapping(t *testing.T) {
function Test_isValidReading (line 527) | func Test_isValidReading(t *testing.T) {
FILE: smc/raw.go
constant keyCount (line 16) | keyCount = "#KEY"
constant maxKeys (line 19) | maxKeys = 4096
type RawKey (line 23) | type RawKey struct
function GetRaw (line 31) | func GetRaw() []RawKey {
FILE: smc/rawtemp.go
constant rawTempMin (line 16) | rawTempMin = float32(-100.0)
constant rawTempMax (line 17) | rawTempMax = float32(200.0)
function RawKeyToFloat32 (line 29) | func RawKeyToFloat32(k RawKey) (float32, bool) {
function finiteInRange (line 70) | func finiteInRange(v float32) (float32, bool) {
FILE: smc/rawtemp_test.go
function Test_RawKeyToFloat32 (line 23) | func Test_RawKeyToFloat32(t *testing.T) {
FILE: smc/smc.go
constant AppleSMC (line 21) | AppleSMC = "AppleSMC"
constant FanNum (line 22) | FanNum = "FNum"
constant BattNum (line 23) | BattNum = "BNum"
constant BattPwr (line 24) | BattPwr = "BATP"
constant BattInf (line 25) | BattInf = "BSIn"
constant KeyWildcard (line 26) | KeyWildcard = "%"
constant maxFans (line 29) | maxFans = 32
constant TempUnit (line 31) | TempUnit = "°C"
constant minTempCelsius (line 34) | minTempCelsius = 10.0
type SensorStat (line 38) | type SensorStat struct
function filteredTemp (line 52) | func filteredTemp() []SensorStat {
function openSMC (line 62) | func openSMC() (uint, error) {
function GetAll (line 72) | func GetAll() map[string]any {
function GetBattery (line 92) | func GetBattery() map[string]any {
function getBattery (line 104) | func getBattery(c uint) map[string]any {
function GetCurrent (line 129) | func GetCurrent() map[string]any {
function GetFans (line 141) | func GetFans() map[string]any {
function getFans (line 153) | func getFans(c uint) map[string]any {
function getGenericSensors (line 191) | func getGenericSensors(conn uint, unit string, smcSlice []SensorStat) ma...
function isValidReading (line 221) | func isValidReading(val float32, unit string) bool {
function addGeneric (line 235) | func addGeneric(generic map[string]any, conn uint, key, desc, unit strin...
function GetPower (line 251) | func GetPower() map[string]any {
function GetTemperature (line 263) | func GetTemperature() map[string]any {
function GetVoltage (line 275) | func GetVoltage() map[string]any {
function filterForPlatform (line 290) | func filterForPlatform(smcSlice []SensorStat) []SensorStat {
function resolveFamily (line 305) | func resolveFamily() string {
function platformMatches (line 329) | func platformMatches(rowPlatform, family string) bool {
function LookupTempDesc (line 350) | func LookupTempDesc(key, family string) (string, bool) {
function MappedTempKeys (line 385) | func MappedTempKeys(family string) map[string]string {
FILE: smc/smc_test.go
function Test_filterForPlatform (line 17) | func Test_filterForPlatform(t *testing.T) {
function Test_filterForPlatform_empty (line 31) | func Test_filterForPlatform_empty(t *testing.T) {
function Test_filterForPlatform_allOnly (line 36) | func Test_filterForPlatform_allOnly(t *testing.T) {
function Test_filterForPlatform_appleSiliconInclusion (line 53) | func Test_filterForPlatform_appleSiliconInclusion(t *testing.T) {
function filterByFamily (line 84) | func filterByFamily(smcSlice []SensorStat, family string) []SensorStat {
function Test_filterByFamily_A18 (line 116) | func Test_filterByFamily_A18(t *testing.T) {
function Test_filterByFamily_A18_GeneratedTable (line 150) | func Test_filterByFamily_A18_GeneratedTable(t *testing.T) {
function Test_filterForPlatform_exactFamilyMatch (line 191) | func Test_filterForPlatform_exactFamilyMatch(t *testing.T) {
function Test_filterByFamily_NoPCIeLeakOnAppleSilicon (line 224) | func Test_filterByFamily_NoPCIeLeakOnAppleSilicon(t *testing.T) {
function Test_platformMatches (line 236) | func Test_platformMatches(t *testing.T) {
function Test_LookupTempDesc_directMatch (line 273) | func Test_LookupTempDesc_directMatch(t *testing.T) {
function Test_MappedTempKeys_M5 (line 336) | func Test_MappedTempKeys_M5(t *testing.T) {
function Test_MappedTempKeys_familyIsolation (line 364) | func Test_MappedTempKeys_familyIsolation(t *testing.T) {
FILE: stress/stress_darwin.go
constant QoSUserInteractive (line 33) | QoSUserInteractive = 0x21
constant QoSUserInitiated (line 34) | QoSUserInitiated = 0x19
constant QoSBackground (line 35) | QoSBackground = 0x09
function SetAffinityTag (line 49) | func SetAffinityTag(tag int) int {
function SetQoS (line 60) | func SetQoS(qosClass int) int {
Condensed preview — 90 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (824K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 191,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n "
},
{
"path": ".github/workflows/camo-purge.yml",
"chars": 260,
"preview": "name: Purge camo cache\non:\n workflow_dispatch:\n push:\n tags:\n - \"*\"\n schedule:\n - cron: \"0 7 * * *\"\njobs:\n"
},
{
"path": ".gitignore",
"chars": 512,
"preview": "### Go ###\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\niSMC\ndist/\nvendor/\n\n# Test binary, build "
},
{
"path": ".golangci.yml",
"chars": 602,
"preview": "version: \"2\"\nlinters:\n default: all\n disable:\n - cyclop\n - depguard\n - dupl\n - exhaustruct\n - forbidigo"
},
{
"path": ".goreleaser.yml",
"chars": 1348,
"preview": "before:\n hooks:\n - go mod tidy\nbuilds:\n - flags:\n - -trimpath\n env:\n - CGO_ENABLED=1\n ldflags: |\n "
},
{
"path": "AGENTS.md",
"chars": 778,
"preview": "# AGENTS.md\n\nThis repository already contains `CLAUDE.md` with detailed build, architecture, and workflow guidance.\n\n## "
},
{
"path": "CLAUDE.md",
"chars": 3346,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 4942,
"preview": "# iSMC\n\n[](https://github.com/dkorunic/iSMC/bl"
},
{
"path": "Taskfile.yml",
"chars": 1774,
"preview": "version: \"3\"\n\nvars:\n TARGET: iSMC\n GIT_LAST_TAG:\n sh: git describe --abbrev=0 --tags 2>/dev/null || echo latest\n G"
},
{
"path": "cmd/all.go",
"chars": 561,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/batt.go",
"chars": 540,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/curr.go",
"chars": 541,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/fans.go",
"chars": 523,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/guess.go",
"chars": 29399,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/guess_test.go",
"chars": 16551,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/hw.go",
"chars": 541,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/power.go",
"chars": 531,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/raw.go",
"chars": 1477,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/root.go",
"chars": 994,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/temp.go",
"chars": 553,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/version.go",
"chars": 727,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "cmd/volt.go",
"chars": 541,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage cmd\n\nimport (\n"
},
{
"path": "go.mod",
"chars": 762,
"preview": "module github.com/dkorunic/iSMC\n\ngo 1.26\n\nreplace github.com/dkorunic/iSMC/gosmc => ./gosmc\n\nrequire (\n\tgithub.com/dkoru"
},
{
"path": "go.sum",
"chars": 2792,
"preview": "github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=\ngithub.com/clipperhouse/uax29/v2"
},
{
"path": "gosmc/.clang-format",
"chars": 7620,
"preview": "---\nLanguage: Cpp\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignArrayOfStructures: None\nAlignConsecu"
},
{
"path": "gosmc/LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "gosmc/README.md",
"chars": 6481,
"preview": "# gosmc\n\nA Go package providing CGo bindings to the Apple System Management Controller (SMC) via macOS IOKit. It wraps t"
},
{
"path": "gosmc/go.mod",
"chars": 47,
"preview": "module github.com/dkorunic/iSMC/gosmc\n\ngo 1.26\n"
},
{
"path": "gosmc/gosmc.go",
"chars": 5754,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage gosmc\n\n// #cgo"
},
{
"path": "gosmc/smc.c",
"chars": 13533,
"preview": "/*\n * Apple System Management Control (SMC) Tool\n * Copyright (C) 2006 devnull\n * Copyright (C) 2026 Dinko Korunic\n *\n *"
},
{
"path": "gosmc/smc.h",
"chars": 4132,
"preview": "/*\n * Apple System Management Control (SMC) Tool\n * Copyright (C) 2006 devnull\n *\n * This program is free software; you "
},
{
"path": "gosmc/values.go",
"chars": 3909,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\npackage gosmc\n\n// IOReturn values\nconst ("
},
{
"path": "hid/get.go",
"chars": 12766,
"preview": "// Copyright (C) 2022 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage hid\n\n/*\n#cgo C"
},
{
"path": "hid/hid.go",
"chars": 1015,
"preview": "// Copyright (C) 2022 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage hid\n\nimport \"C"
},
{
"path": "main.go",
"chars": 214,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage main\n\nimport ("
},
{
"path": "output/influxoutput.go",
"chars": 3286,
"preview": "// Copyright (C) 2023 Seaburr\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport (\n\t\"fm"
},
{
"path": "output/influxoutput_test.go",
"chars": 5960,
"preview": "// Copyright (C) 2023 Seaburr\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport (\n\t\"by"
},
{
"path": "output/jsonoutput.go",
"chars": 2821,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/jsonoutput_test.go",
"chars": 4491,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/output.go",
"chars": 5017,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/output_test.go",
"chars": 5428,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/outputfactory.go",
"chars": 569,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\n// Fact"
},
{
"path": "output/outputfactory_test.go",
"chars": 1169,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/tableoutput.go",
"chars": 2035,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "output/tableoutput_test.go",
"chars": 5067,
"preview": "// Copyright (C) 2022 Roland Schaer\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage output\n\nimport "
},
{
"path": "platform/get.go",
"chars": 4454,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage platform\n\n/*\n#"
},
{
"path": "platform/mapping.go",
"chars": 18418,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage platform\n\ntype"
},
{
"path": "platform/mapping_test.go",
"chars": 10172,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage platform\n\nimpo"
},
{
"path": "reports/report-a18.txt",
"chars": 92005,
"preview": " #KEY [ui32] 2332 (bytes 00 00 09 1c)\n AC-B [si8 ] -1 (bytes ff)\n AC-C [flag] false (bytes 00)\n AC-I [ui16] "
},
{
"path": "reports/report-intel-t2.txt",
"chars": 6082,
"preview": " TB0T [flt ] 19 (bytes 98 99 99 41)\n TB1T [flt ] 19 (bytes 98 99 99 41)\n TB2T [flt ] 19 (bytes c8 cc 98 41)\n T"
},
{
"path": "reports/report-m1-max.txt",
"chars": 9000,
"preview": " TAOL [flt ] (bytes 00 80 d0 41)\n TB0T [flt ] (bytes 30 33 db 41)\n TB1T [flt ] (bytes 60 66 da 41)\n TB2T [flt"
},
{
"path": "reports/report-m1-pro-2.txt",
"chars": 7704,
"preview": " TAOL [flt ] (bytes 00 00 b9 41)\n TB0T [flt ] (bytes 60 66 ba 41)\n TB1T [flt ] (bytes c8 cc b8 41)\n TB2T [flt"
},
{
"path": "reports/report-m1-pro.txt",
"chars": 7668,
"preview": " TAOL [flt ] (bytes 00 00 09 42)\n TB0T [flt ] (bytes 00 00 12 42)\n TB1T [flt ] (bytes 00 00 12 42)\n TB2T [flt"
},
{
"path": "reports/report-m1-ultra.txt",
"chars": 12600,
"preview": " TB0p [flt ] (bytes 68 a1 24 42)\n TB1p [flt ] (bytes a0 04 0c 42)\n TC10 [flt ] (bytes c0 18 18 42)\n TC11 [flt"
},
{
"path": "reports/report-m2.txt",
"chars": 6317,
"preview": " TAOL [flt ] 0 (bytes 00 00 00 00)\n TB0T [flt ] 28.699997 (bytes 98 99 e5 41)\n TB1T [flt ] 28.699997 (bytes 98 "
},
{
"path": "reports/report-m3-max-2.txt",
"chars": 16394,
"preview": " TAOL [flt ] 23.375 (bytes 00 00 bb 41)\n TB0T [flt ] 24 (bytes 00 00 c0 41)\n TB1T [flt ] 23.899994 (bytes 30 33"
},
{
"path": "reports/report-m3-max.txt",
"chars": 14681,
"preview": " TAOL [flt ] 24 (bytes 00 80 be 41)\n TB0T [flt ] 23 (bytes 98 99 b5 41)\n TB1T [flt ] 23 (bytes 98 99 b5 41)\n T"
},
{
"path": "reports/report-m3-pro-2.txt",
"chars": 9540,
"preview": " TAOL [flt ] (bytes 00 80 8d 41)\n TB0T [flt ] (bytes 00 00 98 41)\n TB1T [flt ] (bytes 00 00 98 41)\n TB2T [flt"
},
{
"path": "reports/report-m3-pro-3.txt",
"chars": 9540,
"preview": " TAOL [flt ] (bytes 00 80 c9 41)\n TB0T [flt ] (bytes c8 cc d0 41)\n TB1T [flt ] (bytes 30 33 cf 41)\n TB2T [flt"
},
{
"path": "reports/report-m3-pro.txt",
"chars": 9540,
"preview": " TAOL [flt ] (bytes 00 80 b8 41)\n TB0T [flt ] (bytes 98 99 b9 41)\n TB1T [flt ] (bytes 98 99 b9 41)\n TB2T [flt"
},
{
"path": "reports/report-m4-2.txt",
"chars": 8732,
"preview": " TAOL [flt ] 27 (bytes 00 c0 d4 41)\n TB0T [flt ] 27 (bytes 30 33 d7 41)\n TB1T [flt ] 27 (bytes 30 33 d7 41)\n T"
},
{
"path": "reports/report-m4-3.txt",
"chars": 7053,
"preview": " TCMb [flt ] 48 (bytes 31 7c 41 42)\n TCMz [flt ] 75 (bytes 00 c0 96 42)\n TH0a [flt ] 31 (bytes 00 c0 f9 41)\n T"
},
{
"path": "reports/report-m4-4.txt",
"chars": 7344,
"preview": " T5SP [flt ] (bytes 0c d9 25 42)\n TAOL [flt ] (bytes 00 f0 d5 41)\n TB0T [flt ] (bytes 00 00 06 42)\n TB1T [flt"
},
{
"path": "reports/report-m4-max.txt",
"chars": 13214,
"preview": " TB0p [flt ] 32.00731 (bytes 7c 07 00 42)\n TBBp [flt ] 28.014801 (bytes 50 1e e0 41)\n TCDX [flt ] 30.22125 (byt"
},
{
"path": "reports/report-m4-pro-2.txt",
"chars": 11664,
"preview": " TAOL [flt ] (bytes 00 90 d8 41)\n TB0T [flt ] (bytes 98 99 e5 41)\n TB1T [flt ] (bytes 98 99 e5 41)\n TB2T [flt"
},
{
"path": "reports/report-m4-pro.txt",
"chars": 11664,
"preview": " TAOL [flt ] (bytes 00 50 e6 41)\n TB0T [flt ] (bytes 30 33 03 42)\n TB1T [flt ] (bytes 30 33 03 42)\n TB2T [flt"
},
{
"path": "reports/report-m4.txt",
"chars": 8734,
"preview": " TAOL [flt ] 20 (bytes 00 10 a1 41)\n TB0T [flt ] 19 (bytes c8 cc 98 41)\n TB1T [flt ] 19 (bytes c8 cc 98 41)\n T"
},
{
"path": "reports/report-m5-max-2.txt",
"chars": 16210,
"preview": " TAOL [flt ] 19.757812 (bytes 00 10 9e 41)\n TB0T [flt ] 19.5 (bytes 00 00 9c 41)\n TB1T [flt ] 19.5 (bytes 00 00"
},
{
"path": "reports/report-m5-max.txt",
"chars": 13104,
"preview": " TAOL [flt ] (bytes 00 40 cc 41)\n TB0T [flt ] (bytes c8 cc c4 41)\n TB1T [flt ] (bytes c8 cc c4 41)\n TB2T [flt"
},
{
"path": "reports/report-m5-pro.txt",
"chars": 11169,
"preview": " TAOL [flt ] 23 (bytes 00 20 bb 41)\n TB0T [flt ] 26 (bytes 60 66 ce 41)\n TB1T [flt ] 26 (bytes 60 66 ce 41)\n T"
},
{
"path": "reports/reports.go",
"chars": 3976,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\n// Package reports emb"
},
{
"path": "reports/reports_test.go",
"chars": 4456,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage reports\n\nimpor"
},
{
"path": "smc/conv.go",
"chars": 4627,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/conv_test.go",
"chars": 10987,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/gen-sensors.sh",
"chars": 1159,
"preview": "#!/bin/sh\nset -e\n\nif [ -z \"$1\" ]; then\n echo \"Missing target filename\"\n exit 1\nfi\n\nT=$(awk -F: '!/^[[:space:]]*\\/\\// {"
},
{
"path": "smc/get.go",
"chars": 2538,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/mapping_test.go",
"chars": 21091,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/raw.go",
"chars": 1547,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/rawtemp.go",
"chars": 1962,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/rawtemp_test.go",
"chars": 3851,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/sensors.go",
"chars": 78256,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n// Code generated by go generate. DO NOT "
},
{
"path": "smc/smc.go",
"chars": 10534,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "smc/smc_test.go",
"chars": 12448,
"preview": "// Copyright (C) 2019 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\npackage smc\n\nimport (\n"
},
{
"path": "src/current.txt",
"chars": 1720,
"preview": "CPU Core:IC0C\nCPU VccIO:IC1C\nCPU VccSA:IC2C\nCPU High Side:IC0R\nCPU DRAM:IC5R\nCPU PLL:IC8R\nCPU GFX:IC0G\nCPU Memory:IC0M\nG"
},
{
"path": "src/fans.txt",
"chars": 131,
"preview": "Fan %d Current Speed:F%dAc\nFan %d Minimal Speed:F%dMn\nFan %d Maximum Speed:F%dMx\nFan %d Safe Speed:F%dSf\nFan %d Target S"
},
{
"path": "src/power.txt",
"chars": 2573,
"preview": "CPU Core %:PC%C\nCPU Core (IMON):PCAM\nCPU Package:PCPC\nCPU Total:PCTR\nCPU Package Total:PCPT\nCPU Package Total (SMC):PCPR"
},
{
"path": "src/temp.txt",
"chars": 31269,
"preview": "// All\nThermal Zone %:TZ%C:All\nCPU Diode %:TC%D:All\nCPU Diode Virtual %:TC%E:All\nCPU Diode Filtered %:TC%F:All\nCPU Heats"
},
{
"path": "src/voltage.txt",
"chars": 971,
"preview": "CPU IA:VCAC\nCPU System Agent:VCSC\nCPU Core %:VC%C\nGPU Intel Graphics:VCTC\nGPU %:VG%C\nMemory %:VM%R\nCMOS:Vb0R\nDC In:VD0R\n"
},
{
"path": "stress/stress_darwin.go",
"chars": 2133,
"preview": "// Copyright (C) 2026 Dinko Korunic\n// SPDX-License-Identifier: GPL-3.0-only\n\n//go:build darwin\n\n// Package stress prov"
}
]
About this extraction
This page contains the full source code of the dkorunic/iSMC GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 90 files (753.5 KB), approximately 324.6k tokens, and a symbol index with 402 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.