Skip to content

runtime: add missing MemStats fields and ThreadCreateProfile stub - #5785

Open
v1rtl wants to merge 2 commits into
tinygo-org:devfrom
v1rtl:runtime-memstats-prometheus
Open

v1rtl wants to merge 2 commits into
tinygo-org:devfrom
v1rtl:runtime-memstats-prometheus

Conversation

@v1rtl

@v1rtl v1rtl commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the compile failure in #3879.

github.com/prometheus/client_golang does not build under TinyGo because of missing runtime API surface. It produces 24 type check errors, all of them undefined fields or functions. This adds that surface.

The failure is at package level in prometheus, so it cannot be avoided by skipping the Go collector. Any import of the package fails.

MemStats

Adds the 11 fields upstream defines that TinyGo lacked: Lookups, StackInuse, StackSys, MSpanInuse, MSpanSys, MCacheInuse, MCacheSys, BuckHashSys, OtherSys, NextGC, GCCPUFraction.

TinyGo has no runtime structures for these to describe, so they always report 0 rather than carry made up values. Lookups is unused upstream too. Existing fields are untouched and still populated as before.

Fields follow upstream's section grouping. This adds a Garbage collector statistics heading and moves NumGC under it, since it was under off heap statistics.

ThreadCreateProfile

A stub reporting an empty profile, plus the StackRecord type it needs. Matches the existing SetBlockProfileRate and SetMutexProfileFraction stubs in debug.go. Prometheus calls ThreadCreateProfile(nil) to count OS threads.

Open questions

Left as a draft because three things want a maintainer opinion.

  1. Whether to populate some fields instead of reporting 0. NextGC for the blocks collector and StackInuse if goroutine stacks were accounted separately are both possible with more work. I kept the whole set at 0 so the change stays small.
  2. Whether ThreadCreateProfile should return (0, true) or (1, true). I chose (0, true) as a consistent empty profile, matching NumCgoCall returning 0. Returning 1 is arguably more truthful since a thread does exist, but then the record contents would be wrong. This decides whether Prometheus reports go_threads as 0 or 1.
  3. Whether ReadMemStats should set the new fields explicitly. It does not zero *m on entry, so a reused MemStats keeps stale values in them. This predates the PR but the new fields widen it.

Testing

Compiled the #3879 reproducer against this branch. All 24 errors are gone and it runs.

$ tinygo build -target=wasip1 -o out.wasm .
$ wasmtime out.wasm
metric families: 6

ReadMemStats still reports real values for existing fields, new ones read 0.

HeapAlloc 65712 Sys 171696 NumGC 1
new: 0 0 0 0 0 0 0 0 0 0 0
ThreadCreateProfile: 0 true

Reproducer:

package main

import (
	"fmt"

	"github.com/prometheus/client_golang/prometheus"
)

var counter = prometheus.NewCounter(prometheus.CounterOpts{
	Name: "test_total",
	Help: "A test counter.",
})

func main() {
	prometheus.MustRegister(counter)
	counter.Inc()

	mfs, err := prometheus.DefaultGatherer.Gather()
	if err != nil {
		fmt.Println("gather error:", err)
		return
	}
	fmt.Println("metric families:", len(mfs))
}

@deadprogram

Copy link
Copy Markdown
Member

@v1rtl please make sure to follow AGENTS.md

@v1rtl

v1rtl commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

sorry for that, I'll ammend my commits to follow it, I opened the draft PR just to test things but I'll clean it up before making it non-draft

Adds the MemStats fields upstream Go defines that TinyGo did not have:
Lookups, StackInuse, StackSys, MSpanInuse, MSpanSys, MCacheInuse,
MCacheSys, BuckHashSys, OtherSys, NextGC and GCCPUFraction.

TinyGo has no runtime structures for these to describe, so they always
report 0 rather than carry made up values. They exist so that packages
reading MemStats can be compiled.

github.com/prometheus/client_golang fails to type check against TinyGo
because these fields are missing. See tinygo-org#3879.
Adds runtime.StackRecord and a runtime.ThreadCreateProfile stub that
always reports an empty profile, matching the existing
SetBlockProfileRate and SetMutexProfileFraction stubs.

github.com/prometheus/client_golang calls ThreadCreateProfile to count
OS threads. See tinygo-org#3879.
@v1rtl
v1rtl force-pushed the runtime-memstats-prometheus branch from 539ba65 to ef4d9e8 Compare September 29, 2026 10:31
@v1rtl

v1rtl commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

should be clean now, marking as ready for review

@v1rtl
v1rtl marked this pull request as ready for review September 29, 2026 10:33
@deadprogram

Copy link
Copy Markdown
Member

Thanks for working on this @v1rtl. I ran it on my local machine and took a good look. The following is edited from an automated review:

  1. The description says these are "the 11 fields upstream defines that TinyGo lacked", but upstream also has LastGC, PauseTotalNs, PauseNs, PauseEnd, NumForcedGC, EnableGC, DebugGC and BySize. Leaving those out seems right, since PauseNs, PauseEnd and BySize alone would add about 4.5KB to the struct on microcontrollers. Perhaps reword it as "some of the missing upstream defines"?

  2. There is no test for the new API. Please add a small testdata program that reads the new fields and calls ThreadCreateProfile.

  3. On your open questions. Keeping all the new fields at 0 seems fine for now. NextGC with the blocks GC could be a follow up, since that GC only runs when the heap is full, so HeapSys might be a fair value. For ThreadCreateProfile, (0, true) seems better than 1, since -scheduler=threads can have more than one thread. For ReadMemStats, it never writes the new fields, so they only keep what the caller put there, which seems ok.

What do you think?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants