feat: report the runtime memory limit in the memory endpoint - #3050
feat: report the runtime memory limit in the memory endpoint#3050Ember-Moth wants to merge 1 commit into
Conversation
The oslimit field of /memory has been hardcoded to 0 with a "maybe we need it in the future" note. Report the soft limit the Go runtime is actually enforcing, whether it came from GOMEMLIMIT or debug.SetMemoryLimit, so it is possible to confirm a limit took effect. The runtime spells "no limit" as math.MaxInt64, which is not a useful number to hand a dashboard, so that case keeps reporting 0 and the default behaviour is unchanged. Measured with a rule-set of 112332 + 26822 domains and 5687 CIDRs, anonymous RSS after load: no GOMEMLIMIT 15.4 MB GOMEMLIMIT=37MiB 9.6 MB The limit changes the footprint substantially, which matters on platforms with a hard memory ceiling such as an Apple network extension, but until now there was no way to see whether one was in effect.
|
However, as things stand, it is difficult to see any real utility in reporting this value to the API; it appears to serve only to verify whether your environment variables are set correctly, representing nothing more than an additional runtime cost. |
|
Fair enough on the utility — I'll close this. Two things worth leaving on the record though. The cost is smaller than it looks: debug.SetMemoryLimit(-1) measures 15.8 ns uncontended and 18.9 ns with four goroutines hammering the allocator for mheap_.lock, against the os.ReadFile("/proc/pid/statm") that t.Memory() already does every tick in the same loop — a few orders of magnitude more. So the added cost is noise relative to what the handler already pays. And you're right that reading it per tick was unjustified: nothing in mihomo changes the limit at runtime, so I was designing for a capability that does not exist. Reading once at handler entry would have been correct. The utility case is genuinely thin, though. GOMEMLIMIT works today without this, and log-level: debug already exposes pprof, which gives a far more useful picture than a single ceiling number. Closing — easy to revive if an inuse-against-ceiling view is ever wanted. |
Problem
The
/memoryendpoint has always reported a hardcodedoslimit: 0:Meanwhile the Go runtime's soft memory limit (
GOMEMLIMITordebug.SetMemoryLimit) has a substantial effect on mihomo's footprint. Measured on linux/amd64 with rule-sets of 112,332 + 26,822 domains and 5,687 CIDRs loaded, anonymous RSS:GOMEMLIMIT=37MiBOn platforms with a hard memory ceiling — an Apple Network Extension gets roughly 50 MB before jetsam kills it, small routers get less — setting this limit is one of the most effective knobs available. But there is currently no way to confirm through the API that a limit is actually in effect: a typo in the environment variable and you silently run unlimited.
Change
Fill the field with the limit the runtime is actually enforcing:
memoryLimit()reads it viadebug.SetMemoryLimit(-1)— a negative argument is the documented read-only form and does not modify the limit.math.MaxInt64, which is not a useful number to hand a dashboard, so that case keeps reporting0. Default behaviour is unchanged: anyone not setting a limit sees exactly what they see today.runtime/mgcpacer.go,in < 0branch); once per second per watcher this is negligible.Dashboards consuming
/memorywill start receiving a non-zerooslimitwhen (and only when) the user has configured a limit — which is the point:inuseagainstoslimitis exactly the "how close am I to the ceiling" view.Verification
Unit test covers the mapping in both directions plus the property that reading does not alter the limit (guarding the
-1read-only form against future edits).End to end, same config, three runs:
/memoryreportsGOMEMLIMIT{"inuse":38199296,"oslimit":0}GOMEMLIMIT=37MiB{"inuse":37253120,"oslimit":38797312}GOMEMLIMIT=100MiB{"inuse":43155456,"oslimit":104857600}38797312 = 37 × 1024², 104857600 = 100 × 1024².
Notes
This only reports the limit; it does not set one. Making the limit a first-class config option (as opposed to an environment variable) may be worth a separate discussion — this change is useful either way, since it also reports limits set via
GOMEMLIMITtoday.