goframework-io-www

2 lines · 692.1 KiB Permalink Raw
1// Written by goframework.io's tools/www-site.py; do not edit.
2window.GOFRAMEWORK = {"text":{"site":{"title":"Go Framework","description":"The best Go lang framework in the universe.","footer":"Terms: [The Free License](https://licenseplanet.net/the-free-license)","powered_by_label":"Powered by","powered_by":["[simple-http-router](https://github.com/codemodify/simple-http-router)","[simple-git-server](https://github.com/codemodify/simple-git-server)","[simple-git-server-repo-viewer](https://github.com/codemodify/simple-git-server-repo-viewer)","[simple-http-fileserver](https://github.com/codemodify/simple-http-fileserver)"],"source_viewer":"https://src.goframework.io"},"header":{"issues":"Issues","issues_link":"https://github.com/codemodify/goframework/issues","discussions":"Discussions","discussions_link":"https://github.com/codemodify/goframework/discussions","source":"Source code","source_link":"https://src.goframework.io/","about":"How this server runs","about_close":"Close"},"home":{"headline":"The best Go lang framework in the universe.","lead":"A set of basic building blocks carefully designed to work well together.","intro":"Modules are split into small chunks of independed functionalities as opt-in model.\nIf you need to use just one module without the bloat - you can - there is no downloading half the internet of dependencies.\n\nInterfaces, contracts and specs let you plug in your own implementation if you are not happy with the defaults.\n","runs_on_label":"OSes:","runs_on":["Linux","FreeBSD","OpenBSD","NetBSD","macOS","Windows"],"groups_title":"The groups","tile_examples":["example","examples"],"stats":{"modules":"modules in {groups} groups","checks":"checks against test suites, references, real servers and systems","measurements":"measurements against other libraries"}},"performance":{"title":"Go Framework","badge":"benchmarks"},"search":{"title":"{query}: goframework","badge":"search","heading":"{modules} and {groups} for “{query}”","every":"Every module: {count}","module":["module","modules"],"group":["group","groups"]},"side":{"introduction":"Introduction","performance":"Performance","heading":"{groups} groups · {modules} modules","filter":"Filter: systemd, s3, panic, yaml…","no_match":"Nothing matches “{query}”.","all_results":"Every match for “{query}” →"},"group":{"page_title":"{pattern}: {title} · goframework","module_title":"{module} · {title} · goframework","tabs":{"overview":"Summary","modules":"Modules","examples":"Examples","source":"Code","performance":"Perf","competition":"Competition","recorded":"Recorded"},"stats":{"modules":["module","modules"]},"overview":{"code_label":"The code","output_label":"What it printed","where_title":"Dependencies","built_on":"Built on","built_on_none":"None: the standard library only.","built_on_it":"Built on it","built_on_it_none":"Nothing yet: applications use it directly."},"modules":{"title":"The modules","note":"Open one for what it does, its code, what it was checked against and how it measures."},"competition":{"title":"Against the competition","note":"What else exists for this, what it does well and where it falls short, from the research behind each module.","replaces":"What it replaces","empty":"No comparison with other libraries is written down for this group."},"performance":{"title":"Measured against other libraries","note":"Read from the files the benchmarks wrote: the machine, the Go version, the rounds and the command that reproduces each.","empty":"No speed comparison with other libraries for this group yet.","instead":"What it was checked against instead"},"examples":{"title":"Examples","note":"Real programs: the code, and what each printed on the systems it ran on."},"recorded":{"title":"Recorded","note":"The modules ran a scenario; every event was written down. Step through it.","intro":"A recording: the real modules ran this scenario, and every event was written down with what each part reported after it. Step through it; at any step, read why it happens and how the code does it."},"source":{"title":"Source","note":"Each module's repository: its files at any branch or tag, its readme, how to clone it. A card opens it in a new tab.","none":"These modules are not on src.goframework.io yet."}},"module":{"from_readme":"From its readme","checked":"Checked against","measured":"Measured against other libraries","replaces":"Replaces:","not_when":"Not when:","new":"New:","uses":"Uses","this":"This module","used_by":"Used by","none":"none","third_party":"Third-party code:","get_note":"from the first release","about":"About","source":"Source","browse":"Browse {module}'s files on src.goframework.io","source_none":"{module} is not on src.goframework.io yet.","badge_measured":"measured","badge_checks":["check","checks"],"badge_verified":"verified","badge_new":"new","verified_means":"Verified: checked on every system it supports, and against real servers or reference test suites where it speaks to one"},"example":{"usual":"The usual way:","with":"With goframework:","printed":"What it printed","printed_each":"What it printed, on each system","ran":"Ran on {system} with {go}, {date}:","file":"The whole program:","choose":"Choose one:"},"measure":{"against":"Against {against}.","goroutines_on":"Goroutines on","cpus":["CPU","CPUs"],"measured":"Measured:","lower":"lower is better","log_scale":"bars on a log scale, the values span {span}×","highlighted":"Highlighted: goframework.","not_measured":"not measured","every_number":"Every number","from":"From","reproduce":"Reproduce:"}},"groups":[{"id":"app","title":"Applications","pattern":"app*","purpose":"Blueprint to easily create: app / app + plugins / app + service / app + service + plugins","replaces":["signal.NotifyContext, a shutdown deadline, recover and os.Exit, written in every main","coreos/go-systemd's SdNotify and x/sys/windows/svc, for a program run as a service","hashicorp/go-plugin and natefinch/pie"],"modules":["app-spec","app","app-as-service","app-w-plugins","app-as-service-w-plugins","app-plugin","app-samples"],"blocks":[{"type":"stages","title":"An application's life","intro":"Start, Run and Stop are the application's, all optional; the module runs them, and the same steps hold in all four modules.","stages":[{"label":"START","text":"Sets up: listens, connects, loads. With app-w-plugins the plugins start first, each answering its handshake. A Start that fails cleans up after itself, and Stop is not called.","modules":["app","app-w-plugins"]},{"label":"READY","text":"When Start returns. app-as-service tells the service manager: systemd hears READY=1, the Windows Service Control Manager sees the service running. Under app, ready means nothing to anyone.","modules":["app-as-service","app-as-service-w-plugins"]},{"label":"RUN","text":"The work, until it returns or a stop request cancels its context. A panic comes back as an error with the application's frames, and Stop still runs.","modules":["app","crashproof"]},{"label":"STOP REQUEST","text":"Ctrl+C or SIGTERM, the service manager's stop, or Run returning. A second request while stopping ends the program at once.","modules":["app","app-as-service"]},{"label":"STOP","text":"Lets go of what Start set up, within App.StopTimeout, 30 seconds by default; Run must have returned by then too. Then the plugins stop, and the child processes still running are stopped.","modules":["app-spec","app-w-plugins","system-process-all"]},{"label":"EXIT","text":"0 after a requested stop or Run returning nil, 1 for a failure or an overrun deadline, 2 for a panic. What ended the application is logged, a panic with its stack; the log is flushed and closed last.","modules":["app","logging-config"]}]},{"type":"example","id":"app/lifecycle","example":{"id":"app/lifecycle","title":"Stop on SIGTERM, within a deadline, with the right exit code","problem":"A queue worker runs until its service manager sends SIGTERM, then commits what it handled and exits. A message it cannot read must not end it without committing, and a commit that hangs must not keep it from stopping.","notice":"Both stop cleanly on SIGTERM. When the worker panics on a garbled message, the hand-written main crashes with the runtime's dump and never commits; under app the panic comes back from Run as an error: Stop still commits, the panic is logged with the application's frames, and the exit code is 2. A commit slower than StopTimeout (1 s here) ends the program at the deadline with exit code 1. The supervisor sends SIGTERM once two orders are handled.","parts":[{"name":"usual","label":"os/signal and context","code":"func usual(settings Settings) {\n\tctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)\n\tdefer stop()\n\tworker := &Worker{settings: settings}\n\tlog.Print(\"ready\")\n\terr := worker.Work(ctx) // until SIGTERM or Ctrl+C\n\tlog.Print(\"stopping\")\n\tdeadline, cancel := context.WithTimeout(context.Background(), time.Second)\n\tdefer cancel()\n\terr = errors.Join(err, worker.Commit(deadline))\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\tlog.Print(err)\n\t\tos.Exit(1)\n\t}\n}"},{"name":"with","label":"app","code":"func with(settings Settings) {\n\tvar worker *Worker\n\tapp.Run(app.App[Settings]{\n\t\tName: \"worker\", Version: \"1.0.0\", Config: settings,\n\t\tLogging: logconfig.Config{Level: logconfig.Debug}, // to show starting, ready, stopping, stopped\n\t\tStopTimeout: time.Second,\n\t\tStart: func(ctx context.Context, a *app.Context[Settings]) error {\n\t\t\tworker = &Worker{settings: a.Config()}\n\t\t\treturn nil\n\t\t},\n\t\tRun: func(ctx context.Context, a *app.Context[Settings]) error { return worker.Work(ctx) },\n\t\tStop: func(ctx context.Context, a *app.Context[Settings]) error { return worker.Commit(ctx) },\n\t}) // ends the program: 0 stopped, 1 failed or overran the deadline, 2 panicked\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run -trimpath ./app/lifecycle","exit":0,"name":"linux","output":"### os/signal and context: SIGTERM while working\n 2026/10/17 04:54:28 ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026/10/17 04:54:29 stopping\n committed 2 orders\n> exit code 0, after 0.4 s\n\n### os/signal and context: a message it cannot read\n 2026/10/17 04:54:29 ready\n handled order 1\n handled order 2\n panic: runtime error: index out of range [1] with length 1\n\n goroutine 1 [running]:\n main.(*Worker).Work(0x112263a51cc8, {0x7f0508, 0x112263b32180})\n \tgoframework.io/www-examples/app/lifecycle/main.go:65 +0x1f0\n main.usual({{0x112263b32100?, 0x5e66c0?, 0x7fff10e7718d?}, 0x7c47e0?})\n \tgoframework.io/www-examples/app/lifecycle/main.go:109 +0x1b0\n main.main()\n \tgoframework.io/www-examples/app/lifecycle/main.go:84 +0x212\n> exit code 2, after 0.3 s\n\n### app: SIGTERM while working\n 2026-10-17 04:54:29.617 | debug | starting\n 2026-10-17 04:54:29.617 | debug | ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026-10-17 04:54:29.817 | debug | stopping\n committed 2 orders\n 2026-10-17 04:54:30.018 | debug | stopped\n> exit code 0, after 0.4 s\n\n### app: a message it cannot read\n 2026-10-17 04:54:30.020 | debug | starting\n 2026-10-17 04:54:30.020 | debug | ready\n handled order 1\n handled order 2\n 2026-10-17 04:54:30.321 | debug | stopping\n committed 2 orders\n 2026-10-17 04:54:30.521 | error | \"run panicked: runtime error: index out of range [1] with length 1\\n\\tmain.(*Worker).Work (goframework.io/www-examples/app/lifecycle/main.go:65)\\n\\tmain.with.func2 (goframework.io/www-examples/app/lifecycle/main.go:133)\"\n 2026-10-17 04:54:30.521 | debug | stopped\n> exit code 2, after 0.5 s\n\n### app: a commit slower than the 1 s stop deadline\n 2026-10-17 04:54:30.524 | debug | starting\n 2026-10-17 04:54:30.524 | debug | ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026-10-17 04:54:30.724 | debug | stopping\n 2026-10-17 04:54:31.725 | error | stop: the application did not stop within its deadline\n 2026-10-17 04:54:31.725 | debug | stopped\n> exit code 1, after 1.2 s\n"}],"file":"www-examples/app/lifecycle/main.go"}},{"type":"table","title":"Exit codes","source":"app/readme","table":"Exit code","caption":"From app's readme; the same in the four modules. Under a service manager, 1 and 2 are failures, so its restart settings apply.","header":["Exit code","When"],"rows":[["0","after Ctrl+C or SIGTERM, or Run returning nil"],["1","Start, Run or Stop failed; stopping overran its deadline; a second Ctrl+C or SIGTERM came while stopping"],["2","Start, Run or Stop panicked"]],"heading":"app"},{"type":"table","title":"Four modules, chosen by import","source":"app/readme","table":"Import","caption":"From app's readme. Each module's package is named app and gives the same App and Context, so moving between them changes the import line, and for plugins passes the plugins.","header":["Import","Runs the application"],"rows":[["goframework.io/app","started by hand or by a script"],["goframework.io/app-as-service","as the program of a user or system service"],["goframework.io/app-w-plugins","with plugins, separate programs built with app-plugin"],["goframework.io/app-as-service-w-plugins","both"]],"heading":"app"},{"type":"example","id":"app/as-service","example":{"id":"app/as-service","title":"From a program to a service: change the import","problem":"An HTTP server written with app runs by hand. Run by systemd as a unit of Type=notify, the units that need it should start only once it listens, and stopping it should be reported as stopping.","notice":"The two programs differ in their import line only. The supervisor listens where systemd listens for a service's state, the socket NOTIFY_SOCKET names, here in a temporary folder. Built with app, the server never says it is ready; with app-as-service, systemd hears READY=1 when Start returns and STOPPING=1 on SIGTERM. On Windows the same import reports the states to the Service Control Manager; started by hand, both run alike.","parts":[{"name":"app","label":"goframework.io/app","code":"import app \"goframework.io/app\"\n\nfunc runApp(greeter *Greeter) {\n\tapp.Run(app.App[Settings]{\n\t\tName: \"greeter\", Version: \"1.0.0\",\n\t\tConfig: Settings{Address: \"127.0.0.1:0\"},\n\t\tStart: greeter.Start, Run: greeter.Run, Stop: greeter.Stop,\n\t})\n}"},{"name":"service","label":"goframework.io/app-as-service","code":"import app \"goframework.io/app-as-service\"\n\nfunc runService(greeter *Greeter) {\n\tapp.Run(app.App[Settings]{\n\t\tName: \"greeter\", Version: \"1.0.0\",\n\t\tConfig: Settings{Address: \"127.0.0.1:0\"},\n\t\tStart: greeter.Start, Run: greeter.Run, Stop: greeter.Stop,\n\t})\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./app/as-service","exit":0,"name":"linux","output":"### goframework.io/app, run as systemd runs a Type=notify service\n 2026-10-17 04:49:00.101 | info | listening on 127.0.0.1:46547\n> 2 s and no READY=1: systemd still shows the service activating, and the units that need it wait\n> GET / answers: hello from greeter 1.0.0\n> SIGTERM\n> exit code 0\n\n### goframework.io/app-as-service, the same code, run the same way\n 2026-10-17 04:49:02.204 | info | listening on 127.0.0.1:41477\n> systemd hears READY=1, 0.3 s after the start: Start returned\n> GET / answers: hello from greeter 1.0.0\n> SIGTERM\n> systemd hears STOPPING=1\n> exit code 0\n"}],"file":"www-examples/app/as-service/main.go"}},{"type":"table","title":"What each service manager hears","source":"app-as-service/readme","table":"Service manager","caption":"From app-as-service's readme. The sample service ran installed under systemd on Debian and under the Service Control Manager on Windows Server 2025.","header":["Service manager","While Start runs","When Start returns","Stopping"],"rows":[["systemd","activating; systemctl start waits when the unit is Type=notify","READY=1: active, and units needing this one start","STOPPING=1, then Stop"],["Windows Service Control Manager","\"Starting\", with progress, so the 30-second limit never cuts a long Start short","\"Running\": dependent services start, and stopping is accepted","\"Stopping\", with progress, during Stop"],["launchd, rc.d, OpenRC, SysV","(no readiness protocol)","","SIGTERM, then Stop"]],"heading":"app-as-service"},{"type":"example","id":"app/plugins","example":{"id":"app/plugins","title":"A plugin that crashes, and the application goes on","problem":"An application hands texts to a plugin, a separate program, through a Go interface. A bug in the plugin must come back as an error, not take the application down, and a plugin whose process died must be running again without anyone restarting it.","notice":"The method's panic comes back as an error naming the plugin, and the plugin goes on. When its process dies, the call fails at once with the plugin not available, what the plugin printed is logged under plugin=\"upper\", and it is started again after 1 s: the next call works. The plugin is this same program started again; Converter's stub, converter_stub.go, was written by go generate.","parts":[{"name":"plugin","label":"the plugin: app-plugin","code":"type upper struct{}\n\nfunc (upper) Convert(ctx context.Context, text string) (string, error) {\n\tswitch text {\n\tcase \"\":\n\t\tvar words []string\n\t\treturn words[0], nil // a bug: the method panics\n\tcase \"crash\":\n\t\tvar cache map[string]string\n\t\tgo func() { cache[text] = \"\" }() // a bug in a goroutine: the process dies\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn strings.ToUpper(text), nil\n}\n\nfunc servePlugin() {\n\tplugin.Serve(plugin.Info{Name: \"upper\", Version: \"1.0.0\"},\n\t\tplugin.Implement[Converter](upper{}, Version))\n}"},{"name":"with","label":"the application: app-w-plugins","code":"func runApplication() {\n\tself, _ := os.Executable()\n\tplugins := app.NewPlugins(app.PluginsConfig{Plugins: []app.Plugin{{\n\t\tName: \"upper\", Command: []string{self, \"plugin\"}, Required: true,\n\t}}})\n\tconverter := NewConverter(plugins) // the stub: calls the plugin that offers Converter\n\tapp.Run(app.App[struct{}]{\n\t\tName: \"converter\",\n\t\tRun: func(ctx context.Context, a *app.Context[struct{}]) error {\n\t\t\tconvert := func(text string) {\n\t\t\t\tresult, err := converter.Convert(ctx, text)\n\t\t\t\ta.Log.Infof(\"Convert(%q): %q, error: %v\", text, result, err)\n\t\t\t}\n\t\t\tconvert(\"hello\")\n\t\t\tconvert(\"\") // the method panics: an error, and the plugin goes on\n\t\t\tconvert(\"crash\") // its process dies: an error, and it is restarted after 1 s\n\t\t\tconvert(\"again\") // fails at once while it restarts\n\t\t\ttime.Sleep(1500 * time.Millisecond)\n\t\t\tconvert(\"again\")\n\t\t\treturn nil // done: the plugin is stopped, the program exits with 0\n\t\t},\n\t}, plugins)\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run -trimpath ./app/plugins","exit":0,"name":"linux","output":"2026-10-17 04:54:32.064 | info | Convert(\"hello\"): \"HELLO\", error: <nil>\n2026-10-17 04:54:32.064 | info | Convert(\"\"): \"\", error: plugin upper: main.Converter.Convert: panicked: runtime error: index out of range [0] with length 0\n2026-10-17 04:54:32.066 | info | panic: assignment to entry in nil map plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | goroutine 34 [running]: plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | main.upper.Convert.func1() plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | \"\\tgoframework.io/www-examples/app/plugins/main.go:50 +0x26\" plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | created by main.upper.Convert in goroutine 18 plugin=\"upper\"\n2026-10-17 04:54:32.066 | info | \"\\tgoframework.io/www-examples/app/plugins/main.go:50 +0xa5\" plugin=\"upper\"\n2026-10-17 04:54:32.067 | info | Convert(\"crash\"): \"\", error: main.Converter.Convert: plugin upper: the plugin is not available\n2026-10-17 04:54:32.067 | info | Convert(\"again\"): \"\", error: main.Converter.Convert: plugin upper: the plugin is not available\n2026-10-17 04:54:32.067 | warning | it exited: exit code 2; restarting in 1s plugin=\"upper\"\n2026-10-17 04:54:33.567 | info | Convert(\"again\"): \"AGAIN\", error: <nil>\n"}],"file":"www-examples/app/plugins/main.go"}},{"type":"facts","items":[{"value":"0.04 to 0.4 ms","label":"for a 64 MiB buffer to reach a plugin the application started, through shared memory, against 59 to 208 ms as bytes; on six systems","source":"app.md"},{"value":"0.6 to 2 GB/s","label":"byte streams to and from a plugin on the loopback paths, through a child's pipes and a Unix socket, on six systems; Windows' pipes toward the plugin about 180 MB/s","source":"app.md"},{"value":"9 bytes","label":"a frame's header: kind, stream and length; the messages are JSON-RPC 2.0, with nothing to compile","source":"app.md"}]},{"type":"table","title":"Plugins, compared","source":"app.md","table":"against the others","caption":"From app.md, the design agreed on 2026-10-14. Its build steps 1 to 5 are done (the protocol, dialling in and out, streams and shared memory, compiled-in plugins); the measurements against these libraries and a plugin written in Python (step 6) are not.","header":["","hashicorp/go-plugin","natefinch/pie","this design"],"rows":[["Dependencies","gRPC, protobuf, yamux, hclog","none","none"],["Contract","protobuf with generated code, or net/rpc wrappers by hand","net/rpc","Go interfaces; a stub generated from Go itself"],["Other languages","through protobuf","gob ties plugins to Go","JSON-RPC and a 9-byte frame header"],["Plugin to application calls","a separate broker","no","the same interfaces"],["Large data","messages; no shared memory","messages","shared memory, raw frames"],["Streams","gRPC streams","no","flow-controlled, both ways"],["Connection direction","started, or reattached","started","per plugin: started, dials in, dialled"],["Supervision","no: the application kills it","no","restart, backoff, required or optional"],["Control apart from data","no","no","yes"],["Running as a service","no","no","app-as-service-w-plugins"]],"heading":"against the others"}],"sample":{"code":"func with(settings Settings) {\n\tvar worker *Worker\n\tapp.Run(app.App[Settings]{\n\t\tName: \"worker\", Version: \"1.0.0\", Config: settings,\n\t\tLogging: logconfig.Config{Level: logconfig.Debug}, // to show starting, ready, stopping, stopped\n\t\tStopTimeout: time.Second,\n\t\tStart: func(ctx context.Context, a *app.Context[Settings]) error {\n\t\t\tworker = &Worker{settings: a.Config()}\n\t\t\treturn nil\n\t\t},\n\t\tRun: func(ctx context.Context, a *app.Context[Settings]) error { return worker.Work(ctx) },\n\t\tStop: func(ctx context.Context, a *app.Context[Settings]) error { return worker.Commit(ctx) },\n\t}) // ends the program: 0 stopped, 1 failed or overran the deadline, 2 panicked\n}","example":"app/lifecycle","title":"Stop on SIGTERM, within a deadline, with the right exit code","file":"www-examples/app/lifecycle/main.go","output":"### os/signal and context: SIGTERM while working\n 2026/10/17 04:54:28 ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026/10/17 04:54:29 stopping\n committed 2 orders\n> exit code 0, after 0.4 s\n\n### os/signal and context: a message it cannot read\n 2026/10/17 04:54:29 ready\n handled order 1\n handled order 2\n panic: runtime error: index out of range [1] with length 1\n\n goroutine 1 [running]:\n main.(*Worker).Work(0x112263a51cc8, {0x7f0508, 0x112263b32180})\n \tgoframework.io/www-examples/app/lifecycle/main.go:65 +0x1f0\n main.usual({{0x112263b32100?, 0x5e66c0?, 0x7fff10e7718d?}, 0x7c47e0?})\n \tgoframework.io/www-examples/app/lifecycle/main.go:109 +0x1b0\n main.main()\n \tgoframework.io/www-examples/app/lifecycle/main.go:84 +0x212\n> exit code 2, after 0.3 s\n\n### app: SIGTERM while working\n 2026-10-17 04:54:29.617 | debug | starting\n 2026-10-17 04:54:29.617 | debug | ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026-10-17 04:54:29.817 | debug | stopping\n committed 2 orders\n 2026-10-17 04:54:30.018 | debug | stopped\n> exit code 0, after 0.4 s\n\n### app: a message it cannot read\n 2026-10-17 04:54:30.020 | debug | starting\n 2026-10-17 04:54:30.020 | debug | ready\n handled order 1\n handled order 2\n 2026-10-17 04:54:30.321 | debug | stopping\n committed 2 orders\n 2026-10-17 04:54:30.521 | error | \"run panicked: runtime error: index out of range [1] with length 1\\n\\tmain.(*Worker).Work (goframework.io/www-examples/app/lifecycle/main.go:65)\\n\\tmain.with.func2 (goframework.io/www-examples/app/lifecycle/main.go:133)\"\n 2026-10-17 04:54:30.521 | debug | stopped\n> exit code 2, after 0.5 s\n\n### app: a commit slower than the 1 s stop deadline\n 2026-10-17 04:54:30.524 | debug | starting\n 2026-10-17 04:54:30.524 | debug | ready\n handled order 1\n handled order 2\n> SIGTERM\n 2026-10-17 04:54:30.724 | debug | stopping\n 2026-10-17 04:54:31.725 | error | stop: the application did not stop within its deadline\n 2026-10-17 04:54:31.725 | debug | stopped\n> exit code 1, after 1.2 s\n","system":"linux","runs":1},"measured":[],"competition":[{"title":"The leading ways to add plugins","source":"competition.md","heading":"12. Plugins","blocks":[{"t":"table","header":["Approach","★","Release","Notes"],"rows":[["hashicorp/go-plugin","6.1k","v1.8.0, 2026-04","plugins as subprocesses over net/rpc or gRPC; log forwarding; reattach; checksums and TLS; needs gRPC, protobuf, yamux, hclog"],["Go `plugin`","stdlib","—","Linux, FreeBSD and macOS only; same toolchain required; its documentation suggests IPC instead"],["wazero","6.4k","v1.12.0, 2026-05","WebAssembly without dependencies or cgo; WASI preview 1"],["extism Go SDK","185","2025","dormant"],["yaegi, expr, goja, gopher-lua, starlark-go","—","—","interpreters and embedded languages"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"isolation;","items":[]},{"text":"a versioned handshake;","items":[]},{"text":"bidirectional and streaming calls;","items":[]},{"text":"discovery;","items":[]},{"text":"integrity checks;","items":[]},{"text":"log forwarding;","items":[]},{"text":"limits and timeouts;","items":[]},{"text":"hot reload;","items":[]},{"text":"Windows.","items":[]}]},{"t":"p","text":"**Gap:** a dependency-free subprocess plugin system with a handshake."}]},{"title":"Against hashicorp/go-plugin and natefinch/pie","source":"app.md","heading":"against the others","blocks":[{"t":"table","header":["","hashicorp/go-plugin","natefinch/pie","this design"],"rows":[["Dependencies","gRPC, protobuf, yamux, hclog","none","none"],["Contract","protobuf with generated code, or net/rpc wrappers by hand","net/rpc","Go interfaces; a stub generated from Go itself"],["Other languages","through protobuf","gob ties plugins to Go","JSON-RPC and a 9-byte frame header"],["Plugin to application calls","a separate broker","no","the same interfaces"],["Large data","messages; no shared memory","messages","shared memory, raw frames"],["Streams","gRPC streams","no","flow-controlled, both ways"],["Connection direction","started, or reattached","started","per plugin: started, dials in, dialled"],["Supervision","no: the application kills it","no","restart, backoff, required or optional"],["Control apart from data","no","no","yes"],["Running as a service","no","no","`app-as-service-w-plugins`"]]}]}]},{"id":"callstack","title":"Call stacks","pattern":"callstack","purpose":"Read the call stack: file, line and function, cheaply.","replaces":["runtime.Callers with runtime.CallersFrames","go-stack/stack","the stacks of go-errors/errors and pkg/errors"],"modules":["callstack"],"blocks":[{"type":"example","id":"crashes/stack","example":{"id":"crashes/stack","title":"Find the line that panicked","problem":"A deferred recover caught a panic. Report the function and the line that raised it, not the recovery code.","notice":"runtime.Callers starts at the deferred function and goes through the runtime's panic code before it reaches parse. callstack.PanicStack starts at parse, and gives the package, the function and the receiver as separate fields.","parts":[{"name":"usual","label":"runtime.Callers","code":"defer func() {\n\tif value := recover(); value != nil {\n\t\tfmt.Println(\"panic:\", value)\n\t\tpcs := make([]uintptr, 64)\n\t\tframes := runtime.CallersFrames(pcs[:runtime.Callers(1, pcs)])\n\t\tfor more := true; more; {\n\t\t\tvar frame runtime.Frame\n\t\t\tframe, more = frames.Next()\n\t\t\tfmt.Printf(\"\\t%s (%s:%d)\\n\", frame.Function, filepath.Base(frame.File), frame.Line)\n\t\t}\n\t}\n}()\nnew(Importer).Load(records)"},{"name":"with","label":"callstack","code":"defer func() {\n\tif value := recover(); value != nil {\n\t\tfmt.Println(\"panic:\", value)\n\t\tstack := callstack.PanicStack() // starts at the function that panicked\n\t\tfor _, frame := range stack {\n\t\t\tfmt.Printf(\"\\t%s.%s (%s:%d)\\n\", frame.Package, frame.Func, filepath.Base(frame.File), frame.Line)\n\t\t}\n\t\tfmt.Printf(\"panicked in %s, a method of %s, at line %d\\n\", stack[0].Func, stack[0].Receiver, stack[0].Line)\n\t}\n}()\nnew(Importer).Load(records)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./crashes/stack","exit":0,"name":"linux","output":"### runtime.Callers\npanic: runtime error: index out of range [2] with length 2\n\tmain.usual.func1 (main.go:46)\n\truntime.gopanic (panic.go:859)\n\truntime.panicBounds64 (panic.go:236)\n\truntime.panicBounds (asm_amd64.s:1622)\n\tmain.(*Importer).parse (main.go:27)\n\tmain.(*Importer).Load (main.go:20)\n\tmain.usual (main.go:54)\n\tmain.main (main.go:35)\n\truntime.main (proc.go:302)\n\truntime.goexit (asm_amd64.s:1264)\n\n### callstack.PanicStack\npanic: runtime error: index out of range [2] with length 2\n\tmain.(*Importer).parse (main.go:27)\n\tmain.(*Importer).Load (main.go:20)\n\tmain.with (main.go:70)\n\tmain.main (main.go:37)\n\truntime.main (proc.go:302)\n\truntime.goexit (asm_amd64.s:1264)\npanicked in (*Importer).parse, a method of *Importer, at line 27\n"}],"file":"www-examples/crashes/stack/main.go"}}],"sample":{"code":"defer func() {\n\tif value := recover(); value != nil {\n\t\tfmt.Println(\"panic:\", value)\n\t\tstack := callstack.PanicStack() // starts at the function that panicked\n\t\tfor _, frame := range stack {\n\t\t\tfmt.Printf(\"\\t%s.%s (%s:%d)\\n\", frame.Package, frame.Func, filepath.Base(frame.File), frame.Line)\n\t\t}\n\t\tfmt.Printf(\"panicked in %s, a method of %s, at line %d\\n\", stack[0].Func, stack[0].Receiver, stack[0].Line)\n\t}\n}()\nnew(Importer).Load(records)","example":"crashes/stack","title":"Find the line that panicked","file":"www-examples/crashes/stack/main.go","output":"### runtime.Callers\npanic: runtime error: index out of range [2] with length 2\n\tmain.usual.func1 (main.go:46)\n\truntime.gopanic (panic.go:859)\n\truntime.panicBounds64 (panic.go:236)\n\truntime.panicBounds (asm_amd64.s:1622)\n\tmain.(*Importer).parse (main.go:27)\n\tmain.(*Importer).Load (main.go:20)\n\tmain.usual (main.go:54)\n\tmain.main (main.go:35)\n\truntime.main (proc.go:302)\n\truntime.goexit (asm_amd64.s:1264)\n\n### callstack.PanicStack\npanic: runtime error: index out of range [2] with length 2\n\tmain.(*Importer).parse (main.go:27)\n\tmain.(*Importer).Load (main.go:20)\n\tmain.with (main.go:70)\n\tmain.main (main.go:37)\n\truntime.main (proc.go:302)\n\truntime.goexit (asm_amd64.s:1264)\npanicked in (*Importer).parse, a method of *Importer, at line 27\n","system":"linux","runs":1},"measured":["callstack"],"competition":[{"title":"The leading Go libraries for debugging and crashes","source":"competition.md","heading":"10. Debugging and crashes","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["maruel/panicparse v2","3.7k","2025-03","parses and deduplicates goroutine dumps"],["uber-go/goleak","5.3k","v1.3.0, **2023**","goroutine leak checks in tests"],["google/gops","7.0k","2026-01","agent with stacks, memory, GC, pprof, trace; depends on cobra and gopsutil v3"],["sasha-s/go-deadlock","1.2k","2026-03","drop-in mutexes that detect order inversions and long waits"],["cockroachdb/errors","2.5k","2026-06","stacks, network encoding, PII-safe details, Sentry; 13 dependencies"],["samber/oops","1.0k","2026-09","structured errors"],["pkg/errors, go-spew","—","2020, 2018","frozen"],["getsentry/sentry-go","1.1k","2026-10","crash and error reporting"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"panics captured in every goroutine and reported;","items":[]},{"text":"crash output sent to a monitor (`debug.SetCrashOutput`);","items":[]},{"text":"deduplicated dumps;","items":[]},{"text":"leak and deadlock detection;","items":[]},{"text":"a live diagnostics endpoint;","items":[]},{"text":"errors with stacks and attributes, compatible with `Is`, `As` and `AsType`;","items":[]},{"text":"flight-recorder snapshots.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"one pipeline from panic to crash monitor to restart;","items":[]},{"text":"stale core tools;","items":[]},{"text":"heavy dependencies.","items":[]}]}]}]},{"id":"cliflags","title":"Command line","pattern":"cliflags","purpose":"Flags, commands and subcommands for command-line programs.","replaces":["the flag package with a FlagSet per command","spf13/cobra with pflag","urfave/cli","alecthomas/kong"],"modules":["cliflags"],"blocks":[{"type":"example","id":"cli/commands","example":{"id":"cli/commands","title":"Commands and flags from Go structs","problem":"A backup tool with two commands, a global --verbose flag with a short name, a required flag, a default that an environment variable can change, and help for each command.","notice":"The flag package takes global flags only before the command, reads no environment variable and leaves required flags to your code; its help lists flags, not commands. cliflags takes -v anywhere, reads BACKUP_TARGET, checks --source, and writes help with the commands, and each flag's type, default and variable.","parts":[{"name":"usual","label":"flag","code":"func usual() {\n\tverbose := flag.Bool(\"verbose\", false, \"Say what is done\")\n\tflag.BoolVar(verbose, \"v\", false, \"Say what is done (shorthand)\")\n\tflag.Parse()\n\tswitch flag.Arg(0) {\n\tcase \"create\":\n\t\tcreate := flag.NewFlagSet(\"create\", flag.ExitOnError)\n\t\tsource := create.String(\"source\", \"\", \"Folder to back up (required)\")\n\t\ttarget := create.String(\"target\", \"./backups\", \"Folder that keeps the backups\")\n\t\tkeep := create.Uint(\"keep\", 7, \"Backups to keep\")\n\t\tcompress := create.Bool(\"compress\", false, \"Compress the backup\")\n\t\tcreate.Parse(flag.Args()[1:])\n\t\tif *source == \"\" {\n\t\t\tfmt.Fprintln(os.Stderr, \"create: -source is required\")\n\t\t\tos.Exit(2)\n\t\t}\n\t\tfmt.Printf(\"create: source=%s target=%s keep=%d compress=%t verbose=%t\\n\", *source, *target, *keep, *compress, *verbose)\n\tcase \"restore\":\n\t\trestore := flag.NewFlagSet(\"restore\", flag.ExitOnError)\n\t\tfrom := restore.String(\"from\", \"\", \"Backup to restore (required)\")\n\t\tinto := restore.String(\"into\", \".\", \"Folder to restore into\")\n\t\trestore.Parse(flag.Args()[1:])\n\t\tif *from == \"\" {\n\t\t\tfmt.Fprintln(os.Stderr, \"restore: -from is required\")\n\t\t\tos.Exit(2)\n\t\t}\n\t\tfmt.Printf(\"restore: from=%s into=%s verbose=%t\\n\", *from, *into, *verbose)\n\tdefault:\n\t\tfmt.Fprintln(os.Stderr, \"usage: backup [-v] create|restore [flags]\")\n\t\tos.Exit(2)\n\t}\n}"},{"name":"with","label":"cliflags","code":"type Globals struct {\n\tVerbose bool `flagName:\"verbose\" flagShortName:\"v\" flagDescription:\"Say what is done\"`\n}\n\ntype CreateFlags struct {\n\tSource string `flagName:\"source\" flagShortName:\"s\" flagRequired:\"true\" flagDescription:\"Folder to back up\"`\n\tTarget string `flagName:\"target\" flagShortName:\"t\" flagDefault:\"./backups\" flagEnv:\"BACKUP_TARGET\" flagDescription:\"Folder that keeps the backups\"`\n\tKeep uint `flagName:\"keep\" flagShortName:\"k\" flagDefault:\"7\" flagDescription:\"Backups to keep\"`\n\tCompress bool `flagName:\"compress\" flagShortName:\"z\" flagDescription:\"Compress the backup\"`\n}\n\n\ntype RestoreFlags struct {\n\tFrom string `flagName:\"from\" flagShortName:\"f\" flagRequired:\"true\" flagDescription:\"Backup to restore\"`\n\tInto string `flagName:\"into\" flagShortName:\"i\" flagDefault:\".\" flagDescription:\"Folder to restore into\"`\n}\n\nfunc newCLI() (*cliflags.Command, error) {\n\troot := &cliflags.Command{\n\t\tName: \"backup\", Description: \"Backs up folders and restores them\", Flags: &Globals{},\n\t\tExamples: []string{\"backup create --source=photos -z\", \"backup -v restore --from=photos-1\"},\n\t}\n\terr := root.AddCommand(\n\t\t&cliflags.Command{\n\t\t\tName: \"create\", Description: \"Back up a folder\", Flags: &CreateFlags{},\n\t\t\tExamples: []string{\"backup create --source=photos -z\"},\n\t\t\tHandler: func(inv *cliflags.Invocation) error {\n\t\t\t\tflags, globals := inv.Flags.(*CreateFlags), inv.Globals.(*Globals)\n\t\t\t\tfmt.Printf(\"create: source=%s target=%s keep=%d compress=%t verbose=%t\\n\", flags.Source, flags.Target, flags.Keep, flags.Compress, globals.Verbose)\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t&cliflags.Command{\n\t\t\tName: \"restore\", Description: \"Restore a backup\", Flags: &RestoreFlags{},\n\t\t\tHandler: func(inv *cliflags.Invocation) error {\n\t\t\t\tflags, globals := inv.Flags.(*RestoreFlags), inv.Globals.(*Globals)\n\t\t\t\tfmt.Printf(\"restore: from=%s into=%s verbose=%t\\n\", flags.From, flags.Into, globals.Verbose)\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t)\n\treturn root, err\n}\n\nfunc with() {\n\tif err := cliflags.Execute(newCLI); err != nil { // runs the command, or writes its help\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./cli/commands parse","exit":0,"name":"parse","output":"$ backup create --source photos --keep 3 (flag)\ncreate: source=photos target=./backups keep=3 compress=false verbose=false\n\n$ backup create --source photos --keep 3 (cliflags)\ncreate: source=photos target=./backups keep=3 compress=false verbose=false\n\n$ backup create --source photos -v (flag)\nflag provided but not defined: -v\nUsage of create:\n -compress\n \tCompress the backup\n -keep uint\n \tBackups to keep (default 7)\n -source string\n \tFolder to back up (required)\n -target string\n \tFolder that keeps the backups (default \"./backups\")\n[exit status 2]\n\n$ backup create --source photos -v (cliflags)\ncreate: source=photos target=./backups keep=7 compress=false verbose=true\n\n$ backup restore --into /srv (flag)\nrestore: -from is required\n[exit status 2]\n\n$ backup restore --into /srv (cliflags)\nmissing required flag 'from'\n[exit status 1]\n\n$ BACKUP_TARGET=/mnt/usb backup create --source photos (flag)\ncreate: source=photos target=./backups keep=7 compress=false verbose=false\n\n$ BACKUP_TARGET=/mnt/usb backup create --source photos (cliflags)\ncreate: source=photos target=/mnt/usb keep=7 compress=false verbose=false\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./cli/commands help","exit":0,"name":"help","output":"$ backup -h (flag)\nUsage of backup:\n -v\tSay what is done (shorthand)\n -verbose\n \tSay what is done\n\n$ backup help (cliflags)\n\n Backs up folders and restores them\n━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Usage │ backup COMMAND --FLAG(s)\n━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Commands │ backup create ─ Back up a folder\n │ backup restore ─ Restore a backup\n │\n──────────┼─────────────────────────────────────────────────────────────────\n Flags │ name │ type │ required │ default │ description\n │ ────────────────┼──────┼──────────┼─────────┼──────────────────\n │ --verbose or -v │ bool │ no │ │ Say what is done\n──────────┼─────────────────────────────────────────────────────────────────\n Examples │ backup create --source=photos -z\n │ backup -v restore --from=photos-1\n │\n\n\n$ backup create -h (flag)\nUsage of create:\n -compress\n \tCompress the backup\n -keep uint\n \tBackups to keep (default 7)\n -source string\n \tFolder to back up (required)\n -target string\n \tFolder that keeps the backups (default \"./backups\")\n\n$ backup create help (cliflags)\n\n Back up a folder\n━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Usage │ backup create --FLAG(s)\n━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Flags │ name │ type │ required │ default │ env │ description\n │ ─────────────────┼────────┼──────────┼───────────┼───────────────┼───────────────────────────────\n │ --source or -s │ string │ yes │ │ │ Folder to back up\n │ --target or -t │ string │ no │ ./backups │ BACKUP_TARGET │ Folder that keeps the backups\n │ --keep or -k │ uint │ no │ 7 │ │ Backups to keep\n │ --compress or -z │ bool │ no │ │ │ Compress the backup\n │ ─────────────────┼────────┼──────────┼───────────┼───────────────┼───────────────────────────────\n Globals │ --verbose or -v │ bool │ no │ │ │ Say what is done\n──────────┼───────────────────────────────────────────────────────────────────────────────────────────────────\n Examples │ backup create --source=photos -z\n │\n"}],"file":"www-examples/cli/commands/main.go"}}],"sample":{"code":"type CreateFlags struct {\n\tSource string `flagName:\"source\" flagShortName:\"s\" flagRequired:\"true\" flagDescription:\"Folder to back up\"`\n\tTarget string `flagName:\"target\" flagShortName:\"t\" flagDefault:\"./backups\" flagEnv:\"BACKUP_TARGET\" flagDescription:\"Folder that keeps the backups\"`\n\tKeep uint `flagName:\"keep\" flagShortName:\"k\" flagDefault:\"7\" flagDescription:\"Backups to keep\"`\n\tCompress bool `flagName:\"compress\" flagShortName:\"z\" flagDescription:\"Compress the backup\"`\n}\n// …\n&cliflags.Command{\n\tName: \"create\", Description: \"Back up a folder\", Flags: &CreateFlags{},\n\tExamples: []string{\"backup create --source=photos -z\"},\n\tHandler: func(inv *cliflags.Invocation) error {\n\t\tflags, globals := inv.Flags.(*CreateFlags), inv.Globals.(*Globals)\n\t\tfmt.Printf(\"create: source=%s target=%s keep=%d compress=%t verbose=%t\\n\", flags.Source, flags.Target, flags.Keep, flags.Compress, globals.Verbose)\n\t\treturn nil\n\t},\n},\n// …\nfunc with() {\n\tif err := cliflags.Execute(newCLI); err != nil { // runs the command, or writes its help\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}","example":"cli/commands","title":"Commands and flags from Go structs","file":"www-examples/cli/commands/main.go","output":"$ backup create --source photos --keep 3 (flag)\ncreate: source=photos target=./backups keep=3 compress=false verbose=false\n\n$ backup create --source photos --keep 3 (cliflags)\ncreate: source=photos target=./backups keep=3 compress=false verbose=false\n\n$ backup create --source photos -v (flag)\nflag provided but not defined: -v\nUsage of create:\n -compress\n \tCompress the backup\n -keep uint\n \tBackups to keep (default 7)\n -source string\n \tFolder to back up (required)\n -target string\n \tFolder that keeps the backups (default \"./backups\")\n[exit status 2]\n\n$ backup create --source photos -v (cliflags)\ncreate: source=photos target=./backups keep=7 compress=false verbose=true\n\n$ backup restore --into /srv (flag)\nrestore: -from is required\n[exit status 2]\n\n$ backup restore --into /srv (cliflags)\nmissing required flag 'from'\n[exit status 1]\n\n$ BACKUP_TARGET=/mnt/usb backup create --source photos (flag)\ncreate: source=photos target=./backups keep=7 compress=false verbose=false\n\n$ BACKUP_TARGET=/mnt/usb backup create --source photos (cliflags)\ncreate: source=photos target=/mnt/usb keep=7 compress=false verbose=false\n","system":"linux","runs":2},"measured":[],"competition":[{"title":"The leading Go libraries for command lines","source":"competition.md","heading":"5. CLI","blocks":[{"t":"table","header":["Library","★","Release","Highlights"],"rows":[["spf13/cobra + pflag","44.7k","v1.10.2, 2025-12","subcommands, persistent flags, flag groups, argument validators, suggestions, completion for bash, zsh, fish and PowerShell, man and Markdown docs"],["urfave/cli v3","24.3k","v3.14.0, 2026-10","aliases and prefix matching, completion for 4 shells, docs, env and file sources; stdlib-only core"],["alecthomas/kong","3.2k","2026-08","struct-tag grammar, env tags, config loaders, hooks, validation; completion only through a third party"],["peterbourgon/ff v4","1.4k","beta since 2025-08","flags, env and config files"],["charmbracelet/fang v2","2.0k","2026-03","styled cobra: help, errors, version, man pages; 13 dependencies"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"subcommands, POSIX flags;","items":[]},{"text":"generated, grouped help;","items":[]},{"text":"positional arguments and durations;","items":[]},{"text":"completion for 4 shells, dynamic values included;","items":[]},{"text":"env and config layering with defined precedence;","items":[]},{"text":"man and Markdown pages;","items":[]},{"text":"validation;","items":[]},{"text":"`--version` from build info;","items":[]},{"text":"no global state;","items":[]},{"text":"NO_COLOR.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"showing where each value came from;","items":[]},{"text":"completion and man pages in struct-tag libraries.","items":[]}]},{"t":"p","text":"goframework's cliflags has bash and zsh completion. It lacks fish, PowerShell, man pages and provenance. It leaves out positional arguments, durations and `--help` by decision."}]}]},{"id":"codecs","title":"Codecs","pattern":"codecs*","purpose":"Compression and encodings Go's standard library does not have.","replaces":["ulikunitz/xz's lzma package, for raw LZMA and LZMA2","google.golang.org/protobuf and protoc's generated code, for messages written and read by hand","nwaples/rardecode's decompression","golang.org/x/text's charmap, for CP437"],"modules":["codecs-compress-lzma","codecs-compress-ppmd","codecs-compress-rar","codecs-compress-bcj","codecs-compress-delta","codecs-compress-lznt1","codecs-data-protobuf","codecs-text-charsets"],"blocks":[{"type":"stages","title":"A file of a 7z archive, as it is read","intro":"7z keeps a file as a chain of coders. Reading undoes them in turn, each a plain io.Reader over the one before: the codecs, and the cryptography modules for the key and the cipher. From vfs-source-archive-7z's readme.","stages":[{"label":"ARCHIVE","text":"The headers, plain, compressed or encrypted, name the file's block: its packed streams and its coders, in order. A solid block is decoded in order, so reading in the archive's order decodes it once.","modules":["vfs-source-archive-7z"]},{"label":"KEY","text":"For an encrypted block, the key from the password: SHA-256 over the salt, the password in UTF-16 and a counter, 2^19 times by default; derived once.","modules":["cryptography-kdf-7z"]},{"label":"DECRYPT","text":"AES-256 in CBC mode over the stream, whole blocks as they come.","modules":["cryptography-cipher-aes-cbc"]},{"label":"DECOMPRESS","text":"LZMA, LZMA2 or PPMd variant H; deflate and bzip2 from the standard library.","modules":["codecs-compress-lzma","codecs-compress-ppmd"]},{"label":"UNFILTER","text":"Machine code's call and jump targets made relative again (x86's BCJ and BCJ2, ARM, ARM Thumb, ARM64, PowerPC, SPARC); Delta for sounds and pictures.","modules":["codecs-compress-bcj","codecs-compress-delta"]},{"label":"FILE","text":"A file read through from its start is checked against its CRC; a wrong password is ErrAuth.","modules":["vfs-source-archive-7z","vfs"]}]},{"type":"example","id":"codecs/lzma","example":{"id":"codecs/lzma","title":"Decompress LZMA, as xz and 7-Zip wrote it","problem":"Read a .lzma file, or the LZMA2 stream inside a .7z, in Go: the standard library reads gzip, zlib, bzip2 and LZW, not LZMA. Stream it, and know when it is damaged.","notice":"Both streams decode to the input, byte for byte: xz's .lzma, which ends with a marker, and 7-Zip's raw LZMA2, whose size 7z keeps. A stream cut short is io.ErrUnexpectedEOF; .lzma keeps no checksum, yet each of the hundred changes here broke the stream and was found.","parts":[{"name":"with","label":"codecs-compress-lzma","code":"file, err := os.Open(filepath.Join(testdata, \"alone.lzma\"))\ncheck(err)\ndefer file.Close()\nreader, err := lzma.NewReader(file) // 13 bytes of header, then the stream\ncheck(err)\nhash := sha256.New()\ndecoded, err := io.Copy(hash, reader) // an io.Reader: streamed, never held whole\ncheck(err)\n\t// …\nstream, err := os.Open(filepath.Join(testdata, \"7z-LZMA2-0.bin\"))\ncheck(err)\ndefer stream.Close()\nreader, err = lzma.NewReader2(stream, dictionary, int64(len(original))) // 7z gives the size\ncheck(err)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./codecs/lzma","exit":0,"name":"linux","output":"the input: 333401 bytes, SHA-256 50f1acceb99da246…\n\nalone.lzma, by xz --format=lzma: lc 3, lp 0, pb 2, a dictionary of 8 MiB, size unknown (an end marker ends the stream)\n decoded 333401 bytes, the input's: true\n\n7z-LZMA2-0.bin, by 7-Zip: raw LZMA2, property 8, a dictionary of 64 KiB\n decoded 333401 bytes, the input's: true\n\nalone.lzma, damaged:\n cut in half: 231206 bytes, then io.ErrUnexpectedEOF\n 100 bytes changed, one at a time: 1 io.ErrUnexpectedEOF, 99 lzma.ErrData, 0 decoded wrong without an error, 0 decoded right\n"}],"file":"www-examples/codecs/lzma/main.go"}},{"type":"example","id":"codecs/protobuf","example":{"id":"codecs/protobuf","title":"Write and read protobuf without generated code","problem":"Send a protobuf message, or read one whose .proto you don't have, without protoc, generated code or google.golang.org/protobuf: field by field, as protoc --decode_raw reads one.","notice":"The four messages written are the encoding guide's bytes. The message read is a real OTLP request, as metrics-exporters-otlp pushes it, caught on 127.0.0.1: field 1 is resource_metrics, holding the resource's service.name and the metric orders_placed, a sum (7) whose data point carries its label, two times in nanoseconds and the value 3 as a double.","parts":[{"name":"write","label":"writing, field by field","code":"test1 := protobuf.AppendUint(nil, 1, 150) // message Test1 { int32 a = 1; }, a = 150\ntest2 := protobuf.AppendString(nil, 2, \"testing\") // message Test2 { string b = 2; }\ntest3 := protobuf.AppendMessage(nil, 3, func(b []byte) []byte { // message Test3 { Test1 c = 3; }\n\treturn protobuf.AppendUint(b, 1, 150) // the length goes before it once known\n})\ntest4 := protobuf.AppendPackedVarints(nil, 4, []uint64{3, 270, 86942}) // repeated int32 e = 4, packed"},{"name":"read","label":"reading, without the .proto","code":"// dump prints a message's fields, as protoc --decode_raw does: a\n// length-delimited field that is text is a string, one that parses is a\n// message, printed within braces; others are bytes.\nfunc dump(message []byte, indent string) {\n\tfor r := protobuf.NewReader(message); r.Next(); {\n\t\tswitch r.Type() {\n\t\tcase protobuf.Varint:\n\t\t\tfmt.Printf(\"%s%d: %d\\n\", indent, r.Field(), r.Uint())\n\t\tcase protobuf.Fixed64:\n\t\t\tvalue := r.Fixed64()\n\t\t\tfmt.Printf(\"%s%d: %d%s\\n\", indent, r.Field(), value, double(value))\n\t\tcase protobuf.Fixed32:\n\t\t\tfmt.Printf(\"%s%d: 0x%08x\\n\", indent, r.Field(), r.Fixed32())\n\t\tcase protobuf.Bytes:\n\t\t\tvalue := r.Bytes()\n\t\t\tswitch {\n\t\t\tcase text(value):\n\t\t\t\tfmt.Printf(\"%s%d: %q\\n\", indent, r.Field(), value)\n\t\t\tcase parses(value):\n\t\t\t\tfmt.Printf(\"%s%d {\\n\", indent, r.Field())\n\t\t\t\tdump(value, indent+\" \")\n\t\t\t\tfmt.Printf(\"%s}\\n\", indent)\n\t\t\tdefault:\n\t\t\t\tfmt.Printf(\"%s%d: bytes %x\\n\", indent, r.Field(), value)\n\t\t\t}\n\t\tdefault:\n\t\t\tr.Skip() // groups, long deprecated\n\t\t}\n\t}\n}\n\n// parses tells whether data reads to its end as a message.\nfunc parses(data []byte) bool {\n\tr := protobuf.NewReader(data)\n\tfor r.Next() {\n\t\tr.Skip()\n\t}\n\treturn r.Err() == nil\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./codecs/protobuf","exit":0,"name":"linux","output":"Written, beside the encoding guide's bytes:\n Test1 089601 guide 089601 same: true\n Test2 120774657374696e67 guide 120774657374696e67 same: true\n Test3 1a03089601 guide 1a03089601 same: true\n Test4 2206038e029ea705 guide 2206038e029ea705 same: true\n\nRead without its .proto: an OTLP push, 141 bytes of application/x-protobuf\n 1 {\n 1 {\n 1 {\n 1: \"service.name\"\n 2 {\n 1: \"shop\"\n }\n }\n }\n 2 {\n 1 {\n 1: \"goframework.io/metrics\"\n }\n 2 {\n 1: \"orders_placed\"\n 2: \"Orders placed.\"\n 7 {\n 1 {\n 7 {\n 1: \"region\"\n 2 {\n 1: \"eu\"\n }\n }\n 2: 1792226943670434100\n 3: 1792226943670444392\n 4: 4613937818241073152, as a double 3\n }\n 2: 2\n 3: 1\n }\n }\n }\n }\n"}],"file":"www-examples/codecs/protobuf/main.go"}},{"type":"facts","items":[{"value":"20,000","label":"random messages written by codecs-data-protobuf and by Google's protowire: the same bytes; on fuzzed input both agree whether it parses","source":"codecs-data-protobuf/readme"},{"value":"2²⁹−1","label":"the largest field number taken, the specification's limit, inside groups too; Google's low-level protowire takes up to 2³¹−1","source":"codecs-data-protobuf/readme"},{"value":"34","label":"times a PPMd model of 1 MiB runs out and starts again while decoding what 7-Zip encoded: its memory is carved as the encoder carves it","source":"vfs.md"}]}],"sample":{"code":"file, err := os.Open(filepath.Join(testdata, \"alone.lzma\"))\ncheck(err)\ndefer file.Close()\nreader, err := lzma.NewReader(file) // 13 bytes of header, then the stream\ncheck(err)\nhash := sha256.New()\ndecoded, err := io.Copy(hash, reader) // an io.Reader: streamed, never held whole\ncheck(err)","example":"codecs/lzma","title":"Decompress LZMA, as xz and 7-Zip wrote it","file":"www-examples/codecs/lzma/main.go","output":"the input: 333401 bytes, SHA-256 50f1acceb99da246…\n\nalone.lzma, by xz --format=lzma: lc 3, lp 0, pb 2, a dictionary of 8 MiB, size unknown (an end marker ends the stream)\n decoded 333401 bytes, the input's: true\n\n7z-LZMA2-0.bin, by 7-Zip: raw LZMA2, property 8, a dictionary of 64 KiB\n decoded 333401 bytes, the input's: true\n\nalone.lzma, damaged:\n cut in half: 231206 bytes, then io.ErrUnexpectedEOF\n 100 bytes changed, one at a time: 1 io.ErrUnexpectedEOF, 99 lzma.ErrData, 0 decoded wrong without an error, 0 decoded right\n","system":"linux","runs":1},"measured":[],"competition":[]},{"id":"config","title":"Configuration","pattern":"config*","purpose":"Load settings from files and the environment into a Go struct.","replaces":["encoding/json and os.Getenv, written setting by setting","spf13/viper and knadh/koanf, for files and the environment into a struct","a YAML, TOML, HCL, INI, .env or properties library per format"],"modules":["config-spec","config-json","config-jsonc","config-yaml","config-toml","config-ini","config-dotenv","config-properties","config-hcl","config-xml","config-all","config-cli","config-source-file"],"blocks":[{"type":"stages","title":"Layers, into one struct","intro":"Load takes the struct holding its defaults and the layers in order; later layers win, key by key. Every format parses into one tree whose keys and values know their source, line and column, and one engine does the rest, the same for all.","stages":[{"label":"DEFAULTS","text":"The values in the struct handed to Load. A setting no layer sets keeps its default; a load starts from them, never from the value in use, and on any problem leaves the struct as it was.","modules":["config-spec"]},{"label":"FILES","text":"One file, a conf.d folder of them in name order, a user's file under ~. A missing file is skipped unless Required; read through vfs, from the disk or from any vfs FS, at most 16 MiB each.","modules":["config-source-file"]},{"label":"FORMATS","text":"Each document is parsed by its format's module, picked by its extension in config-all. Typed formats (JSON, TOML, YAML, HCL) bind strictly; text formats carry text, converted to each field's type. No YAML tags make types, no HCL functions run, no XML entities expand.","modules":["config-all","config-json","config-jsonc","config-yaml","config-toml","config-hcl","config-ini","config-dotenv","config-properties","config-xml"]},{"label":"ENVIRONMENT","text":"Variables PREFIX_KEY, __ nesting: ACME_DATABASE__HOST is database.host. Every PREFIX_ variable must be a setting, as every key of a file must. PREFIX_KEY_FILE reads a secret from the file it names, as Docker and Kubernetes mount them.","modules":["config-spec","config-source-file"]},{"label":"VALUES","text":"Settings by path, such as the flags the program parsed: config reads no command line, so the flag parser still refuses every flag it does not know.","modules":["config-spec","cliflags"]},{"label":"STRUCT","text":"Binding: unknown keys and wrong types are all reported at once, each as source:line:column; once everything bound, required settings and Validate, at any depth. The load returns Origins: Of(path) and Explain() say where each value came from, secrets masked.","modules":["config-spec"]}]},{"type":"example","id":"config/settings","example":{"id":"config/settings","title":"Settings from a file and the environment, into one struct","problem":"acme-api reads its settings from settings.yaml, with defaults in code, and lets environment variables override them: ACME_PORT, and the database password from the file ACME_DATABASE__PASSWORD_FILE names, as Docker and Kubernetes mount secrets. A broken file must be reported, and once it works, an operator asks where a value came from.","notice":"In the broken run, encoding/json reports the first mistake, with no line, and skips the misspelt databse without a word; the environment is never reached. config-yaml reports all four mistakes at once, each with its file, line and column, the variable's too. In the fixed run, each value names where it came from, and the password is masked. The standard library reads no YAML, so encoding/json reads the same settings from settings.json.","parts":[{"name":"usual","label":"encoding/json and os.Getenv","code":"func usual() (Settings, error) {\n\tsettings := defaults()\n\tdata, err := os.ReadFile(\"settings.json\")\n\tif err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\treturn settings, err\n\t}\n\tif err == nil {\n\t\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\t\treturn settings, err\n\t\t}\n\t}\n\tif value := os.Getenv(\"ACME_PORT\"); value != \"\" {\n\t\tport, err := strconv.Atoi(value)\n\t\tif err != nil {\n\t\t\treturn settings, fmt.Errorf(\"ACME_PORT: %w\", err)\n\t\t}\n\t\tsettings.Port = port\n\t}\n\tif value := os.Getenv(\"ACME_DEBUG\"); value != \"\" {\n\t\tdebug, err := strconv.ParseBool(value)\n\t\tif err != nil {\n\t\t\treturn settings, fmt.Errorf(\"ACME_DEBUG: %w\", err)\n\t\t}\n\t\tsettings.Debug = debug\n\t}\n\tif path := os.Getenv(\"ACME_DATABASE__PASSWORD_FILE\"); path != \"\" {\n\t\tpassword, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn settings, err\n\t\t}\n\t\tsettings.Database.Password = strings.TrimSuffix(string(password), \"\\n\")\n\t}\n\treturn settings, nil\n}"},{"name":"with","label":"config-yaml","code":"settings := defaults()\norigins, err := config.Load(&settings,\n\tsource.Open(\"settings.yaml\"),\n\tsource.Environment(\"ACME\"), // ACME_PORT, ACME_DATABASE__PASSWORD_FILE, ...\n)\nif err != nil {\n\tfmt.Println(err) // every problem, each with its file, line and column\n\treturn\n}\nfor _, setting := range origins.Explain() {\n\tfmt.Println(setting) // the value, and where it came from\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/settings broken","exit":0,"name":"broken","output":"settings.yaml:\n 1 # acme-api's settings\n 2 port: eighty\n 3 workers: 4.5\n 4 databse:\n 5 host: 127.0.0.1\n 6 log: info\nenvironment:\n ACME_DEBUG=maybe\n\n### encoding/json and os.Getenv, reading the same settings from settings.json\njson: cannot unmarshal string into Go struct field Settings.port of type int\n\n### config-yaml\nsettings.yaml:2:7: port is an integer, not the string \"eighty\"\nsettings.yaml:3:10: workers is an integer, not the number 4.5\nsettings.yaml:4:1: databse is not a setting of Settings\n$ACME_DEBUG: debug is a boolean (true, false, yes, no, on, off, 1, 0), not \"maybe\"\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/settings fixed","exit":0,"name":"fixed","output":"settings.yaml:\n 1 # acme-api's settings\n 2 port: 8000\n 3 workers: 4\n 4 database:\n 5 host: 127.0.0.1\n 6 log: info\nenvironment:\n ACME_PORT=9090\n ACME_DEBUG=true\n ACME_DATABASE__PASSWORD_FILE=password\n\n### encoding/json and os.Getenv, reading the same settings from settings.json\n{Port:9090 Workers:4 Debug:true Log:info Database:{Host:127.0.0.1 Password:s3cret}}\n\n### config-yaml\nPort = 9090 ($ACME_PORT)\nWorkers = 4 (settings.yaml:3:10)\nDebug = true ($ACME_DEBUG)\nLog = \"info\" (settings.yaml:6:6)\nDatabase.Host = \"127.0.0.1\" (settings.yaml:5:9)\nDatabase.Password = **** ($ACME_DATABASE__PASSWORD_FILE)\n"}],"file":"www-examples/config/settings/main.go"}},{"type":"example","id":"config/layers","example":{"id":"config/layers","title":"Every layer of a configuration, and where each value came from","problem":"acme-api's package installs a TOML file; each machine adds files to a conf.d folder, in whatever format its operators like; a user may have a file of their own; containers set environment variables; and a flag on the command line wins over all of them. The program needs one struct, and an operator needs to know which layer set each value.","notice":"Later layers win key by key: the conf.d folder's YAML file changes the database's pool and leaves its host, its JSON file changes the log level. The user's file is missing, so it is skipped. In the flags run, --port wins over the file; in the other, the file's port stands. config reads no command line: the program hands over the flags that were set, here parsed with the standard library's flag package.","parts":[{"name":"with","label":"config-all and config-source-file","code":"settings := Settings{Port: 8080, Workers: 1, Log: \"warning\"} // defaults, in code\n// Later layers win, key by key.\norigins, err := config.Load(&settings,\n\tsource.Open(\"etc/acme/config.toml\"), // the package's\n\tsource.Folder(\"etc/acme/conf.d\"), // this machine's: each file, by name\n\tsource.Open(\"~/.config/acme/config.yaml\"), // the user's: missing, so skipped\n\tsource.Environment(\"ACME\"), // ACME_WORKERS\n\tconfig.Values(\"the command line\", flags), // the flags that were set\n)\nif err != nil {\n\tfmt.Println(err)\n\tos.Exit(1)\n}\nfor _, setting := range origins.Explain() {\n\tfmt.Println(setting)\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/layers","exit":0,"name":"files","output":"etc/acme/config.toml:\n # acme-api's settings, as the package installs them\n port = 8000\n log = \"info\"\n\n [database]\n host = \"127.0.0.1\"\n pool = 10\netc/acme/conf.d/10-database.yaml:\n # a larger pool for this machine\n database:\n pool: 40\netc/acme/conf.d/20-logging.json:\n {\n \"log\": \"debug\"\n }\nenvironment: ACME_WORKERS=8\ncommand line: no flags\n\nport = 8000 (etc/acme/config.toml:2:8)\nworkers = 8 ($ACME_WORKERS)\nlog = \"debug\" (etc/acme/conf.d/20-logging.json:2:10)\ndebug = false (default)\ndatabase.host = \"127.0.0.1\" (etc/acme/config.toml:6:8)\ndatabase.pool = 40 (etc/acme/conf.d/10-database.yaml:3:9)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/layers --debug --port 9000","exit":0,"name":"flags","output":"etc/acme/config.toml:\n # acme-api's settings, as the package installs them\n port = 8000\n log = \"info\"\n\n [database]\n host = \"127.0.0.1\"\n pool = 10\netc/acme/conf.d/10-database.yaml:\n # a larger pool for this machine\n database:\n pool: 40\netc/acme/conf.d/20-logging.json:\n {\n \"log\": \"debug\"\n }\nenvironment: ACME_WORKERS=8\ncommand line: --debug --port 9000\n\nport = 9000 (the command line)\nworkers = 8 ($ACME_WORKERS)\nlog = \"debug\" (etc/acme/conf.d/20-logging.json:2:10)\ndebug = true (the command line)\ndatabase.host = \"127.0.0.1\" (etc/acme/config.toml:6:8)\ndatabase.pool = 40 (etc/acme/conf.d/10-database.yaml:3:9)\n"}],"file":"www-examples/config/layers/main.go"}},{"type":"example","id":"config/formats","example":{"id":"config/formats","title":"The same settings in nine formats","problem":"A team keeps acme-api's settings in TOML, another wants YAML, an old deployment has an INI file and the containers set .env files. The program should take whichever it is given, into the same struct.","notice":"Each run writes the same struct in one format and reads the file back: the same values, each with the line and column it was read from in that format. A duration is written 1m30s and a size 512MiB in every format. The formats without types (INI, .env, properties, XML) hold text, converted to each field's type when read; a list is comma-separated text in INI, .env and properties, and a repeated element in XML.","parts":[{"name":"settings","label":"the settings","code":"// Settings are acme-api's settings: one struct, whatever the format.\ntype Settings struct {\n\tName string `config:\"name\"`\n\tListen string `config:\"listen\"`\n\tTimeout time.Duration `config:\"timeout\"` // written \"1m30s\"\n\tMaxUpload config.Size `config:\"max_upload\"` // written \"512MiB\"\n\tDebug bool `config:\"debug\"`\n\tLanguages []string `config:\"languages\"`\n\tDatabase struct {\n\t\tHost string `config:\"host\"`\n\t\tPort int `config:\"port\"`\n\t\tPool int `config:\"pool\"`\n\t} `config:\"database\"`\n}"},{"name":"with","label":"config-all","code":"data, err := config.EncodeAs(name, settings) // name is settings.yaml, settings.toml...: its extension picks the format\nif err != nil {\n\tfmt.Println(err)\n\tos.Exit(1)\n}\nos.WriteFile(name, data, 0o600)\n\nvar back Settings\norigins, err := config.Load(&back, source.Open(name).Required()) // read in its extension's format\nif err != nil {\n\tfmt.Println(err)\n\tos.Exit(1)\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats yaml","exit":0,"name":"yaml","output":"settings.yaml, as EncodeAs writes it:\n\nname: acme-api\nlisten: 127.0.0.1:8080\ntimeout: 1m30s\nmax_upload: 512MiB\ndebug: false\nlanguages:\n - en\n - fr\ndatabase:\n host: 127.0.0.1\n port: 5432\n pool: 10\n\nread back by Load as YAML: the same settings: true\nname = \"acme-api\" (settings.yaml:1:7)\nlisten = \"127.0.0.1:8080\" (settings.yaml:2:9)\ntimeout = 1m30s (settings.yaml:3:10)\nmax_upload = 512MiB (settings.yaml:4:13)\ndebug = false (settings.yaml:5:8)\nlanguages = [\"en\", \"fr\"] (settings.yaml:7:3)\ndatabase.host = \"127.0.0.1\" (settings.yaml:10:9)\ndatabase.port = 5432 (settings.yaml:11:9)\ndatabase.pool = 10 (settings.yaml:12:9)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats toml","exit":0,"name":"toml","output":"settings.toml, as EncodeAs writes it:\n\nname = \"acme-api\"\nlisten = \"127.0.0.1:8080\"\ntimeout = \"1m30s\"\nmax_upload = \"512MiB\"\ndebug = false\nlanguages = [\"en\", \"fr\"]\n\n[database]\nhost = \"127.0.0.1\"\nport = 5432\npool = 10\n\nread back by Load as TOML: the same settings: true\nname = \"acme-api\" (settings.toml:1:8)\nlisten = \"127.0.0.1:8080\" (settings.toml:2:10)\ntimeout = 1m30s (settings.toml:3:11)\nmax_upload = 512MiB (settings.toml:4:14)\ndebug = false (settings.toml:5:9)\nlanguages = [\"en\", \"fr\"] (settings.toml:6:13)\ndatabase.host = \"127.0.0.1\" (settings.toml:9:8)\ndatabase.port = 5432 (settings.toml:10:8)\ndatabase.pool = 10 (settings.toml:11:8)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats json","exit":0,"name":"json","output":"settings.json, as EncodeAs writes it:\n\n{\n \"name\": \"acme-api\",\n \"listen\": \"127.0.0.1:8080\",\n \"timeout\": \"1m30s\",\n \"max_upload\": \"512MiB\",\n \"debug\": false,\n \"languages\": [\n \"en\",\n \"fr\"\n ],\n \"database\": {\n \"host\": \"127.0.0.1\",\n \"port\": 5432,\n \"pool\": 10\n }\n}\n\nread back by Load as JSON: the same settings: true\nname = \"acme-api\" (settings.json:2:11)\nlisten = \"127.0.0.1:8080\" (settings.json:3:13)\ntimeout = 1m30s (settings.json:4:14)\nmax_upload = 512MiB (settings.json:5:17)\ndebug = false (settings.json:6:12)\nlanguages = [\"en\", \"fr\"] (settings.json:7:16)\ndatabase.host = \"127.0.0.1\" (settings.json:12:13)\ndatabase.port = 5432 (settings.json:13:13)\ndatabase.pool = 10 (settings.json:14:13)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats jsonc","exit":0,"name":"jsonc","output":"settings.jsonc, as EncodeAs writes it:\n\n{\n \"name\": \"acme-api\",\n \"listen\": \"127.0.0.1:8080\",\n \"timeout\": \"1m30s\",\n \"max_upload\": \"512MiB\",\n \"debug\": false,\n \"languages\": [\n \"en\",\n \"fr\"\n ],\n \"database\": {\n \"host\": \"127.0.0.1\",\n \"port\": 5432,\n \"pool\": 10\n }\n}\n\nread back by Load as JSONC: the same settings: true\nname = \"acme-api\" (settings.jsonc:2:11)\nlisten = \"127.0.0.1:8080\" (settings.jsonc:3:13)\ntimeout = 1m30s (settings.jsonc:4:14)\nmax_upload = 512MiB (settings.jsonc:5:17)\ndebug = false (settings.jsonc:6:12)\nlanguages = [\"en\", \"fr\"] (settings.jsonc:7:16)\ndatabase.host = \"127.0.0.1\" (settings.jsonc:12:13)\ndatabase.port = 5432 (settings.jsonc:13:13)\ndatabase.pool = 10 (settings.jsonc:14:13)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats hcl","exit":0,"name":"hcl","output":"settings.hcl, as EncodeAs writes it:\n\nname = \"acme-api\"\nlisten = \"127.0.0.1:8080\"\ntimeout = \"1m30s\"\nmax_upload = \"512MiB\"\ndebug = false\nlanguages = [\"en\", \"fr\"]\ndatabase {\n host = \"127.0.0.1\"\n port = 5432\n pool = 10\n}\n\nread back by Load as HCL: the same settings: true\nname = \"acme-api\" (settings.hcl:1:8)\nlisten = \"127.0.0.1:8080\" (settings.hcl:2:10)\ntimeout = 1m30s (settings.hcl:3:11)\nmax_upload = 512MiB (settings.hcl:4:14)\ndebug = false (settings.hcl:5:9)\nlanguages = [\"en\", \"fr\"] (settings.hcl:6:13)\ndatabase.host = \"127.0.0.1\" (settings.hcl:8:10)\ndatabase.port = 5432 (settings.hcl:9:10)\ndatabase.pool = 10 (settings.hcl:10:10)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats ini","exit":0,"name":"ini","output":"settings.ini, as EncodeAs writes it:\n\nname = acme-api\nlisten = 127.0.0.1:8080\ntimeout = 1m30s\nmax_upload = 512MiB\ndebug = false\nlanguages = en, fr\n\n[database]\nhost = 127.0.0.1\nport = 5432\npool = 10\n\nread back by Load as INI: the same settings: true\nname = \"acme-api\" (settings.ini:1:8)\nlisten = \"127.0.0.1:8080\" (settings.ini:2:10)\ntimeout = 1m30s (settings.ini:3:11)\nmax_upload = 512MiB (settings.ini:4:14)\ndebug = false (settings.ini:5:9)\nlanguages = [\"en\", \"fr\"] (settings.ini:6:13)\ndatabase.host = \"127.0.0.1\" (settings.ini:9:8)\ndatabase.port = 5432 (settings.ini:10:8)\ndatabase.pool = 10 (settings.ini:11:8)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats env","exit":0,"name":"env","output":"settings.env, as EncodeAs writes it:\n\nNAME=acme-api\nLISTEN=127.0.0.1:8080\nTIMEOUT=1m30s\nMAX_UPLOAD=512MiB\nDEBUG=false\nLANGUAGES='en, fr'\nDATABASE__HOST=127.0.0.1\nDATABASE__PORT=5432\nDATABASE__POOL=10\n\nread back by Load as .env: the same settings: true\nname = \"acme-api\" (settings.env:1:6)\nlisten = \"127.0.0.1:8080\" (settings.env:2:8)\ntimeout = 1m30s (settings.env:3:9)\nmax_upload = 512MiB (settings.env:4:12)\ndebug = false (settings.env:5:7)\nlanguages = [\"en\", \"fr\"] (settings.env:6:11)\ndatabase.host = \"127.0.0.1\" (settings.env:7:16)\ndatabase.port = 5432 (settings.env:8:16)\ndatabase.pool = 10 (settings.env:9:16)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats properties","exit":0,"name":"properties","output":"settings.properties, as EncodeAs writes it:\n\nname = acme-api\nlisten = 127.0.0.1:8080\ntimeout = 1m30s\nmax_upload = 512MiB\ndebug = false\nlanguages = en, fr\ndatabase.host = 127.0.0.1\ndatabase.port = 5432\ndatabase.pool = 10\n\nread back by Load as Java properties: the same settings: true\nname = \"acme-api\" (settings.properties:1:8)\nlisten = \"127.0.0.1:8080\" (settings.properties:2:10)\ntimeout = 1m30s (settings.properties:3:11)\nmax_upload = 512MiB (settings.properties:4:14)\ndebug = false (settings.properties:5:9)\nlanguages = [\"en\", \"fr\"] (settings.properties:6:13)\ndatabase.host = \"127.0.0.1\" (settings.properties:7:17)\ndatabase.port = 5432 (settings.properties:8:17)\ndatabase.pool = 10 (settings.properties:9:17)\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./config/formats xml","exit":0,"name":"xml","output":"settings.xml, as EncodeAs writes it:\n\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<config>\n <name>acme-api</name>\n <listen>127.0.0.1:8080</listen>\n <timeout>1m30s</timeout>\n <max_upload>512MiB</max_upload>\n <debug>false</debug>\n <languages>en</languages>\n <languages>fr</languages>\n <database>\n <host>127.0.0.1</host>\n <port>5432</port>\n <pool>10</pool>\n </database>\n</config>\n\nread back by Load as XML: the same settings: true\nname = \"acme-api\" (settings.xml:3:9)\nlisten = \"127.0.0.1:8080\" (settings.xml:4:11)\ntimeout = 1m30s (settings.xml:5:12)\nmax_upload = 512MiB (settings.xml:6:15)\ndebug = false (settings.xml:7:10)\nlanguages = [\"en\", \"fr\"] (settings.xml:8:3)\ndatabase.host = \"127.0.0.1\" (settings.xml:11:11)\ndatabase.port = 5432 (settings.xml:12:11)\ndatabase.pool = 10 (settings.xml:13:11)\n"}],"file":"www-examples/config/formats/main.go"}},{"type":"table","title":"Nine formats, one API","source":"config-all/readme","table":"Extension","caption":"From config-all's readme: the extension picks the format. Every format module has the same Load, Decode, Encode and sources under the package name config, so a program reading one format moves to another by changing its import.","header":["Extension","Format","Module"],"rows":[[".json","JSON","config-json"],[".jsonc","JSON with comments","config-jsonc"],[".ini, .cfg","INI","config-ini"],[".env",".env","config-dotenv"],[".properties","Java properties","config-properties"],[".toml","TOML 1.0","config-toml"],[".yaml, .yml","YAML 1.2, a safe subset","config-yaml"],[".hcl","HCL 2, literals only","config-hcl"],[".xml","XML, elements as keys","config-xml"]],"heading":"config-all"},{"type":"table","title":"What each format reads","source":"config.md","table":"Format","caption":"From config.md. Encoding writes each format's plain form: comments and the original layout are not kept.","header":["Format","Scope"],"rows":[["JSON","RFC 8259; duplicate keys are errors; numbers kept exact until bound"],["JSONC","JSON plus comments and trailing commas"],["INI","[section] and [section.sub], key = value or key: value, ; and # comments, quoted values, keys before the first section global; repeated keys are errors; no interpolation"],[".env","KEY=value, export KEY=value, quotes and escapes, comments; keys nest with __; no interpolation"],["properties","Java's: =, : or space, line continuations, \\uXXXX escapes, ! and # comments, UTF-8; dotted keys nest"],["TOML","1.0 in full, checked against the official toml-test suite"],["YAML","1.2 core schema: block and flow mappings and sequences, plain, quoted, literal and folded scalars, comments, one document, anchors, aliases and << merges within the expansion limit, the core tags; any other tag or feature is an error naming its line. Checked against the yaml-test-suite cases of that subset"],["HCL","HCL 2 native syntax: attributes, blocks and their labels (labels key maps), literals (strings, numbers, booleans, lists, objects); expressions, functions and ${} interpolation are errors"],["XML","elements are keys, their text the value, attributes keys too, repeated elements a list; a DOCTYPE or entity is an error"]],"heading":"formats"},{"type":"facts","items":[{"value":"402","label":"cases of the official YAML test suite, all passing: invalid documents fail, valid ones parse to their JSON, the 72 outside the subset are refused with their message","source":"config-yaml/readme"},{"value":"208 + 501","label":"valid and invalid TOML 1.0.0 cases of the official toml-test suite, all passing","source":"config-toml/readme"},{"value":"0.32 s","label":"to load a 4.4 MB JSON document of 100 000 entries","source":"config.md"},{"value":"16 MiB","label":"the largest source; trees at most 64 deep and 1 million nodes, YAML aliases at most 10 000 nodes, each checked before the memory is spent","source":"config.md"}]}],"sample":{"code":"settings := defaults()\norigins, err := config.Load(&settings,\n\tsource.Open(\"settings.yaml\"),\n\tsource.Environment(\"ACME\"), // ACME_PORT, ACME_DATABASE__PASSWORD_FILE, ...\n)\nif err != nil {\n\tfmt.Println(err) // every problem, each with its file, line and column\n\treturn\n}\nfor _, setting := range origins.Explain() {\n\tfmt.Println(setting) // the value, and where it came from\n}","example":"config/settings","title":"Settings from a file and the environment, into one struct","file":"www-examples/config/settings/main.go","output":"settings.yaml:\n 1 # acme-api's settings\n 2 port: eighty\n 3 workers: 4.5\n 4 databse:\n 5 host: 127.0.0.1\n 6 log: info\nenvironment:\n ACME_DEBUG=maybe\n\n### encoding/json and os.Getenv, reading the same settings from settings.json\njson: cannot unmarshal string into Go struct field Settings.port of type int\n\n### config-yaml\nsettings.yaml:2:7: port is an integer, not the string \"eighty\"\nsettings.yaml:3:10: workers is an integer, not the number 4.5\nsettings.yaml:4:1: databse is not a setting of Settings\n$ACME_DEBUG: debug is a boolean (true, false, yes, no, on, off, 1, 0), not \"maybe\"\n","system":"linux","runs":2},"measured":[],"competition":[{"title":"The leading Go libraries for configuration","source":"competition.md","heading":"6. Configuration","blocks":[{"t":"table","header":["Library","★","Release","Highlights"],"rows":[["spf13/viper","30.5k","v1.21.0, 2025-09","many formats, env, flags, remote stores, watching; lowercases keys; stalled releases"],["knadh/koanf v2","4.2k","v2.3.8, 2026-10","providers and parsers as separate modules; watching; 4 dependencies in core"],["caarlos0/env v11","6.3k","2026-05","env into structs, no dependencies, `required`, `file` values"],["sethvargo/go-envconfig v2","1.2k","2026-09","prefixes, defaults, decoders, mockable lookups"],["cue, pkl-go","—","2026","schemas and validation; pkl runs an external program"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"layered sources with explicit precedence;","items":[]},{"text":"struct binding with defaults, required fields and validation;","items":[]},{"text":"env prefixes, `_FILE` secrets;","items":[]},{"text":"hot reload with an atomic swap;","items":[]},{"text":"detection of unknown keys;","items":[]},{"text":"typed access.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"reporting where values came from;","items":[]},{"text":"YAML or TOML without dependencies, since only JSON is in the standard library.","items":[]}]},{"t":"p","text":"goframework's `config` reads one JSON file."}]}]},{"id":"crashproof","title":"Crash protection","pattern":"crashproof","purpose":"Keep a panic in any goroutine from crashing the program.","replaces":["a deferred recover written in every goroutine"],"modules":["crashproof"],"blocks":[{"type":"example","id":"crashes/goroutine","example":{"id":"crashes/goroutine","title":"A panic in a goroutine","problem":"Work runs in goroutines, and one of them hits a bug. The panic must be reported with its stack, and the program must go on with the rest.","notice":"With the go statement, the panic in order 2 ends the program: order 3 is never shipped and \"all orders handled\" is never printed. crashproof.Go ends only that goroutine; the handler gets the value and the stack, starting at the line that panicked.","parts":[{"name":"usual","label":"the go statement","code":"var wg sync.WaitGroup\nfor _, o := range orders {\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tship(o)\n\t}()\n}\nwg.Wait()\nfmt.Println(\"all orders handled\")"},{"name":"with","label":"crashproof","code":"crashproof.OnPanic(func(p crashproof.Panic) { // once, in main; usually it logs\n\tfmt.Println(\"panic:\", p.Value)\n\tfor _, frame := range p.Frames {\n\t\tfmt.Printf(\"\\t%s.%s (%s:%d)\\n\", frame.Package, frame.Func, filepath.Base(frame.File), frame.Line)\n\t}\n})\n\nvar wg sync.WaitGroup\nfor _, o := range orders {\n\twg.Add(1)\n\tcrashproof.Go(func() {\n\t\tdefer wg.Done()\n\t\tship(o)\n\t})\n}\nwg.Wait()\nfmt.Println(\"all orders handled\")"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./crashes/goroutine","exit":0,"name":"linux","output":"### the go statement\norder 1 shipped, first item: book\nthe program crashed: exit status 2\nstderr began with: panic: runtime error: index out of range [0] with length 0\n\n### crashproof.Go\norder 1 shipped, first item: book\npanic: runtime error: index out of range [0] with length 0\n\tmain.ship (main.go:33)\n\tmain.with.func2 (main.go:86)\norder 3 shipped, first item: lamp\nall orders handled\n"}],"file":"www-examples/crashes/goroutine/main.go"}}],"sample":{"code":"crashproof.OnPanic(func(p crashproof.Panic) { // once, in main; usually it logs\n\tfmt.Println(\"panic:\", p.Value)\n\tfor _, frame := range p.Frames {\n\t\tfmt.Printf(\"\\t%s.%s (%s:%d)\\n\", frame.Package, frame.Func, filepath.Base(frame.File), frame.Line)\n\t}\n})\n\nvar wg sync.WaitGroup\nfor _, o := range orders {\n\twg.Add(1)\n\tcrashproof.Go(func() {\n\t\tdefer wg.Done()\n\t\tship(o)\n\t})\n}\nwg.Wait()\nfmt.Println(\"all orders handled\")","example":"crashes/goroutine","title":"A panic in a goroutine","file":"www-examples/crashes/goroutine/main.go","output":"### the go statement\norder 1 shipped, first item: book\nthe program crashed: exit status 2\nstderr began with: panic: runtime error: index out of range [0] with length 0\n\n### crashproof.Go\norder 1 shipped, first item: book\npanic: runtime error: index out of range [0] with length 0\n\tmain.ship (main.go:33)\n\tmain.with.func2 (main.go:86)\norder 3 shipped, first item: lamp\nall orders handled\n","system":"linux","runs":1},"measured":[],"competition":[{"title":"The leading Go libraries for debugging and crashes","source":"competition.md","heading":"10. Debugging and crashes","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["maruel/panicparse v2","3.7k","2025-03","parses and deduplicates goroutine dumps"],["uber-go/goleak","5.3k","v1.3.0, **2023**","goroutine leak checks in tests"],["google/gops","7.0k","2026-01","agent with stacks, memory, GC, pprof, trace; depends on cobra and gopsutil v3"],["sasha-s/go-deadlock","1.2k","2026-03","drop-in mutexes that detect order inversions and long waits"],["cockroachdb/errors","2.5k","2026-06","stacks, network encoding, PII-safe details, Sentry; 13 dependencies"],["samber/oops","1.0k","2026-09","structured errors"],["pkg/errors, go-spew","—","2020, 2018","frozen"],["getsentry/sentry-go","1.1k","2026-10","crash and error reporting"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"panics captured in every goroutine and reported;","items":[]},{"text":"crash output sent to a monitor (`debug.SetCrashOutput`);","items":[]},{"text":"deduplicated dumps;","items":[]},{"text":"leak and deadlock detection;","items":[]},{"text":"a live diagnostics endpoint;","items":[]},{"text":"errors with stacks and attributes, compatible with `Is`, `As` and `AsType`;","items":[]},{"text":"flight-recorder snapshots.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"one pipeline from panic to crash monitor to restart;","items":[]},{"text":"stale core tools;","items":[]},{"text":"heavy dependencies.","items":[]}]}]}]},{"id":"cryptography","title":"Cryptography","pattern":"cryptography*","purpose":"The algorithms file formats and network protocols need, beyond Go's standard library.","replaces":["golang.org/x/crypto/md4, which Go marks deprecated","CMAC, CCM and 7-Zip's and RAR's key derivations copied into each project that speaks them"],"modules":["cryptography-aead-aes-ccm","cryptography-mac-aes-cmac","cryptography-cipher-aes-cbc","cryptography-kdf-sp800-108","cryptography-kdf-7z","cryptography-kdf-rar","cryptography-cipher-zipcrypto","cryptography-hash-md4"],"blocks":[{"type":"stages","title":"An SMB 3 session, from the password to each message","intro":"How vfs-source-remote-smb logs in and protects what it sends: four of these modules, beside the standard library's HMAC, SHA-512 and AES-GCM.","stages":[{"label":"PASSWORD","text":"NTLM's key: MD4 of the password in UTF-16 (NTOWFv1), then HMAC-MD5 of the user and the domain under it (NTOWFv2). MD4 is broken; NTLM needs it.","modules":["cryptography-hash-md4"]},{"label":"LOGIN","text":"NTLMv2 in SPNEGO answers the server's challenge, and the session's key comes out of it. In 3.1.1 the handshake is hashed (SHA-512) into the keys, so a changed handshake fails, and the server's signature on the login is checked.","modules":["vfs-source-remote-smb"]},{"label":"KEYS","text":"SP 800-108 in counter mode with HMAC-SHA256 derives a key for each purpose from the session's: signing, and encryption in each direction, each named by its label (\"SMBSigningKey\", \"SMBC2SCipherKey\").","modules":["cryptography-kdf-sp800-108"]},{"label":"SIGN","text":"Every request is signed, unless it is encrypted: AES-CMAC from SMB 3.0 on; HMAC-SHA256, the standard library's, in 2.1.","modules":["cryptography-mac-aes-cmac"]},{"label":"ENCRYPT","text":"Where the share or the caller wants it: AES-CCM in 3.0 and 3.0.2; AES-GCM, the standard library's, or AES-CCM in 3.1.1, as negotiated.","modules":["cryptography-aead-aes-ccm"]}]},{"type":"example","id":"cryptography/aes-cmac","example":{"id":"cryptography/aes-cmac","title":"AES-CMAC, checked against RFC 4493","problem":"Sign messages with AES-CMAC, as SMB 3 does, in Go: the standard library has HMAC, not CMAC. Check it against the standard's examples before trusting it.","notice":"The four codes are RFC 4493's, byte for byte. A MAC hides nothing: the message stays readable, and the code proves it came from a holder of the key, unchanged. One digit changed, the code no longer matches.","parts":[{"name":"with","label":"cryptography-mac-aes-cmac","code":"got, err := cmac.Sum(key, message[:example.length]) // key: 16, 24 or 32 bytes\n\t// …\ncode, err := cmac.Sum(key, signed) // sent with the message\ncheck(err)\n\t// …\ncomputed, err := cmac.Sum(key, arrived) // by the receiver, who holds the key too\ncheck(err)\nvalid := hmac.Equal(code[:], computed[:]) // compared in constant time"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./cryptography/aes-cmac","exit":0,"name":"linux","output":"RFC 4493's examples, key 2b7e151628aed2a6abf7158809cf4f3c\n Example 1, 0 bytes: RFC bb1d6929e95937287fa37d129b756746\n got bb1d6929e95937287fa37d129b756746 same: true\n Example 2, 16 bytes: RFC 070a16b46b4d4144f79bdd9dd04a287c\n got 070a16b46b4d4144f79bdd9dd04a287c same: true\n Example 3, 40 bytes: RFC dfa66747de9ae63030ca32611497c827\n got dfa66747de9ae63030ca32611497c827 same: true\n Example 4, 64 bytes: RFC 51f0bebf7e3b9d92fc49741779363cfe\n got 51f0bebf7e3b9d92fc49741779363cfe same: true\n\nSigned, then verified:\n \"transfer 100 to account 7\" with the code sent: valid true\n \"transfer 900 to account 7\" with the code sent: valid false\n"}],"file":"www-examples/cryptography/aes-cmac/main.go"}},{"type":"example","id":"cryptography/ntlm","example":{"id":"cryptography/ntlm","title":"NTLM's password key: MD4, checked against RFC 1320 and MS-NLMP","problem":"Log in to a Windows or Samba share with NTLMv2 from Go: its key starts with an MD4 of the password, and the standard library has no MD4. Check MD4 against its RFC, then the key against Microsoft's worked example.","notice":"MD4 matches RFC 1320's seven test messages, and the key MS-NLMP's example. MD4 is broken: two messages of one MD4 are easy to make. It is here because NTLM needs it, not for fingerprints: take SHA-256 from the standard library for those.","parts":[{"name":"with","label":"cryptography-hash-md4","code":"ntowfv1 := md4.Sum(utf16le(\"Password\")) // NTOWFv1: MD4 of the password in UTF-16LE\nmac := hmac.New(md5.New, ntowfv1[:]) // NTOWFv2: HMAC-MD5 under it\nmac.Write(utf16le(strings.ToUpper(\"User\") + \"Domain\")) // of the user, upper-cased, and the domain\nkey := mac.Sum(nil) // the key every NTLMv2 response is made with"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./cryptography/ntlm","exit":0,"name":"linux","output":"MD4, RFC 1320's test suite:\n \"\" 31d6cfe0d16ae931b73c59d7e0c089c0 same: true\n \"a\" bde52cb31de33e46245e05fbdbd6fb24 same: true\n \"abc\" a448017aaf21d8525fc10ae87aa6729d same: true\n \"message digest\" d9130a8164549fe818874806e1c7014b same: true\n \"abcdefghijklmnopqrstuvwxyz\" d79e1c308aa5bbcdeea8ed63df412da9 same: true\n \"ABCDEFGHIJKLMNOPQRST…\" 043f8582f241db351ce627e153e7f0e4 same: true\n \"12345678901234567890…\" e33b4ddc9c38f2199c3e7b164fcc0536 same: true\n\nNTLMv2's key, MS-NLMP 4.2.4's example:\n NTOWFv1 a4f49c406510bdcab6824ee7c30fd852\n NTOWFv2 0c868a403bfd7a93a3001ef22ef02e3f\n MS-NLMP 0c868a403bfd7a93a3001ef22ef02e3f same: true\n"}],"file":"www-examples/cryptography/ntlm/main.go"}},{"type":"example","id":"cryptography/zipcrypto","example":{"id":"cryptography/zipcrypto","title":"Read an old zip's ZipCrypto, and see why it is weak","problem":"Old zips, and new ones made with Info-ZIP's zip -P, are encrypted with ZipCrypto, PKWARE's traditional cipher. Decrypt a file Info-ZIP's zip encrypted, in Go: archive/zip reads no encryption.","notice":"The file decrypts to the original. The password is checked by one byte of the header, so about 1 in 256 wrong passwords pass it (33 of 10000 here) and only the file's CRC-32, after decrypting it all, tells them apart. ZipCrypto detects no change to the data, and its readme says a few known bytes of the data break it in minutes. Read old zips with it; encrypt new ones with AES.","parts":[{"name":"with","label":"cryptography-cipher-zipcrypto","code":"data := bytes.Clone(entry)\nzipcrypto.New([]byte(\"secret\")).Decrypt(data) // in place\nheader, file := data[:12], data[12:] // the header's last byte checks the password"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./cryptography/zipcrypto","exit":0,"name":"linux","output":"entry.bin: 4404 bytes, a 12-byte header and the file\n the header's check byte: 26, the archive's: 26\n the file: 4392 bytes, 500 lines, \"line 1\" to \"line 500\", the original's: true\n\n10000 wrong passwords: 33 pass the check byte, 0 decrypt to the file (its CRC-32 tells)\n"}],"file":"www-examples/cryptography/zipcrypto/main.go"}},{"type":"text","title":"What each was checked against","body":"MD4 against RFC 1320's test suite; AES-CMAC against RFC 4493's examples; AES-CCM against NIST SP 800-38C's examples; SP 800-108 against OpenSSL 3's KBKDF; AES-CBC against OpenSSL; 7-Zip's key against a file 7-Zip encrypted; RAR's keys against archives RAR 6.24 encrypted; ZipCrypto against what Info-ZIP's zip encrypted. Through vfs-source-remote-smb, NTLMv2 against MS-NLMP's worked example (4.2.4), and the whole against Samba 4.22 and Windows Server 2025. From each module's readme, and vfs.md."},{"type":"facts","items":[{"value":"2^19","label":"SHA-256 rounds in 7-Zip's key from a password, by default: slow on purpose, so guessing passwords is slow too","source":"cryptography-kdf-7z/readme"},{"value":"2^18","label":"SHA-1 rounds in RAR 3's key from a password, for RAR 4 archives","source":"cryptography-kdf-rar/readme"},{"value":"28","label":"characters: past them, RAR 3's SHA-1 writes into its own input as it hashes; this keeps the quirk, so archives with long passwords open","source":"cryptography-kdf-rar/readme"}]}],"sample":{"code":"code, err := cmac.Sum(key, signed) // sent with the message\ncheck(err)\n// …\ncomputed, err := cmac.Sum(key, arrived) // by the receiver, who holds the key too\ncheck(err)\nvalid := hmac.Equal(code[:], computed[:]) // compared in constant time","example":"cryptography/aes-cmac","title":"AES-CMAC, checked against RFC 4493","file":"www-examples/cryptography/aes-cmac/main.go","output":"RFC 4493's examples, key 2b7e151628aed2a6abf7158809cf4f3c\n Example 1, 0 bytes: RFC bb1d6929e95937287fa37d129b756746\n got bb1d6929e95937287fa37d129b756746 same: true\n Example 2, 16 bytes: RFC 070a16b46b4d4144f79bdd9dd04a287c\n got 070a16b46b4d4144f79bdd9dd04a287c same: true\n Example 3, 40 bytes: RFC dfa66747de9ae63030ca32611497c827\n got dfa66747de9ae63030ca32611497c827 same: true\n Example 4, 64 bytes: RFC 51f0bebf7e3b9d92fc49741779363cfe\n got 51f0bebf7e3b9d92fc49741779363cfe same: true\n\nSigned, then verified:\n \"transfer 100 to account 7\" with the code sent: valid true\n \"transfer 900 to account 7\" with the code sent: valid false\n","system":"linux","runs":1},"measured":[],"competition":[]},{"id":"dsa-collections","title":"Data structures","pattern":"dsa-collections*","purpose":"Sets, sorted maps, queues and a concurrent map, generic.","replaces":["sync.Map","puzpuzpuz/xsync","google/btree and tidwall/btree","gammazero/deque","container/heap with its interface"],"modules":["dsa-collections-set","dsa-collections-map-sorted","dsa-collections-map-concurrent","dsa-collections-queue-priority","dsa-collections-queue-ring","dsa-collections-queue-concurrent"],"blocks":[{"type":"example","id":"collections/typed","example":{"id":"collections/typed","title":"Count, range and keep the last few","problem":"Count hits from many goroutines; answer which items cost between 100 and 250; keep a log's last three lines.","notice":"sync.Map stores any: every read needs a type assertion, and an update in place is a load-and-swap loop. concurrent.Map is typed, and Compute updates under the key's bucket lock. The sorted map answers ranges and floors; the ring drops the oldest line when full.","parts":[{"name":"usual","label":"sync.Map","code":"var hits sync.Map // keys and values are any\nvar wait sync.WaitGroup\nfor range 8 {\n\twait.Go(func() {\n\t\tfor range 1000 {\n\t\t\tfor { // no update in place: load, then swap if nobody changed it meanwhile\n\t\t\t\told, _ := hits.LoadOrStore(\"/items\", 0)\n\t\t\t\tif hits.CompareAndSwap(\"/items\", old, old.(int)+1) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\nwait.Wait()\ntotal, _ := hits.Load(\"/items\")\nfmt.Println(\"sync.Map, 8 goroutines × 1000:\", total.(int)) // a type assertion at every read"},{"name":"with","label":"dsa-collections","code":"var counts concurrent.Map[string, int] // typed; the zero value is ready\nvar group sync.WaitGroup\nfor range 8 {\n\tgroup.Go(func() {\n\t\tfor range 1000 {\n\t\t\tcounts.Compute(\"/items\", func(old int, _ bool) (int, concurrent.Action) { return old + 1, concurrent.Store })\n\t\t}\n\t})\n}\ngroup.Wait()\nn, _ := counts.Load(\"/items\")\nfmt.Println(\"concurrent.Map, 8 goroutines × 1000:\", n)\n\nprices := sorted.NewMap[int, string](cmp.Compare[int])\nfor price, name := range map[int]string{120: \"lamp\", 80: \"mug\", 300: \"chair\", 210: \"rug\", 45: \"pen\"} {\n\tprices.Set(price, name)\n}\nfmt.Print(\"priced 100 to 250:\")\nfor price, name := range prices.Range(100, 250) {\n\tfmt.Printf(\" %s %d,\", name, price)\n}\nunder, name, _ := prices.Floor(250)\nfmt.Printf(\" and the dearest at most 250: %s %d\\n\", name, under)\n\nlast := ring.New[string](3) // the last 3 lines; older ones fall off\nfor _, line := range []string{\"started\", \"listening\", \"request 1\", \"request 2\", \"request 3\"} {\n\tlast.PushOverwrite(line)\n}\nfmt.Print(\"the last 3 lines:\")\nfor line := range last.All() {\n\tfmt.Printf(\" %q\", line)\n}\nfmt.Println()"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run -trimpath ./collections/typed","exit":0,"name":"linux","output":"sync.Map, 8 goroutines × 1000: 8000\nconcurrent.Map, 8 goroutines × 1000: 8000\npriced 100 to 250: lamp 120, rug 210, and the dearest at most 250: rug 210\nthe last 3 lines: \"request 1\" \"request 2\" \"request 3\"\n"}],"file":"www-examples/collections/typed/main.go"}}],"sample":{"code":"var counts concurrent.Map[string, int] // typed; the zero value is ready\nvar group sync.WaitGroup\nfor range 8 {\n\tgroup.Go(func() {\n\t\tfor range 1000 {\n\t\t\tcounts.Compute(\"/items\", func(old int, _ bool) (int, concurrent.Action) { return old + 1, concurrent.Store })\n\t\t}\n\t})\n}\ngroup.Wait()\nn, _ := counts.Load(\"/items\")\nfmt.Println(\"concurrent.Map, 8 goroutines × 1000:\", n)","example":"collections/typed","title":"Count, range and keep the last few","file":"www-examples/collections/typed/main.go","output":"sync.Map, 8 goroutines × 1000: 8000\nconcurrent.Map, 8 goroutines × 1000: 8000\npriced 100 to 250: lamp 120, rug 210, and the dearest at most 250: rug 210\nthe last 3 lines: \"request 1\" \"request 2\" \"request 3\"\n","system":"linux","runs":1},"measured":["dsa-collections-map-sorted","dsa-collections-map-concurrent","dsa-collections-queue-ring","dsa-collections-queue-concurrent"],"competition":[{"title":"What Go has, and why these","source":"dsa.md","heading":"why now, and why like this","blocks":[{"t":"p","text":"Go 1.27 has `container/heap`, `container/list` and `container/ring`, none generic, and an untyped `sync.Map`. Go is designing generic collections (proposal 80590: set, hashed and sorted maps and sets, heap, insertion-ordered maps), first in `golang.org/x/exp/collection` for Go 1.28; the standard library \"not before 1.29, if ever\"; none of them concurrent. Outside, the containers are scattered across small projects of one maintainer each, often untyped or dormant (gods' last stable release is from 2022)."},{"t":"p","text":"So the sequential collections follow Go's proposed APIs closely, method names included: if Go ships its own, ours become thin or retire, and code moves over by changing an import. The lasting value is in what Go isn't designing: concurrent queues, and whatever measurements show it needs."}]},{"title":"The leading Go libraries for data structures","source":"competition.md","heading":"11. Data structures","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["emirpasic/gods","17.5k","v1.18.1, **2022**; v2 alpha 2024","the most-starred, still without generics in a stable release"],["samber/lo","21.4k","2026-03","about 250 generic helpers, with iterator and parallel variants"],["puzpuzpuz/xsync v4","1.7k","2026-04","no dependencies; concurrent map, counters, MPMC/SPSC queues"],["golang-set v3, gammazero/deque, tidwall/btree","—","2026","small single-purpose libraries"],["caches: golang-lru v2, otter v2, ristretto v2","—","2023–2026","—"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"generics and `iter.Seq` iterators;","items":[]},{"text":"comparators matching `cmp`;","items":[]},{"text":"no dependencies;","items":[]},{"text":"concurrent variants;","items":[]},{"text":"json/v2 marshalers.","items":[]}]},{"t":"p","text":"**Gap:** a coherent, generic, maintained set. The containers are scattered across small single-maintainer projects."}]}]},{"id":"events","title":"Events","pattern":"events","purpose":"Pass typed events between the parts of a program, in order, without one slow part stalling the rest.","replaces":["asaskevich/EventBus","kelindar/event","cskr/pubsub","a channel per subscriber"],"modules":["events"],"blocks":[{"type":"example","id":"events/slow-subscriber","example":{"id":"events/slow-subscriber","title":"One slow subscriber, one that panics","problem":"Orders go to a counter, to a mailer that takes a millisecond an order, and to an audit that panics on one bad order. The counter must not wait for the mailer, and the audit's panic must not end the program.","notice":"With channels the publisher moves at the mailer's pace. With events each subscription has its own bounded queue and rule: the mailer drops its oldest orders when 16 behind, the audit's panic becomes an error to OnError, and Stats counts every event. Timings are this run's own.","parts":[{"name":"usual","label":"a channel per subscriber","code":"counted, mailed := make(chan Order, 16), make(chan Order, 16)\nvar wait sync.WaitGroup\ncount, mails := 0, 0\nwait.Go(func() {\n\tfor range counted {\n\t\tcount++\n\t}\n})\nwait.Go(func() {\n\tfor range mailed {\n\t\ttime.Sleep(time.Millisecond) // the mailer is slow\n\t\tmails++\n\t}\n})\nstart := time.Now()\nfor id := range orders {\n\tcounted <- Order{id}\n\tmailed <- Order{id} // waits whenever the mailer is 16 behind\n}\ntook := time.Since(start)\nclose(counted)\nclose(mailed)\nwait.Wait()\n// a panic in either goroutine would have ended the program"},{"name":"with","label":"events","code":"crashproof.OnPanic(func(p crashproof.Panic) {}) // once, in main: a handler's panic is reported, not fatal\nbus := events.New()\nvar count atomic.Int64\nOrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\tcount.Add(1) // Sync, the default: runs inside Publish\n\treturn nil\n})\nmailer := OrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\ttime.Sleep(time.Millisecond) // the mailer is slow\n\treturn nil\n}, events.Async(16, events.DropOldest)) // its own queue; when full, the oldest order goes\naudit := OrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\tif o.ID == 7 {\n\t\tpanic(\"audit: order 7 has no customer\")\n\t}\n\treturn nil\n}, events.Async(256, events.Block), events.OnError(func(err error) {\n\tfmt.Println(\"audit failed, and goes on:\", err)\n}))\n\nstart := time.Now()\nfor id := range orders {\n\tif err := OrderPlaced.Publish(ctx, bus, Order{id}); err != nil {\n\t\tfmt.Println(\"publish:\", err)\n\t}\n}\ntook := time.Since(start)\nbus.Close(ctx) // handles what is queued, then closes every subscription"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run -trimpath ./events/slow-subscriber","exit":0,"name":"linux","output":"### a channel per subscriber\npublishing 200 orders took 190ms: the publisher kept the mailer's pace\ncounted 200, mailed 200\n\n### events\naudit failed, and goes on: events: a handler panicked: audit: order 7 has no customer\npublishing 200 orders took 30µs: nobody waited for the mailer\ncounted 200; mailed 16, dropped 184 (DropOldest); audited 199, panicked 1\n"}],"file":"www-examples/events/slow-subscriber/main.go"}},{"type":"flow","id":"events","title":"Slow and failing subscribers, recorded"}],"sample":{"code":"crashproof.OnPanic(func(p crashproof.Panic) {}) // once, in main: a handler's panic is reported, not fatal\nbus := events.New()\nvar count atomic.Int64\nOrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\tcount.Add(1) // Sync, the default: runs inside Publish\n\treturn nil\n})\nmailer := OrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\ttime.Sleep(time.Millisecond) // the mailer is slow\n\treturn nil\n}, events.Async(16, events.DropOldest)) // its own queue; when full, the oldest order goes\naudit := OrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\tif o.ID == 7 {\n\t\tpanic(\"audit: order 7 has no customer\")\n\t}\n\treturn nil\n}, events.Async(256, events.Block), events.OnError(func(err error) {\n\tfmt.Println(\"audit failed, and goes on:\", err)\n}))\n\nstart := time.Now()\nfor id := range orders {\n\tif err := OrderPlaced.Publish(ctx, bus, Order{id}); err != nil {\n\t\tfmt.Println(\"publish:\", err)\n\t}\n}\ntook := time.Since(start)","example":"events/slow-subscriber","title":"One slow subscriber, one that panics","file":"www-examples/events/slow-subscriber/main.go","output":"### a channel per subscriber\npublishing 200 orders took 190ms: the publisher kept the mailer's pace\ncounted 200, mailed 200\n\n### events\naudit failed, and goes on: events: a handler panicked: audit: order 7 has no customer\npublishing 200 orders took 30µs: nobody waited for the mailer\ncounted 200; mailed 16, dropped 184 (DropOldest); audited 199, panicked 1\n","system":"linux","runs":1},"measured":["events"],"competition":[{"title":"What goes wrong with event buses","source":"events.md","heading":"why","blocks":[{"t":"p","text":"What goes wrong with event buses, in Go and elsewhere:"},{"t":"list","ordered":false,"items":[{"text":"untyped payloads fail at run time (Guava's EventBus, whose own documents now recommend against using it; asaskevich/EventBus, unmaintained);","items":[]},{"text":"a goroutine per message loses order and piles up goroutines (watermill's in-memory bus has open issues on both);","items":[]},{"text":"one slow subscriber blocks everyone (cskr/pubsub), or memory grows without bound;","items":[]},{"text":"unsubscribing by comparing functions fails for closures, and unsubscribing from inside a handler deadlocks;","items":[]},{"text":"a panic in a handler ends the program;","items":[]},{"text":"fast numbers can hide latency: kelindar/event's 12–39 ns comes from waking subscribers every 500 µs.","items":[]}]}]},{"title":"The leading Go libraries for events and messaging","source":"competition.md","heading":"9. Events, messaging and pipelines","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["ThreeDotsLabs/watermill","9.9k","v1.5.3, 2026-08","publisher/subscriber interfaces, a router with retry, poison queue, throttling, breaker; CQRS; many backends; 12 dependencies"],["asaskevich/EventBus","2.0k","no tags; last push 2024","reflection-based, untyped; effectively unmaintained"],["kelindar/event","559","2025-05","generic, typed, in-process"],["nats.go / nats-server","6.8k / 20.9k","2026-10","JetStream, key-value; server embeddable"],["Redpanda Connect / Bento","8.8k / 2.2k","2026-10","pipelines; part of Connect is under an enterprise licence; Bento is the open fork"],["sourcegraph/conc","10.4k","2023","structured concurrency"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"**in-process:**","items":[{"text":"typed topics;","items":[]},{"text":"delivery that is synchronous or asynchronous, with explicit backpressure;","items":[]},{"text":"ordering, wildcards;","items":[]},{"text":"per-handler panic isolation;","items":[]},{"text":"context, metrics.","items":[]}]},{"text":"**distributed:**","items":[{"text":"acknowledgements;","items":[]},{"text":"retries, dead-letter queues;","items":[]},{"text":"the outbox pattern;","items":[]},{"text":"CloudEvents.","items":[]}]},{"text":"**pipelines:**","items":[{"text":"declarative config;","items":[]},{"text":"batching;","items":[]},{"text":"end-to-end acknowledgement;","items":[]},{"text":"a mapping language.","items":[]}]}]},{"t":"p","text":"**Gap:** a typed, dependency-free, maintained in-process bus with real backpressure."}]}]},{"id":"flow","title":"Structured concurrency","pattern":"flow","purpose":"Run work one after another or at once, with nothing left running behind.","replaces":["errgroup plus WaitGroup written by hand","sourcegraph/conc","alitto/pond"],"modules":["flow"],"blocks":[{"type":"example","id":"concurrency/fail-fast","example":{"id":"concurrency/fail-fast","title":"Three fetches at once: one fails, one panics","problem":"Fetch users, orders and prices at the same time. Orders fails; the prices code has a bug and panics. The caller should get every error and keep running.","notice":"errgroup lets a panic end the program on purpose: its source explains that recovering would delay panics and hide their stacks. flow recovers it into the block's error, with its stack, sends it to the one handler the program set, and returns every error: the program decides, once, whether a panic is survivable.","parts":[{"name":"usual","label":"errgroup","code":"group, ctx := errgroup.WithContext(context.Background())\ngroup.Go(func() error { return fetch(ctx, \"users\", 50*time.Millisecond, nil) })\ngroup.Go(func() error { return fetch(ctx, \"orders\", 100*time.Millisecond, errors.New(\"orders: 503\")) })\ngroup.Go(func() error { return prices(ctx) })\nerr := group.Wait()\nfmt.Println(\"Wait returned:\", err)"},{"name":"with","label":"flow","code":"crashproof.OnPanic(func(p crashproof.Panic) { // once, in main: report panics, do not crash\n\tfmt.Println(\" logged:\", p.Value)\n})\nerr := flow.Parallel(\n\tfunc(ctx context.Context) error { return fetch(ctx, \"users\", 50*time.Millisecond, nil) },\n\tfunc(ctx context.Context) error { return fetch(ctx, \"orders\", 100*time.Millisecond, errors.New(\"orders: 503\")) },\n\tprices,\n).Run(context.Background())\nfmt.Println(\"Run returned:\", err)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./concurrency/fail-fast","exit":0,"name":"linux","output":"### errgroup (golang.org/x/sync v0.24.0), run as its own process\n users: done after 50ms\n orders: done after 100ms\n Wait returned: orders: 503\n panic: assignment to entry in nil map\n (9 lines of goroutine stack follow)\n the process ended: exit status 2\n\n### flow\n users: done after 50ms\n orders: done after 100ms\n logged: assignment to entry in nil map\nRun returned: orders: 503\nflow: a block panicked: assignment to entry in nil map\nthe program goes on\n"}],"file":"www-examples/concurrency/fail-fast/main.go"}},{"type":"flow","id":"flow","title":"A plan whose parallel step fails, recorded"}],"sample":{"code":"crashproof.OnPanic(func(p crashproof.Panic) { // once, in main: report panics, do not crash\n\tfmt.Println(\" logged:\", p.Value)\n})\nerr := flow.Parallel(\n\tfunc(ctx context.Context) error { return fetch(ctx, \"users\", 50*time.Millisecond, nil) },\n\tfunc(ctx context.Context) error { return fetch(ctx, \"orders\", 100*time.Millisecond, errors.New(\"orders: 503\")) },\n\tprices,\n).Run(context.Background())\nfmt.Println(\"Run returned:\", err)","example":"concurrency/fail-fast","title":"Three fetches at once: one fails, one panics","file":"www-examples/concurrency/fail-fast/main.go","output":"### errgroup (golang.org/x/sync v0.24.0), run as its own process\n users: done after 50ms\n orders: done after 100ms\n Wait returned: orders: 503\n panic: assignment to entry in nil map\n (9 lines of goroutine stack follow)\n the process ended: exit status 2\n\n### flow\n users: done after 50ms\n orders: done after 100ms\n logged: assignment to entry in nil map\nRun returned: orders: 503\nflow: a block panicked: assignment to entry in nil map\nthe program goes on\n","system":"linux","runs":1},"measured":["flow"],"competition":[{"title":"What Go leaves to you","source":"flow.md","heading":"why","blocks":[{"t":"p","text":"Go makes starting work easy and finishing it hard:"},{"t":"list","ordered":false,"items":[{"text":"a goroutine that nobody waits for leaks, and Go never collects it;","items":[]},{"text":"a panic in any goroutine ends the whole program;","items":[]},{"text":"`errgroup` returns the first error only, and leaves panics alone on purpose (passing them to `Wait` was added in January 2025 and taken back in June 2025: it delays them, loses their stack and risks deadlocks);","items":[]},{"text":"results gathered from several goroutines need channels or locks, written anew each time;","items":[]},{"text":"a study of real Go programs found most blocking bugs in code passing messages over channels (Tu et al., ASPLOS 2019).","items":[]}]},{"t":"p","text":"Other languages settled on the same answer: Python's `asyncio.TaskGroup` and Trio's nurseries, Kotlin's `coroutineScope`, Swift's task groups, Java's `StructuredTaskScope` (still a preview in JDK 27), Rust's `JoinSet`: work started in a scope ends with it."}]},{"title":"What a bare go statement leaves to you","source":"flow/readme","heading":"why: what a bare go statement leaves to you","blocks":[{"t":"code","text":"var user User\nvar orders []Order\nvar userErr, ordersErr error\nvar wg sync.WaitGroup\nwg.Go(func() { user, userErr = loadUser(ctx) })\nwg.Go(func() { orders, ordersErr = loadOrders(ctx) })\nwg.Wait()"},{"t":"list","ordered":false,"items":[{"text":"a panic in either goroutine ends the program: `recover` works only in the goroutine that panicked;","items":[]},{"text":"when one load fails, the other runs on to its end: nothing tells it to stop;","items":[]},{"text":"a forgotten `Wait` still compiles: the goroutines outlive the function, a leak or a data race on `user` and `orders`;","items":[]},{"text":"each error is carried by hand, in a variable of its own.","items":[]}]},{"t":"p","text":"flow makes each of these the default: nothing outlives the call, the first error cancels the rest, a panic is an error, every error comes back. The idea is structured concurrency, as in Swift, Kotlin, Java 21 and Python's Trio: concurrent work lives inside a block of code, as a local variable lives inside its function."}]},{"title":"Compared with what Go has: the standard library by hand, and errgroup","source":"flow/readme","heading":"compared with what Go has","blocks":[{"t":"p","text":"The same server, each connection's work a plan. Calling `Run` in the loop waits for each connection before accepting the next: one at a time."},{"t":"code","text":"for {\n\tconn, err := listener.Accept()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = flow.Step(authenticate(conn)).Step(reply(conn)).Run(ctx) // waits: one connection at a time\n}"},{"t":"p","text":"`go` in front of `Run` runs them at once, and gives back what a bare `go` leaves to you: nobody waits for the handlers, each `Run`'s error is thrown away, there is no limit shared by all, no failure stops the others."},{"t":"code","text":"go flow.Step(authenticate(conn)).Step(reply(conn)).Run(ctx) // at once, but owned by no one"},{"t":"p","text":"With the standard library alone, it is assembled by hand: a WaitGroup, a channel as a semaphore, a cancellable context, errors under a mutex."},{"t":"code","text":"ctx, cancel := context.WithCancelCause(ctx)\ndefer cancel(nil)\nvar wg sync.WaitGroup\nlimit := make(chan struct{}, 100)\nvar mutex sync.Mutex\nvar errs []error\nfor {\n\tconn, err := listener.Accept()\n\tif err != nil {\n\t\tbreak\n\t}\n\tlimit <- struct{}{}\n\twg.Go(func() {\n\t\tdefer func() { <-limit }()\n\t\tif err := flow.Step(authenticate(conn)).Step(reply(conn)).Run(ctx); err != nil {\n\t\t\tmutex.Lock()\n\t\t\terrs = append(errs, err)\n\t\t\tmutex.Unlock()\n\t\t\tcancel(err)\n\t\t}\n\t})\n}\nwg.Wait()\nreturn errors.Join(errs...)"},{"t":"p","text":"It works; every piece is made by hand, and easy to get wrong: a limit never released, a cancel forgotten, `errs` read without the mutex. The usual answer is errgroup, from `golang.org/x/sync`, kept by the Go team outside the standard library:"},{"t":"code","text":"g, ctx := errgroup.WithContext(ctx)\ng.SetLimit(100)\nfor {\n\tconn, err := listener.Accept()\n\tif err != nil {\n\t\tbreak\n\t}\n\tg.Go(func() error { return flow.Step(authenticate(conn)).Step(reply(conn)).Run(ctx) })\n}\nreturn g.Wait()"},{"t":"p","text":"Here, with a plan inside, errgroup does nearly what a scope does: it waits for all, limits, cancels the rest at the first error, and the plan turns its steps' panics into errors. What a scope adds is concrete, and incremental:"},{"t":"table","header":["","errgroup","`flow.Scope`"],"rows":[["dependency","`golang.org/x/sync`, outside the standard library","none: goframework's modules use the standard library alone"],["errors returned","the first","all of them, joined, the first first"],["a panic in a block","ends the program, on purpose (its source says why)","that block's error, after crashproof's handler saw it"],["results","through shared variables","`s.Spawn`: a `Future` per block"],["timeout","built by hand with `context.WithTimeout`","`flow.Timeout(d)`, its cause kept"],["names in profiles","no","`flow.Name`, `flow.Named`"],["cost of a small block","263 ns","312 ns (below)"]]},{"t":"p","text":"`flow.Scope` is errgroup without the dependency, with panics as errors, every error, futures and timeouts. Where errgroup is already used and none of those is missed, it serves as well."}]}]},{"id":"logging","title":"Logging","pattern":"logging*","purpose":"Record what a program does, to the console, files or anywhere else.","replaces":["uber-go/zap","rs/zerolog","sirupsen/logrus","natefinch/lumberjack, for rotation by size","slog handlers written by hand for redaction, fan-out and queues"],"modules":["logging-spec","logging-styles-classic","logging-styles-context","logging-styles-withfields","logging-advanced","logging-dispatchers-async","logging-dispatchers-buffered","logging-dispatchers-multi","logging-dispatchers-groupsort","logging-filters-tolevel","logging-filters-onlylevel","logging-filters-redact","logging-filters-sampling","logging-encoders-text","logging-encoders-json","logging-persisters-console","logging-persisters-consolewcolors","logging-persisters-file","logging-persisters-rollingfile","logging-persisters-writer","logging-persisters-wineventlog","logging-config","logging-adapters-slog","logging-samples"],"blocks":[{"type":"stages","title":"The pipe","intro":"Each block is a module with one duty. spec.Pipe(stages...).To(output) links stages in the order records flow; a pipe can be a stage or a branch of another pipe. Filters and dispatchers are both stages: in the examples the level and the redaction come before the queue, so the queue never holds a secret. logging-spec holds the contracts and the levels; logging-config builds a whole pipe from data.","stages":[{"label":"STYLE","text":"Turns the calls your code makes into records: printf-style calls, typed fields with request attributes carried in a context, Logrus-style WithFields, call trees for concurrent work, or log/slog through the adapter. Disabled levels are skipped before any formatting or clock read.","modules":["logging-styles-classic","logging-styles-context","logging-styles-withfields","logging-advanced","logging-adapters-slog"]},{"label":"DISPATCHERS","text":"Decides where and when records go: to several branches at once, each with its own filters and output; through a bounded queue to one background goroutine that never drops a record; in batches, when full or on a timer; or grouped by operation and ordered by time.","modules":["logging-dispatchers-multi","logging-dispatchers-async","logging-dispatchers-buffered","logging-dispatchers-groupsort"]},{"label":"FILTERS","text":"Drops or changes records: a minimum level that can change while the program runs, only the levels listed, the first records of each kind per interval and then every Nth, and the values of named keys replaced with [REDACTED] at any depth.","modules":["logging-filters-tolevel","logging-filters-onlylevel","logging-filters-sampling","logging-filters-redact"]},{"label":"ENCODER","text":"Turns a record into bytes: one readable line, or one JSON object per line. Typed values, errors and groups encode without allocating, and a record is always one line.","modules":["logging-encoders-text","logging-encoders-json"]},{"label":"PERSISTER","text":"Stores the bytes: on the console, with a colour per level on a terminal; in a file; in a file rotated by size; in any io.Writer; in the Windows Event Log; or in a log/slog handler.","modules":["logging-persisters-console","logging-persisters-consolewcolors","logging-persisters-file","logging-persisters-rollingfile","logging-persisters-writer","logging-persisters-wineventlog","logging-adapters-slog"]}]},{"type":"example","id":"logging/pipe","example":{"id":"logging/pipe","title":"Levels, a hidden password, JSON to a file and text to the console","problem":"Warnings and errors go to the console as text; everything from info up goes to a file as JSON, for a log collector. A password may reach neither, and debug records are switched on while the program runs.","notice":"Both write the same records. slog redacts in a ReplaceAttr given to each handler; the pipe redacts once, before its queue and its two branches, so neither keeps the password. The pipe's outputs are written by a background goroutine (async); slog writes on the calling one. In the JSON, the pipe keeps attributes under attrs and writes levels in lowercase.","parts":[{"name":"usual","label":"log/slog","code":"file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)\nif err != nil {\n\treturn err\n}\ndefer file.Close()\nhide := func(groups []string, attr slog.Attr) slog.Attr {\n\tif attr.Key == \"password\" {\n\t\tattr.Value = slog.StringValue(\"[REDACTED]\")\n\t}\n\treturn attr\n}\nlevel := new(slog.LevelVar) // info\nlog := slog.New(slog.NewMultiHandler(\n\tslog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelWarn, ReplaceAttr: hide}),\n\tslog.NewJSONHandler(file, &slog.HandlerOptions{Level: level, ReplaceAttr: hide}),\n))\n\nlog.Debug(\"cache warmed\")\nlog.Info(\"user signed in\", \"user\", \"ann\", \"password\", \"hunter2\")\nlog.Warn(\"database slow\",\n\tslog.Group(\"db\", \"host\", \"127.0.0.1\", \"password\", \"s3cret\"),\n\t\"took\", 1500*time.Millisecond)\n\nlevel.Set(slog.LevelDebug) // for example from an admin endpoint\nlog.Debug(\"now debug is on\")"},{"name":"with","label":"the logging pipe","code":"output, err := file.Open(path, jsonl.New())\nif err != nil {\n\treturn err\n}\nlevel := tolevel.Min(spec.LevelInfo)\nroot := spec.Pipe(level, redact.Keys(\"password\"), async.New()).To(\n\tmulti.New(\n\t\tspec.Pipe(tolevel.Min(spec.LevelWarning)).To(console.NewWithOptions(console.Options{Writer: os.Stdout})),\n\t\toutput,\n\t),\n)\nlog := contextual.New(root)\ndefer log.Close() // delivers what is queued, then closes the file\n\nctx := context.Background()\nlog.Debug(ctx, \"cache warmed\")\nlog.Info(ctx, \"user signed in\", field.String(\"user\", \"ann\"), field.String(\"password\", \"hunter2\"))\nlog.Warning(ctx, \"database slow\",\n\tfield.Group(\"db\", field.String(\"host\", \"127.0.0.1\"), field.String(\"password\", \"s3cret\")),\n\tfield.Duration(\"took\", 1500*time.Millisecond))\n\nlevel.SetLevel(spec.LevelDebug) // for example from an admin endpoint\nlog.Debug(ctx, \"now debug is on\")"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./logging/pipe","exit":0,"name":"linux","output":"### log/slog: the console\ntime=2026-10-17T04:42:11.025-04:00 level=WARN msg=\"database slow\" db.host=127.0.0.1 db.password=[REDACTED] took=1.5s\n\nslog.log:\n{\"time\":\"2026-10-17T04:42:11.024929902-04:00\",\"level\":\"INFO\",\"msg\":\"user signed in\",\"user\":\"ann\",\"password\":\"[REDACTED]\"}\n{\"time\":\"2026-10-17T04:42:11.025054659-04:00\",\"level\":\"WARN\",\"msg\":\"database slow\",\"db\":{\"host\":\"127.0.0.1\",\"password\":\"[REDACTED]\"},\"took\":1500000000}\n{\"time\":\"2026-10-17T04:42:11.025070153-04:00\",\"level\":\"DEBUG\",\"msg\":\"now debug is on\"}\n\n### goframework: the console\n2026-10-17 04:42:11.025 | warning | database slow db.host=\"127.0.0.1\" db.password=\"[REDACTED]\" took=1.5s\n\napp.log:\n{\"time\":\"2026-10-17T08:42:11.025120031Z\",\"level\":\"info\",\"msg\":\"user signed in\",\"attrs\":{\"user\":\"ann\",\"password\":\"[REDACTED]\"}}\n{\"time\":\"2026-10-17T08:42:11.025122862Z\",\"level\":\"warning\",\"msg\":\"database slow\",\"attrs\":{\"db\":{\"host\":\"127.0.0.1\",\"password\":\"[REDACTED]\"},\"took\":1500000000}}\n{\"time\":\"2026-10-17T08:42:11.025124164Z\",\"level\":\"debug\",\"msg\":\"now debug is on\"}\n"}],"file":"www-examples/logging/pipe/main.go"}},{"type":"example","id":"logging/slog","example":{"id":"logging/slog","title":"log/slog calls into the pipe","problem":"Your code and the libraries you use log with log/slog. Send all of it through one pipe, with one level, one redaction and one output, and the request ID on every line.","notice":"The library's lines carry the request ID from the context, and its card number is hidden, as on the program's own lines. Its debug record is dropped by the pipe's level. slog's Warn arrives as warning, and its group as order.amount and order.currency.","parts":[{"name":"library","label":"a library's code, with log/slog","code":"// charge is a library's code: it logs with log/slog and knows nothing else.\nfunc charge(ctx context.Context, card string, amount float64) {\n\tlog := slog.With(\"component\", \"billing\")\n\tlog.DebugContext(ctx, \"card checked\", \"card\", card)\n\tlog.InfoContext(ctx, \"charging card\", \"card\", card, \"amount\", amount)\n\tif amount > 50 {\n\t\tlog.WarnContext(ctx, \"amount needs review\", slog.Group(\"order\", \"amount\", amount, \"currency\", \"EUR\"))\n\t}\n}"},{"name":"with","label":"logging-adapters-slog","code":"root := spec.Pipe(tolevel.Min(spec.LevelInfo), redact.Keys(\"card\")).To(\n\tconsole.NewWithOptions(console.Options{Writer: os.Stdout}),\n)\nlog := contextual.New(root)\ndefer log.Close()\n\n// slog calls go into the same pipe, with the attributes the context carries\nslog.SetDefault(slog.New(slogadapter.NewHandlerWithOptions(root, slogadapter.Options{ContextAttrs: contextual.Attrs})))\n\nctx := contextual.WithAttrs(context.Background(), field.String(\"request_id\", \"r-42\"))\nlog.Info(ctx, \"order placed\", field.Int(\"order\", 1001))\ncharge(ctx, \"4111 1111 1111 1111\", 59.90)\nlog.Success(ctx, \"order paid\", field.Int(\"order\", 1001))"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./logging/slog","exit":0,"name":"linux","output":"2026-10-17 04:42:11.060 | info | order placed request_id=\"r-42\" order=1001\n2026-10-17 04:42:11.060 | info | charging card component=\"billing\" request_id=\"r-42\" card=\"[REDACTED]\" amount=59.9\n2026-10-17 04:42:11.060 | warning | amount needs review component=\"billing\" request_id=\"r-42\" order.amount=59.9 order.currency=\"EUR\"\n2026-10-17 04:42:11.060 | success | order paid request_id=\"r-42\" order=1001\n"}],"file":"www-examples/logging/slog/main.go"}},{"type":"example","id":"logging/config","example":{"id":"logging/config","title":"The same pipe, from a configuration file","problem":"Where logs go and how much is logged changes without a new build: the pipe is described in logging.json and built in one call. A mistake in the file must stop the program before any file is created.","notice":"The pipe writes what the hand-built one writes, with tokens hidden too. The mistaken configuration names a file first and an unknown level second: it is refused whole, and audit.log is never created.","parts":[{"name":"with","label":"logging-config","code":"var config logconfig.Config\nif err := json.Unmarshal(configuration, &config); err != nil {\n\treturn err\n}\nroot, err := logconfig.New(config) // checks everything before opening a file\nif err != nil {\n\treturn err\n}\nlog := contextual.New(root)\ndefer log.Close()\n\nctx := context.Background()\nlog.Debug(ctx, \"cache warmed\")\nlog.Info(ctx, \"user signed in\", field.String(\"user\", \"ann\"), field.String(\"password\", \"hunter2\"))\nlog.Warning(ctx, \"token about to expire\", field.String(\"token\", \"eyJhbGciOi\"), field.Duration(\"left\", 90*time.Second))\n\nroot.SetLevel(logconfig.Debug) // for example on a signal\nlog.Debug(ctx, \"now debug is on\")"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./logging/config","exit":0,"name":"linux","output":"### the console\n2026-10-17 04:42:11.095 | warning | token about to expire token=\"[REDACTED]\" left=1m30s\n\n### app.log\n{\"time\":\"2026-10-17T08:42:11.09502318Z\",\"level\":\"info\",\"msg\":\"user signed in\",\"attrs\":{\"user\":\"ann\",\"password\":\"[REDACTED]\"}}\n{\"time\":\"2026-10-17T08:42:11.095025979Z\",\"level\":\"warning\",\"msg\":\"token about to expire\",\"attrs\":{\"token\":\"[REDACTED]\",\"left\":90000000000}}\n{\"time\":\"2026-10-17T08:42:11.095026947Z\",\"level\":\"debug\",\"msg\":\"now debug is on\"}\n\n### a mistaken configuration\n{\"outputs\": [{\"type\": \"file\", \"path\": \"audit.log\"}, {\"type\": \"stdout\", \"level\": \"verbose\"}]}\nlogging config: outputs[1]: level: unknown level \"verbose\"\naudit.log created: false\n"}],"file":"www-examples/logging/config/main.go"}},{"type":"table","title":"The same calls in each library","source":"logging-samples/readme","table":"Library","caption":"From logging-samples' readme: a plain message, and a message with an integer field.","header":["Library","Plain message","With an integer field"],"rows":[["this framework, classic","log.Info(\"started\")","log.With(field.Int(\"status\", 200)).Info(\"done\")"],["this framework, context","log.Info(ctx, \"started\")","log.Info(ctx, \"done\", field.Int(\"status\", 200))"],["this framework, fields","log.Info(\"started\")","log.WithFields(fields.Fields{\"status\": 200}).Info(\"done\")"],["log/slog","slog.Info(\"started\")","slog.Info(\"done\", \"status\", 200)"],["zap","log.Info(\"started\")","log.Info(\"done\", zap.Int(\"status\", 200))"],["zerolog","log.Info().Msg(\"started\")","log.Info().Int(\"status\", 200).Msg(\"done\")"],["Logrus","log.Info(\"started\")","log.WithField(\"status\", 200).Info(\"done\")"]],"heading":""}],"sample":{"code":"output, err := file.Open(path, jsonl.New())\nif err != nil {\n\treturn err\n}\nlevel := tolevel.Min(spec.LevelInfo)\nroot := spec.Pipe(level, redact.Keys(\"password\"), async.New()).To(\n\tmulti.New(\n\t\tspec.Pipe(tolevel.Min(spec.LevelWarning)).To(console.NewWithOptions(console.Options{Writer: os.Stdout})),\n\t\toutput,\n\t),\n)\nlog := contextual.New(root)\ndefer log.Close() // delivers what is queued, then closes the file\n// …\nlog.Info(ctx, \"user signed in\", field.String(\"user\", \"ann\"), field.String(\"password\", \"hunter2\"))","example":"logging/pipe","title":"Levels, a hidden password, JSON to a file and text to the console","file":"www-examples/logging/pipe/main.go","output":"### log/slog: the console\ntime=2026-10-17T04:42:11.025-04:00 level=WARN msg=\"database slow\" db.host=127.0.0.1 db.password=[REDACTED] took=1.5s\n\nslog.log:\n{\"time\":\"2026-10-17T04:42:11.024929902-04:00\",\"level\":\"INFO\",\"msg\":\"user signed in\",\"user\":\"ann\",\"password\":\"[REDACTED]\"}\n{\"time\":\"2026-10-17T04:42:11.025054659-04:00\",\"level\":\"WARN\",\"msg\":\"database slow\",\"db\":{\"host\":\"127.0.0.1\",\"password\":\"[REDACTED]\"},\"took\":1500000000}\n{\"time\":\"2026-10-17T04:42:11.025070153-04:00\",\"level\":\"DEBUG\",\"msg\":\"now debug is on\"}\n\n### goframework: the console\n2026-10-17 04:42:11.025 | warning | database slow db.host=\"127.0.0.1\" db.password=\"[REDACTED]\" took=1.5s\n\napp.log:\n{\"time\":\"2026-10-17T08:42:11.025120031Z\",\"level\":\"info\",\"msg\":\"user signed in\",\"attrs\":{\"user\":\"ann\",\"password\":\"[REDACTED]\"}}\n{\"time\":\"2026-10-17T08:42:11.025122862Z\",\"level\":\"warning\",\"msg\":\"database slow\",\"attrs\":{\"db\":{\"host\":\"127.0.0.1\",\"password\":\"[REDACTED]\"},\"took\":1500000000}}\n{\"time\":\"2026-10-17T08:42:11.025124164Z\",\"level\":\"debug\",\"msg\":\"now debug is on\"}\n","system":"linux","runs":1},"measured":["logging-spec"],"competition":[{"title":"The leading Go libraries for logging","source":"competition.md","heading":"4. Logging","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["log/slog","stdlib","Go 1.27","handlers, groups, `ReplaceAttr`, `MultiHandler`; no rotation, sampling, async or OS sinks"],["uber-go/zap","24.7k","v1.28.0, 2026-04","allocation-free fields, sampling, hooks, buffered writer"],["rs/zerolog","12.5k","v1.35.1, 2026-04","allocation-free chained API, samplers, hooks, journald writer, non-blocking writer"],["sirupsen/logrus","25.8k","v1.10.2","maintenance mode; points to slog"],["phuslu/log","876","2026-10","**no dependencies**; file rotation, async, syslog, journald, **Event Log** writers"],["charmbracelet/log v2","3.4k","2026-09","pretty console output"],["natefinch/lumberjack","5.5k","v2.2.1, **2023**","rotation by size, age and count with gzip; dormant"],["DeRuina/timberjack","167","2026-09","lumberjack with time-based rotation and zstd"],["OpenTelemetry Go","6.6k","v1.47.0","the logs signal is still a release candidate"]]},{"t":"p","text":"zap's own benchmark, 10 fields per call: zerolog 380 ns and 1 allocation, zap 656 ns and 5, slog 2479 ns and 40, logrus 11654 ns and 79."},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"slog interop in both directions;","items":[]},{"text":"few or no allocations;","items":[]},{"text":"per-component levels at runtime;","items":[]},{"text":"context and trace IDs;","items":[]},{"text":"sampling;","items":[]},{"text":"async with a stated drop or backpressure policy;","items":[]},{"text":"rotation by size and time, with compression and retention;","items":[]},{"text":"journald, syslog (RFC 5424 over TCP or TLS) and Event Log sinks;","items":[]},{"text":"NO_COLOR;","items":[]},{"text":"redaction.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"every sink in one dependency-free package (only phuslu/log comes close);","items":[]},{"text":"rotation, which is weak and assumes one writing process;","items":[]},{"text":"syslog and the Event Log, both rare;","items":[]},{"text":"stable OpenTelemetry logs.","items":[]}]},{"t":"p","text":"goframework lacks syslog and journald sinks, rotation by time, compression, and trace IDs."}]},{"title":"Calls compared with log/slog, zap, zerolog and Logrus","source":"logging-samples/readme","heading":"Calls compared","blocks":[{"t":"table","header":["Library","Plain message","With an integer field"],"rows":[["this framework, classic","`log.Info(\"started\")`","`log.With(field.Int(\"status\", 200)).Info(\"done\")`"],["this framework, context","`log.Info(ctx, \"started\")`","`log.Info(ctx, \"done\", field.Int(\"status\", 200))`"],["this framework, fields","`log.Info(\"started\")`","`log.WithFields(fields.Fields{\"status\": 200}).Info(\"done\")`"],["log/slog","`slog.Info(\"started\")`","`slog.Info(\"done\", \"status\", 200)`"],["zap","`log.Info(\"started\")`","`log.Info(\"done\", zap.Int(\"status\", 200))`"],["zerolog","`log.Info().Msg(\"started\")`","`log.Info().Int(\"status\", 200).Msg(\"done\")`"],["Logrus","`log.Info(\"started\")`","`log.WithField(\"status\", 200).Info(\"done\")`"]]}]}]},{"id":"metrics","title":"Metrics","pattern":"metrics*","purpose":"Count and time what a program does, for Prometheus, OpenTelemetry or StatsD.","replaces":["prometheus/client_golang","the OpenTelemetry Go SDK and its exporters","VictoriaMetrics/metrics"],"modules":["metrics-spec","metrics-collectors-runtime","metrics-exporters-prometheus","metrics-exporters-otlp","metrics-exporters-statsd","metrics-exporters-console","metrics-exporters-memory"],"blocks":[{"type":"example","id":"metrics/expose","example":{"id":"metrics/expose","title":"Count requests and time them, for Prometheus","problem":"An HTTP server counts its requests by route and status, times them, and serves the numbers on /metrics for Prometheus to scrape.","notice":"Counters get _total and units their suffix, as Prometheus names things. The latencies are real, so the buckets differ from run to run.","parts":[{"name":"with","label":"metrics-spec and metrics-exporters-prometheus","code":"registry := metrics.NewRegistry()\nrequests := registry.CounterVec(\"http_requests\", \"Requests handled.\", []string{\"route\", \"status\"})\nlatency := registry.Histogram(\"http_request_duration\", \"Time to handle a request.\", metrics.Seconds,\n\tmetrics.Bounds(0.005, 0.025, 0.1, 0.5))\n\nhttp.Handle(\"/metrics\", prometheus.Handler(registry))\nhttp.HandleFunc(\"/items\", func(w http.ResponseWriter, r *http.Request) {\n\tstart := time.Now()\n\tfmt.Fprintln(w, \"the items\")\n\trequests.With(\"/items\", \"200\").Inc() // one atomic add, no allocation\n\tlatency.Observe(time.Since(start).Seconds())\n})"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./metrics/expose","exit":0,"name":"linux","output":"$ curl /metrics\n# HELP http_request_duration_seconds Time to handle a request.\n# TYPE http_request_duration_seconds histogram\nhttp_request_duration_seconds_bucket{le=\"0.005\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.025\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.1\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.5\"} 3\nhttp_request_duration_seconds_bucket{le=\"+Inf\"} 3\nhttp_request_duration_seconds_sum 2.8759e-05\nhttp_request_duration_seconds_count 3\n# HELP http_requests_total Requests handled.\n# TYPE http_requests_total counter\nhttp_requests_total{route=\"/items\",status=\"200\"} 3\nhttp_requests_total{route=\"/items\",status=\"404\"} 1\n"}],"file":"www-examples/metrics/expose/main.go"}},{"type":"flow","id":"metrics","title":"Pushed through an outage, recorded"},{"type":"table","title":"Checked against the real receivers","source":"benchmarks/interop/readme","table":"Receiver","caption":"In a VM, each release checked against its project's published SHA-256: the exporters' output read back from each receiver and compared, 238 checks, every value as recorded (benchmarks/interop, 2026-10-17).","header":["Receiver","Takes"],"rows":[["Prometheus 3.15.0","scrapes twice, as text (0.0.4) and as OpenMetrics 1.0; OTLP at /api/v1/otlp"],["OpenTelemetry Collector (contrib) 0.163.1","OTLP/HTTP, written out as OTLP JSON; its statsd receiver"],["VictoriaMetrics 1.154.0","scrapes; OTLP at /opentelemetry"],["statsd_exporter 0.31.0","StatsD, DogStatsD and Influx tags"],["Telegraf 1.40.2","StatsD (its statsd input), written out as JSON"]],"heading":"interop"},{"type":"table","title":"The receiver down for minutes","source":"benchmarks/interop/readme","table":"2026-10-17, the Collector out 3 minutes each way","caption":"2000 series pushed every second over OTLP while the Collector was stopped, then hung (SIGSTOP), 3 minutes each: memory and goroutines stayed as while running, every failed push was counted, and the last request held every final count.","header":["2026-10-17, the Collector out 3 minutes each way","heap","goroutines","pushes","failed"],"rows":[["running","3.0 to 3.8 MB","5","60","0"],["stopped: connections refused","3.0 MB","3","0","179"],["running again","3.0 to 3.1 MB","5","61","0"],["hung: connections never answered","3.1 MB","5","0","179"],["running again","3.1 MB","5","61","0"]],"heading":"interop"}],"sample":{"code":"registry := metrics.NewRegistry()\nrequests := registry.CounterVec(\"http_requests\", \"Requests handled.\", []string{\"route\", \"status\"})\nlatency := registry.Histogram(\"http_request_duration\", \"Time to handle a request.\", metrics.Seconds,\n\tmetrics.Bounds(0.005, 0.025, 0.1, 0.5))\n\nhttp.Handle(\"/metrics\", prometheus.Handler(registry))\nhttp.HandleFunc(\"/items\", func(w http.ResponseWriter, r *http.Request) {\n\tstart := time.Now()\n\tfmt.Fprintln(w, \"the items\")\n\trequests.With(\"/items\", \"200\").Inc() // one atomic add, no allocation\n\tlatency.Observe(time.Since(start).Seconds())\n})","example":"metrics/expose","title":"Count requests and time them, for Prometheus","file":"www-examples/metrics/expose/main.go","output":"$ curl /metrics\n# HELP http_request_duration_seconds Time to handle a request.\n# TYPE http_request_duration_seconds histogram\nhttp_request_duration_seconds_bucket{le=\"0.005\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.025\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.1\"} 3\nhttp_request_duration_seconds_bucket{le=\"0.5\"} 3\nhttp_request_duration_seconds_bucket{le=\"+Inf\"} 3\nhttp_request_duration_seconds_sum 2.8759e-05\nhttp_request_duration_seconds_count 3\n# HELP http_requests_total Requests handled.\n# TYPE http_requests_total counter\nhttp_requests_total{route=\"/items\",status=\"200\"} 3\nhttp_requests_total{route=\"/items\",status=\"404\"} 1\n","system":"linux","runs":1},"measured":["metrics-spec"],"competition":[{"title":"What goes wrong with telemetry in Go today","source":"metrics.md","heading":"why","blocks":[{"t":"p","text":"What goes wrong with telemetry in Go today (the research of 2026-10-16, measured on this machine):"},{"t":"list","ordered":false,"items":[{"text":"**Cost on the hot path.** OpenTelemetry Go 1.47 takes 54 ns to add to a counter where an atomic takes 5.8; 116 to 337 ns and 1 to 4 allocations with two attributes; 632 ns and 3 allocations a span; 156 ns for a span that is never sampled. client_golang takes 6 ns a counter, VictoriaMetrics/metrics 6.7.","items":[]},{"text":"**Weight.** OpenTelemetry adds 9.4 MB to a binary, 24 third-party modules, gRPC included even for an HTTP-only exporter (an issue open since 2022); client_golang 2.9 MB.","items":[]},{"text":"**Memory per series.** 2.5 KB in OpenTelemetry (growing with the CPU count), 500 B in client_golang, 147 B in VictoriaMetrics/metrics.","items":[]},{"text":"**Unbounded labels.** A label fed from outside (a user agent, a path) grows memory without end (CVE-2023-45142); OpenTelemetry's cap merges the excess into one series, so queries by label undercount.","items":[]},{"text":"**Lost data and shutdown.** A full queue drops spans silently; spans still queued vanish without an explicit flush; blocking instead of dropping is documented as able to \"severely affect\" the program.","items":[]},{"text":"**Context.** A goroutine started from a fresh context breaks the trace.","items":[]},{"text":"**Moving names.** Semantic conventions changed in most releases of 2025–26, breaking dashboards.","items":[]},{"text":"**Weight around it, not in Go.** The \"needs Java\" impression comes from backends and agents (Elasticsearch, Cassandra, Kafka, JMX scrapers), not from the program; single-binary backends exist (Prometheus, VictoriaMetrics, Tempo, OpenObserve).","items":[]}]}]}]},{"id":"system-info","title":"System information","pattern":"system-info*","purpose":"Find out what machine a program runs on.","replaces":["gopsutil's host, cpu, mem and disk packages, for what is installed","jaypipes/ghw","elastic/go-sysinfo's host information","WMI queries, and programs such as ioreg or dmidecode run and their output parsed"],"modules":["system-info-spec","system-info-all","system-info-cli","system-info-source-linux","system-info-source-macos","system-info-source-windows","system-info-source-freebsd","system-info-source-openbsd","system-info-source-netbsd","system-info-source-shared-bsd","system-info-source-shared-cpuid","system-info-source-shared-devicetree","system-info-source-shared-edid","system-info-source-shared-smbios","system-info-source-shared-ids","system-info-source-shared-input"],"blocks":[{"type":"example","id":"sysinfo/machine","example":{"id":"sysinfo/machine","title":"What is this machine?","problem":"A support report, an installer or a licence check needs to know what the program runs on: the system and its version, the processor, memory, graphics, monitors and disks, on every system the program ships for.","notice":"Go's runtime knows the system's name, the architecture and how many logical CPUs the program may use. system-info reads the rest through each system's own interfaces, as an ordinary user, and every value it could not read is printed with the reason.","parts":[{"name":"usual","label":"Go's runtime","code":"fmt.Printf(\" %s on %s, %d logical CPUs\\n\", runtime.GOOS, runtime.GOARCH, runtime.NumCPU())"},{"name":"with","label":"system-info","code":"machine, _ := all.Machine() // an error means nothing was read; Missing says why\nshow(\"machine\", join(machine.Vendor, machine.Product, machine.Family, machine.Chassis),\n\tmachine.Missing, \"Vendor\", \"Product\", \"Chassis\")\nshow(\"hypervisor\", machine.Hypervisor, machine.Missing, \"Hypervisor\")\n\nsystem, _ := all.OS()\nshow(\"system\", join(system.Name, system.Version, system.Build), system.Missing, \"Name\", \"Version\")\nshow(\"kernel\", join(system.Kernel, system.KernelVersion, system.Architecture),\n\tsystem.Missing, \"KernelVersion\", \"Architecture\")\n\ncpu, _ := all.CPU()\nshow(\"processor\", cpu.Model, cpu.Missing, \"Model\")\nshow(\"\", join(count(cpu.Cores, \"cores\"), count(cpu.Threads, \"threads\"), with(\"up to\", hertz(cpu.MaxFrequency))),\n\tcpu.Missing, \"Cores\", \"Threads\", \"MaxFrequency\")\nfor _, kind := range cpu.CoreTypes {\n\tshow(\"\", join(count(kind.Cores, kind.Kind+\" cores\"), count(kind.Threads, \"threads\")), nil)\n}\n\nmemory, _ := all.Memory()\nshow(\"memory\", join(with(binary(memory.Installed), \"installed\"), with(binary(memory.Usable), \"usable\"),\n\tcount(len(memory.Modules), \"modules\")), memory.Missing, \"Installed\", \"Usable\", \"Modules\")\n\ngraphics, _ := all.Graphics()\nshow(\"graphics\", several(len(graphics.GPUs), \"GPU\"), graphics.Missing, \"GPUs\")\nfor _, gpu := range graphics.GPUs {\n\tshow(\" GPU\", join(gpu.Vendor, gpu.Model, gpu.Kind, with(binary(gpu.Memory), \"of its own\")),\n\t\tgpu.Missing, \"Vendor\", \"Model\", \"Kind\", \"Memory\")\n}\n\ndisplays, _ := all.Displays()\nshow(\"displays\", several(len(displays.Monitors), \"monitor\"), displays.Missing, \"Monitors\")\nfor _, monitor := range displays.Monitors {\n\tshow(\" monitor\", join(monitor.Vendor, monitor.Model, with(\"made\", number(monitor.Year)), monitor.Connection,\n\t\tarea(monitor.Width, monitor.Height, \"mm\"), area(monitor.NativeWidth, monitor.NativeHeight, \"pixels\")),\n\t\tmonitor.Missing, \"Vendor\", \"Model\", \"Year\", \"Connection\", \"Width\", \"Height\", \"NativeWidth\", \"NativeHeight\")\n}\n\nstorage, _ := all.Storage()\nshow(\"storage\", several(len(storage.Disks), \"disk\"), storage.Missing, \"Disks\")\nfor _, disk := range storage.Disks {\n\tshow(\" disk\", join(disk.Vendor, disk.Model, decimal(disk.Size), disk.Media, disk.Bus),\n\t\tdisk.Missing, \"Vendor\", \"Model\", \"Size\", \"Media\", \"Bus\")\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for linux, run on a Debian 13 VM, as nobody","exit":0,"name":"linux","output":"Go's runtime\n linux on amd64, 2 logical CPUs\n\nsystem-info, linux's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: the firmware gives no known chassis type\n hypervisor kvm\n system Debian GNU/Linux, 13\n kernel Linux, 6.12.111+deb13-cloud-amd64, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads\n not read: max frequency: no CPU frequency driver reports it\n not read: max frequency: this processor does not report its frequency through CPUID\n 2 standard cores, 2 threads\n memory 967.7 MiB usable\n not read: installed, modules: only root may read the SMBIOS memory devices, and EDAC describes none\n graphics 1 GPU\n GPU virtual\n not read: vendor, model: the system's pci.ids does not know the ID\n not read: memory: a virtual GPU has no memory of its own\n displays 0 monitors\n not read: monitors: no graphics driver is loaded, so the monitors are not known\n storage 1 disk\n disk 8.6 GB, virtio\n not read: vendor, model: a virtio disk does not describe itself\n not read: media: a virtio disk does not say whether it rotates\n"},{"system":"macos","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for macos, run on the test Mac, macOS 15.8.1 (Intel), as a user","exit":0,"name":"macos","output":"Go's runtime\n darwin on amd64, 16 logical CPUs\n\nsystem-info, macos's sources\n machine Apple Inc., MacBookPro15,1, MacBook Pro, Laptop\n hypervisor none\n system macOS, 15.8.1, 24H32\n kernel Darwin, 24.6.0, amd64\n processor Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz\n 8 cores, 16 threads, up to 5 GHz\n 8 standard cores, 16 threads\n memory 32 GiB installed, 32 GiB usable, 2 modules\n graphics 2 GPUs\n GPU Intel UHD Graphics 630, integrated\n not read: vendor: macOS keeps no list of PCI names, and no pci.ids is installed\n not read: memory: an integrated GPU has no memory of its own\n GPU Radeon Pro 560X, discrete, 4 GiB of its own\n not read: vendor: macOS keeps no list of PCI names, and no pci.ids is installed\n displays 1 monitor\n monitor Color LCD, made 2017, internal, 331 × 207 mm, 2880 × 1800 pixels\n not read: vendor: the system has no name for the maker code APP\n storage 1 disk\n disk APPLE SSD AP1024M, 1 TB, ssd, nvme\n not read: vendor: the source leaves it empty\n"},{"system":"windows","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for windows, run on a Windows Server 2025 VM","exit":0,"name":"windows","output":"Go's runtime\n windows on amd64, 2 logical CPUs\n\nsystem-info, windows's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: Windows keeps the chassis type only in SMBIOS\n not read: chassis: the SMBIOS chassis type is unknown\n hypervisor kvm\n system Windows Server 2025 Standard Evaluation, 24H2, 26100.32230\n kernel Windows NT, 10.0.26100.32230, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads, up to 3.4 GHz\n 2 standard cores, 2 threads\n memory 4 GiB installed, 4 GiB usable, 1 module\n graphics 1 GPU\n GPU Microsoft Basic Display Adapter, virtual\n not read: vendor: the display driver does not name it\n not read: memory: a virtual GPU has no memory of its own\n displays 1 monitor\n monitor QEMU Monitor, made 2014, displayport, 325 × 203 mm, 1280 × 800 pixels\n not read: vendor: the system has no name for the maker code RHT\n storage 1 disk\n disk QEMU HARDDISK, 42.9 GB, ide\n not read: vendor: the source leaves it empty\n not read: media: the disk does not say whether it seeks: A device attached to the system is not functioning.\n"},{"system":"freebsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for freebsd, run on a FreeBSD 15.1 VM, as nobody","exit":0,"name":"freebsd","output":"Go's runtime\n freebsd on amd64, 2 logical CPUs\n\nsystem-info, freebsd's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: the firmware gives the chassis type \"Other\"\n hypervisor kvm\n system FreeBSD, 15.1, RELEASE\n kernel FreeBSD, 15.1-RELEASE, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads\n not read: max frequency: no cpufreq driver is attached\n not read: max frequency: this processor does not report its frequency through CPUID\n 2 standard cores, 2 threads\n memory 1 GiB installed, 985.5 MiB usable\n not read: modules: FreeBSD describes memory modules only in the raw SMBIOS tables, which this source does not read\n graphics 1 GPU\n GPU virtual\n not read: vendor, model: the system's pci_vendors does not know the ID\n not read: memory: FreeBSD reports no GPU memory size\n displays 0 monitors\n not read: monitors: FreeBSD gives a monitor's EDID only through its graphics driver's DRM device, which this source does not read\n storage 1 disk\n disk 8.6 GB, virtio\n not read: model: the source leaves it empty\n not read: vendor: GEOM describes a disk in one string, the model\n not read: media: the disk does not report its rotation rate\n"},{"system":"openbsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for openbsd, run on an OpenBSD 7.9 VM, as nobody","exit":0,"name":"openbsd","output":"Go's runtime\n openbsd on amd64, 2 logical CPUs\n\nsystem-info, openbsd's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: OpenBSD does not export it\n hypervisor kvm\n system OpenBSD, 7.9, GENERIC.MP#449\n kernel OpenBSD, 7.9, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads\n not read: max frequency: OpenBSD reports only the current clock rate\n not read: max frequency: this processor does not report its frequency through CPUID\n 2 performance cores, 2 threads\n memory 1007.9 MiB usable\n not read: installed: OpenBSD reports only the memory it manages\n not read: modules: OpenBSD does not export the SMBIOS memory devices\n graphics 1 GPU\n GPU Bochs VGA\n not read: vendor: OpenBSD gives a PCI device's IDs to root only: open /dev/pci: permission denied\n not read: kind: telling the kind needs the PCI vendor, which only root may read\n not read: memory: OpenBSD reports no GPU memory size\n displays 0 monitors\n not read: monitors: OpenBSD does not export the monitors' EDID\n storage 1 disk\n disk VirtIO, Block Device, 8.6 GB, virtio\n not read: media: OpenBSD does not report whether a disk spins\n"},{"system":"netbsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./sysinfo/machine for netbsd, run on a NetBSD 11.0 VM, as nobody","exit":0,"name":"netbsd","output":"Go's runtime\n netbsd on amd64, 2 logical CPUs\n\nsystem-info, netbsd's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: machdep.dmi.chassis-type repeats the chassis vendor; the type is only in the raw SMBIOS table\n hypervisor kvm\n system NetBSD, 11.0, GENERIC\n kernel NetBSD, 11.0, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads\n not read: max frequency: no frequency driver is attached\n not read: max frequency: this processor does not report its frequency through CPUID\n 2 standard cores, 2 threads\n memory 1023.5 MiB usable\n not read: installed: NetBSD reports only the memory it manages\n not read: modules: NetBSD describes memory modules only in the raw SMBIOS tables, which this source does not read\n graphics 1 GPU\n GPU virtual\n not read: vendor: NetBSD names devices in its kernel, as one string\n not read: model: the boot log does not name the device\n not read: memory: NetBSD reports no GPU memory size\n displays 0 monitors\n not read: monitors: no display driver gives the monitors' EDID\n storage 1 disk\n disk Virtio Block Device, 8.6 GB, virtio\n not read: vendor: NetBSD gives a disk's identity to root only, through ATA and SCSI commands this source does not send\n not read: media: NetBSD does not report whether a disk spins\n"}],"file":"www-examples/sysinfo/machine/main.go"}},{"type":"stages","title":"How a value is read","intro":"Sources are kept apart from topics: a source can answer several topics, and system-info-all combines, when the program is compiled, the sources of the system it is built for.","stages":[{"label":"NATIVE","text":"The kernel, the IORegistry and the registry, reached without cgo and without golang.org/x/sys.","modules":["system-native-windows","system-native-macos","system-native-bsd"]},{"label":"SYSTEM","text":"One source for each system, reading its own interfaces: sysfs and procfs, sysctl and the IORegistry, the registry and Windows' APIs, kenv, the boot log, drvctl. No program is run.","modules":["system-info-source-linux","system-info-source-macos","system-info-source-windows","system-info-source-freebsd","system-info-source-openbsd","system-info-source-netbsd","system-info-source-shared-bsd"]},{"label":"FORMATS","text":"Standard formats, decoded once for every system: SMBIOS tables, EDID, CPUID, device trees, the system's own pci.ids, usb.ids and pnp.ids, the kinds of input devices. The parsers are fuzzed.","modules":["system-info-source-shared-smbios","system-info-source-shared-edid","system-info-source-shared-cpuid","system-info-source-shared-devicetree","system-info-source-shared-ids","system-info-source-shared-input"]},{"label":"COMBINE","text":"The system's sources, the preferred first: the first that gives a field wins, and when none does, every source's reason is kept.","modules":["system-info-all"]},{"label":"TOPICS","text":"The contract: each topic as plain data, with Missing listing every field not read, the reason in words and an error to test with errors.Is.","modules":["system-info-spec"]},{"label":"COMMAND","text":"The system-info command: the inventory as text with units, or JSON, and a redacted report to share.","modules":["system-info-cli"]}]},{"type":"table","title":"Why a value is missing","source":"system-info-spec/readme","table":"Error","caption":"From system-info-spec's readme. Each field not read is in its topic's Missing with a reason in words and one of these errors. A list that is empty and not in Missing was read and holds nothing.","header":["Error","Means","Example"],"rows":[["ErrPermission","needs more privileges (it is os.ErrPermission)","serial numbers on Linux as a user"],["ErrUnsupported","this hardware or system has no source","SMBIOS on a Raspberry Pi"],["ErrBlank","the firmware left it blank or put a placeholder","\"To Be Filled By O.E.M.\", an all-zero UUID"],["ErrMasked","a container hides it","Docker hides /sys/firmware"],["ErrUnavailable","not readable in the current state","the speed of a link that is down"],["ErrSession","not readable from this kind of session","display data from a service"]],"heading":"missing"},{"type":"table","title":"Where the values come from","source":"system-info-all/readme","table":"System","caption":"From system-info-all's readme. The first source that gives a field wins.","header":["System","Sources, preferred first"],"rows":[["Linux","CPUID (hypervisor, x86 features and caches), sysfs and procfs, the device tree (Raspberry Pi and other boards), SMBIOS (root)"],["macOS","CPUID (Intel), sysctl and the IORegistry, SMBIOS (Intel Macs)"],["Windows","CPUID (x86), the registry and Windows' APIs, SMBIOS (any user)"],["FreeBSD","CPUID (x86), kenv and sysctl, the device tree (boards)"],["OpenBSD","CPUID (x86), sysctl and the boot log"],["NetBSD","CPUID (x86), sysctl and drvctl"]],"heading":"sources by system"},{"type":"table","title":"Where it was verified","source":"system-info-all/platforms.md","table":"Summary","caption":"From system-info-all's platforms.md, checked on 2026-10-14: the integration test reads every topic on the real system and checks that every empty field says why. The real runs were on amd64, the Raspberry Pi run on arm64.","header":["System","Verified on","Integration test"],"rows":[["Linux","Debian 13 (glibc) and Alpine 3.24 (musl) in QEMU; a Raspberry Pi 4 in QEMU (raspi4b)","pass, as root and as nobody; the Pi as root"],["macOS","macOS 15.8.1, Intel (MacBookPro15,1)","pass, as a user"],["Windows","Windows Server 2025 (10.0.26100, BIOS) and Windows 11 Enterprise 26H2 (10.0.26300, UEFI with Secure Boot and a TPM 2.0) in QEMU","pass, as Administrator and as a standard user"],["FreeBSD","FreeBSD 15.1-RELEASE in QEMU","pass, as root and as nobody"],["OpenBSD","OpenBSD 7.9 in QEMU","pass, as root and as nobody"],["NetBSD","NetBSD 11.0 in QEMU","pass, as root and as nobody"]],"heading":"Summary"},{"type":"table","title":"Compared with the Go libraries people use","source":"competition.md","table":"1. System and hardware information","caption":"From competition.md, checked on 2026-10-14 against each library's readme, go.mod and sources.","header":["Library","★","Release","Covers","Systems","cgo, WMI, programs","Missing values"],"rows":[["shirou/gopsutil v4","11.9k","v4.26.9, 2026-10-01","CPU, memory, disks, network, host, load, processes, temperatures, docker and cgroups (Linux), Windows services","Linux, Windows, macOS, FreeBSD, OpenBSD, Solaris, AIX; NetBSD without processes","WMI through go-ole on Windows; runs netstat, swapctl, geom, ioreg, sw_vers and ps on the BSDs and macOS","ErrNotImplementedError or zero"],["jaypipes/ghw","1.9k","v0.26.0, 2026-09-26","CPU, memory modules, block devices, NUMA, NICs, PCI, GPUs, accelerators, USB, chassis, BIOS, board, product, TPM","Linux and Windows; part of macOS; no BSD","WMI; runs ethtool and macOS tools","warnings as text on stderr"],["elastic/go-sysinfo","404","v1.15.5","host, OS, machine ID, containers, memory, CPU time, process details","Linux, macOS, Windows, AIX","cgo optional on macOS","optional interfaces"],["zcalusic/sysinfo","580","2024","OS, kernel, product, board, chassis, BIOS, CPU, RAM, storage, network","Linux only, root for DMI","—","silent"]],"heading":"1. System and hardware information"},{"type":"facts","items":[{"value":"14","label":"topics, from the machine and its firmware to sound devices and cameras; the identifiers are one of them, read only when asked for","source":"system-info-spec/readme"},{"value":"6","label":"systems the integration test passed on, on 2026-10-14: Linux, macOS, Windows, FreeBSD, OpenBSD and NetBSD","source":"system-info-all/platforms.md"},{"value":"0","label":"programs run: every value comes from a file, sysctl, ioctl, registry key, firmware table or library call","source":"system-info-spec/readme"}]}],"sample":{"code":"machine, _ := all.Machine() // an error means nothing was read; Missing says why\nshow(\"machine\", join(machine.Vendor, machine.Product, machine.Family, machine.Chassis),\n\tmachine.Missing, \"Vendor\", \"Product\", \"Chassis\")\nshow(\"hypervisor\", machine.Hypervisor, machine.Missing, \"Hypervisor\")\n\nsystem, _ := all.OS()\nshow(\"system\", join(system.Name, system.Version, system.Build), system.Missing, \"Name\", \"Version\")\nshow(\"kernel\", join(system.Kernel, system.KernelVersion, system.Architecture),\n\tsystem.Missing, \"KernelVersion\", \"Architecture\")\n\ncpu, _ := all.CPU()\nshow(\"processor\", cpu.Model, cpu.Missing, \"Model\")","example":"sysinfo/machine","title":"What is this machine?","file":"www-examples/sysinfo/machine/main.go","output":"Go's runtime\n linux on amd64, 2 logical CPUs\n\nsystem-info, linux's sources\n machine QEMU, Standard PC (i440FX + PIIX, 1996)\n not read: chassis: the firmware gives no known chassis type\n hypervisor kvm\n system Debian GNU/Linux, 13\n kernel Linux, 6.12.111+deb13-cloud-amd64, amd64\n processor Intel(R) Core(TM) Ultra 7 265U\n 2 cores, 2 threads\n not read: max frequency: no CPU frequency driver reports it\n not read: max frequency: this processor does not report its frequency through CPUID\n 2 standard cores, 2 threads\n memory 967.7 MiB usable\n not read: installed, modules: only root may read the SMBIOS memory devices, and EDAC describes none\n graphics 1 GPU\n GPU virtual\n not read: vendor, model: the system's pci.ids does not know the ID\n not read: memory: a virtual GPU has no memory of its own\n displays 0 monitors\n not read: monitors: no graphics driver is loaded, so the monitors are not known\n storage 1 disk\n disk 8.6 GB, virtio\n not read: vendor, model: a virtio disk does not describe itself\n not read: media: a virtio disk does not say whether it rotates\n","system":"linux","runs":6},"measured":[],"competition":[{"title":"The leading Go libraries for system information","source":"competition.md","heading":"1. System and hardware information","blocks":[{"t":"table","header":["Library","★","Release","Covers","Systems","cgo, WMI, programs","Missing values"],"rows":[["shirou/gopsutil v4","11.9k","v4.26.9, 2026-10-01","CPU, memory, disks, network, host, load, processes, temperatures, docker and cgroups (Linux), Windows services","Linux, Windows, macOS, FreeBSD, OpenBSD, Solaris, AIX; NetBSD without processes","WMI through go-ole on Windows; runs netstat, swapctl, geom, ioreg, sw_vers and ps on the BSDs and macOS","`ErrNotImplementedError` or zero"],["jaypipes/ghw","1.9k","v0.26.0, 2026-09-26","CPU, memory modules, block devices, NUMA, NICs, PCI, GPUs, accelerators, USB, chassis, BIOS, board, product, TPM","Linux and Windows; part of macOS; no BSD","WMI; runs ethtool and macOS tools","warnings as text on stderr"],["elastic/go-sysinfo","404","v1.15.5","host, OS, machine ID, containers, memory, CPU time, process details","Linux, macOS, Windows, AIX","cgo optional on macOS","optional interfaces"],["zcalusic/sysinfo","580","2024","OS, kernel, product, board, chassis, BIOS, CPU, RAM, storage, network","Linux only, root for DMI","—","silent"]]},{"t":"p","text":"Specialists:"},{"t":"list","ordered":false,"items":[{"text":"klauspost/cpuid for CPU features;","items":[]},{"text":"prometheus/procfs for Linux;","items":[]},{"text":"distatus/battery, last released 2023;","items":[]},{"text":"NVIDIA go-nvml and google/gousb, both cgo;","items":[]},{"text":"go4vl for V4L2 cameras on Linux.","items":[]}]},{"t":"p","text":"No significant new general library has appeared since 2024."},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"no cgo, useful without root;","items":[]},{"text":"JSON results, per-OS fields;","items":[]},{"text":"container and VM detection;","items":[]},{"text":"fixture tests;","items":[]},{"text":"at least Linux, Windows and macOS.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"one package across all topics: nobody covers monitors, audio, input, cameras, batteries and GPUs together;","items":[]},{"text":"saying why a value is missing;","items":[]},{"text":"native BSD support instead of parsing program output;","items":[]},{"text":"Windows without WMI;","items":[]},{"text":"virtualization detection beyond Linux.","items":[]}]},{"t":"p","text":"goframework's system-info fills every one of these."}]}]},{"id":"system-native","title":"Native OS access","pattern":"system-native*","purpose":"Call the operating system beyond Go's syscall package.","replaces":["cgo","golang.org/x/sys, for the calls goframework's modules make"],"modules":["system-native-windows","system-native-macos","system-native-bsd"],"blocks":[{"type":"example","id":"native/version","example":{"id":"native/version","title":"Which version of the system is this, and when did it boot?","problem":"Go's runtime names the system but not its version. Windows tells programs without a manifest that it is 6.2; macOS and the BSDs keep the version and the boot time in sysctls, the boot time as a struct that Go's syscall.Sysctl cannot read.","notice":"Every call works for an ordinary user, without cgo or golang.org/x/sys. system-info, system-process and system-service make calls like these for applications.","parts":[{"name":"windows","label":"system-native-windows","code":"info := native.RtlGetVersion() // GetVersionEx would say 6.2 to a program without a manifest\nfmt.Printf(\"version: %d.%d, build %d\\n\", info.MajorVersion, info.MinorVersion, info.BuildNumber)\n\nkey, err := native.OpenKey(native.HKEY_LOCAL_MACHINE, `SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion`,\n\tnative.KEY_QUERY_VALUE|native.KEY_WOW64_64KEY)\nif err != nil {\n\treturn err\n}\ndefer key.Close()\nproduct, _, err := key.String(\"ProductName\")\nif err != nil {\n\treturn err\n}\nrelease, _, _ := key.String(\"DisplayVersion\") // 24H2; older ones have none\nfmt.Printf(\"product: %s %s\\n\", product, release)"},{"name":"macos","label":"system-native-macos","code":"product, err := native.SysctlByName(\"kern.osproductversion\")\nif err != nil {\n\treturn err\n}\nkernel, err := native.SysctlByName(\"kern.osrelease\")\nif err != nil {\n\treturn err\n}\nfmt.Printf(\"version: macOS %s, Darwin %s\\n\", text(product), text(kernel))\nboot, err := native.SysctlByName(\"kern.boottime\") // a struct timeval, not a string\nif err != nil {\n\treturn err\n}\nbooted(boot)"},{"name":"bsd","label":"system-native-bsd","code":"system, err := native.Sysctl([]int32{ctlKern, kernOSType})\nif err != nil {\n\treturn err\n}\nrelease, err := native.Sysctl([]int32{ctlKern, kernOSRelease})\nif err != nil {\n\treturn err\n}\nfmt.Printf(\"version: %s %s\\n\", text(system), text(release))\nboot, err := native.Sysctl([]int32{ctlKern, kernBootTime}) // a struct, not a string\nif err != nil {\n\treturn err\n}\nbooted(boot)"}],"runs":[{"system":"windows","go":"go1.27.2","date":"2026-10-17","command":"go build ./native/version for windows, run on a Windows Server 2025 VM","exit":0,"name":"windows","output":"Go's runtime: windows on amd64\nversion: 10.0, build 26100\nproduct: Windows Server 2025 Standard Evaluation 24H2\n"},{"system":"macos","go":"go1.27.2","date":"2026-10-17","command":"go build ./native/version for macos, run on the test Mac, macOS 15.8.1 (Intel), as a user","exit":0,"name":"macos","output":"Go's runtime: darwin on amd64\nversion: macOS 15.8.1, Darwin 24.6.0\nbooted: 2026-10-10 06:24 UTC, up for 179 h 16 min\n"},{"system":"freebsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./native/version for freebsd, run on a FreeBSD 15.1 VM, as nobody","exit":0,"name":"freebsd","output":"Go's runtime: freebsd on amd64\nversion: FreeBSD 15.1-RELEASE\nbooted: 2026-10-16 20:23 UTC, up for 21 h 18 min\n"},{"system":"openbsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./native/version for openbsd, run on an OpenBSD 7.9 VM, as nobody","exit":0,"name":"openbsd","output":"Go's runtime: openbsd on amd64\nversion: OpenBSD 7.9\nbooted: 2026-10-16 20:23 UTC, up for 21 h 18 min\n"},{"system":"netbsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./native/version for netbsd, run on a NetBSD 11.0 VM, as nobody","exit":0,"name":"netbsd","output":"Go's runtime: netbsd on amd64\nversion: NetBSD 11.0\nbooted: 2026-10-16 20:23 UTC, up for 21 h 18 min\n"}],"file":"www-examples/native/version/main.go"}},{"type":"stages","title":"One module for each family of systems","intro":"Each reaches what the standard library's syscall package does not, for the modules named here. Everything in system-native-macos and system-native-bsd works for an ordinary user.","stages":[{"label":"WINDOWS","text":"Win32 and native calls by Microsoft's own names: the registry, version and firmware, processes and Job Objects, storage, network adapters, devices, batteries, services, the Event Log, shared memory and the console. For system-process, system-service, system-info, terminal and the Event Log persister.","modules":["system-native-windows"]},{"label":"MACOS","text":"libSystem, the IORegistry and CoreFoundation through small assembly trampolines into the system libraries, as Go's standard library calls them; property lists decoded; Core Audio loaded only when asked for. For system-process and system-info.","modules":["system-native-macos"]},{"label":"BSD","text":"sysctl by number and by name, FreeBSD's kenv and device tree, network interfaces decoded in each system's own layout, mounts read without waiting on any, NetBSD's drvctl properties and envsys sensors. For system-process and system-info.","modules":["system-native-bsd"]}]},{"type":"table","title":"What system-native-windows calls","source":"system-native-windows/readme","table":"Area","caption":"From system-native-windows' readme. What syscall offers (handles, processes, tokens, CreateProcess, CreateFile, DeviceIoControl, basic registry reads) is used from there.","header":["Area","Functions"],"rows":[["registry","OpenKey, a Key's String, Integer, Strings, SetStrings, DeleteValue; ExpandEnvironmentStrings"],["system","RtlGetVersion, IsWow64Process2, NtQuerySystemInformation, GetSystemFirmwareTable, GetFirmwareType, GetLogicalProcessorInformationEx, GlobalMemoryStatusEx, GetPhysicallyInstalledSystemMemory, CallNtPowerInformation, TbsiGetDeviceInfo, IsProcessorFeaturePresent"],["processes","Processes, ProcessBasic, ProcessCommandLine, QueryFullProcessImageName, WaitForMultipleObjects, CreateEvent, SetEvent, GenerateConsoleCtrlEvent, ComposeCommandLine, DecomposeCommandLine"],["jobs and creation","CreateJobObject, SetJobLimits, TerminateJobObject, AttributeList (inherited handles, jobs), StartupInfoEx for syscall.CreateProcess"],["storage","Volumes, VolumePathNames, GetVolumeInformation, GetDiskFreeSpaceEx, GetDriveType, GetLogicalDrives, QueryDosDevice, WNetGetConnection, DeviceInterfaces"],["network","GetAdaptersAddresses, GetIfEntry2Ex"],["devices","DeviceIDs, LocateDevNode, and a DevInst's ID, Parent, Children, Property (typed), Text, Texts, Number"],["batteries","OpenBattery, BatteryTag, BatteryInfo, BatteryText, QueryBattery (the battery class's IOCTLs)"],["services","the service control manager's functions, and StartServiceCtrlDispatcher, RegisterServiceCtrlHandlerEx and SetServiceStatus for a dispatcher"],["Event Log","RegisterEventSource, ReportEvent, DeregisterEventSource"],["shared memory","CreateFileMapping (paging-file backed, named), OpenFileMapping, MapViewOfFile (a []byte), UnmapViewOfFile"],["console","GetConsoleMode, SetConsoleMode, GetConsoleScreenBufferInfo, ReadConsole, ReadConsoleInput (key, mouse, size and focus records), GetNumberOfConsoleInputEvents, GetConsoleCP"]],"heading":"system-native-windows"},{"type":"text","title":"How the calls are made","body":"Windows: every DLL is loaded from the system directory only, never from the program's directory or PATH, and a function a Windows lacks gives ErrNotAvailable instead of a crash. Errors are syscall.Errno, so errors.Is(err, fs.ErrNotExist) works. Every structure's size and field offsets and every constant were checked at compile time against golang.org/x/sys's when the module was written, for amd64, arm64 and 386. macOS: pointer arguments stay put for the length of a call, and the property list decoder never panics on malformed input; it is fuzzed. OpenBSD: every system call goes through libc, by Go functions that can grow the stack, so buffers handed to the kernel are kept off the stack."}],"sample":{"code":"info := native.RtlGetVersion() // GetVersionEx would say 6.2 to a program without a manifest\nfmt.Printf(\"version: %d.%d, build %d\\n\", info.MajorVersion, info.MinorVersion, info.BuildNumber)\n\nkey, err := native.OpenKey(native.HKEY_LOCAL_MACHINE, `SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion`,\n\tnative.KEY_QUERY_VALUE|native.KEY_WOW64_64KEY)\nif err != nil {\n\treturn err\n}\ndefer key.Close()\nproduct, _, err := key.String(\"ProductName\")\nif err != nil {\n\treturn err\n}\nrelease, _, _ := key.String(\"DisplayVersion\") // 24H2; older ones have none\nfmt.Printf(\"product: %s %s\\n\", product, release)","example":"native/version","title":"Which version of the system is this, and when did it boot?","file":"www-examples/native/version/main.go","output":"Go's runtime: windows on amd64\nversion: 10.0, build 26100\nproduct: Windows Server 2025 Standard Evaluation 24H2\n","system":"windows","runs":5},"measured":[],"competition":[]},{"id":"system-process","title":"Processes","pattern":"system-process*","purpose":"Start, find, stop and supervise processes and everything they start.","replaces":["os/exec with process groups and Job Objects written by hand","gopsutil's process listing","supervisord, or a restart loop written by hand"],"modules":["system-process-spec","system-process-all","system-process-linux","system-process-macos","system-process-windows","system-process-freebsd","system-process-openbsd","system-process-netbsd","system-process-monitor","system-process-samples"],"blocks":[{"type":"example","id":"processes/stop-tree","example":{"id":"processes/stop-tree","title":"Stop a process, and everything it started","problem":"A program starts a worker; the worker starts children of its own. Stop the worker, and nothing it started may keep running.","notice":"os/exec's Kill ends the worker only: its children and their children keep running, orphaned. system-process gives a started process a tree of its own, so Stop reaches all of it.","parts":[{"name":"usual","label":"os/exec","code":"command := exec.Command(self, \"worker\", marker)\ncommand.Start()\ntime.Sleep(time.Second)\ncommand.Process.Kill() // the worker ends; its children do not\ncommand.Wait()"},{"name":"with","label":"system-process","code":"worker, err := all.Start(spec.Definition{Program: self, Args: []string{\"worker\", marker}})\nif err != nil {\n\tfmt.Println(err)\n\tos.Exit(1)\n}\ntime.Sleep(time.Second)\nworker.Stop() // asks the whole tree to stop, waits, then kills what is left"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./processes/stop-tree","exit":0,"name":"linux","output":"linux: a worker that starts two children, each starting one more\n\nos/exec, Process.Kill: 4 still running: [child (PID 3108668) child (PID 3108669) grandchild (PID 3108681) grandchild (PID 3108683)]\nsystem-process, Stop: nothing left running\n"},{"system":"windows","go":"go1.27.2","date":"2026-10-17","command":"go build ./processes/stop-tree for windows, run on a Windows Server 2025 VM","exit":0,"name":"windows","output":"windows: a worker that starts two children, each starting one more\n\nos/exec, Process.Kill: 4 still running: [child (PID 2728) child (PID 1764) grandchild (PID 2248) grandchild (PID 1184)]\nsystem-process, Stop: nothing left running\n"},{"system":"macos","go":"go1.27.2","date":"2026-10-17","command":"go build ./processes/stop-tree for macos, run on a Mac, macOS 15.8 (Intel)","exit":0,"name":"macos","output":"macos: a worker that starts two children, each starting one more\n\nos/exec, Process.Kill: 4 still running: [grandchild (PID 90911) grandchild (PID 90910) child (PID 90909) child (PID 90908)]\nsystem-process, Stop: nothing left running\n"}],"file":"www-examples/processes/stop-tree/main.go"}},{"type":"table","title":"Where it was verified","source":"system-process-all/platforms.md","table":"System","caption":"From system-process-all's platforms.md, checked on 2026-10-13: the same integration test on each real system. arm64 is compiled, not run.","header":["System","Module","Verified on","Integration test"],"rows":[["Linux","system-process-linux","Debian 13 (glibc); Alpine 3.24 (musl, busybox)","pass, as root and as nobody"],["macOS","system-process-macos","macOS 15.8.1 (Intel)","pass, as a user and as root"],["Windows","system-process-windows","Windows Server 2025 (10.0.26100)","pass, as Administrator and as a plain user"],["FreeBSD","system-process-freebsd","FreeBSD 15.1-RELEASE","pass, as root and as nobody"],["OpenBSD","system-process-openbsd","OpenBSD 7.9","pass, as root and as nobody"],["NetBSD","system-process-netbsd","NetBSD 11.0","pass, as root and as nobody"],["any","system-process-all, -monitor","unit tests","the monitor runs on fakes"]],"heading":"Summary"}],"sample":{"code":"worker, err := all.Start(spec.Definition{Program: self, Args: []string{\"worker\", marker}})\nif err != nil {\n\tfmt.Println(err)\n\tos.Exit(1)\n}\ntime.Sleep(time.Second)\nworker.Stop() // asks the whole tree to stop, waits, then kills what is left","example":"processes/stop-tree","title":"Stop a process, and everything it started","file":"www-examples/processes/stop-tree/main.go","output":"linux: a worker that starts two children, each starting one more\n\nos/exec, Process.Kill: 4 still running: [child (PID 3108668) child (PID 3108669) grandchild (PID 3108681) grandchild (PID 3108683)]\nsystem-process, Stop: nothing left running\n","system":"linux","runs":3},"measured":[],"competition":[{"title":"The leading Go libraries for processes","source":"competition.md","heading":"3. Processes","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["os/exec and os","stdlib","Go 1.27","`Cmd.Cancel`, `WaitDelay`, pidfd inside, `Process.WithHandle`"],["go-cmd/cmd","1.0k","v1.4.3, 2024","async start, stop and status; group stop on Unix, plain kill on Windows"],["creack/pty / aymanbagabas/go-pty","2.1k / 88","2024 / 2026-05","pseudo-terminals; go-pty adds Windows ConPTY"],["gopsutil/process","—","monthly","list, parents and children, usage, signals"],["thejerf/suture v4","1.4k","2024","Erlang-style supervisor trees, but of goroutines"],["ochinchina/supervisord","4.3k","v0.7.6, 2026-09","supervisord in Go: restarts, stop signals, groups, web UI"],["process-compose","2.8k","v1.122.0, 2026-08","ordering, health checks, restarts, replicas, cron, TUI, API"],["hashicorp/go-reap, go-reaper","—","—","reaping zombies as PID 1"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"context cancellation;","items":[]},{"text":"graceful stop: TERM, then KILL; CTRL_BREAK on Windows;","items":[]},{"text":"killing the whole tree: groups, Job Objects;","items":[]},{"text":"race-free waits through pidfd;","items":[]},{"text":"bounded output;","items":[]},{"text":"pseudo-terminals including ConPTY;","items":[]},{"text":"restarts with backoff and crash-loop detection;","items":[]},{"text":"health checks;","items":[]},{"text":"reaping.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"one API over process groups, Job Objects and pidfd;","items":[]},{"text":"tree management on Windows;","items":[]},{"text":"process supervision embeddable as a library.","items":[]}]},{"t":"p","text":"goframework has the first three; it lacks pseudo-terminals."}]}]},{"id":"system-service","title":"Services","pattern":"system-service*","purpose":"Install and control services, on every system's service manager.","replaces":["kardianos/service","takama/daemon","unit files, plists, rc.d scripts and sc.exe calls written by hand"],"modules":["system-service-spec","system-service-all","system-service-systemd","system-service-sysv","system-service-openrc","system-service-launchd","system-service-windows","system-service-freebsd","system-service-openbsd","system-service-netbsd","system-service-cli"],"blocks":[{"type":"example","id":"services/everywhere","example":{"id":"services/everywhere","title":"One service definition, every service manager","problem":"Install a Go program as a service, started at boot and restarted when it fails, on Linux, macOS, Windows and the BSDs: without writing a systemd unit, a launchd plist, an rc.d script and sc.exe calls by hand.","notice":"A manager that cannot do what the definition asks refuses it, before anything is written. Asked only for what it supports, each gets its own native file. The other tabs are the same program installing itself for real, as root in throwaway VMs: systemd on Debian 13, the Service Control Manager on Windows Server 2025, rc.d on FreeBSD 15.1.","parts":[{"name":"define","label":"the definition","code":"// definition is acme-api's service, asking only for what the service\n// manager supports: a manager refuses anything else, rather than quietly\n// leaving it out.\nfunc definition(supports func(spec.Feature) bool) spec.Service {\n\texecutable, _ := os.Executable()\n\tservice := spec.Service{\n\t\tName: \"acme-api\",\n\t\tDescription: \"Acme's API\",\n\t\tExecutable: executable,\n\t\tArgs: []string{\"run\"},\n\t\tStartAtBoot: true,\n\t}\n\tswitch {\n\tcase supports(spec.FeatureRestartOnFailure):\n\t\tservice.Restart = spec.RestartOnFailure\n\tcase supports(spec.FeatureRestartAlways):\n\t\tservice.Restart = spec.RestartAlways\n\t}\n\tif supports(spec.FeatureNetworkDependency) {\n\t\tservice.DependsOn = []string{spec.DependencyNetwork}\n\t}\n\treturn service\n}"},{"name":"install","label":"install and start it","code":"func install() error {\n\tmanager, err := all.New(definition(all.Supports)) // this system's: systemd, launchd, the SCM, rc.d...\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := manager.Install(); err != nil { // writes its file, enables it at boot\n\t\treturn err\n\t}\n\treturn manager.Start()\n}"},{"name":"run","label":"the program, run by the service manager","code":"func run() error {\n\treturn all.Run(func(ctx context.Context, ready func()) error {\n\t\tserver := &http.Server{Addr: \"127.0.0.1:8099\"}\n\t\tgo server.ListenAndServe()\n\t\tready() // the service manager now reports it running\n\t\t<-ctx.Done() // asked to stop: by the service manager, SIGTERM or Ctrl+C\n\t\tshutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\treturn server.Shutdown(shutdown)\n\t})\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./services/everywhere render","exit":0,"name":"render","output":"### every manager asked for restart on failure, after the network\nsystemd ok\nlaunchd (macOS) refused: not supported by this service manager: DependsOn: launchd does not support depending on the network\nWindows Service Control Manager ok\nFreeBSD rc.d refused: not supported by this service manager: Restart: FreeBSD rc.d does not support restarting on failure only\nOpenBSD rc.d refused: not supported by this service manager: Restart: OpenBSD rc.d does not support restarting on failure only\nNetBSD rc.d refused: not supported by this service manager: Restart: NetBSD rc.d does not support restarting on failure only\nOpenRC refused: not supported by this service manager: Restart: openrc does not support restarting on failure only\nSysV init refused: not supported by this service manager: Restart: sysv does not support restarting on failure only\n\n### each asked for what it supports\n=== systemd\n# Generated by goframework system-service\n\n[Unit]\nDescription=Acme's API\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=exec\nExecStart=/usr/local/bin/acme-api run\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n\n=== launchd (macOS)\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<!-- Generated by goframework system-service -->\n<plist version=\"1.0\">\n<dict>\n\t<key>Label</key>\n\t<string>acme-api</string>\n\t<key>ServiceDescription</key>\n\t<string>Acme's API</string>\n\t<key>ProgramArguments</key>\n\t<array>\n\t\t<string>/usr/local/bin/acme-api</string>\n\t\t<string>run</string>\n\t</array>\n\t<key>RunAtLoad</key>\n\t<true/>\n\t<key>KeepAlive</key>\n\t<dict>\n\t\t<key>SuccessfulExit</key>\n\t\t<false/>\n\t</dict>\n</dict>\n</plist>\n\n=== Windows Service Control Manager\n@echo off\nrem Generated by goframework system-service\nsc.exe create acme-api binPath= \"\\\"C:\\Program Files\\Acme\\acme-api.exe\\\" run\" start= auto obj= LocalSystem DisplayName= acme-api depend= Tcpip\nsc.exe description acme-api \"Acme's API\"\nsc.exe failure acme-api reset= 86400 actions= restart/1000\nsc.exe failureflag acme-api 1\n\n=== FreeBSD rc.d\n#!/bin/sh\n#\n# Generated by goframework system-service\n#\n# PROVIDE: acme_api\n# REQUIRE: LOGIN NETWORKING\n# KEYWORD: shutdown\n\n. /etc/rc.subr\n\nname=acme_api\nrcvar=acme_api_enable\ndesc='Acme'\\''s API'\n\nload_rc_config $name\n\n: ${acme_api_enable:=NO}\n\npidfile=/var/run/acme_api.pid\nprocname=/usr/sbin/daemon\ncommand=/usr/sbin/daemon\ncommand_args='-f -P /var/run/acme_api.pid -p /var/run/acme_api.child.pid -r -S -T acme-api -- /usr/local/bin/acme-api run'\n\nrun_rc_command \"$1\"\n\n=== OpenBSD rc.d\n#!/bin/ksh\n#\n# Generated by goframework system-service\n#\n# Description: Acme's API\n# DependsOn: network (rc(8) starts pkg_scripts after netstart(8))\n\ndaemon=/usr/local/bin/acme-api\ndaemon_flags=run\ndaemon_logger=daemon.notice\n\n. /etc/rc.d/rc.subr\n\npexp='/usr/local/bin/acme-api run'\nrc_bg=YES\nrc_reload=NO\n\nrc_cmd $1\n\n=== NetBSD rc.d\n#!/bin/sh\n#\n# Generated by goframework system-service\n#\n# PROVIDE: acme_api\n# REQUIRE: DAEMON NETWORKING\n# KEYWORD: shutdown\n#\n# Description: Acme's API\n\n$_rc_subr_loaded . /etc/rc.subr\n\nname=acme_api\nrcvar=$name\ncommand=/usr/local/bin/acme-api\npidfile=/var/run/acme_api.pid\nstart_cmd=acme_api_start\nstop_postcmd=acme_api_poststop\n\nacme_api_start()\n{\n\tif [ -n \"$rc_pid\" ]; then\n\t\techo 1>&2 \"${name} already running? (pid=$rc_pid).\"\n\t\treturn 1\n\tfi\n\techo \"Starting ${name}.\"\n\t{\n\t\t(\n\t\t\texec 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&-\n\t\t\texec /usr/local/bin/acme-api run\n\t\t) </dev/null 2>&1 &\n\t\techo $! >$pidfile\n\t} | {\n\t\texec 3<&0 </dev/null >/dev/null 2>&1\n\t\tlogger -p daemon.notice -t acme-api <&3 &\n\t}\n}\n\nacme_api_poststop()\n{\n\trm -f $pidfile\n}\n\nload_rc_config $name\n\n: ${acme_api:=NO}\n\nrun_rc_command \"$1\"\n\n=== OpenRC\n#!/sbin/openrc-run\n# Generated by goframework system-service\n\ndescription='Acme'\\''s API'\ncommand=/usr/local/bin/acme-api\ncommand_args=run\noutput_log=/var/log/acme-api.log\nerror_log=/var/log/acme-api.log\nsupervisor=supervise-daemon\nrespawn_delay=0\nrespawn_max=0\nretry=TERM/30/KILL/5\npidfile=/run/acme-api.pid\n\ndepend() {\n\tneed net\n}\n\nstart_pre() {\n\t[ -e /var/log/acme-api.log ] || checkpath --file --mode 0640 /var/log/acme-api.log\n}\n\n=== SysV init\n#!/bin/sh\n# Generated by goframework system-service\n### BEGIN INIT INFO\n# Provides: acme-api\n# Required-Start: $remote_fs $syslog $network\n# Required-Stop: $remote_fs $syslog $network\n# Default-Start: 2 3 4 5\n# Default-Stop: 0 1 6\n# Short-Description: Acme's API\n# Description: Acme's API\n### END INIT INFO\n\nPATH=/sbin:/usr/sbin:/bin:/usr/bin\nexport PATH\n\nname=acme-api\nexecutable=/usr/local/bin/acme-api\nstdout=/var/log/acme-api.log\nstderr=/var/log/acme-api.log\npidfile=/run/acme-api.pid\n\nfailed() {\n\techo \"$name: $executable did not start; see $stderr\" >&2\n\treturn 1\n}\n\nrunning() {\n\t/sbin/start-stop-daemon --status --pidfile \"$pidfile\" --exec \"$executable\"\n}\n\nstart() {\n\tset -- run\n\t/sbin/start-stop-daemon --start --quiet --oknodo --background --no-close \\\n\t\t--make-pidfile --pidfile \"$pidfile\" \\\n\t\t--exec \"$executable\" -- \"$@\" </dev/null >>\"$stdout\" 2>>\"$stderr\" || { failed; return; }\n\t# start-stop-daemon returns before the program runs: wait until it does.\n\ttries=50\n\tuntil running; do\n\t\tpid=$(cat \"$pidfile\" 2>/dev/null)\n\t\ttries=$((tries - 1))\n\t\tif [ \"$tries\" -eq 0 ] || { [ -n \"$pid\" ] && ! kill -0 \"$pid\" 2>/dev/null; }; then\n\t\t\tfailed\n\t\t\treturn\n\t\tfi\n\t\tsleep 0.1\n\tdone\n}\n\nstop() {\n\t/sbin/start-stop-daemon --stop --quiet --oknodo --retry TERM/30/KILL/5 --pidfile \"$pidfile\" --exec \"$executable\" || return 1\n\trm -f \"$pidfile\"\n}\n\ncase \"$1\" in\nstart)\n\tstart\n\t;;\nstop)\n\tstop\n\t;;\nrestart|force-reload)\n\tstop && start\n\t;;\nstatus)\n\trunning\n\tcode=$?\n\tcase $code in\n\t0) echo \"$name is running\" ;;\n\t1) echo \"$name is not running but $pidfile exists\" ;;\n\t3) echo \"$name is not running\" ;;\n\t*) echo \"$name: unable to determine the status\" ;;\n\tesac\n\texit \"$code\"\n\t;;\n*)\n\techo \"Usage: $0 {start|stop|restart|force-reload|status}\" >&2\n\texit 3\n\t;;\nesac\n"},{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go build ./services/everywhere for linux, run on a Debian 13 VM, as root (service demo)","exit":0,"name":"systemd","output":"installing acme-api with this system's service manager\nstatus: running, enabled at boot: true, running: true\nafter Stop: stopped\nuninstalled\n"},{"system":"windows","go":"go1.27.2","date":"2026-10-17","command":"go build ./services/everywhere for windows, run on a Windows Server 2025 VM, as Administrator (service demo)","exit":0,"name":"Windows SCM","output":"installing acme-api with this system's service manager\nstatus: running, enabled at boot: true, running: true\nafter Stop: stopped\nuninstalled\n"},{"system":"freebsd","go":"go1.27.2","date":"2026-10-17","command":"go build ./services/everywhere for freebsd, run on a FreeBSD 15.1 VM, as root (service demo)","exit":0,"name":"FreeBSD rc.d","output":"installing acme-api with this system's service manager\nstatus: running, enabled at boot: true, running: true\nafter Stop: stopped\nuninstalled\n"}],"file":"www-examples/services/everywhere/main.go"}},{"type":"table","title":"What each service manager can do","source":"system-service-all/readme","table":"user services","caption":"From system-service-all's readme. A setting a manager cannot honour is an error naming the field (ErrUnsupported); all.Supports answers first. The last row names the system each was verified on, in throwaway QEMU VMs and on a Mac.","header":["","systemd","SysV","OpenRC","launchd","Windows","FreeBSD","OpenBSD","NetBSD"],"rows":[["user services","yes","–","–","yes","–","–","–","–"],["restart on failure","yes","–","–","yes","yes","–","–","–"],["restart always","yes","–","yes","yes","yes¹","yes","–","–"],["working directory","yes","yes","yes","yes","–","yes","yes","yes"],["environment","yes","yes","yes","yes","yes","yes","yes","yes"],["group","yes","yes","yes","yes","–","–","–","yes"],["log files","yes","yes","yes","yes","–","one file²","most²","yes"],["start after the network","yes","yes","yes","–","yes³","yes","yes","yes"],["verified","Debian 13 VM","Debian 13 VM, sysvinit","Alpine 3.24 VM","macOS 15.8","Windows Server 2025 VM","FreeBSD 15.1 VM","OpenBSD 7.9 VM","NetBSD 11.0 VM"]],"heading":"what each manager supports"},{"type":"table","title":"Compared with the Go libraries people use","source":"system-service-all/readme","table":"system-service","caption":"Checked on 2026-10-12 against each library's code and open issues.","header":["","system-service","kardianos/service","takama/daemon"],"rows":[["systemd","system and user units","user units never start (multi-user.target)","system units"],["SysV","LSB scripts, update-rc.d","own rcN.d links","yes"],["OpenRC","yes","yes","–"],["Upstart","– (unmaintained since 2014)","yes","yes"],["launchd","bootstrap, bootout, enable","legacy load, unload","legacy load, unload"],["Windows","least access; passwords only at install","yes","LocalSystem only"],["FreeBSD rc.d","rcvar, daemon(8), sysrc","no rcvar (#391)","yes, no restart"],["OpenBSD, NetBSD","yes","– (#139, open since 2018)","–"],["run as a service","SCM with progress, signals, systemd readiness","SCM without progress, signals","minimal"],["restart","never, on-failure, always, delay","systemd only, delay fixed at 120 s","fixed per platform"],["a setting the platform lacks","error naming the field","ignored","ignored"],["values in generated files","quoted for every format, proven by running them","partly (unquoted systemd Environment)","plists not escaped"],["status","state, PID, enabled, from machine-readable output","running, stopped, unknown","text"],["existing services","open any (sshd, cron), write back losslessly where the format is data","–","–"],["render without installing","yes","–","–"],["third-party modules","none","golang.org/x/sys","golang.org/x/sys, robfig/cron in go.mod"],["last release","–","v1.3.0, July 2026","v1.0.0, July 2020"]],"heading":"compared with other Go libraries"},{"type":"facts","items":[{"value":"1.9 MB","label":"a program that only runs as a service, stripped, on Linux: the installers are not linked","source":"system-service-all/readme"}]}],"sample":{"code":"executable, _ := os.Executable()\nservice := spec.Service{\n\tName: \"acme-api\",\n\tDescription: \"Acme's API\",\n\tExecutable: executable,\n\tArgs: []string{\"run\"},\n\tStartAtBoot: true,\n}\n// …\nfunc install() error {\n\tmanager, err := all.New(definition(all.Supports)) // this system's: systemd, launchd, the SCM, rc.d...\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := manager.Install(); err != nil { // writes its file, enables it at boot\n\t\treturn err\n\t}\n\treturn manager.Start()\n}","example":"services/everywhere","title":"One service definition, every service manager","file":"www-examples/services/everywhere/main.go","output":"### every manager asked for restart on failure, after the network\nsystemd ok\nlaunchd (macOS) refused: not supported by this service manager: DependsOn: launchd does not support depending on the network\nWindows Service Control Manager ok\nFreeBSD rc.d refused: not supported by this service manager: Restart: FreeBSD rc.d does not support restarting on failure only\nOpenBSD rc.d refused: not supported by this service manager: Restart: OpenBSD rc.d does not support restarting on failure only\nNetBSD rc.d refused: not supported by this service manager: Restart: NetBSD rc.d does not support restarting on failure only\nOpenRC refused: not supported by this service manager: Restart: openrc does not support restarting on failure only\nSysV init refused: not supported by this service manager: Restart: sysv does not support restarting on failure only\n\n### each asked for what it supports\n=== systemd\n# Generated by goframework system-service\n\n[Unit]\nDescription=Acme's API\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=exec\nExecStart=/usr/local/bin/acme-api run\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n\n=== launchd (macOS)\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<!-- Generated by goframework system-service -->\n<plist version=\"1.0\">\n<dict>\n\t<key>Label</key>\n\t<string>acme-api</string>\n\t<key>ServiceDescription</key>\n\t<string>Acme's API</string>\n\t<key>ProgramArguments</key>\n\t<array>\n\t\t<string>/usr/local/bin/acme-api</string>\n\t\t<string>run</string>\n\t</array>\n\t<key>RunAtLoad</key>\n\t<true/>\n\t<key>KeepAlive</key>\n\t<dict>\n\t\t<key>SuccessfulExit</key>\n\t\t<false/>\n\t</dict>\n</dict>\n</plist>\n\n=== Windows Service Control Manager\n@echo off\nrem Generated by goframework system-service\nsc.exe create acme-api binPath= \"\\\"C:\\Program Files\\Acme\\acme-api.exe\\\" run\" start= auto obj= LocalSystem DisplayName= acme-api depend= Tcpip\nsc.exe description acme-api \"Acme's API\"\nsc.exe failure acme-api reset= 86400 actions= restart/1000\nsc.exe failureflag acme-api 1\n\n=== FreeBSD rc.d\n#!/bin/sh\n#\n# Generated by goframework system-service\n#\n# PROVIDE: acme_api\n# REQUIRE: LOGIN NETWORKING\n# KEYWORD: shutdown\n\n. /etc/rc.subr\n\nname=acme_api\nrcvar=acme_api_enable\ndesc='Acme'\\''s API'\n\nload_rc_config $name\n\n: ${acme_api_enable:=NO}\n\npidfile=/var/run/acme_api.pid\nprocname=/usr/sbin/daemon\ncommand=/usr/sbin/daemon\ncommand_args='-f -P /var/run/acme_api.pid -p /var/run/acme_api.child.pid -r -S -T acme-api -- /usr/local/bin/acme-api run'\n\nrun_rc_command \"$1\"\n\n=== OpenBSD rc.d\n#!/bin/ksh\n#\n# Generated by goframework system-service\n#\n# Description: Acme's API\n# DependsOn: network (rc(8) starts pkg_scripts after netstart(8))\n\ndaemon=/usr/local/bin/acme-api\ndaemon_flags=run\ndaemon_logger=daemon.notice\n\n. /etc/rc.d/rc.subr\n\npexp='/usr/local/bin/acme-api run'\nrc_bg=YES\nrc_reload=NO\n\nrc_cmd $1\n\n=== NetBSD rc.d\n#!/bin/sh\n#\n# Generated by goframework system-service\n#\n# PROVIDE: acme_api\n# REQUIRE: DAEMON NETWORKING\n# KEYWORD: shutdown\n#\n# Description: Acme's API\n\n$_rc_subr_loaded . /etc/rc.subr\n\nname=acme_api\nrcvar=$name\ncommand=/usr/local/bin/acme-api\npidfile=/var/run/acme_api.pid\nstart_cmd=acme_api_start\nstop_postcmd=acme_api_poststop\n\nacme_api_start()\n{\n\tif [ -n \"$rc_pid\" ]; then\n\t\techo 1>&2 \"${name} already running? (pid=$rc_pid).\"\n\t\treturn 1\n\tfi\n\techo \"Starting ${name}.\"\n\t{\n\t\t(\n\t\t\texec 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&-\n\t\t\texec /usr/local/bin/acme-api run\n\t\t) </dev/null 2>&1 &\n\t\techo $! >$pidfile\n\t} | {\n\t\texec 3<&0 </dev/null >/dev/null 2>&1\n\t\tlogger -p daemon.notice -t acme-api <&3 &\n\t}\n}\n\nacme_api_poststop()\n{\n\trm -f $pidfile\n}\n\nload_rc_config $name\n\n: ${acme_api:=NO}\n\nrun_rc_command \"$1\"\n\n=== OpenRC\n#!/sbin/openrc-run\n# Generated by goframework system-service\n\ndescription='Acme'\\''s API'\ncommand=/usr/local/bin/acme-api\ncommand_args=run\noutput_log=/var/log/acme-api.log\nerror_log=/var/log/acme-api.log\nsupervisor=supervise-daemon\nrespawn_delay=0\nrespawn_max=0\nretry=TERM/30/KILL/5\npidfile=/run/acme-api.pid\n\ndepend() {\n\tneed net\n}\n\nstart_pre() {\n\t[ -e /var/log/acme-api.log ] || checkpath --file --mode 0640 /var/log/acme-api.log\n}\n\n=== SysV init\n#!/bin/sh\n# Generated by goframework system-service\n### BEGIN INIT INFO\n# Provides: acme-api\n# Required-Start: $remote_fs $syslog $network\n# Required-Stop: $remote_fs $syslog $network\n# Default-Start: 2 3 4 5\n# Default-Stop: 0 1 6\n# Short-Description: Acme's API\n# Description: Acme's API\n### END INIT INFO\n\nPATH=/sbin:/usr/sbin:/bin:/usr/bin\nexport PATH\n\nname=acme-api\nexecutable=/usr/local/bin/acme-api\nstdout=/var/log/acme-api.log\nstderr=/var/log/acme-api.log\npidfile=/run/acme-api.pid\n\nfailed() {\n\techo \"$name: $executable did not start; see $stderr\" >&2\n\treturn 1\n}\n\nrunning() {\n\t/sbin/start-stop-daemon --status --pidfile \"$pidfile\" --exec \"$executable\"\n}\n\nstart() {\n\tset -- run\n\t/sbin/start-stop-daemon --start --quiet --oknodo --background --no-close \\\n\t\t--make-pidfile --pidfile \"$pidfile\" \\\n\t\t--exec \"$executable\" -- \"$@\" </dev/null >>\"$stdout\" 2>>\"$stderr\" || { failed; return; }\n\t# start-stop-daemon returns before the program runs: wait until it does.\n\ttries=50\n\tuntil running; do\n\t\tpid=$(cat \"$pidfile\" 2>/dev/null)\n\t\ttries=$((tries - 1))\n\t\tif [ \"$tries\" -eq 0 ] || { [ -n \"$pid\" ] && ! kill -0 \"$pid\" 2>/dev/null; }; then\n\t\t\tfailed\n\t\t\treturn\n\t\tfi\n\t\tsleep 0.1\n\tdone\n}\n\nstop() {\n\t/sbin/start-stop-daemon --stop --quiet --oknodo --retry TERM/30/KILL/5 --pidfile \"$pidfile\" --exec \"$executable\" || return 1\n\trm -f \"$pidfile\"\n}\n\ncase \"$1\" in\nstart)\n\tstart\n\t;;\nstop)\n\tstop\n\t;;\nrestart|force-reload)\n\tstop && start\n\t;;\nstatus)\n\trunning\n\tcode=$?\n\tcase $code in\n\t0) echo \"$name is running\" ;;\n\t1) echo \"$name is not running but $pidfile exists\" ;;\n\t3) echo \"$name is not running\" ;;\n\t*) echo \"$name: unable to determine the status\" ;;\n\tesac\n\texit \"$code\"\n\t;;\n*)\n\techo \"Usage: $0 {start|stop|restart|force-reload|status}\" >&2\n\texit 3\n\t;;\nesac\n","system":"linux","runs":4},"measured":[],"competition":[{"title":"The leading Go libraries for services","source":"competition.md","heading":"2. System services","blocks":[{"t":"table","header":["Library","★","Release","Service managers","User services","Reads existing services"],"rows":[["kardianos/service","4.8k","v1.3.0, 2026-07-06","systemd, SysV, Upstart, OpenRC, procd, rcS, launchd, SCM, FreeBSD rc, Solaris SMF, AIX","yes","its own status only"],["takama/daemon","2.0k","v1.0.0, 2020","systemd, SysV, Upstart, launchd, Windows, FreeBSD","macOS agents","no"],["coreos/go-systemd","2.7k","v22.7.0, 2026-01","systemd only: socket activation, notify, D-Bus, journal, logind","via the user bus","yes"],["x/sys/windows/svc","—","x/sys v0.49.0","the SCM, recovery actions, Event Log","—","yes"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"install, uninstall, start, stop, restart, status;","items":[]},{"text":"detecting interactive versus managed runs;","items":[]},{"text":"systemd, launchd and the SCM at least, plus user services;","items":[]},{"text":"restart policy with backoff;","items":[]},{"text":"account, environment and working directory;","items":[]},{"text":"log redirection;","items":[]},{"text":"readiness and watchdog;","items":[]},{"text":"Windows recovery actions.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"no OpenBSD rcctl, NetBSD rc, runit, s6 or dinit anywhere;","items":[]},{"text":"reading existing services across systems;","items":[]},{"text":"typed options (kardianos takes a `map[string]interface{}`);","items":[]},{"text":"dependencies unimplemented in kardianos on Linux and launchd.","items":[]}]},{"t":"p","text":"goframework covers everything but runit, s6 and dinit."}]},{"title":"Compared with other Go libraries","source":"system-service-all/readme","heading":"compared with other Go libraries","blocks":[{"t":"p","text":"Checked on 2026-10-12 against the libraries' current code and issues."},{"t":"table","header":["","**system-service**","kardianos/service","takama/daemon"],"rows":[["systemd","system and user units","user units never start (multi-user.target)","system units"],["SysV","LSB scripts, update-rc.d","own rcN.d links","yes"],["OpenRC","yes","yes","–"],["Upstart","– (unmaintained since 2014)","yes","yes"],["launchd","bootstrap, bootout, enable","legacy load, unload","legacy load, unload"],["Windows","least access; passwords only at install","yes","LocalSystem only"],["FreeBSD rc.d","rcvar, daemon(8), sysrc","no rcvar (#391)","yes, no restart"],["OpenBSD, NetBSD","yes","– (#139, open since 2018)","–"],["run as a service","SCM with progress, signals, systemd readiness","SCM without progress, signals","minimal"],["restart","never, on-failure, always, delay","systemd only, delay fixed at 120 s","fixed per platform"],["a setting the platform lacks","error naming the field","ignored","ignored"],["values in generated files","quoted for every format, proven by running them","partly (unquoted systemd Environment)","plists not escaped"],["status","state, PID, enabled, from machine-readable output","running, stopped, unknown","text"],["existing services","open any (sshd, cron), write back losslessly where the format is data","–","–"],["render without installing","yes","–","–"],["third-party modules","none","`golang.org/x/sys`","`golang.org/x/sys`, `robfig/cron` in go.mod"],["last release","–","v1.3.0, July 2026","v1.0.0, July 2020"]]}]}]},{"id":"terminal","title":"Terminal","pattern":"terminal*","purpose":"Programs for the terminal: colours, progress, keys and full-screen interfaces.","replaces":["golang.org/x/term","fatih/color","schollz/progressbar and vbauerster/mpb","rivo/uniseg and go-runewidth","escape sequences written by hand"],"modules":["terminal","terminal-colors","terminal-progress-spec","terminal-progress-line","terminal-progress-tree","terminal-progress-samples","terminal-ui","terminal-ui-widgets","terminal-ui-samples"],"blocks":[{"type":"example","id":"terminal/colors","example":{"id":"terminal/colors","title":"Colour on a terminal, none in a log file","problem":"A build tool prints warnings in colour. Its output is often a file or a CI log, where escape sequences show up as noise, and terminals differ in how many colours they show.","notice":"The sequence written by hand lands in the log file as it is. terminal-colors finds that the file is no terminal and writes the text alone. On a terminal the same style becomes RGB, the nearest of the 256-colour palette or the nearest of the 16 ANSI colours, by what the terminal shows. This page shows text: the sequences are printed quoted, \\x1b being the escape character.","parts":[{"name":"usual","label":"an escape sequence by hand","code":"fmt.Fprintf(log, \"\\x1b[1;38;2;255;128;0m%s\\x1b[0m\\n\", \"disk almost full\") // bold orange, wherever it goes"},{"name":"with","label":"terminal-colors","code":"warning := colors.Style{Foreground: colors.RGB(255, 128, 0), Attributes: colors.Bold}\nprofile := colors.Detect(log) // a file is no terminal: Plain, unless FORCE_COLOR is set\nfmt.Fprintln(log, warning.Render(profile, \"disk almost full\"))"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./terminal/colors","exit":0,"name":"linux","output":"escape sequence written by hand, into build.log:\n \"\\x1b[1;38;2;255;128;0mdisk almost full\\x1b[0m\\n\"\n\nterminal-colors, into build.log (profile plain):\n \"disk almost full\\n\"\n\nthe same style, as each kind of terminal gets it:\n true colour #ff8000 \"\\x1b[0;1;38;2;255;128;0mdisk almost full\\x1b[0m\"\n 256 colours palette 208 \"\\x1b[0;1;38;5;208mdisk almost full\\x1b[0m\"\n 16 colours yellow \"\\x1b[0;1;33mdisk almost full\\x1b[0m\"\n plain no colour \"disk almost full\"\n\nthis program's standard output: no terminal, profile plain\n"}],"file":"www-examples/terminal/colors/main.go"}},{"type":"example","id":"terminal/progress","example":{"id":"terminal/progress","title":"Progress on a terminal, plain lines in a log","problem":"A long download shows its progress. On a terminal the line should move in place; in a CI log or a redirected file it should not fill the log with carriage returns and escape sequences.","notice":"The carriage returns written by hand all land in the log, on one line. terminal-progress-line finds that the file is no terminal and writes a plain line when the task starts and when it ends. On a terminal the same display draws one line in place; this page shows no terminal, so the screen of a terminal in memory is printed as text.","parts":[{"name":"usual","label":"a carriage return by hand","code":"for done := int64(0); done <= size; done += 12 << 20 {\n\tfmt.Fprintf(log, \"\\rDownloading %3d%%\", done*100/size) // \\r: back to the start of the line\n\ttime.Sleep(100 * time.Millisecond)\n}\nfmt.Fprintln(log)"},{"name":"with","label":"terminal-progress-line","code":"display := line.New(spec.Options{Output: log}) // a file is no terminal: plain lines\ndownload := display.Add(\"Downloading\")\nfor done := int64(0); done <= size; done += 12 << 20 {\n\tdownload.Progress(done, size)\n\tdownload.Message(fmt.Sprintf(\"%d of %d MiB\", done>>20, size>>20))\n\ttime.Sleep(100 * time.Millisecond)\n}\ndownload.Succeed()\ndisplay.Close()"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./terminal/progress","exit":0,"name":"linux","output":"a carriage return written by hand, into ci.log:\n \"\\rDownloading 0%\\rDownloading 25%\\rDownloading 50%\\rDownloading 75%\\rDownloading 100%\\n\"\n\nterminal-progress-line, into ci.log:\n \"▶ Downloading\\n\"\n \"✓ Downloading 48 of 48 MiB 500ms\\n\"\n\nthe same display on a terminal of 60 columns, halfway and at the end:\n ┌────────────────────────────────────────────────────────────┐\n │⠼ Downloading 24 of 48 MiB █████ 50% │\n │ │\n └────────────────────────────────────────────────────────────┘\n ┌────────────────────────────────────────────────────────────┐\n │✓ Downloading 48 of 48 MiB 752ms │\n │ │\n └────────────────────────────────────────────────────────────┘\n"}],"file":"www-examples/terminal/progress/main.go"}},{"type":"example","id":"terminal/widths","example":{"id":"terminal/widths","title":"Fit any name in a column","problem":"A file list puts names in a column of fixed width and cuts the long ones. Names hold accents, Chinese or Japanese characters and emoji, which a terminal draws in zero, one or two cells: counting runes, as fmt's padding does, leaves the column ragged.","notice":"A rune is not a cell: a combining accent takes none, a CJK character two, a family of three people joined into one emoji two, where GNOME Terminal, xterm and Alacritty draw six. Cut to 8 runes, the names take 5 to 10 cells; cut by terminal/cells, each takes 8, cut between characters. The names are printed last and quoted, since this page's font does not draw wide characters in exactly two cells.","parts":[{"name":"usual","label":"runes","code":"for _, name := range names {\n\trunes := []rune(name)\n\tcut := string(runes[:min(8, len(runes))])\n\tfmt.Printf(\"%5d %q → %q\\n\", utf8.RuneCountInString(name), name, cut) // fmt's %-8s counts runes too\n}"},{"name":"with","label":"terminal/cells","code":"for _, name := range names {\n\tcut := cells.Truncate(name, 8, \"…\")\n\tfmt.Printf(\"%5d %5d %q → %q\\n\", cells.Width(name), cells.CodePoints.Width(name), name, cut)\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./terminal/widths","exit":0,"name":"linux","output":"runes; the name, cut to 8 runes:\n 9 \"notes.txt\" → \"notes.tx\"\n 12 \"résumé.pdf\" → \"résumé\"\n 7 \"日本語.txt\" → \"日本語.txt\"\n 14 \"👨\\u200d👩\\u200d👧 trip.jpg\" → \"👨\\u200d👩\\u200d👧 tr\"\n 11 \"❤️ love.txt\" → \"❤️ love.\"\n\ncells; cells where a code point is drawn at a time; the name, cut to 8 cells:\n 9 9 \"notes.txt\" → \"notes.t…\"\n 10 10 \"résumé.pdf\" → \"résumé.…\"\n 10 10 \"日本語.txt\" → \"日本語.…\"\n 11 15 \"👨\\u200d👩\\u200d👧 trip.jpg\" → \"👨\\u200d👩\\u200d👧 trip…\"\n 11 10 \"❤️ love.txt\" → \"❤️ love…\"\n\nhow wide each cut is, in cells: cut to 8 runes, cut by terminal/cells:\n 8 8 \"notes.txt\"\n 6 8 \"résumé.pdf\"\n 10 8 \"日本語.txt\"\n 5 8 \"👨\\u200d👩\\u200d👧 trip.jpg\"\n 8 8 \"❤️ love.txt\"\n"}],"file":"www-examples/terminal/widths/main.go"}},{"type":"stages","title":"From the device to an application","intro":"Each layer is a module of its own, built on the one below, so a program links only what it uses.","stages":[{"label":"TERMINAL","text":"The device: whether a file is a terminal, its size and resizes, raw mode, passwords. Keys, the mouse, pasted text and focus as events: legacy bytes, kitty's keyboard protocol, modifyOtherKeys, win32-input-mode and Windows' key records come out the same. Every escape sequence, and a parser for them. Widths in grapheme clusters. A terminal in memory for tests.","modules":["terminal"]},{"label":"COLOURS","text":"Styles fitted to the terminal: RGB, the 256-colour palette, the 16 ANSI colours, or nothing for output that is no terminal.","modules":["terminal-colors"]},{"label":"PROGRESS","text":"Tasks a program reports on, drawn in place as one line or a tree on a terminal, written as plain lines anywhere else; log lines go above them.","modules":["terminal-progress-spec","terminal-progress-line","terminal-progress-tree","terminal-progress-samples"]},{"label":"APPLICATIONS","text":"A retained tree of widgets with a box model and flexbox layouts, focus, commands, layers and themes, full screen or inline; the terminal given back as found on return, a panic, a signal or Ctrl+Z.","modules":["terminal-ui","terminal-ui-widgets","terminal-ui-samples"]}]},{"type":"table","title":"How the colours are chosen","source":"terminal-colors/readme","table":"Environment","caption":"From terminal-colors' readme. NO_COLOR wins over everything. On Windows, Detect switches the console's escape sequences on; the legacy console, which cannot, is Plain.","header":["Environment","Profile"],"rows":[["NO_COLOR set, CLICOLOR=0, TERM=dumb","Plain"],["FORCE_COLOR 1, 2 or 3, CLICOLOR_FORCE","colours even into a pipe or file"],["not a terminal","Plain"],["COLORTERM=truecolor or 24bit; kitty, ghostty, WezTerm, iTerm2, foot, Alacritty, VS Code; a Windows console","TrueColor"],["TERM with 256color, Apple's Terminal","ANSI256"],["any other terminal","ANSI16"]],"heading":"detection"},{"type":"table","title":"How wide text is","source":"terminal/readme","table":"Text","caption":"From terminal's readme: grapheme clusters by Unicode's rules (UAX #29), measured as current terminals draw them. Terminals that place a code point at a time (GNOME Terminal, xterm, Alacritty) are measured by a second model.","header":["Text","Cells"],"rows":[["e and a combining accent","1"],["日本, hi","4"],["😀, 👍🏽, 👨‍👩‍👧, 🇩🇪","2 each"],["❤ / ❤️ (with VS16)","1 / 2"],["controls, zero width space","0"]],"heading":"widths"},{"type":"table","title":"The widgets","source":"terminal-ui-widgets/readme","table":"Widget","caption":"From terminal-ui-widgets' readme. A list, table or tree holds no data: it asks for the rows it shows.","header":["Widget","Shows","Keys and mouse"],"rows":[["Label","text, wrapped or not, aligned",""],["Button","a titled button that runs a function","Enter, Space, a click"],["Checkbox, Switch","on or off","Enter, Space, a click"],["RadioGroup","one choice of several","Up, Down; Enter, Space, a click"],["Input","a line of text: placeholder, mask, limit, validation","the shell's: Left, Right, Home, End, Ctrl+A, Ctrl+E, word moves with Ctrl or Alt, Backspace, Delete, Ctrl+W, Ctrl+U, Ctrl+K, Enter; paste; a click puts the cursor"],["List","rows drawn on demand, any number","Up, Down, Page Up, Page Down, Home, End, Enter; a click, the wheel"],["Table","rows of cells under titles; columns Auto, Cells, Fill or Percent","as List; a click on a title asks to sort"],["Tree","nodes holding others, loaded when first expanded","Up, Down, Right, Left, Space, Enter; a click, ▸ ▾"],["Tabs","one page of several","Left, Right; Ctrl+Page Up, Ctrl+Page Down; a click"],["ScrollView","a child taller than its room, following the focus","Page Up, Page Down, Ctrl+Home, Ctrl+End; the wheel"],["Split","two widgets with a divider","the mouse drags it"],["Confirm, Alert","a dialog with a message and buttons","Tab, Enter; Escape cancels"],["ShowMenu, MenuBar","menus of items, separators, disabled items, keys shown","Up, Down, Enter; Left and Right between a bar's menus; a click"],["Notify","a toast at the bottom right, for four seconds; they stack",""],["KeyHints","the keys of the commands active where the focus is",""],["StatusBar","text at the left and the right",""],["OpenCommandPalette, AddCommandPalette","the active commands, filtered as typed","typing, Up, Down, Enter; Escape"],["Spinner, ProgressBar","something running; how much is done, to an eighth of a cell",""]],"heading":"terminal-ui-widgets"},{"type":"table","title":"The Go libraries people use","source":"competition.md","table":"7. Terminal","caption":"From competition.md, checked on 2026-10-14.","header":["Library","★","Release","Notes"],"rows":[["bubbletea v2, bubbles, lipgloss v2, huh v2","45.4k / 9.0k / 11.9k / 7.2k","v2.1.0, 2026-10","Elm architecture, cell renderer, color downsampling, forms with an accessible mode; moved to charm.land; 8 dependencies"],["tview + tcell v3","14.1k / 5.2k","2025–2026","widgets"],["pterm","5.6k","2026-10","tables, trees, charts, spinners, progress, prompts"],["fatih/color","8.0k","2026-03","ANSI colors, NO_COLOR"],["vbauerster/mpb v8","2.5k","2026-09","several bars, dynamic totals, ETA"],["schollz/progressbar v3","4.7k","2026-06","one bar"],["golang.org/x/term","—","2026-10","raw mode, size, password reading"]],"heading":"7. Terminal"},{"type":"facts","items":[{"value":"13.5 KB","label":"the width tables, generated from Unicode's files, with a lookup in constant time","source":"terminal/readme"},{"value":"about 0.5 ns","label":"to measure a character of printable ASCII, without allocating","source":"terminal/readme"},{"value":"6","label":"systems terminal's tests passed on, with and without a terminal: Debian 13, macOS 15, Windows Server 2025 and Windows 11, FreeBSD 15.1, OpenBSD 7.9, NetBSD 11.0","source":"terminal/readme"}]}],"sample":{"code":"warning := colors.Style{Foreground: colors.RGB(255, 128, 0), Attributes: colors.Bold}\nprofile := colors.Detect(log) // a file is no terminal: Plain, unless FORCE_COLOR is set\nfmt.Fprintln(log, warning.Render(profile, \"disk almost full\"))","example":"terminal/colors","title":"Colour on a terminal, none in a log file","file":"www-examples/terminal/colors/main.go","output":"escape sequence written by hand, into build.log:\n \"\\x1b[1;38;2;255;128;0mdisk almost full\\x1b[0m\\n\"\n\nterminal-colors, into build.log (profile plain):\n \"disk almost full\\n\"\n\nthe same style, as each kind of terminal gets it:\n true colour #ff8000 \"\\x1b[0;1;38;2;255;128;0mdisk almost full\\x1b[0m\"\n 256 colours palette 208 \"\\x1b[0;1;38;5;208mdisk almost full\\x1b[0m\"\n 16 colours yellow \"\\x1b[0;1;33mdisk almost full\\x1b[0m\"\n plain no colour \"disk almost full\"\n\nthis program's standard output: no terminal, profile plain\n","system":"linux","runs":1},"measured":[],"competition":[{"title":"The leading Go libraries for terminals","source":"competition.md","heading":"7. Terminal","blocks":[{"t":"table","header":["Library","★","Release","Notes"],"rows":[["bubbletea v2, bubbles, lipgloss v2, huh v2","45.4k / 9.0k / 11.9k / 7.2k","v2.1.0, 2026-10","Elm architecture, cell renderer, color downsampling, forms with an accessible mode; moved to `charm.land`; 8 dependencies"],["tview + tcell v3","14.1k / 5.2k","2025–2026","widgets"],["pterm","5.6k","2026-10","tables, trees, charts, spinners, progress, prompts"],["fatih/color","8.0k","2026-03","ANSI colors, NO_COLOR"],["vbauerster/mpb v8","2.5k","2026-09","several bars, dynamic totals, ETA"],["schollz/progressbar v3","4.7k","2026-06","one bar"],["golang.org/x/term","—","2026-10","raw mode, size, password reading"]]},{"t":"p","text":"**The bar:**"},{"t":"list","ordered":false,"items":[{"text":"color profile detection (NO_COLOR, CLICOLOR_FORCE, COLORTERM, Windows VT) with downsampling, and plain output when not on a terminal;","items":[]},{"text":"Unicode-aware widths, resize handling;","items":[]},{"text":"raw mode and passwords;","items":[]},{"text":"modern keyboard, paste and mouse;","items":[]},{"text":"hyperlinks and clipboard;","items":[]},{"text":"an accessible mode;","items":[]},{"text":"prompts with validation;","items":[]},{"text":"several bars with ETA;","items":[]},{"text":"tables and trees.","items":[]}]},{"t":"p","text":"**Gaps:**"},{"t":"list","ordered":false,"items":[{"text":"lighter dependency trees;","items":[]},{"text":"API stability;","items":[]},{"text":"maintained prompt libraries (survey is archived, promptui stale);","items":[]},{"text":"accessibility.","items":[]}]}]},{"title":"Go terminal UI frameworks compared","source":"terminal-ui.md","heading":"Go comparison","blocks":[{"t":"table","header":["","Bubble Tea v2","tcell v3","tview","vaxis","gocui (lazygit)","go-tui"],"rows":[["latest","v2.1.0, 2026-10","v3.6.0, 2026-10","v0.42.0, 2025-08","v0.17.1, 2026-07","in tree","v0.22.1, 2026-09"],["modules","17 (+UV unversioned)","7","11","3","tcell v3","4"],["rendering","string view re-parsed, full-frame diff on a tick","dirty cells","full redraw, tcell diff","immediate cells, diff","named views","element tree, double buffer"],["layout","Join/Place; UV Cassowary","none","Flex, Grid, Pages","constraints, flex","coordinates","flexbox"],["inline","yes","no","no","yes","no","yes"],["kitty keyboard","yes","yes","no","yes","through tcell","?"],["images","no","sixel with LockRegion","blocks","kitty, sixel","no","no"],["testing","teatest (experimental), x/vttest","private mocks","SimulationScreen (v2)","ui/uitest","own harness","examples"]]}]},{"title":"Terminal UI frameworks in the field","source":"terminal-ui.md","heading":"the field","blocks":[{"t":"table","header":["Framework","Model","Best at","Weak at"],"rows":[["Bubble Tea v2 (Go, 2026-02) with Lip Gloss, Bubbles, Ultraviolet","Elm architecture; the view is a string","modern protocols (kitty keyboard, 2026, 2027), a declarative terminal-state struct, SSH serving (Wish), inline mode","strings re-parsed per frame; 60 fps pacing delays input (13 ms measured, 1.3 ms with an immediate flush, #1857); no layout engine; nested-model boilerplate; terminal left broken after panics; no images (open since 2021)"],["tcell v3 (Go)","retained cells with dirty diff","the low level: no terminfo, kitty keyboard, 7 modules","its VT test emulator is private; needs env overrides when terminals answer queries wrongly"],["tview (Go)","retained widget tree on tcell v2","forms, tables, trees, flex and grid layouts","one maintainer, stuck on tcell 2.8 and Go 1.18: no CSI u keys, no clipboard"],["vaxis (Go)","immediate cells; a Flutter-like `ui` package","query-only capability detection, kitty graphics and sixel, inline live region, headless test runner","pre-1.0, small community, API churn"],["go-tui, Terma (Go, 2026)","templates with flexbox; Textual-like","Terma is by Textual's author, and Posting 3 is built on it: the closest Go competitor","young"],["Ratatui (Rust)","immediate mode, double buffer, Cassowary","the widget ecosystem a stable core crate allows; snapshot tests; Codex","async and state left to the application"],["Textual (Python)","retained DOM, reactive attributes, CSS","productivity: TCSS, about 37 widgets, workers, devtools with live CSS, browser serving, SVG snapshot tests","Python; its company wound down in 2025"],["Ink (JS)","React with Yoga flexbox","inline by default, the only real screen-reader mode; Gemini CLI, Copilot CLI","full redraw by default, widgets in separate packages"],["OpenTUI (TS and Zig)","a native core owns the scene tree","speed of its v0.6 core; Markdown, code, diff and terminal widgets; OpenCode","0.x, near-daily breaking changes, needs Bun"],["FTXUI, notcurses, Terminal.Gui, Brick, Cursive","various","FTXUI's print-above inline mode, notcurses' images and video, Terminal.Gui's 50+ widgets with joined borders",""]]},{"t":"p","text":"The trend of 2025 and 2026 comes from AI coding tools: Claude Code, Codex, Gemini CLI and OpenCode are mostly inline, with flicker-free diffing, streaming Markdown and a good multi-line editor. terminal-progress's kit already has the inline primitive: a live region with finished lines committed to scrollback."}]},{"title":"Programming models compared","source":"terminal-ui.md","heading":"programming models compared","blocks":[{"t":"p","text":"How an application describes its screen and reacts to input. A counter with a \"+\" button, sketched in each:"},{"t":"code","text":"// Elm (Bubble Tea): one model, messages, a view computed from the model\nfunc (m model) Update(msg Msg) (Model, Cmd) {\n\tif key, ok := msg.(KeyMsg); ok && key.String() == \"+\" {\n\t\tm.count++\n\t}\n\treturn m, nil\n}\nfunc (m model) View() string { return fmt.Sprintf(\"count: %d\", m.count) }\n\n// immediate mode (Ratatui, Dear ImGui): every frame redraws from the state\ndraw(func(frame *Frame) {\n\tframe.Text(top, fmt.Sprintf(\"count: %d\", count))\n\tif frame.Button(bottom, \"+\") { // true when clicked during this frame\n\t\tcount++\n\t}\n})\n\n// retained tree (tview, Textual, Terminal.Gui, GUI toolkits): widgets live on\nlabel := ui.NewLabel(\"count: 0\")\nbutton := ui.NewButton(\"+\", func() { count++; label.SetText(fmt.Sprintf(\"count: %d\", count)) })\napp.Run(ui.Column(label, button))\n\n// declarative components (React and Ink, Flutter, SwiftUI): a function of state, reconciled\nfunc Counter(c *ui.Context) ui.Node {\n\tcount := ui.UseState(c, 0)\n\treturn ui.Column(ui.Text(fmt.Sprintf(\"count: %d\", count.Get())), ui.Button(\"+\", func() { count.Set(count.Get() + 1) }))\n}"},{"t":"table","header":["","Elm","Immediate","Retained","Declarative"],"rows":[["where state lives","one model; children's models inside it","the application, all of it (scroll, selection, focus too)","in the widgets, next to the application's data","in components (hooks, SetState)"],["what changes the screen","a message through Update, then View of everything","the next frame","setters on widgets, which invalidate themselves","setting state, which re-runs the component and reconciles"],["focus, mouse, hit-testing","the application's work","the application's work, from last frame's rectangles","the framework's, from the tree","the framework's"],["redrawn","the whole view (diffed after)","the whole frame (diffed after)","only invalidated widgets","only changed components"],["concurrency","only Update mutates; commands return messages","the application's","one UI goroutine; others post to it","one UI goroutine; others post to it"],["boilerplate","high: routing messages and commands through every parent","low for drawing, high for interactive state","low for standard widgets; keeping widgets and data in step","low once learnt; rules (keys, hooks) to follow"],["testability","Update is a function: very good","draw into a buffer: good","drive the app headless: good","drive the app headless: good"],["framework size","small","small","large","largest (reconciliation)"],["fit with Go","type switches on messages, no sum types","natural","natural: structs, methods, goroutines","no JSX: builder code or code generation"],["used by","Bubble Tea, Brick","Ratatui, vaxis' core, FTXUI's DOM","tview, Textual, Terminal.Gui, Cursive, Qt, GTK","Ink, OpenTUI, vaxis `ui`, go-tui, iocraft"]]},{"t":"list","ordered":false,"items":[{"text":"Elm is predictable and its Update is easy to test, but every component needs its own Update and View, parents route every message to children and merge their commands, and state that crosses components (focus, layout) turns into a maze; Charm's own Crush settled on one top-level model. Each keystroke rebuilds the whole view: costly for an editor or a multiplexer with many panes.","items":[]},{"text":"Immediate mode is the simplest to understand and never gets out of step, since the screen is always drawn from the state. But the application keeps every bit of interactive state itself (cursor, scroll offset, selection, which widget has focus), handles input apart from drawing, and redraws everything each frame; focus traversal, an accessibility tree and an inspector need persistent identities it does not have.","items":[]},{"text":"A retained tree is what GUI toolkits do: widgets keep their own state and handle focus, mouse, scrolling and selection themselves, only invalidated regions repaint, and their persistent identities allow focus chains, inspectors, animations and an accessibility mode. The cost is keeping widget state and application data in step, and updates from other goroutines must go through the UI goroutine (tview's QueueUpdateDraw deadlocks show what happens otherwise). Textual's reactive attributes ease the first: a bound value that repaints its widgets when set.","items":[]},{"text":"Declarative components combine immediate mode's \"the screen is a function of the state\" with retained identities, at the price of the most complex framework (reconciliation, keys, lifecycles), rules to learn, allocation on every update, and, in Go, without JSX, verbose builders or templates compiled to Go.","items":[]}]},{"t":"p","text":"Recommendation: a retained tree, with optional bound values (a `Value[T]` whose Set invalidates the widgets that show it) and an immediate-mode Canvas for custom drawing. The target applications are long-lived, stateful and latency-sensitive: an editor widget owns its buffer and cursor and repaints the lines that changed; each multiplexer pane owns its emulator and invalidates its region when output arrives; a commander's copy queue posts progress with `app.Do`."}]}]},{"id":"vfs","title":"Files and storage","pattern":"vfs*","purpose":"Files anywhere, through one interface: disks, archives, servers, the cloud, disk images.","replaces":["io/fs and os, where writes, a context or ranged reads are needed","spf13/afero","a client per store: aws-sdk-go's S3, pkg/sftp, go-smb2, jlaffaye/ftp","archive/zip and archive/tar, for archives read and written as folders","qemu-nbd and mount, which take root, to read a disk image"],"modules":["vfs","vfs-operations","vfs-watch","vfs-source-disk","vfs-source-memory","vfs-source-archive-zip","vfs-source-archive-tar","vfs-source-archive-7z","vfs-source-archive-rar","vfs-source-remote-s3","vfs-source-remote-smb","vfs-source-remote-sftp","vfs-source-remote-ftp","vfs-source-remote-webdav","vfs-source-remote-nfs","vfs-source-remote-gdrive","vfs-source-remote-http","vfs-device-partitions","vfs-device-vhd","vfs-device-vmdk","vfs-device-qcow2","vfs-source-filesystem-ntfs","vfs-source-filesystem-ext4","vfs-source-filesystem-fat","vfs-source-filesystem-exfat","vfs-source-filesystem-iso9660","vfs-source-filesystem-udf"],"blocks":[{"type":"stages","title":"A disk image, layer by layer","intro":"Each layer reads the one below at offsets (a vfs.Device, an io.ReaderAt with a size), so nothing is mounted and nothing needs root. Any layer may be left out: an ext4 image file, a qcow2 on an SFTP server, a zip in a bucket. From vfs.md, section 6.","stages":[{"label":"STORE","text":"Where the image is: a folder on disk, memory, or a remote store. Remote stores read ranges (S3, WebDAV, HTTP and Drive by Range, FTP by REST, SFTP, SMB and NFS at offsets), so a disk image or an archive on one is not fetched whole.","modules":["vfs-source-disk","vfs-source-memory","vfs-source-remote-smb","vfs-source-remote-sftp","vfs-source-remote-s3","vfs-source-remote-nfs","vfs-source-remote-webdav","vfs-source-remote-http","vfs-source-remote-ftp","vfs-source-remote-gdrive"]},{"label":"IMAGE OR ARCHIVE","text":"A virtual disk gives the disk it holds, as a device: VHD and VHDX, qcow2, VMDK, with their parents or backing files found beside them. An archive gives a file system of its own: zip and tar, read and written; 7z and RAR, read.","modules":["vfs-device-vhd","vfs-device-qcow2","vfs-device-vmdk","vfs-source-archive-zip","vfs-source-archive-tar","vfs-source-archive-7z","vfs-source-archive-rar"]},{"label":"PARTITIONS","text":"MBR or GPT: each partition a device. GPT's header and entries are checked by their CRCs, and the backup at the disk's end is read when the primary is damaged.","modules":["vfs-device-partitions"]},{"label":"FILE SYSTEM","text":"A device read as a file system, read-only: NTFS, ext2 to ext4, FAT and exFAT, ISO 9660 and UDF. Links are followed inside it only; what leads out is ErrEscapes.","modules":["vfs-source-filesystem-ntfs","vfs-source-filesystem-ext4","vfs-source-filesystem-fat","vfs-source-filesystem-exfat","vfs-source-filesystem-iso9660","vfs-source-filesystem-udf"]},{"label":"FILES","text":"Every layer ends in a vfs.FS: ReadFile, Walk and Glob on it, and copy, mirror, compare or watch between it and any other.","modules":["vfs","vfs-operations","vfs-watch"]}]},{"type":"example","id":"vfs/disk-image","example":{"id":"vfs/disk-image","title":"Read a file out of a Windows disk image, without mounting it","problem":"A VHDX holds a Windows disk: a GPT, an NTFS partition, files on it. Read one file out of it on Linux, without root. Attaching the image (qemu-nbd) and mounting the partition both take root.","notice":"Each layer is a reader over the one below: the file on disk, the VHDX's disk, the GPT's partition, NTFS's files. Nothing is mounted. compressed.txt was compressed by Windows (LZNT1) and is read whole. Links lead nowhere outside the file system: a junction to N:\\ and a link to C:\\ are ErrEscapes.","parts":[{"name":"with","label":"vfs-device-vhd, vfs-device-partitions, vfs-source-filesystem-ntfs","code":"images, err := disk.Open(folder) // the folder holding windows.vhdx\ncheck(err)\nfile, err := images.Stat(ctx, \"windows.vhdx\")\ncheck(err)\nfmt.Printf(\"file: windows.vhdx, %d MiB on disk\\n\", file.Size>>20)\n\nimage, err := vhd.Open(ctx, images, \"windows.vhdx\") // the disk it holds, read at offsets\ncheck(err)\ndefer image.Close()\nfmt.Printf(\"image: %s, %s, a disk of %d MiB\\n\", image.Format, image.Kind, image.Size()>>20)\n\ntable, err := partitions.Read(image)\ncheck(err)\nfmt.Printf(\"partitions: %s, sectors of %d bytes\\n\", strings.ToUpper(table.Scheme), table.SectorSize)\nfor _, partition := range table.Partitions {\n\tfmt.Printf(\" %d %-21s %-30q at %5d KiB, %5d KiB\\n\", partition.Number, partition.Kind,\n\t\tpartition.Name, partition.Start>>10, partition.Length>>10)\n}\n\nlast := table.Partitions[len(table.Partitions)-1]\nwindows, err := ntfs.New(last, fmt.Sprintf(\"windows.vhdx!/%d\", last.Number)) // a read-only vfs.FS\ncheck(err)\nfmt.Printf(\"file system: NTFS, labelled %q\\n\", windows.Label())\nlisting(ctx, windows, \".\")\nlisting(ctx, windows, \"extras\")\n\nname := \"extras/compressed.txt\" // compressed by Windows' compact: LZNT1\nentry, err := windows.Stat(ctx, name)\ncheck(err)\ndata, err := vfs.ReadFile(ctx, windows, name)\ncheck(err)\nlines := strings.Split(strings.TrimSuffix(string(data), \"\\n\"), \"\\n\")\nfmt.Printf(\"file: %s, %d bytes, modified %s\\n\", name, entry.Size, entry.Modified.Format(\"2006-01-02 15:04:05 MST\"))\nfmt.Printf(\" %d lines, %q to %q\\n\", len(lines), lines[0], lines[len(lines)-1])"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./vfs/disk-image","exit":0,"name":"linux","output":"file: windows.vhdx, 36 MiB on disk\nimage: VHDX, dynamic, a disk of 32 MiB\npartitions: GPT, sectors of 512 bytes\n 1 Microsoft basic data \"Basic data partition\" at 64 KiB, 32640 KiB\nfile system: NTFS, labelled \"VFS Windows\"\n ./ holds 10 entries:\n Long Name With Spaces ü.txt A 6 bytes\n MixedCase.TXT A 5 bytes\n a.txt A 1 byte\n empty folder\n extras folder\n folder folder\n hidden.txt HA 6 bytes\n lower.txt A 5 bytes\n many folder\n readonly.txt RA 8 bytes\n extras/ holds 9 entries:\n absolute A link to C:\\Windows\\win.ini\n compressed.txt A 468894 bytes\n folderlink link to ../folder/deeper\n junction link to N:\\folder\n link A link to ../a.txt\n links folder\n sparse.bin A 1048576 bytes\n stream.txt A 4 bytes\n wof.txt A 468894 bytes\nfile: extras/compressed.txt, 468894 bytes, modified 2026-10-15 23:49:27 UTC\n 20000 lines, \"compressible line 1\" to \"compressible line 20000\"\n\nLinks are followed inside the file system only; WOF's compact files are not read:\n extras/link read: \"a\"\n extras/junction ErrEscapes: the link leads out of the file system\n extras/absolute ErrEscapes: the link leads out of the file system\n extras/wof.txt ErrUnsupported: Windows keeps its data elsewhere\n"}],"file":"www-examples/vfs/disk-image/main.go"}},{"type":"example","id":"vfs/mirror","example":{"id":"vfs/mirror","title":"Publish a build to a folder, again and again","problem":"A site is built in memory, then published to a folder on disk; it is built again, and published again. Copy what changed, remove what the build no longer holds, and leave the rest.","notice":"os.CopyFS refuses a file already there, so each build after the first is removed and copied whole, and the copies take the time they were made. Mirror walks both trees first and shows its plan before it changes anything: build 2 replaces one file, copies one and removes one; build 3 changes nothing. The build's times are kept, which is how the next plan tells a file the same.","parts":[{"name":"usual","label":"os.CopyFS","code":"target := filepath.Join(root, \"usual\")\nerr := os.CopyFS(target, vfs.IOFS(ctx, built)) // fails on a file already there\nif errors.Is(err, fs.ErrExist) {\n\tfmt.Println(\" refused:\", relative(err))\n\tos.RemoveAll(target) // so: all of it removed, and copied again\n\terr = os.CopyFS(target, vfs.IOFS(ctx, built))\n}"},{"name":"with","label":"vfs-operations","code":"public, err := disk.Open(filepath.Join(root, \"public\"))\ncheck(err)\njob := operations.Mirror(ctx, built, \"site\", public, \"site\",\n\toperations.Prune(), // removes what the build no longer holds\n\toperations.Approve(func(plan operations.Plan) bool {\n\t\tshow(plan) // decided before anything changes\n\t\treturn true\n\t}))\nreport, err := job.Wait()\ncheck(err)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./vfs/mirror","exit":0,"name":"linux","output":"os.CopyFS\n build 1\n done: 4 files copied, all of them\n build 2: index.html changed, news.html new, about.html gone\n refused: open usual/site/css/site.css: file exists\n done: 4 files copied, all of them\n build 3: nothing changed\n refused: open usual/site/css/site.css: file exists\n done: 4 files copied, all of them\n index.html's time: when it was copied, not the build's\n\nvfs-operations, Mirror\n build 1\n plan: 3 folders to make, 4 files to copy (90 bytes), 0 to remove, 0 the same\n make folder site\n copy site/about.html\n make folder site/css\n copy site/css/site.css\n make folder site/img\n copy site/img/logo.svg\n copy site/index.html\n done: 4 files copied (90 bytes), 0 removed, 0 left as they were\n build 2: index.html changed, news.html new, about.html gone\n plan: 0 folders to make, 2 files to copy (41 bytes), 1 to remove, 2 the same\n replace site/index.html\n copy site/news.html\n remove site/about.html\n done: 2 files copied (41 bytes), 1 removed, 2 left as they were\n build 3: nothing changed\n plan: 0 folders to make, 0 files to copy (0 bytes), 0 to remove, 4 the same\n done: 0 files copied (0 bytes), 0 removed, 4 left as they were\n index.html's time: the build's, 10:05\n"}],"file":"www-examples/vfs/mirror/main.go"}},{"type":"example","id":"vfs/zip","example":{"id":"vfs/zip","title":"Unpack a password-protected zip, and pack a folder as one","problem":"A zip came encrypted with WinZip's AES-256, as 7-Zip and WinZip make them. Unpack it into a folder with its password, then pack the folder into a new zip.","notice":"archive/zip lists the entries but reads none: method 99 is WinZip's AES. Through vfs the archive is one more file system: unpacking is a copy out of it, packing a copy into a new one, with the same call. A wrong password is an error matching vfs.ErrAuth. The new zip is a plain one: archive/zip reads it and checks its CRCs.","parts":[{"name":"usual","label":"archive/zip","code":"archive, err := stdzip.OpenReader(filepath.Join(fixtures, \"aes256.zip\"))\ncheck(err)\ndefer archive.Close()\nfor _, file := range archive.File {\n\tif file.Mode().IsDir() {\n\t\tcontinue\n\t}\n\treader, err := file.Open() // method 99, WinZip AES: not one it reads\n\tif err == nil {\n\t\t_, err = io.Copy(io.Discard, reader)\n\t}\n\tfmt.Printf(\" %-20s method %d: %v\\n\", file.Name, file.Method, err)\n}"},{"name":"with","label":"vfs-source-archive-zip, vfs-operations","code":"testdata, err := disk.Open(fixtures)\ncheck(err)\nout, err := disk.Open(temporary)\ncheck(err)\nencrypted, err := zip.Open(ctx, testdata, \"aes256.zip\", zip.Password(\"secret\"))\ncheck(err)\ndefer encrypted.Close()\nunpacked, err := operations.Copy(ctx, encrypted, \".\", out, \"unpacked\").Wait() // unpacking is a copy out\ncheck(err)\nfmt.Printf(\" unpacked: %d files, %d bytes, %d folders\\n\", unpacked.Files, unpacked.Bytes, unpacked.Folders)\n\npacking, err := zip.Create(ctx, out, \"repacked.zip\") // a write-only FS: the archive appears at Close\ncheck(err)\npacked, err := operations.Copy(ctx, out, \"unpacked\", packing, \"unpacked\").Wait() // packing is a copy in\ncheck(err)\ncheck(packing.Close())\nfmt.Printf(\" packed: %d files, %d bytes, into repacked.zip\\n\", packed.Files, packed.Bytes)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./vfs/zip","exit":0,"name":"linux","output":"archive/zip\n a.txt method 99: zip: unsupported compression algorithm\n folder/big.txt method 99: zip: unsupported compression algorithm\n folder/deeper/c.txt method 99: zip: unsupported compression algorithm\n\nvfs-source-archive-zip\n unpacked: 3 files, 40547 bytes, 3 folders\n packed: 3 files, 40547 bytes, into repacked.zip\n\nWhat was unpacked:\n unpacked/a.txt 15 bytes, starting \"secret conte\"\n unpacked/folder/big.txt 40527 bytes, starting \"uCN7X0X9CPEV\"\n unpacked/folder/deeper/c.txt 5 bytes, starting \"deep\\n\"\n with a wrong password, errors.Is(err, vfs.ErrAuth): true\n\narchive/zip reads repacked.zip:\n unpacked/ folder\n unpacked/folder/ folder\n unpacked/folder/deeper/ folder\n unpacked/folder/deeper/c.txt 5 bytes, method 8, CRC checked: true\n unpacked/a.txt 15 bytes, method 8, CRC checked: true\n unpacked/folder/big.txt 40527 bytes, method 8, CRC checked: true\n"}],"file":"www-examples/vfs/zip/main.go"}},{"type":"table","title":"What each source does","source":"vfs.md","table":"Source","caption":"From vfs.md, section 3. ✓ native · ~ partial, emulated or server-dependent · – none. ¹ A new archive, written in order and finished at Close: packing is a copy into it. What a source emulates (S3's rename: a copy, then a removal) its Caps says; nothing is emulated silently.","header":["Source","Write","Atomic","Append","Random read","Rename","Native copy","Mode/times/owner","Links","Lock","Watch","Usage","IDs/births","Versions"],"rows":[["disk","✓","✓ temp, sync, rename, sync","✓","✓","✓","✓ reflink, copy_file_range, clonefile","✓ ✓ ✓ (privilege)","✓ sym, hard","✓","✓","✓","✓ ✓","–"],["memory","✓","✓","✓","✓","✓","✓","✓ ✓ ✓","✓","✓ in-process","✓ exact","✓ a limit","✓ ✓","✓ counter"],["zip","creates¹","✓ archive","–","✓ stored, ~ compressed","–","–","read","read","–","–","–","– –","✓ CRC-32"],["tar","creates¹","✓ archive","–","✓ plain, ~ compressed","–","–","read","read sym, hard","–","–","–","✓ –","–"],["iso9660","–","","","✓","","","read (Rock Ridge)","read","","","","✓ –","–"],["7z","–","","","~ solid blocks","","","read","","","","","– –","✓ CRC"],["rar","–","","","~ solid","","","read","","","","","– –","✓ CRC"],["s3","✓","✓ PUT","–","✓ Range","~ copy, remove","✓ CopyObject","– ~ metadata –","–","–","–","–","– –","✓ ETag"],["webdav","✓","~ PUT, as the server spools it","–","✓ Range","✓ MOVE","✓ COPY","– – –","–","~ session, class 2 with both scopes","–","~ RFC 4331","– –","✓ ETag"],["web (HTTP)","–","","","✓ Range, or skipped","","","","","","–","","","✓ ETag"],["ftp","✓","~ temp, RNTO","✓ APPE","✓ REST","✓","–","– ~ MLSD and MFMT –","–","–","–","–","– –","–"],["sftp","✓","✓ temp, fsync, posix-rename","✓","✓","✓","~ copy-data","✓ ✓ ✓ (Windows: – ✓ –)","✓ sym, hard","–","–","✓ statvfs","– –","–"],["smb","✓","✓ temp, rename (POSIX where taken)","✓","✓","✓","✓ COPYCHUNK","~ read-only ✓ –","~ hard","✓ session","✓ CHANGE_NOTIFY","✓","✓ ✓","–"],["gdrive","✓","✓ revision","–","✓ Range","✓","✓","– ✓ –","~ shortcuts, read","–","✓ changes API, polled","✓ quota","✓ ✓","✓ md5"],["nfs","✓","✓ temp, COMMIT, rename","~ at the end","✓","✓","–","✓ ✓ ✓ (by number)","✓ sym, hard","–","–","✓ FSSTAT","✓ –","–"]],"heading":"3. sources: each feature per provider"},{"type":"text","title":"Checked against real servers, and files real tools made","body":"The sources' tests pass on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS. The remote sources ran against real servers in a Debian VM (tools/vfs-servers.sh): HTTP, WebDAV, S3 and FTP against Apache, nginx, lighttpd, Caddy, vsftpd, pyftpdlib and MinIO; SFTP against Debian's OpenSSH 10 and Windows Server 2025's; SMB against Samba 4.22 (2.1, 3.0, 3.0.2, 3.1.1, encrypted, guest) and Windows Server 2025's own server; NFS against Linux's server. Google Drive only against a fake of its API: no account was given. Archives, images and file systems are read as real tools wrote them: zip and 7-Zip 25.01, GNU tar and bsdtar, RAR 6.24, xorriso, qemu-img, mkfs.fat, mkfs.exfat, mke2fs, mkntfs and mkudffs, and Windows Server itself for NTFS, UDF, exFAT, FAT32 and differencing VHD and VHDX. The image and file system parsers are fuzzed. From vfs.md, build order, steps 1 to 5."},{"type":"table","title":"Watching, against fsnotify and Rust's notify","source":"vfs.md","table":"tool","caption":"From vfs.md, section 4: vfs-watch's engine, measured as files-watch, the module it came from, on 2026-10-15 in a Debian VM (2 vCPU, 1 GB, ext4); five interleaved rounds, medians but for latency. The burst is 100 folders of 100 files made at once; the churn, on it, 2 000 files written, 1 000 removed, 500 renamed, 10 folders renamed, 1 000 files made in 20 new folders, 5 folders removed. A tool has told a burst when the paths it keeps are the disk's. The harness was not kept.","header":["tool","latency min / median / p99","burst: told in, wrong","churn: told in, wrong","CPU, burst and churn","peak memory","start on 100k files, memory"],"rows":[["files-watch, Settle(0)","0.1 / 0.3 / 0.6 ms","165 ms, 0","330 ms, 0","506 ms","15 MB","187 ms, 27 MB"],["files-watch, Settle(50ms)","50.4 / 50.5 / 50.8 ms","172 ms, 0","416 ms, 0","333 ms","12 MB","195 ms, 27 MB"],["fsnotify v1.10.1","0.1 / 0.2 / 0.3 ms","164 ms, 0","424 ms, 0","474 ms","10 MB","91 ms, 18 MB"],["notify 8.2.0 (Rust)","0.1 / 0.2 / 0.2 ms","never, 13","never, 8","338 ms","3 MB","266 ms, 8 MB"]],"heading":"4. vfs-watch"}],"sample":{"code":"images, err := disk.Open(folder) // the folder holding windows.vhdx\ncheck(err)\n// …\nimage, err := vhd.Open(ctx, images, \"windows.vhdx\") // the disk it holds, read at offsets\ncheck(err)\ndefer image.Close()\n// …\ntable, err := partitions.Read(image)\ncheck(err)\n// …\nlast := table.Partitions[len(table.Partitions)-1]\nwindows, err := ntfs.New(last, fmt.Sprintf(\"windows.vhdx!/%d\", last.Number)) // a read-only vfs.FS\ncheck(err)\n// …\ndata, err := vfs.ReadFile(ctx, windows, name)\ncheck(err)","example":"vfs/disk-image","title":"Read a file out of a Windows disk image, without mounting it","file":"www-examples/vfs/disk-image/main.go","output":"file: windows.vhdx, 36 MiB on disk\nimage: VHDX, dynamic, a disk of 32 MiB\npartitions: GPT, sectors of 512 bytes\n 1 Microsoft basic data \"Basic data partition\" at 64 KiB, 32640 KiB\nfile system: NTFS, labelled \"VFS Windows\"\n ./ holds 10 entries:\n Long Name With Spaces ü.txt A 6 bytes\n MixedCase.TXT A 5 bytes\n a.txt A 1 byte\n empty folder\n extras folder\n folder folder\n hidden.txt HA 6 bytes\n lower.txt A 5 bytes\n many folder\n readonly.txt RA 8 bytes\n extras/ holds 9 entries:\n absolute A link to C:\\Windows\\win.ini\n compressed.txt A 468894 bytes\n folderlink link to ../folder/deeper\n junction link to N:\\folder\n link A link to ../a.txt\n links folder\n sparse.bin A 1048576 bytes\n stream.txt A 4 bytes\n wof.txt A 468894 bytes\nfile: extras/compressed.txt, 468894 bytes, modified 2026-10-15 23:49:27 UTC\n 20000 lines, \"compressible line 1\" to \"compressible line 20000\"\n\nLinks are followed inside the file system only; WOF's compact files are not read:\n extras/link read: \"a\"\n extras/junction ErrEscapes: the link leads out of the file system\n extras/absolute ErrEscapes: the link leads out of the file system\n extras/wof.txt ErrUnsupported: Windows keeps its data elsewhere\n","system":"linux","runs":1},"measured":["vfs-watch"],"competition":[]},{"id":"xtend","title":"Standard library additions","pattern":"xtend*","purpose":"What Go's own packages lack: errors, time, strings, JSON, locks, random and more.","replaces":["helpers copied from program to program","pkg/errors","sasha-s/go-deadlock","dustin/go-humanize","iancoleman/strcase","evanphx/json-patch","google/uuid and oklog/ulid, for what Go's uuid lacks"],"modules":["xtend-errors","xtend-io","xtend-time","xtend-strings","xtend-math","xtend-json","xtend-random","xtend-channel","xtend-sync","xtend-user"],"blocks":[{"type":"example","id":"xtend/months","example":{"id":"xtend/months","title":"Add a month to 31 January","problem":"A subscription started on 31 January is billed every month; one taken on 29 February renews a year later; a hotel counts the nights of a stay. Each is one line of date arithmetic.","notice":"Go's AddDate normalises 31 February to 3 March and 29 February 2029 to 1 March, as its documentation says; a day counted as 24 hours gives no night for a stay across the change to summer time, which lasts 23. xtend-time clamps to the month's end, as java.time and JavaScript's Temporal do, and counts calendar days.","parts":[{"name":"usual","label":"time","code":"var bills []string\nfor month := range 6 {\n\tbills = append(bills, start.AddDate(0, month, 0).Format(\"Jan 2\"))\n}\nrenewal := leapDay.AddDate(1, 0, 0)\nnights := int(checkOut.Sub(checkIn).Hours() / 24)"},{"name":"with","label":"xtend-time","code":"bills = nil\nfor month := range 6 {\n\tbills = append(bills, timex.AddMonths(start, month).Format(\"Jan 2\"))\n}\nrenewal = timex.AddYears(leapDay, 1)\nnights = timex.DaysBetween(checkIn, checkOut)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/months","exit":0,"name":"linux","output":"time\n monthly bills from 31 January 2026 Jan 31, Mar 3, Mar 31, May 1, May 31, Jul 1\n a year after 29 February 2028 Mar 1 2029\n nights from 28 to 29 March, Berlin 0\n\nxtend-time\n monthly bills from 31 January 2026 Jan 31, Feb 28, Mar 31, Apr 30, May 31, Jun 30\n a year after 29 February 2028 Feb 28 2029\n nights from 28 to 29 March, Berlin 1\n"}],"file":"www-examples/xtend/months/main.go"}},{"type":"example","id":"xtend/overflow","example":{"id":"xtend/overflow","title":"Know when a number does not fit","problem":"A ZIP or WAV writer puts a file's size into a 32-bit header field; an image decoder works out how many bytes to allocate from the width and height a file claims. When the number does not fit, Go wraps it around without a word.","notice":"A 5 GiB file's header says 1 GiB, and a 40000 × 30000 image gets 505,032,704 bytes where it needs 4,800,000,000: both wrapped silently. xtend-math returns the result and whether it fit, for any integer type, so the program can refuse or switch formats. Go declined checked arithmetic in the standard library (issues 24853 and 60858).","parts":[{"name":"usual","label":"Go's conversions and arithmetic","code":"for _, size := range sizes {\n\tfield := uint32(size) // what the 32-bit field gets\n\tfmt.Printf(\" a file of %d bytes: the header says %d\\n\", size, field)\n}\nfor _, image := range images {\n\tbuffer := image[0] * image[1] * 4\n\tfmt.Printf(\" %d × %d pixels: %d bytes to allocate\\n\", image[0], image[1], buffer)\n}"},{"name":"with","label":"xtend-math","code":"for _, size := range sizes {\n\tfield, ok := mathx.Convert[uint32](size)\n\tif !ok {\n\t\tfmt.Printf(\" a file of %d bytes: too large for the field (write ZIP64)\\n\", size)\n\t\tcontinue\n\t}\n\tfmt.Printf(\" a file of %d bytes: the header says %d\\n\", size, field)\n}\nfor _, image := range images {\n\tpixels, ok := mathx.Mul(image[0], image[1])\n\tbuffer, fits := mathx.Mul(pixels, 4)\n\tif !ok || !fits {\n\t\tfmt.Printf(\" %d × %d pixels: too large for an int32, refused\\n\", image[0], image[1])\n\t\tcontinue\n\t}\n\tfmt.Printf(\" %d × %d pixels: %d bytes to allocate\\n\", image[0], image[1], buffer)\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/overflow","exit":0,"name":"linux","output":"Go\n a file of 1048576 bytes: the header says 1048576\n a file of 5368709120 bytes: the header says 1073741824\n 1920 × 1080 pixels: 8294400 bytes to allocate\n 40000 × 30000 pixels: 505032704 bytes to allocate\n\nxtend-math\n a file of 1048576 bytes: the header says 1048576\n a file of 5368709120 bytes: too large for the field (write ZIP64)\n 1920 × 1080 pixels: 8294400 bytes to allocate\n 40000 × 30000 pixels: too large for an int32, refused\n"}],"file":"www-examples/xtend/overflow/main.go"}},{"type":"example","id":"xtend/json-patch","example":{"id":"xtend/json-patch","title":"Change a JSON document without structs","problem":"A deployment tool raises a service's replicas and adds a label in a settings document it has no Go types for, and must leave the rest of the document as it was.","notice":"Through a map, encoding/json sorts the members and reads every number as a float64: 0.50 comes back as 0.5 and the build number loses its last digits. A JSON Patch (RFC 6902) through xtend-json changes only what it names, keeps the members' order and the numbers' text, and applies all or nothing: a failed test leaves the document unchanged.","parts":[{"name":"usual","label":"encoding/json and a map","code":"var settings map[string]any\njson.Unmarshal([]byte(document), &settings)\nif settings[\"image\"] == \"api:1.4.2\" { // change it only if it is still what we read\n\tsettings[\"replicas\"] = 5\n\tsettings[\"labels\"] = map[string]any{\"tier\": \"web\"}\n}\nchanged, _ := json.Marshal(settings)"},{"name":"with","label":"xtend-json","code":"patch := jsontext.Value(`[\n\t{\"op\": \"test\", \"path\": \"/image\", \"value\": \"api:1.4.2\"},\n\t{\"op\": \"replace\", \"path\": \"/replicas\", \"value\": 5},\n\t{\"op\": \"add\", \"path\": \"/labels\", \"value\": {\"tier\": \"web\"}}]`)\npatched, err := jsonx.Patch(jsontext.Value(document), patch)"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/json-patch","exit":0,"name":"linux","output":"before:\n {\"name\":\"api\",\"image\":\"api:1.4.2\",\"replicas\":3,\"limits\":{\"cpu\":0.50,\"memory\":17179869184},\"build\":12345678901234567890}\n\nencoding/json, through a map:\n {\"build\":12345678901234567000,\"image\":\"api:1.4.2\",\"labels\":{\"tier\":\"web\"},\"limits\":{\"cpu\":0.5,\"memory\":17179869184},\"name\":\"api\",\"replicas\":5}\n\nxtend-json, a JSON Patch:\n {\"name\":\"api\",\"image\":\"api:1.4.2\",\"replicas\":5,\"limits\":{\"cpu\":0.50,\"memory\":17179869184},\"build\":12345678901234567890,\"labels\":{\"tier\":\"web\"}}\n\na patch replacing the replicas, then testing for another image:\n 0 bytes back; operation 1: jsonx: a test failed: \"/image\" is \"api:1.4.2\", not \"api:1.4.1\"\n"}],"file":"www-examples/xtend/json-patch/main.go"}},{"type":"example","id":"xtend/random-bias","example":{"id":"xtend/random-bias","title":"Random codes where every symbol is equally likely","problem":"Invite codes, one-time codes and short links are drawn from an alphabet, here the 57 symbols that cannot be mistaken for one another. The usual code takes a random byte modulo the alphabet's length.","notice":"256 is not a multiple of 57, so a byte modulo 57 lands on each of the first 28 symbols 5 times in 256 and on each of the others 4 times: the first come 25% more often, which makes codes easier to guess. xtend-random draws by rejection, from crypto/rand, so each symbol comes as often as the others.","parts":[{"name":"usual","label":"a random byte modulo 57","code":"random := make([]byte, n)\nrand.Read(random)\nvar code strings.Builder\nfor _, b := range random {\n\tcode.WriteByte(alphabet[int(b)%len(alphabet)]) // 256 is not a multiple of 57\n}\nusual := code.String()"},{"name":"with","label":"xtend-random","code":"with := randomx.String(n, alphabet) // by rejection: each symbol equally likely"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/random-bias","exit":0,"name":"linux","output":"an invite code: 6HnHvYoYpQ7Q (byte % 57), e8gnH5KMQfn3 (xtend-random)\n\n1140000 symbols each way: 20000 of each if each were equally likely\n byte % 57 xtend-random\n each of the first 28, on average 22282 20003\n each of the last 29, on average 17796 19996\n"}],"file":"www-examples/xtend/random-bias/main.go"}},{"type":"example","id":"xtend/lines","example":{"id":"xtend/lines","title":"Read another program's output line by line","problem":"A tool logs the output of the programs it runs, one line at a time. That output has progress redrawn with carriage returns, Windows line endings, and now and then a line longer than 64 KiB.","notice":"bufio.Scanner keeps the carriage returns inside one line and stops at the first line over 64 KiB, so the line after it is never read. xtend-io's LineWriter ends a line at \\n, \\r\\n or a lone \\r, passes a long line in parts of 64 KiB, and loses nothing. ScanLines gives Scanner the same line endings.","parts":[{"name":"usual","label":"bufio.Scanner","code":"scanner := bufio.NewScanner(strings.NewReader(output))\nfor scanner.Scan() {\n\tshow(scanner.Bytes())\n}\nif err := scanner.Err(); err != nil {\n\tfmt.Println(\" stopped:\", err)\n}"},{"name":"with","label":"xtend-io","code":"writer := iox.NewLineWriter(show, 0) // lines end at \\n, \\r\\n or a lone \\r\nio.Copy(writer, strings.NewReader(output))\nwriter.Flush()"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/lines","exit":0,"name":"linux","output":"bufio.Scanner\n 48 bytes \"Downloading 10%\\rDownloading 50%\\rDownload\"...\n 10 bytes \"Installing\"\n stopped: bufio.Scanner: token too long\n\nxtend-io\n 15 bytes \"Downloading 10%\"\n 15 bytes \"Downloading 50%\"\n 16 bytes \"Downloading 100%\"\n 10 bytes \"Installing\"\n 65536 bytes \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"...\n 34464 bytes \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"...\n 4 bytes \"done\"\n"}],"file":"www-examples/xtend/lines/main.go"}},{"type":"example","id":"xtend/merge","example":{"id":"xtend/merge","title":"Merge channels without leaking goroutines","problem":"Two searches have given their results on channels. The program reads the first five, from either, then stops reading.","notice":"The merge written by hand keeps one goroutine per input waiting to send a value nobody will read, and one waiting to close the output: three goroutines left running for good. xtend-channel's Merge takes a context, and every goroutine it starts ends when it is cancelled.","parts":[{"name":"usual","label":"a merge by hand","code":"merge := func(ins ...<-chan string) <-chan string {\n\tout := make(chan string)\n\tvar group sync.WaitGroup\n\tfor _, in := range ins {\n\t\tgroup.Go(func() {\n\t\t\tfor value := range in {\n\t\t\t\tout <- value // once nobody reads, this waits for ever\n\t\t\t}\n\t\t})\n\t}\n\tgo func() { group.Wait(); close(out) }()\n\treturn out\n}\nresults := merge(search(\"disk\"), search(\"web\"))\nfor range 5 {\n\t<-results\n}"},{"name":"with","label":"xtend-channel","code":"ctx, cancel := context.WithCancel(context.Background())\nresults = channelx.Merge(ctx, search(\"disk\"), search(\"web\"))\nfor range 5 {\n\t<-results\n}\ncancel() // the merge's goroutines end"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/merge","exit":0,"name":"linux","output":"merged by hand\n goroutines left running: 3\n\nxtend-channel\n goroutines left running: 0\n"}],"file":"www-examples/xtend/merge/main.go"}},{"type":"example","id":"xtend/error-stack","example":{"id":"xtend/error-stack","title":"Errors that say where they were made","problem":"An error made deep in a program reaches main and the log as one line of text. Finding where it was made means searching the code for its message.","notice":"Both errors read the same and both work with errors.Is. The one from xtend-errors also keeps the stack where it was made (program counters, resolved into names only when read), attributes for the log and a code for mapping errors to statuses. Wrap keeps no second stack when the error has one, so the stack points where the trouble began.","parts":[{"name":"usual","label":"fmt.Errorf","code":"func readConfigUsual(path string) error {\n\tif _, err := os.ReadFile(path); err != nil {\n\t\treturn fmt.Errorf(\"reading the configuration: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc startUsual() error {\n\tif err := readConfigUsual(\"settings.json\"); err != nil {\n\t\treturn fmt.Errorf(\"starting the server: %w\", err)\n\t}\n\treturn nil\n}"},{"name":"with","label":"xtend-errors","code":"func readConfig(path string) error {\n\tif _, err := os.ReadFile(path); err != nil {\n\t\terr = errorsx.Errorf(\"reading the configuration: %w\", err) // the stack is kept here\n\t\treturn errorsx.WithCode(errorsx.With(err, \"path\", path), \"config.unreadable\")\n\t}\n\treturn nil\n}\n\nfunc start() error {\n\tif err := readConfig(\"settings.json\"); err != nil {\n\t\treturn errorsx.Wrap(err, \"starting the server\")\n\t}\n\treturn nil\n}"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/error-stack","exit":0,"name":"linux","output":"fmt.Errorf\n starting the server: reading the configuration: open settings.json: no such file or directory\n a missing file: true\n made at: not known; Go's errors keep no stack\n\nxtend-errors\n starting the server: reading the configuration: open settings.json: no such file or directory\n a missing file: true\n code: config.unreadable\n path: settings.json\n made at:\n readConfig, main.go:38\n start, main.go:45\n main, main.go:60\n"}],"file":"www-examples/xtend/error-stack/main.go"}},{"type":"example","id":"xtend/lock-order","example":{"id":"xtend/lock-order","title":"Find a deadlock before it happens","problem":"Two transfers between the same accounts lock them in opposite orders. In tests they run one after the other and pass; in production two run at once and the program hangs.","notice":"The program never hangs, and with sync's Mutex nothing is said. xtend-sync's Mutex, the same type to the code, keeps the order locks are taken in and reports the inversion with where each lock was taken. Checking costs 3.8 µs a lock and unlock (xtend-sync's readme), so releases are built with the tag syncx_plain, which makes the types sync's own.","parts":[{"name":"usual","label":"sync.Mutex","code":"alice, bob := &account{&sync.Mutex{}, 100}, &account{&sync.Mutex{}, 100}\ntransfer(alice, bob, 10)\ntransfer(bob, alice, 5)"},{"name":"with","label":"xtend-sync","code":"alice, bob = &account{&syncx.Mutex{}, 100}, &account{&syncx.Mutex{}, 100}\ntransfer(alice, bob, 10)\ntransfer(bob, alice, 5) // reported: these locks were taken the other way round before"}],"runs":[{"system":"linux","go":"go1.27.2","date":"2026-10-17","command":"go run ./xtend/lock-order","exit":0,"name":"linux","output":"sync.Mutex: Alice 95, Bob 105; nothing reported\nsyncx.Mutex: Alice 95, Bob 105; 1 reported\n\nlock order inversion\n goroutine 1 took Bob's lock:\n transfer, main.go:26\n main, main.go:48\n then Alice's lock:\n transfer, main.go:28\n main, main.go:48\n goroutine 1 took Alice's lock:\n transfer, main.go:26\n main, main.go:47\n then Bob's lock:\n transfer, main.go:28\n main, main.go:47\n"}],"file":"www-examples/xtend/lock-order/main.go"}},{"type":"facts","items":[{"value":"51,000","label":"answers from java.time that xtend-time was checked against, around every DST change of 22 zones chosen for unusual rules","source":"xtend-time/readme"},{"value":"113","label":"cases from the tables of Rust's heck that xtend-strings' case conversion passes","source":"xtend-strings/readme"},{"value":"228 µs","label":"for xtend-strings to sort 1,000 names in natural order, without allocating; maruel/natural takes 499 µs","source":"xtend-strings/readme"},{"value":"210 ns","label":"to make an error with its stack, kept as program counters with one allocation and named only when read","source":"xtend-errors/readme"}]}],"sample":{"code":"bills = nil\nfor month := range 6 {\n\tbills = append(bills, timex.AddMonths(start, month).Format(\"Jan 2\"))\n}\nrenewal = timex.AddYears(leapDay, 1)\nnights = timex.DaysBetween(checkIn, checkOut)","example":"xtend/months","title":"Add a month to 31 January","file":"www-examples/xtend/months/main.go","output":"time\n monthly bills from 31 January 2026 Jan 31, Mar 3, Mar 31, May 1, May 31, Jul 1\n a year after 29 February 2028 Mar 1 2029\n nights from 28 to 29 March, Berlin 0\n\nxtend-time\n monthly bills from 31 January 2026 Jan 31, Feb 28, Mar 31, Apr 30, May 31, Jun 30\n a year after 29 February 2028 Feb 28 2029\n nights from 28 to 29 March, Berlin 1\n","system":"linux","runs":1},"measured":["xtend-strings","xtend-sync"],"competition":[{"title":"What Go's io lacks, with Go's issues","source":"xtend.md","heading":"xtend-io","blocks":[{"t":"p","text":"Problems: `bufio.Scanner` stops at lines of 64 KiB; tools write `\\r\\n` and lone `\\r` (progress bars); `io.Copy` can't be cancelled (Go issue 20280, open); progress needs byte counts; formats that patch a header afterwards (WAV, ranged downloads) need a buffer that seeks and writes at offsets, which Go has never had (issues 21592, 21899, 65076, closed)."},{"t":"list","ordered":false,"items":[{"text":"Keep: `LineWriter` (lines at `\\n`, `\\r\\n` and a lone `\\r`, a size limit, `Flush`), tested and fuzzed.","items":[]},{"text":"`ScanLines`: a `bufio.SplitFunc` with the same rules, for Scanner users.","items":[]},{"text":"`CopyContext(ctx, dst, src)`: stops between chunks when ctx ends; says plainly that a Read already blocked is not interrupted.","items":[]},{"text":"`CountingReader`, `CountingWriter`: bytes so far, read atomically.","items":[]},{"text":"`File`: a file in memory, from dsa-seekablebuffer; Read, Write, Seek, ReadAt, WriteAt, Truncate, WriterTo, ReaderFrom, behaving as `os.File` does (errors on a negative offset or a bad whence, a gap written past the end reads as zeros). vfs-source-memory can keep its files in it.","items":[]},{"text":"Later: a throttled reader and writer; a writer keeping the last N bytes (a process's last output).","items":[]},{"text":"Checked: lines against Python's universal-newline reader on random input; `File` against a real `os.File` in a temporary folder, operation by operation, errors included.","items":[]}]}]},{"title":"What Go's time leaves to its callers","source":"xtend.md","heading":"xtend-time","blocks":[{"t":"p","text":"Problems, which Go documents as intended: `AddDate` overflows months (31 March minus a month is 3 March; issues 10401, 57139, 71334); days counted as 24 hours break across DST; no `d` or `w` in durations (issues 11473, 69114, declined); no ISO 8601 periods (`P1M2D`); nothing from an ISO week back to a date."},{"t":"list","ordered":false,"items":[{"text":"`AddMonths(t, n)`, `AddYears(t, n)`: clamped to the month's end (31 January plus a month is 28 or 29 February), as JavaScript's Temporal and java.time do.","items":[]},{"text":"`StartOfDay`, `StartOfWeek(t, first time.Weekday)`, `StartOfMonth`, `StartOfQuarter`, `StartOfYear`, in t's location; the end of a period is the start of the next, so ranges are half-open.","items":[]},{"text":"`DaysBetween(a, b)`: calendar days, right across DST.","items":[]},{"text":"`ISOWeekStart(year, week)`: the Monday of an ISO week.","items":[]},{"text":"Later: `Period` (years, months, weeks, days and a duration) with ISO 8601 text; `ParseDuration` with `d` and `w`; business days with the caller's holidays; \"3 days ago\" in English.","items":[]},{"text":"Checked: against Temporal's test vectors and java.time on random dates; time zones that skip a day (Pacific/Apia, 2011), half-hour DST (Lord Howe), DST changes at midnight (Brazil's history); ISO weeks for every day from 1600 to 2400.","items":[]}]}]},{"title":"What Go's strings lacks","source":"xtend.md","heading":"xtend-strings","blocks":[{"t":"p","text":"Problems: case conversion that keeps acronyms (`XMLHttpRequest`, `userID`); natural order (\"file9\" before \"file10\"; Go's own pkgsite carries a private one); `strings.Title` deprecated."},{"t":"list","ordered":false,"items":[{"text":"`Words(s)`: splits at case changes, acronyms, digits and separators.","items":[]},{"text":"`Snake`, `Kebab`, `Camel`, `Pascal`, `ScreamingSnake`, with Go's list of initialisms, or the caller's.","items":[]},{"text":"`NaturalCompare(a, b) int`, for `slices.SortFunc`.","items":[]},{"text":"Later: Unicode normalization (NFC, NFD), its tables generated from Unicode's data files as terminal/cells generates its own.","items":[]},{"text":"Not here: display width and truncation (terminal/cells has them), slugs, edit distance.","items":[]},{"text":"Checked: the case tables of Rust's heck and of ettle/strcase, as data; natural sort fuzzed against pkgsite's and maruel/natural; normalization against Unicode's `NormalizationTest.txt`.","items":[]}]}]},{"title":"Overflow and sizes for people: what Go declined or leaves out","source":"xtend.md","heading":"xtend-math","blocks":[{"t":"p","text":"Problems: integer overflow wraps silently, and Go declined checked arithmetic as fit for a library (issues 24853, 60858); gosec's rule G115 flags every narrowing conversion; numbers scaled for people (\"1.5 GiB\", \"2.4 GHz\") are written anew in each program (system-info-cli's `scaled`). config-spec's `Size` is not one of them: it reads and writes sizes exactly, so a configuration saved again keeps its number; it stays as it is."},{"t":"list","ordered":false,"items":[{"text":"`Add`, `Sub`, `Mul` for any integer type, returning `(T, ok)`.","items":[]},{"text":"`Convert[To](v) (To, ok)`: a conversion that reports what it can't hold.","items":[]},{"text":"`Scale(value, step, units...)`: a number for people in the largest unit that keeps it readable, one decimal (bytes, hertz, bits per second, watt hours), as system-info-cli's `scaled` does today.","items":[]},{"text":"`FormatBytes`, `ParseBytes` (IEC and SI), for sizes people type and read; `FormatCount` (thousands), `Ordinal` (1st, 2nd).","items":[]},{"text":"Not here: decimal numbers (a module of its own if money is ever needed).","items":[]},{"text":"Checked: every pair of 8-bit values against wider arithmetic; 64-bit values fuzzed against `math/big`; sizes against go-humanize's tables.","items":[]}]}]},{"title":"Errors without stacks in Go; pkg/errors frozen since 2020","source":"xtend.md","heading":"xtend-errors","blocks":[{"t":"p","text":"Problems: Go errors carry no stack (issues 63358, 60873, open); pkg/errors is frozen since 2020; walking the tree `errors.Join` builds; attributes for logs; a message for users kept apart from one with private details."},{"t":"list","ordered":false,"items":[{"text":"`New(text)`, `Wrap(err, text)`, `Errorf`: capture the stack through callstack (program counters only; names resolved when printed).","items":[]},{"text":"`All(err) iter.Seq[error]`, `AllAs[T](err) iter.Seq[T]`: every error in a joined tree.","items":[]},{"text":"`With(err, key, value, ...)` and `Attrs(err)`: attributes logging-* can read.","items":[]},{"text":"`Code(err) string`: a code for mapping errors to statuses.","items":[]},{"text":"`Stack(err)`: the stack of the innermost error that has one.","items":[]},{"text":"Later: `Public(err, message)`, a message safe to show, which logging-filters-redact can rely on.","items":[]},{"text":"It takes over todo.md's \"errors that carry a stack\" (debug-* family).","items":[]},{"text":"Checked: `errors.Is` and `errors.As` over joined trees; the `%+v` format as pkg/errors prints it; the cost of capturing a stack against cockroachdb/errors.","items":[]}]}]},{"title":"JSON Pointer and Patch: what encoding/json/v2 has and lacks","source":"xtend.md","heading":"xtend-json","blocks":[{"t":"p","text":"Problems: reading or changing one value in a document without structs; PATCH APIs (RFC 6902, RFC 7396); JSON Pointer (RFC 6901) exists in Go only as a type (`jsontext.Pointer`), with nothing to look one up. Canonical JSON (RFC 8785) is in Go already (`jsontext.Value.Canonicalize`)."},{"t":"list","ordered":false,"items":[{"text":"`Get`, `Set`, `Remove` by JSON Pointer, on `jsontext.Value`.","items":[]},{"text":"`Patch(doc, patch)`: RFC 6902, `test` included.","items":[]},{"text":"`MergePatch(doc, patch)`, `CreateMergePatch(a, b)`: RFC 7396.","items":[]},{"text":"Later: `Diff(a, b)` giving an RFC 6902 patch; JSONPath queries (RFC 9535).","items":[]},{"text":"Kept, deprecated: `ToString`, `ToStringIndent`, which the owner's other projects call.","items":[]},{"text":"Checked: the json-patch-tests suite; the RFCs' own examples; the JSONPath compliance suite; `Patch(Diff(a, b), a)` equals b, fuzzed.","items":[]}]}]},{"title":"What Go 1.27's uuid lacks","source":"xtend.md","heading":"xtend-random","blocks":[{"t":"p","text":"Problems: random strings over an alphabet without the modulo bias of `n % len(alphabet)`; IDs that sort by time (UUID v7, ULID); tokens of a given strength."},{"t":"list","ordered":false,"items":[{"text":"`String(n, alphabet)`: from crypto/rand, unbiased.","items":[]},{"text":"`Token(bits)`: a random token as text, in base32 or base64url.","items":[]},{"text":"`SortableID()`: a UUID v7 written in Crockford base32, as a ULID (the same bytes, the same millisecond order).","items":[]},{"text":"What Go 1.27's `uuid` lacks (decided 2026-10-16 to live here): `NewV5` and `NewV3` (IDs from names, with the standard namespaces), `Version(u)`, v7's `Time(u)`, `NewV7At(t)`.","items":[]},{"text":"Checked: a chi-square test on the alphabet generator; v3, v5 and v7 against RFC 9562's vectors and Python's `uuid`; ULID text against oklog/ulid.","items":[]}]}]},{"title":"os/user without cgo, and the real against the effective user","source":"xtend.md","heading":"xtend-user","blocks":[{"t":"p","text":"Problems: \"am I root\" checks the real uid where the effective one matters (.NET, adding its own check, found many Linux implementations doing so); Windows' split tokens (an administrator who is not elevated); without cgo, `os/user` reads only `/etc/passwd`, missing LDAP and SSSD users."},{"t":"list","ordered":false,"items":[{"text":"`Privileged()`: effective uid 0 on Unix; an elevated token on Windows.","items":[]},{"text":"`Elevatable()`: could become privileged (an administrator not elevated; macOS's admin group; sudo rights are not guessed).","items":[]},{"text":"`Current()`: the user's name, display name, home, real and effective ids, groups by id and name.","items":[]},{"text":"The cached name-to-id lookups move here from `system-process-spec/kit/users.go`.","items":[]},{"text":"Checked: in the VMs as root, under sudo and from a setuid program; Windows elevated and not; the Mac with an admin and a standard user.","items":[]}]}]},{"title":"Deadlocks Go's runtime does not report","source":"xtend.md","heading":"xtend-sync","blocks":[{"t":"p","text":"Problems: Go's runtime reports a deadlock only when every goroutine is stuck; lock-order inversions hide until timing exposes them in production; a recursive `RLock` deadlocks when a writer waits. The goroutine-leak profile (Go 1.27) finds goroutines blocked for good, not inversions yet to fire."},{"t":"list","ordered":false,"items":[{"text":"`Mutex`, `RWMutex`: drop-in, keeping a graph of lock order; an inversion is reported with both stacks (callstack); recursive locking is found; a wait past a limit is reported with the holder's stack.","items":[]},{"text":"A build tag makes them plain `sync` types, at no cost.","items":[]},{"text":"Rebuilt on callstack, without the third-party goroutine-ID library the old code needed.","items":[]},{"text":"Checked: an ABBA test, dining philosophers, recursive `RLock` with a waiting writer under synctest; a measured table against go-deadlock.","items":[]}]}]}]}],"modules":{"app-spec":{"group":"app","line":"The contract the four app modules share: App, Start, Run, Stop and their context.","tags":["contract"],"keywords":"","summary":"The contract of goframework's application modules, app, app-as-service, app-w-plugins and app-as-service-w-plugins. Each of them gives these types under its own package name, `app`, so an application moves between them by changing its import.","snippet":"requests := make(chan struct{})\ngo func() { /* exercise the application */ requests <- struct{}{} }()\ncode := kit.Main(application, kit.Requests(requests))","uses":["callstack","crashproof","logging-config","logging-spec","logging-styles-classic","system-native-windows","system-process-all","system-process-spec"],"usedBy":["app","app-as-service","app-as-service-w-plugins","app-plugin","app-samples","app-w-plugins"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You write an application: import app or one of its three siblings. app-spec is the contract they share; its kit runs an App in tests.","measures":[],"checked":[{"kind":"tests","against":"the steps' order, stop requests, failures and panics in each step, the stop deadline, panics in other goroutines, run 20 times with the race detector","result":"","source":"app-spec/readme","quote":"a bad logging configuration. Run 20 times with the race detector."},{"kind":"tests","against":"the plugin protocol on in-memory connections: calls both ways, errors and panics, cancellation, a lost connection, 500 events in order, a 5 MiB message, refused frames and messages","result":"","source":"app-spec/readme","quote":"The protocol on in-memory connections: calls both ways and every method shape, errors and panics, cancellation"},{"kind":"fuzzed","against":"the protocol's frame reader, four million inputs","result":"","source":"app-spec/readme","quote":"the frame reader fuzzed (four million inputs)"},{"kind":"tests","against":"the plugin host without processes: no plugins, an invalid configuration, a required plugin failing for good, a panicking Run","result":"","source":"app-spec/readme","quote":"The host without processes: no plugins, an invalid configuration, a required plugin failing for good, a panicking Run."},{"kind":"systems","against":"Debian, the BSDs, Windows Server 2025 and macOS","result":"","source":"app-spec/readme","quote":"All ran on Debian, the BSDs, Windows Server 2025 and macOS; real plugin processes are tested in app-plugin."}]},"app":{"group":"app","line":"Runs an application from start to exit code; panics come back as errors, never as a crash.","tags":[],"keywords":"main signal sigterm ctrl+c shutdown lifecycle","summary":"Runs an application from start to exit code: Start, then Run until Ctrl+C or SIGTERM, then Stop within a deadline; then it stops the child processes still running, flushes the log and ends the program. Panics in any step come back as an error with their stack, never as a crash.","snippet":"type Settings struct { // the application's configuration: whatever it needs\n\tAddress string `json:\"address\"`\n}\n\ntype Greeter struct{ listener net.Listener; server *http.Server }\n\nfunc (thisRef *Greeter) Start(ctx context.Context, a *app.Context[Settings]) error { // ready when it returns\n\tlistener, err := net.Listen(\"tcp\", a.Config().Address)\n\tthisRef.listener = listener\n\treturn err\n}\nfunc (thisRef *Greeter) Run(ctx context.Context, a *app.Context[Settings]) error { … } // the work\nfunc (thisRef *Greeter) Stop(ctx context.Context, a *app.Context[Settings]) error { … } // let go, within the deadline\n\nfunc main() {\n\tgreeter := &Greeter{}\n\tapp.Run(app.App[Settings]{\n\t\tName: \"greeter\", Version: \"1.0.0\",\n\t\tConfig: Settings{Address: \"127.0.0.1:8080\"},\n\t\tLogging: logconfig.Config{Level: logconfig.Info}, // standard error as text when it has no outputs\n\t\tStart: greeter.Start, Run: greeter.Run, Stop: greeter.Stop,\n\t})\n}","uses":["app-spec"],"usedBy":["app-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"signal.NotifyContext, a shutdown deadline, recover and os.Exit, written in every main","notWhen":"A command that does its work and exits: there is nothing to stop.","measures":[],"checked":[{"kind":"tests","against":"app-spec's kit: the steps' order, stop requests while starting and running, failures and panics in each step, the stop deadline, a second stop request","result":"","source":"app/readme","quote":"In [app-spec](../app-spec)'s kit, which this module and app-as-service share: the steps' order, stop requests while starting and running"},{"kind":"systems","against":"Debian, the BSDs, macOS and Windows, through the samples in app-samples","result":"","source":"app/readme","quote":"The samples in [app-samples](../app-samples) ran on Debian, the BSDs, macOS and Windows."}]},"app-as-service":{"group":"app","line":"The same application, run by a user or system service manager.","tags":[],"keywords":"daemon service systemd windows service","summary":"Runs an application as the program of a user or system service: the same `App` as app, with the service manager's runtime underneath. The code does not change, only the import:","snippet":"import app \"goframework.io/app-as-service\" // was \"goframework.io/app\"","uses":["app-spec","system-service-all","system-service-spec"],"usedBy":["app-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"coreos/go-systemd's SdNotify and x/sys/windows/svc, called by hand","notWhen":"Nothing runs the program but a terminal, or a container runtime that only sends SIGTERM: app is enough.","measures":[],"checked":[{"kind":"real servers","against":"systemd on Debian, the sample service installed: readiness, stop, exit status","result":"","source":"app-as-service/readme","quote":"The sample service ran installed: under systemd on Debian (readiness, stop, exit status)"},{"kind":"real servers","against":"the Windows Service Control Manager on Windows Server 2025, the sample service installed: starting, running, stopping, exit code","result":"","source":"app-as-service/readme","quote":"under the Service Control Manager on Windows Server 2025 (starting, running, stopping, exit code)"},{"kind":"tests","against":"app-spec's kit, for the steps","result":"","source":"app-as-service/readme","quote":"The steps are tested in [app-spec](../app-spec)'s kit."}]},"app-w-plugins":{"group":"app","line":"An application with plugins: separate programs started before Start, stopped after Stop.","tags":[],"keywords":"plugin json-rpc","summary":"Runs an application with plugins: app's `App`, with the plugins started before Start, every one answering its handshake, and stopped after Stop. Plugins are separate programs built with app-plugin; app-as-service-w-plugins runs the same application under a service manager.","snippet":"plugins := app.NewPlugins(app.PluginsConfig{Plugins: []app.Plugin{{\n\tName: \"greeter\",\n\tCommand: []string{\"./greeter-plugin\"}, // relative to this program\n\tRequired: true,\n\tSettings: []byte(`{\"prefix\": \"Hello\"}`),\n}}}, app.Implement[greetapi.Journal](journal, greetapi.Version)) // what the application offers them\ngreetings := greetapi.NewGreetings(plugins) // a generated stub\napp.Run(app.App[Settings]{Name: \"with-plugins\", Start: start, Run: run, Stop: stop}, plugins)","uses":["app-spec"],"usedBy":["app-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"hashicorp/go-plugin and natefinch/pie","notWhen":"Every extension is your own code, built and shipped with the application: a Go package is simpler.","measures":[],"checked":[{"kind":"measured","against":"a 64 MiB buffer passed to a started plugin as bytes, on all six systems","result":"in shared memory it reaches the plugin in 0.04 to 0.4 ms, against 59 to 208 ms as bytes","source":"app.md","quote":"a 64 MiB buffer reaches a started plugin in 0.04 to 0.4 ms by name, against 59 to 208 ms as bytes"},{"kind":"systems","against":"the samples on Debian, Windows Server 2025 and macOS: answers through the plugin, its calls back, events, logs, a stop, and on Windows the plugin ending with an application killed","result":"","source":"app-w-plugins/readme","quote":"The [samples](../app-samples) ran on Debian, Windows Server 2025 and macOS: answers through the plugin, its calls back, events, logs, a stop"},{"kind":"systems","against":"real plugins in the VMs and on the test Mac, with app-spec's kit and app-plugin's tests","result":"","source":"app-w-plugins/readme","quote":"the protocol, the host without processes here, real plugins in the VMs and on the test Mac"},{"kind":"systems","against":"plugins compiled in, on the host and in the integration suites on all six systems","result":"","source":"app.md","quote":"Tested in-process on the host and in the integration suites on all six systems"}]},"app-as-service-w-plugins":{"group":"app","line":"An application with plugins, run by a service manager.","tags":[],"keywords":"plugin service daemon","summary":"Runs an application with plugins as the program of a user or system service: app-w-plugins's application and plugins, with app-as-service's runtime underneath. The code does not change, only the import:","snippet":"import app \"goframework.io/app-as-service-w-plugins\" // was \"goframework.io/app-w-plugins\"","uses":["app-spec","system-service-all","system-service-spec"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"real servers","against":"systemd and the Windows Service Control Manager, the runtime installed, in app-as-service's tests","result":"","source":"app-as-service-w-plugins/readme","quote":"the runtime installed under systemd and the Windows Service Control Manager with app-as-service"},{"kind":"tests","against":"app-plugin's integration tests, for the plugins","result":"","source":"app-as-service-w-plugins/readme","quote":"the plugins in [app-plugin](../app-plugin)'s integration tests"}]},"app-plugin":{"group":"app","line":"What a plugin imports: a separate program the application starts, talks to over JSON-RPC, and stops.","tags":[],"keywords":"plugin json-rpc rpc","summary":"What a plugin of a goframework application imports. A plugin is a separate program: the application (app-w-plugins) starts it, talks to it over its standard input and output, keeps it running and stops it. The contract between them is a Go interface in a package both import; there is no IDL and no compiler to install.","snippet":"package main\n\nfunc main() {\n\tjournal := greetapi.NewJournal(plugin.Application()) // the application's API, through a stub\n\tplugin.Serve(plugin.Info{Name: \"greeter\", Version: \"1.0.0\"},\n\t\tplugin.Implement[greetapi.Greetings](&greeter{journal: journal}, greetapi.Version),\n\t\tplugin.OnEvent(greetapi.LeftEvent, func(ctx context.Context, left greetapi.Left) { ... }),\n\t)\n}","uses":["app-spec","logging-spec","logging-styles-classic","system-process-all","system-process-spec"],"usedBy":["app-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"the plugin side of hashicorp/go-plugin: protobuf with generated code, or net/rpc wrappers by hand","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"real plugin processes on Debian 13, FreeBSD 15, OpenBSD 7.9, NetBSD 11, Windows Server 2025 and macOS 15","result":"the integration tests passed on all six; the frozen-plugin case is not run on Windows","source":"app-plugin/readme","quote":"It passed on Debian 13, FreeBSD 15, OpenBSD 7.9, NetBSD 11, Windows Server 2025 (no freezing there) and macOS 15."},{"kind":"tests","against":"real plugins failing: a crash and the restart, a hung plugin killed and restarted, start failures, giving up after five crashes","result":"","source":"app-plugin/readme","quote":"a crash and the restart, a hung (frozen) plugin killed and restarted, start failures"},{"kind":"measured","against":"streams and buffers through a child process and over a Unix socket, on all six systems","result":"checked byte for byte; 0.6 to 2 GB/s on the loopback paths, about 180 MB/s on Windows' pipes toward the plugin","source":"app.md","quote":"checked byte for byte through a child and over a Unix socket on all six systems, 0.6 to 2 GB/s on the loopback paths"},{"kind":"tests","against":"an application on an in-memory connection: handshake, calls both ways, settings, events, logs, stop, a refused handshake, the application going away","result":"","source":"app-plugin/readme","quote":"the plugin side runs against an application on an in-memory connection"},{"kind":"tests","against":"plugins running on their own, in one process: Unix sockets, tokens, a second application, TLS with certificates","result":"a second connection under the same name, a wrong token and a second application are refused","source":"app-plugin/readme","quote":"a wrong token refused, the application dialling a listening plugin"},{"kind":"tests","against":"the stub generator, on a sample API and broken ones","result":"","source":"app-plugin/readme","quote":"the stub generator against a sample API and broken ones"}]},"app-samples":{"group":"app","line":"Programs built with the app modules.","tags":["examples"],"keywords":"","summary":"Programs built with goframework's application modules.","snippet":"","uses":["app","app-as-service","app-plugin","app-spec","app-w-plugins"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Debian, the BSDs, macOS and Windows","result":"","source":"app/readme","quote":"The samples in [app-samples](../app-samples) ran on Debian, the BSDs, macOS and Windows."},{"kind":"real servers","against":"the service sample installed under systemd on Debian and the Windows Service Control Manager on Windows Server 2025","result":"","source":"app-as-service/readme","quote":"The sample service ran installed: under systemd on Debian (readiness, stop, exit status)"},{"kind":"systems","against":"the plugin samples on Debian, Windows Server 2025 and macOS","result":"","source":"app-w-plugins/readme","quote":"The [samples](../app-samples) ran on Debian, Windows Server 2025 and macOS"}]},"cliflags":{"group":"cliflags","line":"Flags, commands and subcommands defined as Go structs, with help and bash and zsh completion.","tags":[],"keywords":"flags argv arguments cli command completion","summary":"Named Flag definitions in Go structs; each flag can have long name, short name, defaults, an environment variable, be required, be hidden; ex: `--version` and `-v` OR `--long` and `-s`; commands, subcommands, groups + command aliases.","snippet":"package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"goframework.io/cliflags\"\n)\n\ntype GlobalFlags struct {\n\tVerbose bool `flagName:\"verbose\" flagShortName:\"v\" flagDescription:\"Verbose output\"`\n\tJSON bool `flagName:\"json\" flagShortName:\"j\" flagDefault:\"false\" flagDescription:\"Write JSON output\"`\n\tConfig *string `flagName:\"config\" flagShortName:\"C\" flagDescription:\"Optional configuration file\"`\n}\n\ntype StartFlags struct {\n\tName string `flagName:\"name\" flagShortName:\"n\" flagRequired:\"true\" flagDescription:\"Service name\"`\n\tAddress string `flagName:\"address\" flagShortName:\"a\" flagDefault:\"127.0.0.1\" flagDescription:\"Listen address\"`\n\tPort uint16 `flagName:\"port\" flagShortName:\"p\" flagDefault:\"8080\" flagDescription:\"Listen port\"`\n\tWorkers uint8 `flagName:\"workers\" flagShortName:\"w\" flagDefault:\"4\" flagDescription:\"Worker count\"`\n\tTimeout float64 `flagName:\"timeout\" flagShortName:\"t\" flagDefault:\"1.5\" flagDescription:\"Request timeout in seconds\"`\n\tRetries uint `flagName:\"retries\" flagShortName:\"r\" flagDefault:\"3\" flagDescription:\"Retry limit\"`\n\tTLS bool `flagName:\"tls\" flagShortName:\"T\" flagDescription:\"Enable TLS\"`\n\tCertificate *string `flagName:\"certificate\" flagShortName:\"c\" flagDescription:\"Optional certificate file\"`\n\tKey *string `flagName:\"key\" flagShortName:\"k\" flagDescription:\"Optional private key file\"`\n\tDryRun bool `flagName:\"dry-run\" flagShortName:\"d\" flagDescription:\"Preview the operation\"`\n\tDataDir string `flagName:\"data-dir\" flagShortName:\"D\" flagDefault:\"./data\" flagDescription:\"Directory for service data. Long descriptions wrap inside their column so flag metadata stays aligned.\"`\n\tRate float32 `flagName:\"rate\" flagShortName:\"l\" flagDefault:\"0.5\" flagDescription:\"Requests per second\"`\n\tNote *string `flagName:\"note\" flagShortName:\"o\" flagDescription:\"Optional note; unset differs from empty\"`\n\tFormat string `flagName:\"format\" flagShortName:\"f\" flagDefault:\"text\" flagDescription:\"Output format\"`\n\tLegacy bool `flagName:\"legacy\" flagHidden:\"true\"`\n}\n\ntype NamedFlags struct {\n\tName string `flagName:\"name\" flagShortName:\"n\" flagRequired:\"true\" flagDescription:\"Resource name\"`\n}\n\n// The demo reports parsed options; it does not change services or devices.\nfunc report(inv *cliflags.Invocation) error {\n\tglobals := inv.Globals.(*GlobalFlags)\n\tpath := strings.Join(inv.Path, \" \")\n\tif globals.JSON {\n\t\treturn json.NewEncoder(os.Stdout).Encode(map[string]any{\n\t\t\t\"command\": path, \"globals\": globals, \"flags\": inv.Flags,\n\t\t})\n\t}\n\t_, err := fmt.Fprintf(os.Stdout, \"%s (verbose=%t): %+v\\n\", path, globals.Verbose, inv.Flags)\n\treturn err\n}\n\nfunc newCLI() (*cliflags.Command, error) {\n\tservice := &cliflags.Command{Name: \"service\", Aliases: []string{\"svc\"}, Description: \"Manage services\"}\n\tif err := service.AddCommand(\n\t\t&cliflags.Command{\n\t\t\tName: \"start\", Aliases: []string{\"up\"}, Description: \"Start a service\",\n\t\t\tFlags: &StartFlags{},\n\t\t\tExamples: []string{\n\t\t\t\t\"ops service start --name=api -vT\",\n\t\t\t\t\"ops service start --name=api -j --note=\",\n\t\t\t},\n\t\t\tHandler: func(inv *cliflags.Invocation) error {\n\t\t\t\tflags := inv.Flags.(*StartFlags)\n\t\t\t\tif flags.Rate < 0 {\n\t\t\t\t\treturn fmt.Errorf(\"rate must not be negative\")\n\t\t\t\t}\n\t\t\t\treturn report(inv)\n\t\t\t},\n\t\t},\n\t\t&cliflags.Command{Name: \"stop\", Description: \"Stop a service\", Flags: NamedFlags{}, Handler: report},\n\t\t&cliflags.Command{Name: \"status\", Description: \"Show service status\", Flags: NamedFlags{}, Handler: report},\n\t); err != nil {\n\t\treturn nil, err\n\t}\n\n\troot := &cliflags.Command{\n\t\tName: \"ops\", Description: \"Service and device operations\",\n\t\tFlags: &GlobalFlags{}, Handler: report,\n\t\tExamples: []string{\"ops -v service start --name=api\", \"ops add --name=sensor -j\"},\n\t}\n\tif err := root.AddGroup(\"System\", service); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := root.AddGroup(\"Devices\",\n\t\t&cliflags.Command{Name: \"add\", Aliases: []string{\"a\"}, Description: \"Add a device\", Flags: NamedFlags{}, Handler: report},\n\t\t&cliflags.Command{Name: \"remove\", Description: \"Remove a device\", Flags: NamedFlags{}, Handler: report},\n\t); err != nil {\n\t\treturn nil, err\n\t}\n\treturn root, nil\n}\n\nfunc main() {\n\tif err := cliflags.Execute(newCLI); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}","uses":[],"usedBy":["config-cli","system-info-cli","system-service-cli"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"the flag package with a FlagSet per command; spf13/cobra with pflag; alecthomas/kong","notWhen":"You need positional arguments, durations, fish or PowerShell completion, or man pages.","measures":[],"checked":[]},"config-spec":{"group":"config","line":"The engine: layers, binding to a struct, errors with positions, where each value came from.","tags":["contract"],"keywords":"settings","summary":"The contract and the engine of goframework's configuration modules: an application's settings, in any format and from several layers, end up in one Go struct, and the application can ask where each value came from. Each format module, config-json first, gives the same API under the package name `config`, so an application moves between formats by changing its import.","snippet":"","uses":[],"usedBy":["config-all","config-cli","config-dotenv","config-hcl","config-ini","config-json","config-jsonc","config-properties","config-source-file","config-toml","config-xml","config-yaml"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You read settings: import a format module or config-all. config-spec is for writing a format or a source.","measures":[],"checked":[{"kind":"tests","against":"kittest, the suite every format module runs: the same settings in each format bind to the same values with their lines, an unknown key is reported on its line, what is written loads back the same","result":"","source":"config-spec/readme","quote":"`Run(t, Case)` checks a format module: the same settings written in its format bind to the same values"},{"kind":"tests","against":"limits on hostile inputs: deep nesting, alias bombs, huge files","result":"","source":"config.md","quote":"limits on hostile inputs (deep nesting, alias bombs, huge files); fuzzing of every parser"},{"kind":"tests","against":"layers, merging and precedence; Explain with secrets masked","result":"","source":"config.md","quote":"layers, merging and precedence; environment and `_FILE`; Explain with secrets masked."},{"kind":"measured","against":"Values with 100 000 paths, on the host","result":"loads in 0.33 s","source":"config.md","quote":"`Values` with 100 000 paths in 0.33 s"},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass after the kit's change","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."}]},"config-json":{"group":"config","line":"Settings from JSON files and the environment.","tags":[],"keywords":"settings env","summary":"An application's settings from JSON files, the environment and values the application gives, into one Go struct, with where each value came from. Every goframework configuration module has this API, so an application moves to another format by changing its import.","snippet":"import (\n\tconfig \"goframework.io/config-json\"\n\tsource \"goframework.io/config-source-file\"\n)\n\ntype Settings struct {\n\tPort int\n\tToken string `config:\"token,secret\"`\n\tDatabase struct {\n\t\tHost string `config:\",required\"`\n\t}\n}\n\nsettings := Settings{Port: 8080} // defaults, in code\norigins, err := config.Load(&settings,\n\tsource.Open(\"/etc/editor/config.json\"), // missing files are skipped\n\tsource.Folder(\"/etc/editor/conf.d\"), // its .json files, by name\n\tsource.Open(\"~/.config/editor/config.json\"),\n\tsource.Environment(\"EDITOR\"), // EDITOR_PORT, EDITOR_DATABASE__HOST, EDITOR_TOKEN_FILE\n)\n// err, every problem at once, settings left as they were:\n// /etc/editor/config.json:3:3: prot is not a setting of Settings\n// $EDITOR_PORT: port is an integer, not \"eighty\"\n\nfor _, setting := range origins.Explain() {\n\tfmt.Println(setting) // Port = 9090 ($EDITOR_PORT)\n} // token = **** (/etc/editor/config.json:4:12)","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"encoding/json and os.Getenv, written setting by setting","notWhen":"The JSON is not settings, such as an API's messages: encoding/json.","measures":[],"checked":[{"kind":"measured","against":"a 4.4 MB JSON document of 100 000 entries, on the host","result":"loads in 0.32 s","source":"config.md","quote":"a 4.4 MB JSON document of 100 000 entries loads in 0.32 s"},{"kind":"fuzzed","against":"the JSON parser, 12 million inputs","result":"no failure","source":"config.md","quote":"the parser took 12 million fuzzed inputs without a failure"},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"tests pass","source":"config.md","quote":"fuzzed inputs without a failure. Tests pass on Linux, FreeBSD, Windows Server 2025 and macOS."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-jsonc":{"group":"config","line":"JSON with comments and trailing commas, as VS Code writes it.","tags":[],"keywords":"settings","summary":"An application's settings from JSON with comments and trailing commas, as VS Code and TypeScript write their settings, into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"},{"kind":"tests","against":"the readme's example, read by a test","result":"","source":"config.md","quote":"Each readme's example is read by a test, so it stays true."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."}]},"config-yaml":{"group":"config","line":"YAML 1.2, a strict and safe subset.","tags":[],"keywords":"settings yml","summary":"An application's settings from YAML 1.2 files, a strict and safe subset, into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"gopkg.in/yaml.v3 or goccy/go-yaml, for settings files","notWhen":"Your YAML needs several documents, custom tags or keys that are not text: this is a strict, safe subset of YAML 1.2.","measures":[],"checked":[{"kind":"test suite","against":"the official yaml-test-suite, 402 cases (data commit 6ad3d2c)","result":"all pass: 94 invalid documents fail, 229 valid ones parse to their JSON, 5 empty ones set nothing, 2 without JSON parse, and the 72 outside the subset fail with the message listed for each","source":"config.md","quote":"The official yaml-test-suite (data commit 6ad3d2c, MIT, 402 cases) is kept in config-yaml's testdata and all of it passes"},{"kind":"fuzzed","against":"fuzzing seeded with the suite's documents","result":"it found the long-key case; after that it took 10 million inputs","source":"config.md","quote":"Fuzzing, seeded with the suite's documents, took 10 million inputs after it found the long-key case."},{"kind":"measured","against":"a 10 MB document and a flow list of a million values","result":"the document parses in 0.2 s, the list in 0.6 s","source":"config.md","quote":"A 10 MB document parses in 0.2 s, a flow list of a million values in 0.6 s."},{"kind":"tests","against":"a billion laughs document, aliases limited to 10 000 values","result":"an error naming the alias","source":"config.md","quote":"a billion laughs is an error naming the alias"},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-toml":{"group":"config","line":"TOML 1.0.","tags":[],"keywords":"settings","summary":"An application's settings from TOML 1.0 files into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"BurntSushi/toml or pelletier/go-toml, for settings files","notWhen":"You need TOML 1.1's additions: this is TOML 1.0.","measures":[],"checked":[{"kind":"test suite","against":"the official toml-test suite's TOML 1.0.0 cases (commit ff49d10): 208 valid, 501 invalid","result":"all pass, compared as toml-test compares; two bugs put in on purpose were caught by it","source":"config.md","quote":"The official toml-test suite's TOML 1.0.0 cases (commit ff49d10, MIT, 208 valid and 501 invalid) are kept in config-toml's testdata and all pass"},{"kind":"reference","against":"tomli's rules for which tables may be given or extended, and when","result":"","source":"config.md","quote":"Which tables may be given or extended, and when, follows the rules tomli implements"},{"kind":"fuzzed","against":"fuzzing seeded with the suite's valid documents, 22 million inputs","result":"no failure","source":"config.md","quote":"fuzzing, seeded with the suite's valid documents, took 22 million inputs without a failure"},{"kind":"measured","against":"a 3.6 MB document of 100 000 tables","result":"parses in 0.33 s","source":"config.md","quote":"A 3.6 MB document of 100 000 tables parses in 0.33 s."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-ini":{"group":"config","line":"INI files.","tags":[],"keywords":"settings","summary":"An application's settings from INI files into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"go-ini/ini","notWhen":"You need interpolation between keys: there is none.","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing with one property: what parses is written and reads back the same","result":"it found a key holding ], which cannot head a section, since fixed; afterwards INI took 20 million inputs","source":"config.md","quote":"It found two cases, both fixed: an INI key holding `]`, which cannot head a section"},{"kind":"tests","against":"the readme's example, read by a test","result":"","source":"config.md","quote":"Each readme's example is read by a test, so it stays true."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-dotenv":{"group":"config","line":".env files, as Docker Compose writes them.","tags":[],"keywords":"settings env docker","summary":"An application's settings from `.env` files, as Docker Compose writes them, into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"joho/godotenv","notWhen":"You need ${VAR} interpolation: $ is text.","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing with one property: what parses is written and reads back the same","result":".env took 17 million inputs","source":"config.md","quote":"INI took 20 million inputs afterwards, .env 17 million, properties 18 million"},{"kind":"tests","against":"the readme's example, read by a test","result":"","source":"config.md","quote":"Each readme's example is read by a test, so it stays true."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-properties":{"group":"config","line":"Java properties files.","tags":[],"keywords":"settings java","summary":"An application's settings from Java properties files into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"magiconair/properties","notWhen":"","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing with one property: what parses is written and reads back the same","result":"properties took 18 million inputs","source":"config.md","quote":"INI took 20 million inputs afterwards, .env 17 million, properties 18 million"},{"kind":"tests","against":"the readme's example, read by a test","result":"","source":"config.md","quote":"Each readme's example is read by a test, so it stays true."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-hcl":{"group":"config","line":"HCL 2, literals only.","tags":[],"keywords":"settings terraform","summary":"An application's settings from HCL 2 files, literals only, into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"hashicorp/hcl, for settings written as literals","notWhen":"You need HCL's variables, functions or interpolation, as Terraform's language has them: only literals are read.","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing, 30 million inputs","result":"","source":"config.md","quote":"its tests are the spec's forms and fuzzing (30 million inputs)"},{"kind":"tests","against":"the HCL spec's forms; there is no official suite of HCL documents for this subset","result":"","source":"config.md","quote":"There is no official suite of HCL documents for this subset"},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-xml":{"group":"config","line":"XML, elements as keys.","tags":[],"keywords":"settings","summary":"An application's settings from XML files, elements as keys, into one Go struct, with where each value came from. The same API as every goframework configuration module: an application moves to another format by changing its import.","snippet":"","uses":["config-spec"],"usedBy":["config-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"Your settings file has a DOCTYPE, entities, or text beside elements: each is refused.","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing, 14 million inputs","result":"","source":"config.md","quote":"which XML would turn into a line feed. Fuzzed, 14 million inputs."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."},{"kind":"tests","against":"config-spec's kittest, the suite every format module runs","result":"","source":"config.md","quote":"kittest, the suite every format module runs"}]},"config-all":{"group":"config","line":"Any of the nine formats, chosen by each file's extension.","tags":["picks"],"keywords":"settings","summary":"An application's settings from files of any of goframework's nine configuration formats, each file's format chosen by its extension, into one Go struct, with where each value came from. The same API as every format module: an application reading one format moves here, or back, by changing its import.","snippet":"import (\n\tconfig \"goframework.io/config-all\"\n\tsource \"goframework.io/config-source-file\"\n)\n\norigins, err := config.Load(&settings,\n\tsource.Open(\"/etc/editor/config.toml\"),\n\tsource.Folder(\"/etc/editor/conf.d\"), // 10-base.yaml, 20-local.json, ... by name\n\tsource.Open(\"~/.config/editor/config.yaml\"),\n\tsource.Environment(\"EDITOR\"),\n)","uses":["config-dotenv","config-hcl","config-ini","config-json","config-jsonc","config-properties","config-spec","config-toml","config-xml","config-yaml"],"usedBy":["config-cli"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"spf13/viper and knadh/koanf, for files and the environment into a struct","notWhen":"You read one format only: import its module, and link only its parser.","measures":[],"checked":[{"kind":"tests","against":"the same settings written in all nine formats","result":"each reads back to the same values","source":"config.md","quote":"Its test writes the same settings in all nine formats and reads them back to the same values"},{"kind":"tests","against":"one configuration layered across five formats","result":"Explain names each file","source":"config.md","quote":"layers one configuration across five formats, Explain naming each file"},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"tests pass","source":"config.md","quote":"the TOML string \"5432\". - Tests pass on Linux, FreeBSD, Windows Server 2025 and macOS."}]},"config-cli":{"group":"config","line":"The config command: checks files, explains where each setting comes from, converts formats.","tags":["command"],"keywords":"settings validate convert","summary":"The command `config`: checks configuration files in any of goframework's nine formats, explains where each setting of a layered configuration comes from, and converts a file from one format to another. Each file's extension names its format (config-all).","snippet":"","uses":["cliflags","config-all","config-source-file","config-spec","crashproof","terminal"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You want a file checked against your program's struct: the command knows no struct, so every key is a setting; call Load in the program.","measures":[],"checked":[{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"tests pass","source":"config.md","quote":"the TOML string \"5432\". - Tests pass on Linux, FreeBSD, Windows Server 2025 and macOS."}]},"config-source-file":{"group":"config","line":"Reads configuration files for any format module; config itself does no I/O.","tags":[],"keywords":"settings files vfs","summary":"The files of a goframework application's configuration: config itself reads none, and takes these as it takes any source, in any format module.","snippet":"import (\n\tconfig \"goframework.io/config-json\"\n\tsource \"goframework.io/config-source-file\"\n)\n\norigins, err := config.Load(&settings,\n\tsource.Open(\"/etc/editor/config.json\"), // a missing file is skipped\n\tsource.Folder(\"/etc/editor/conf.d\"), // its files, by name, each a layer\n\tsource.Open(\"~/.config/editor/config.json\"),\n\tsource.Environment(\"EDITOR\"), // EDITOR_TOKEN_FILE=/run/secrets/token\n)","uses":["config-spec","vfs","vfs-source-disk","vfs-source-memory"],"usedBy":["config-cli"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"os.ReadFile, and reading PREFIX_KEY_FILE secrets by hand","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"the environment with _FILE secrets","result":"","source":"config.md","quote":"layers, merging and precedence; environment and `_FILE`; Explain with secrets masked."},{"kind":"systems","against":"Linux, FreeBSD, Windows Server 2025 and macOS","result":"the tests of every config module pass","source":"config.md","quote":"Tests of every config module pass again on Linux, FreeBSD, Windows Server 2025 and macOS after the kit's change."}]},"logging-spec":{"group":"logging","line":"The pipe's contracts and levels: Pipe(...).To(output).","tags":["contract"],"keywords":"log logger","summary":"A high-performance logging pipe built from small, independent modules, with no third-party dependencies. Records flow through blocks, each with one duty:","snippet":"file, err := rollingfile.Open(\"app.log\", rollingfile.DefaultRotation(), jsonl.New())\nif err != nil {\n return err\n}\nroot := spec.Pipe(tolevel.Min(spec.LevelInfo), redact.Keys(\"password\"), async.New()).To(\n multi.New(\n spec.Pipe(tolevel.Min(spec.LevelWarning)).To(console.New(nil)),\n file,\n ),\n)\nlog := contextual.New(root)\ndefer log.Close()\n\nlog.Info(ctx, \"request done\", field.Int(\"status\", 200), field.String(\"path\", path))","uses":[],"usedBy":["app-plugin","app-spec","logging-adapters-slog","logging-advanced","logging-config","logging-dispatchers-async","logging-dispatchers-buffered","logging-dispatchers-groupsort","logging-dispatchers-multi","logging-encoders-json","logging-encoders-text","logging-filters-onlylevel","logging-filters-redact","logging-filters-sampling","logging-filters-tolevel","logging-persisters-console","logging-persisters-consolewcolors","logging-persisters-file","logging-persisters-rollingfile","logging-persisters-wineventlog","logging-persisters-writer","logging-samples","logging-styles-classic","logging-styles-context","logging-styles-withfields"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[{"title":"Logging a record","against":"zerolog, zap, log/slog, Logrus, go-kit/log","legend":"Every library writes the same JSON line (RFC3339Nano time, level, message, fields, no caller) to a writer that discards it; field values are computed at runtime. zerolog is ahead with 10 fields and on disabled calls: its chained API does nothing once the level is off, while typed fields are built before any call can check the level.","reproduce":"python3 tools/update-logging-comparison.py","source":"logging-samples/readme","ours":["this framework"],"names":{"this framework":"goframework logging"},"labels":{},"kind":"explorer","measured":"Measured 2026-10-10 with go1.27.1-X:nodwarf5 on Intel(R) Core(TM) Ultra 7 265U (load average 4.1 / 7.3 / 6.8); go-kit/log v0.2.1, Logrus v1.9.4, zap v1.27.0, zerolog v1.32.0.","how":"","metrics":["time per call · allocations per call"],"metric":0,"default":[],"records":[{"path":["Message only"],"name":"this framework","cpu":"","values":[75.0],"shown":["75 ns · 0"]},{"path":["Message only"],"name":"zerolog","cpu":"","values":[111.0],"shown":["111 ns · 0"]},{"path":["Message only"],"name":"zap","cpu":"","values":[185.0],"shown":["185 ns · 0"]},{"path":["Message only"],"name":"zap sugar","cpu":"","values":[185.0],"shown":["185 ns · 0"]},{"path":["Message only"],"name":"log/slog","cpu":"","values":[296.0],"shown":["296 ns · 0"]},{"path":["Message only"],"name":"Logrus","cpu":"","values":[1343.0],"shown":["1,343 ns · 21"]},{"path":["Message only"],"name":"go-kit/log","cpu":"","values":[1372.0],"shown":["1,372 ns · 21"]},{"path":["3 fields"],"name":"this framework","cpu":"","values":[132.0],"shown":["132 ns · 0"]},{"path":["3 fields"],"name":"zerolog","cpu":"","values":[164.0],"shown":["164 ns · 0"]},{"path":["3 fields"],"name":"zap","cpu":"","values":[347.0],"shown":["347 ns · 1"]},{"path":["3 fields"],"name":"zap sugar","cpu":"","values":[503.0],"shown":["503 ns · 4"]},{"path":["3 fields"],"name":"log/slog","cpu":"","values":[443.0],"shown":["443 ns · 0"]},{"path":["3 fields"],"name":"Logrus","cpu":"","values":[2525.0],"shown":["2,525 ns · 36"]},{"path":["3 fields"],"name":"go-kit/log","cpu":"","values":[2268.0],"shown":["2,268 ns · 33"]},{"path":["10 fields"],"name":"this framework","cpu":"","values":[312.0],"shown":["312 ns · 0"]},{"path":["10 fields"],"name":"zerolog","cpu":"","values":[306.0],"shown":["306 ns · 0"]},{"path":["10 fields"],"name":"zap","cpu":"","values":[676.0],"shown":["676 ns · 1"]},{"path":["10 fields"],"name":"zap sugar","cpu":"","values":[1174.0],"shown":["1,174 ns · 7"]},{"path":["10 fields"],"name":"log/slog","cpu":"","values":[1020.0],"shown":["1,020 ns · 3"]},{"path":["10 fields"],"name":"Logrus","cpu":"","values":[5221.0],"shown":["5,221 ns · 65"]},{"path":["10 fields"],"name":"go-kit/log","cpu":"","values":[4242.0],"shown":["4,242 ns · 55"]},{"path":["5 bound + 1 call field"],"name":"this framework","cpu":"","values":[116.0],"shown":["116 ns · 0"]},{"path":["5 bound + 1 call field"],"name":"zerolog","cpu":"","values":[133.0],"shown":["133 ns · 0"]},{"path":["5 bound + 1 call field"],"name":"zap","cpu":"","values":[254.0],"shown":["254 ns · 1"]},{"path":["5 bound + 1 call field"],"name":"zap sugar","cpu":"","values":[308.0],"shown":["308 ns · 2"]},{"path":["5 bound + 1 call field"],"name":"log/slog","cpu":"","values":[353.0],"shown":["353 ns · 0"]},{"path":["5 bound + 1 call field"],"name":"Logrus","cpu":"","values":[3245.0],"shown":["3,245 ns · 41"]},{"path":["5 bound + 1 call field"],"name":"go-kit/log","cpu":"","values":[2903.0],"shown":["2,903 ns · 38"]},{"path":["Disabled (debug at info)"],"name":"this framework","cpu":"","values":[11.0],"shown":["11 ns · 0"]},{"path":["Disabled (debug at info)"],"name":"zerolog","cpu":"","values":[5.0],"shown":["5 ns · 0"]},{"path":["Disabled (debug at info)"],"name":"zap","cpu":"","values":[45.0],"shown":["45 ns · 1"]},{"path":["Disabled (debug at info)"],"name":"zap sugar","cpu":"","values":[31.0],"shown":["31 ns · 2"]},{"path":["Disabled (debug at info)"],"name":"log/slog","cpu":"","values":[13.0],"shown":["13 ns · 0"]},{"path":["Disabled (debug at info)"],"name":"Logrus","cpu":"","values":[259.0],"shown":["259 ns · 7"]},{"path":["Disabled (debug at info)"],"name":"go-kit/log","cpu":"","values":[150.0],"shown":["150 ns · 6"]},{"path":["Parallel, 3 fields"],"name":"this framework","cpu":"","values":[20.0],"shown":["20 ns · 0"]},{"path":["Parallel, 3 fields"],"name":"zerolog","cpu":"","values":[24.0],"shown":["24 ns · 0"]},{"path":["Parallel, 3 fields"],"name":"zap","cpu":"","values":[110.0],"shown":["110 ns · 1"]},{"path":["Parallel, 3 fields"],"name":"zap sugar","cpu":"","values":[198.0],"shown":["198 ns · 4"]},{"path":["Parallel, 3 fields"],"name":"log/slog","cpu":"","values":[166.0],"shown":["166 ns · 0"]},{"path":["Parallel, 3 fields"],"name":"Logrus","cpu":"","values":[864.0],"shown":["864 ns · 36"]},{"path":["Parallel, 3 fields"],"name":"go-kit/log","cpu":"","values":[797.0],"shown":["797 ns · 33"]}],"header":["Scenario","this framework","zerolog","zap","zap sugar","log/slog","Logrus","go-kit/log"],"rows":[["Message only","75 ns · 0","111 ns · 0","185 ns · 0","185 ns · 0","296 ns · 0","1,343 ns · 21","1,372 ns · 21"],["3 fields","132 ns · 0","164 ns · 0","347 ns · 1","503 ns · 4","443 ns · 0","2,525 ns · 36","2,268 ns · 33"],["10 fields","312 ns · 0","306 ns · 0","676 ns · 1","1,174 ns · 7","1,020 ns · 3","5,221 ns · 65","4,242 ns · 55"],["5 bound + 1 call field","116 ns · 0","133 ns · 0","254 ns · 1","308 ns · 2","353 ns · 0","3,245 ns · 41","2,903 ns · 38"],["Disabled (debug at info)","11 ns · 0","5 ns · 0","45 ns · 1","31 ns · 2","13 ns · 0","259 ns · 7","150 ns · 6"],["Parallel, 3 fields","20 ns · 0","24 ns · 0","110 ns · 1","198 ns · 4","166 ns · 0","864 ns · 36","797 ns · 33"]]}],"checked":[{"kind":"other libraries","against":"other Go loggers (tools/logging-compare)","result":"","source":"design.md","quote":"`tools/logging-compare` measures the framework against other Go loggers"},{"kind":"measured","against":"benchmarks with values computed at runtime","result":"A logging call with common field types is allocation-free, groups and errors included.","source":"design.md","quote":"make a logging call with common field types allocation-free, including groups and errors in both encoders; benchmarks use values computed at runtime, not constants"},{"kind":"tests","against":"regression checks run with go test -race","result":"","source":"design.md","quote":"Regression checks cover exact argument values, panic completion, failed writes, shutdown, log retention, formatting, and concurrent access. Run them with `go test -race`"}]},"logging-styles-classic":{"group":"logging","line":"Info, Warningf and the rest: printf-style calls.","tags":[],"keywords":"log printf","summary":"Trace, Debug, Info, Success, Warning, Error, Fatal and Panic, each with an f variant. Disabled levels skip formatting and clock reads; a constant message needs no formatting and allocates nothing, other calls allocate as fmt does. Fatal and Panic record severity only. NewWithOptions adds AddSource, which records the caller. A panic in the pipe is reported by Flush and Close, never raised. Loggers derived with With share the pipe; only the logger created by New closes it.","snippet":"import classic \"goframework.io/logging-styles-classic\"\n\nlog := classic.New(console.New(nil))\ndefer log.Close()\nlog.Info(\"started\")\nlog.Warningf(\"disk at %d%%\", 91)\nrequest := log.With(field.String(\"request_id\", id))","uses":["logging-spec"],"usedBy":["app-plugin","app-spec","logging-samples","logging-styles-withfields"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"the log package's Printf calls","notWhen":"","measures":[],"checked":[]},"logging-styles-context":{"group":"logging","line":"Calls that take a context first, with typed attributes the context carries.","tags":[],"keywords":"log structured context","summary":"Every method takes the context and typed attributes and returns the record's failure; a call with common field types allocates nothing. Records combine bound attributes, then context attributes, then call attributes. The context enriches records; it does not cancel delivery. Attributes bound with With form a spec.Bound, which the JSON and text encoders encode once and reuse. A panic in the pipe is returned as a *spec.PanicError, never raised. AddSource records the call site.","snippet":"import contextual \"goframework.io/logging-styles-context\"\n\nlog := contextual.New(root).With(field.String(\"service\", \"api\"))\nctx = contextual.WithAttrs(ctx, field.String(\"request_id\", id))\nlog.Info(ctx, \"done\", field.Int(\"status\", 200), field.Duration(\"took\", took))","uses":["logging-spec"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"zap's and zerolog's typed fields","notWhen":"","measures":[],"checked":[]},"logging-styles-withfields":{"group":"logging","line":"WithFields with a map, as Logrus users write it.","tags":[],"keywords":"log logrus fields","summary":"The classic methods plus WithFields, which binds map fields in sorted key order with typed values. Convenient for code coming from Logrus; for the fastest calls, use the context style with field constructors.","snippet":"import fields \"goframework.io/logging-styles-withfields\"\n\nlog := fields.New(console.New(nil))\nlog.WithFields(fields.Fields{\"service\": \"api\", \"status\": 200}).Info(\"done\")","uses":["logging-spec","logging-styles-classic"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"Logrus' WithFields","notWhen":"New code that wants the fastest calls: the context style, with field constructors, is faster.","measures":[],"checked":[]},"logging-advanced":{"group":"logging","line":"Concurrent operations logged as readable call trees, by tag and depth.","tags":[],"keywords":"log tree","summary":"Logs concurrent operations as readable call trees: each record carries a tag (the operation) and a depth (its nesting level).","snippet":"import advanced \"goframework.io/logging-advanced\"\n\nroot := spec.Pipe(groupsort.New()).To(writer.New(os.Stderr, advanced.NewEncoder()))\nlog := advanced.New(root)\nlog.Info(\"worker-1\", 0, \"started\")\nlog.Info(\"worker-1\", 1, \"fetching\")\nlog.Close() // delivers the grouped batch","uses":["logging-encoders-text","logging-spec"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"Records go to a collector that reads JSON: the tree layout is for people.","measures":[],"checked":[]},"logging-dispatchers-async":{"group":"logging","line":"One background worker delivers in order; a full queue holds callers back, nothing is dropped.","tags":[],"keywords":"log async queue","summary":"One background worker delivers records in order. A full queue makes callers wait; records are never dropped. Log returns nil once a copy of the record is queued; delivery failures are kept by the output and reported by Flush and Close. Enabled never blocks. Flush waits for every earlier record; Close delivers the queue and closes the rest of the pipe. A panic in the pipe is reported as a failure and the worker continues. QueueSize is capped at MaxQueueSize (1M).","snippet":"import async \"goframework.io/logging-dispatchers-async\"\n\nroot := spec.Pipe(async.New()).To(output) // 256 queued records\nroot = spec.Pipe(async.NewWithOptions(async.Options{QueueSize: 4096})).To(output)","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a channel and a goroutine written by hand","notWhen":"A call must return at once even when the output stalls: a full queue makes callers wait, and no record is dropped.","measures":[],"checked":[]},"logging-dispatchers-buffered":{"group":"logging","line":"Delivers in batches, when full or on a timer.","tags":[],"keywords":"log batch buffer","summary":"A full batch is delivered by the call that fills it; a timer delivers partial batches (a negative FlushInterval disables it). Every record gets one delivery attempt and is never delivered twice; a panic in the output costs only its record and is reported by Flush and Close. Close stops the timer, delivers the rest and closes the rest of the pipe; concurrent Close and Flush calls wait for it. MaxEntries is capped at MaxEntriesLimit (1M).","snippet":"import buffered \"goframework.io/logging-dispatchers-buffered\"\n\nroot := spec.Pipe(buffered.New()).To(file) // batches of 256, at least every second\nroot = spec.Pipe(buffered.NewWithOptions(buffered.Options{MaxEntries: 1000, FlushInterval: 200 * time.Millisecond})).To(file)","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a bufio.Writer around the log file, flushed by hand","notWhen":"Each record must reach the output before the call returns: a batch waits in memory until it is full or its timer fires.","measures":[],"checked":[]},"logging-dispatchers-multi":{"group":"logging","line":"Sends each record down several pipes, each with its own filters and output.","tags":[],"keywords":"log tee fan-out","summary":"Every record goes to every branch; a failing or panicking branch does not stop the others, and Log returns the joined failures. Each branch is a pipe with its own filters, encoder and persister. Flush and Close reach every branch.","snippet":"import multi \"goframework.io/logging-dispatchers-multi\"\n\nroot := multi.New(\n spec.Pipe(tolevel.Min(spec.LevelWarning)).To(console.New(nil)),\n spec.Pipe(redact.Keys(\"token\")).To(file),\n)","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-dispatchers-groupsort":{"group":"logging","line":"Groups concurrent operations' records and orders them by time.","tags":[],"keywords":"log","summary":"Groups the records of concurrent operations by an attribute and orders them by time, so interleaved goroutines read like separate stories.","snippet":"import groupsort \"goframework.io/logging-dispatchers-groupsort\"\n\nroot := spec.Pipe(groupsort.New()).To(writer.New(os.Stderr, advanced.NewEncoder()))\nroot = spec.Pipe(groupsort.NewWithOptions(groupsort.Options{By: \"request_id\", MaxEntries: 4096})).To(output)","uses":["logging-spec"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-filters-tolevel":{"group":"logging","line":"Records at or above a level, changeable while running.","tags":[],"keywords":"log level","summary":"Passes records at the minimum level and above; spec.LevelOff passes nothing. Styles ask Enabled first, so disabled calls skip formatting and clock reads.","snippet":"import tolevel \"goframework.io/logging-filters-tolevel\"\n\nthreshold := tolevel.Min(spec.LevelInfo)\nroot := spec.Pipe(threshold).To(output)\nthreshold.SetLevel(spec.LevelDebug) // safe at any time","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-filters-onlylevel":{"group":"logging","line":"Only the levels listed.","tags":[],"keywords":"log level","summary":"Passes only records at the listed levels. To pass everything above a level, use logging-filters-tolevel.","snippet":"import onlylevel \"goframework.io/logging-filters-onlylevel\"\n\nroot := spec.Pipe(onlylevel.Keep(spec.LevelError, spec.LevelFatal, spec.LevelPanic)).To(alerts)","uses":["logging-spec"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-filters-redact":{"group":"logging","line":"Replaces named values, such as passwords and tokens, with [REDACTED].","tags":[],"keywords":"log secrets password token pii","summary":"Replaces the values of matching attributes with \"[REDACTED]\". Keys match exactly, at every group depth, including inside the groups that spec.Valuer types return; message text is not inspected. Records without sensitive keys pass without allocating; others are passed on as changed copies, so other branches and the caller see the original. Place it before queues and branches so secrets are never kept. Types can also hide their own secrets with spec.Valuer.","snippet":"import redact \"goframework.io/logging-filters-redact\"\n\nroot := spec.Pipe(redact.Keys(\"password\", \"token\")).To(output)","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"slog's ReplaceAttr, given to each handler","notWhen":"The secret is in the message text: only attribute keys are matched.","measures":[],"checked":[]},"logging-filters-sampling":{"group":"logging","line":"The first records of each kind per interval, then every Nth.","tags":[],"keywords":"log rate limit sample","summary":"Per kind of record and per Interval, passes the first First records, then every Every-th one (Every 0 drops the rest). With source capture enabled in the style, records from one call site are one kind, so formatted messages are sampled together; otherwise level and message text identify the kind. Memory is bounded by MaxKeys; further kinds pass unsampled. Sampling can drop errors too.","snippet":"import sampling \"goframework.io/logging-filters-sampling\"\n\nsampler, err := sampling.New(sampling.Config{Interval: time.Second, First: 100, Every: 10, MaxKeys: 1024})\nroot := spec.Pipe(sampler).To(output)","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"zap's and zerolog's samplers","notWhen":"Every error must be kept: sampling drops errors too.","measures":[],"checked":[]},"logging-encoders-text":{"group":"logging","line":"One readable line per record.","tags":[],"keywords":"log format","summary":"One readable line per record:","snippet":"import text \"goframework.io/logging-encoders-text\"\n\nconsole.New(text.New())\nconsole.New(text.NewWithOptions(text.Options{TimeFormat: time.Kitchen, LocalTime: true, ShortLevels: true}))","uses":["logging-spec"],"usedBy":["logging-advanced","logging-config","logging-persisters-console","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-encoders-json":{"group":"logging","line":"One JSON object per line.","tags":[],"keywords":"log jsonl format","summary":"One JSON object per line:","snippet":"import jsonl \"goframework.io/logging-encoders-json\"\n\nfile.Open(\"app.log\", jsonl.New())\nfile.Open(\"app.log\", jsonl.NewWithOptions(jsonl.Options{Flat: true, OmitTime: true}))","uses":["logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-persisters-console":{"group":"logging","line":"Readable records on standard error.","tags":[],"keywords":"log stderr","summary":"Readable records on standard error:","snippet":"import console \"goframework.io/logging-persisters-console\"\n\noutput := console.New(nil) // console.Layout()\noutput = console.New(jsonl.New())\noutput = console.NewWithOptions(console.Options{Writer: os.Stdout})","uses":["logging-encoders-text","logging-persisters-writer","logging-spec"],"usedBy":["logging-config","logging-persisters-consolewcolors","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-persisters-consolewcolors":{"group":"logging","line":"The console, with a colour style per level.","tags":[],"keywords":"log colors ansi","summary":"The console persister with one style per level: colours and attributes such as bold, from terminal-colors.","snippet":"import colored \"goframework.io/logging-persisters-consolewcolors\"\n\noutput := colored.New(nil)\noutput = colored.NewWithOptions(colored.Options{Mode: colored.Always, Palette: palette})","uses":["logging-persisters-console","logging-spec","terminal-colors"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-persisters-file":{"group":"logging","line":"Appends to a file, creating it when missing.","tags":[],"keywords":"log file","summary":"Appends to the file, creating it when missing; history is never erased. Opening errors are returned immediately. Flush syncs the file to storage and Close closes it. A nil encoder writes the message only.","snippet":"import file \"goframework.io/logging-persisters-file\"\n\noutput, err := file.Open(\"app.log\", jsonl.New())\nif err != nil {\n return err\n}","uses":["logging-persisters-writer","logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-persisters-rollingfile":{"group":"logging","line":"Appends and rotates by size: app.log, app-1.log, ...","tags":[],"keywords":"log rotate rotation","summary":"Appends to a file and rotates it by size: app.log, app-1.log, app-2.log, ...","snippet":"import rollingfile \"goframework.io/logging-persisters-rollingfile\"\n\noutput, err := rollingfile.Open(\"app.log\", rollingfile.Rotation{Count: 5, MaxSize: 10 * rollingfile.MiB}, jsonl.New())","uses":["logging-persisters-writer","logging-spec"],"usedBy":["logging-config","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"natefinch/lumberjack","notWhen":"You need rotation by time, compression or deletion by age: it rotates by size and keeps a count of files.","measures":[],"checked":[]},"logging-persisters-writer":{"group":"logging","line":"Any io.Writer, one write per record; the engine behind the others.","tags":[],"keywords":"log writer","summary":"Persists encoded records to any io.Writer, one Write call per record. It is the engine behind the console, file and rolling-file persisters.","snippet":"import writer \"goframework.io/logging-persisters-writer\"\n\noutput := writer.New(os.Stdout, text.New()) // borrows the writer\noutput = writer.NewOwned(conn, jsonl.New()) // closes it on Close\noutput = writer.NewWithOptions(sink, writer.Options{Encoder: jsonl.New(), Concurrent: true})","uses":["logging-spec"],"usedBy":["logging-config","logging-persisters-console","logging-persisters-file","logging-persisters-rollingfile","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"logging-persisters-wineventlog":{"group":"logging","line":"The Windows Event Log.","tags":["Windows"],"keywords":"log eventlog","summary":"Error, Fatal and Panic become error events, Warning becomes a warning event and lower levels become information events, all with EventID 1. The event source must be registered by the installer. The package builds on every platform; outside Windows, Open reports errors.ErrUnsupported. On Windows it calls the Event Log through system-native-windows.","snippet":"import wineventlog \"goframework.io/logging-persisters-wineventlog\"\n\noutput, err := wineventlog.Open(\"my-service\", nil)\nif errors.Is(err, errors.ErrUnsupported) {\n output = console.New(nil) // not on Windows\n}","uses":["logging-spec","system-native-windows"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/sys/windows/svc/eventlog","notWhen":"Outside Windows: Open reports errors.ErrUnsupported. The event source must be registered by your installer.","measures":[],"checked":[]},"logging-config":{"group":"logging","line":"Builds a whole pipe from a plain data object, written in Go or decoded from a file.","tags":[],"keywords":"log configuration","summary":"Builds a complete logging pipe from a data-only object that the application writes in Go or decodes from any file format.","snippet":"import logconfig \"goframework.io/logging-config\"\n\nroot, err := logconfig.New(logconfig.Config{\n Level: logconfig.Info,\n Outputs: []logconfig.Output{\n {Type: logconfig.Stderr, Format: logconfig.Text, Level: logconfig.Warning},\n {Type: logconfig.File, Format: logconfig.JSON, Path: \"app.log\",\n Rotation: &logconfig.Rotation{Count: 5, MaxSize: 10 << 20}},\n },\n Async: &logconfig.Async{},\n Redact: []string{\"password\", \"token\"},\n})\nif err != nil {\n return err\n}\nlog := classic.New(root)\ndefer log.Close()\nlog.Info(\"ready\")","uses":["logging-dispatchers-async","logging-dispatchers-buffered","logging-dispatchers-multi","logging-encoders-json","logging-encoders-text","logging-filters-redact","logging-filters-sampling","logging-filters-tolevel","logging-persisters-console","logging-persisters-file","logging-persisters-rollingfile","logging-persisters-writer","logging-spec"],"usedBy":["app-spec","logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a pipe built by hand in main","notWhen":"You need a block the configuration does not name, such as groupsort, onlylevel, colours or the Event Log: build the pipe in Go.","measures":[],"checked":[]},"logging-adapters-slog":{"group":"logging","line":"slog calls into a goframework pipe, or a slog handler as an output.","tags":[],"keywords":"log slog log/slog","summary":"NewHandler is a style: groups, WithAttrs/WithGroup and LogValuer values are preserved, it passes the standard slogtest suite, and panics in the pipe are returned and reported by Flush and Close. Options.ContextAttrs adds context attributes (for example contextual.Attrs); Options.AddSource keeps caller locations. FromHandler is a persister: Handle errors are returned and kept. Levels are translated by LevelToSlog and LevelFromSlog.","snippet":"import slogadapter \"goframework.io/logging-adapters-slog\"\n\nslog.SetDefault(slog.New(slogadapter.NewHandler(root))) // slog calls into a framework pipe\noutput := slogadapter.FromHandler(handler) // a slog handler as a framework persister","uses":["logging-spec"],"usedBy":["logging-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a slog handler written by hand","notWhen":"","measures":[],"checked":[{"kind":"test suite","against":"Go's slogtest suite","result":"Passes.","source":"logging-adapters-slog/readme","quote":"groups, WithAttrs/WithGroup and LogValuer values are preserved, it passes the standard slogtest suite"}]},"logging-samples":{"group":"logging","line":"Example programs, and the measured comparison with other loggers.","tags":["examples"],"keywords":"zerolog zap logrus benchmark","summary":"More recipes","snippet":"","uses":["logging-adapters-slog","logging-advanced","logging-config","logging-dispatchers-async","logging-dispatchers-buffered","logging-dispatchers-groupsort","logging-dispatchers-multi","logging-encoders-json","logging-encoders-text","logging-filters-onlylevel","logging-filters-redact","logging-filters-sampling","logging-filters-tolevel","logging-persisters-console","logging-persisters-consolewcolors","logging-persisters-file","logging-persisters-rollingfile","logging-persisters-wineventlog","logging-persisters-writer","logging-spec","logging-styles-classic","logging-styles-context","logging-styles-withfields","terminal-colors"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"other libraries","against":"zerolog, zap, log/slog, Logrus and go-kit/log, writing the same JSON line","result":"","source":"logging-samples/readme","quote":"Every library writes the same JSON line (RFC3339Nano time, level, message, fields, no caller) to a writer that discards it"},{"kind":"measured","against":"zerolog, zap, log/slog, Logrus and go-kit/log: a message only","result":"75 ns and no allocation a call, against 111 ns for zerolog and 296 ns for log/slog.","source":"logging-samples/readme","quote":"| Message only | **75 ns · 0** | 111 ns · 0 | 185 ns · 0 | 185 ns · 0 | 296 ns · 0 |"},{"kind":"measured","against":"zerolog, zap, log/slog, Logrus and go-kit/log: 3 fields","result":"132 ns and no allocation, against 164 ns for zerolog and 347 ns with 1 allocation for zap.","source":"logging-samples/readme","quote":"| 3 fields | **132 ns · 0** | 164 ns · 0 | 347 ns · 1 | 503 ns · 4 | 443 ns · 0 |"},{"kind":"measured","against":"zerolog, zap, log/slog, Logrus and go-kit/log: 10 fields","result":"312 ns against 306 ns for zerolog, which is faster here.","source":"logging-samples/readme","quote":"| 10 fields | 312 ns · 0 | **306 ns · 0** | 676 ns · 1 |"},{"kind":"measured","against":"zerolog, zap, log/slog, Logrus and go-kit/log: a disabled call","result":"11 ns against 5 ns for zerolog, which is faster here.","source":"logging-samples/readme","quote":"| Disabled (debug at info) | 11 ns · 0 | **5 ns · 0** | 45 ns · 1 |"},{"kind":"measured","against":"zerolog, zap, log/slog, Logrus and go-kit/log: 3 fields from parallel goroutines","result":"20 ns and no allocation, against 24 ns for zerolog.","source":"logging-samples/readme","quote":"| Parallel, 3 fields | **20 ns · 0** | 24 ns · 0 | 110 ns · 1 | 198 ns · 4 | 166 ns · 0 |"}]},"metrics-spec":{"group":"metrics","line":"The registry: counters, gauges, histograms, labels and snapshots; recording is one atomic add.","tags":["contract"],"keywords":"telemetry counter gauge histogram prometheus opentelemetry","summary":"The engine of goframework's metrics: a registry of counters, gauges and histograms, with or without labels, recorded with one atomic operation and no allocation, read as a snapshot by exporters (metrics-exporters-*) and filled by collectors (metrics-collectors-*). Its own design: other systems' formats appear only in exporters, which translate.","snippet":"import metrics \"goframework.io/metrics-spec\"\n\nregistry := metrics.NewRegistry()\nrequests := registry.CounterVec(\"http_requests\", \"Requests handled.\", []string{\"route\", \"status\"})\ninFlight := registry.Gauge(\"http_in_flight\", \"Requests being handled.\")\nlatency := registry.Histogram(\"http_request_duration\", \"Time to handle a request.\", metrics.Seconds)\n\nok := requests.With(\"/items\", \"200\") // resolve the labels once, keep the series\nok.Inc() // one atomic add\ninFlight.Inc()\nlatency.Observe(elapsed.Seconds())\n\nregistry.Register(runtime.New()) // metrics-collectors-runtime\nhttp.Handle(\"/metrics\", prometheus.Handler(registry)) // metrics-exporters-prometheus","uses":["dsa-collections-map-concurrent"],"usedBy":["metrics-collectors-runtime","metrics-exporters-console","metrics-exporters-memory","metrics-exporters-otlp","metrics-exporters-prometheus","metrics-exporters-statsd"],"thirdParty":[],"flows":["metrics"],"maturity":"verified","newBecause":"","replaces":"client_golang's registry and the OpenTelemetry metrics SDK","notWhen":"You only need a handful of expvar counters: expvar is enough.","measures":[{"title":"In short, from its readme","against":"client_golang, VictoriaMetrics, OpenTelemetry; a bare atomic as the floor","legend":"Medians of three interleaved rounds. The last row is bytes, not time.","reproduce":"","source":"metrics-spec/readme","ours":["this"],"names":{"this":"metrics-spec"},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["ns an operation, at 14 CPUs"],"metric":0,"default":[],"records":[{"path":["a counter"],"name":"atomic","cpu":"","values":[5.0],"shown":["5.0"]},{"path":["a counter"],"name":"this","cpu":"","values":[5.0],"shown":["5.0"]},{"path":["a counter"],"name":"client_golang","cpu":"","values":[5.1],"shown":["5.1"]},{"path":["a counter"],"name":"VictoriaMetrics","cpu":"","values":[5.0],"shown":["5.0"]},{"path":["a counter"],"name":"OpenTelemetry","cpu":"","values":[45.2],"shown":["45.2"]},{"path":["a counter, from every CPU at once"],"name":"atomic","cpu":"","values":[23.1],"shown":["23.1"]},{"path":["a counter, from every CPU at once"],"name":"this","cpu":"","values":[23.6],"shown":["23.6"]},{"path":["a counter, from every CPU at once"],"name":"client_golang","cpu":"","values":[23.6],"shown":["23.6"]},{"path":["a counter, from every CPU at once"],"name":"VictoriaMetrics","cpu":"","values":[23.8],"shown":["23.8"]},{"path":["a counter, from every CPU at once"],"name":"OpenTelemetry","cpu":"","values":[61.8],"shown":["61.8"]},{"path":["two labels, resolved at each call"],"name":"atomic","cpu":"","values":[null],"shown":["–"]},{"path":["two labels, resolved at each call"],"name":"this","cpu":"","values":[20.7],"shown":["20.7"]},{"path":["two labels, resolved at each call"],"name":"client_golang","cpu":"","values":[37.1],"shown":["37.1"]},{"path":["two labels, resolved at each call"],"name":"VictoriaMetrics","cpu":"","values":[46.5],"shown":["46.5, 1 allocation"]},{"path":["two labels, resolved at each call"],"name":"OpenTelemetry","cpu":"","values":[278.7],"shown":["278.7, 4 allocations"]},{"path":["two labels, the series kept"],"name":"atomic","cpu":"","values":[null],"shown":["–"]},{"path":["two labels, the series kept"],"name":"this","cpu":"","values":[5.0],"shown":["5.0"]},{"path":["two labels, the series kept"],"name":"client_golang","cpu":"","values":[5.1],"shown":["5.1"]},{"path":["two labels, the series kept"],"name":"VictoriaMetrics","cpu":"","values":[5.0],"shown":["5.0"]},{"path":["two labels, the series kept"],"name":"OpenTelemetry","cpu":"","values":[100.8],"shown":["100.8, 1 allocation"]},{"path":["a histogram"],"name":"atomic","cpu":"","values":[null],"shown":["–"]},{"path":["a histogram"],"name":"this","cpu":"","values":[13.7],"shown":["13.7 (10.1 with Bounds)"]},{"path":["a histogram"],"name":"client_golang","cpu":"","values":[17.5],"shown":["17.5 (33.2 native)"]},{"path":["a histogram"],"name":"VictoriaMetrics","cpu":"","values":[11.1],"shown":["11.1"]},{"path":["a histogram"],"name":"OpenTelemetry","cpu":"","values":[61.7],"shown":["61.7"]},{"path":["a histogram, from every CPU at once"],"name":"atomic","cpu":"","values":[22.7],"shown":["22.7"]},{"path":["a histogram, from every CPU at once"],"name":"this","cpu":"","values":[23.8],"shown":["23.8 (55.0 with Bounds)"]},{"path":["a histogram, from every CPU at once"],"name":"client_golang","cpu":"","values":[207.3],"shown":["207.3"]},{"path":["a histogram, from every CPU at once"],"name":"VictoriaMetrics","cpu":"","values":[114.9],"shown":["114.9"]},{"path":["a histogram, from every CPU at once"],"name":"OpenTelemetry","cpu":"","values":[163.3],"shown":["163.3"]},{"path":["bytes a series of two labels takes"],"name":"atomic","cpu":"","values":[null],"shown":["–"]},{"path":["bytes a series of two labels takes"],"name":"this","cpu":"","values":[171.0],"shown":["171"]},{"path":["bytes a series of two labels takes"],"name":"client_golang","cpu":"","values":[514.0],"shown":["514"]},{"path":["bytes a series of two labels takes"],"name":"VictoriaMetrics","cpu":"","values":[157.0],"shown":["157"]},{"path":["bytes a series of two labels takes"],"name":"OpenTelemetry","cpu":"","values":[2519.0],"shown":["2,519"]},{"path":["added to a binary serving /metrics"],"name":"atomic","cpu":"","values":[null],"shown":["–"]},{"path":["added to a binary serving /metrics"],"name":"this","cpu":"","values":[0.28],"shown":["0.28 MB"]},{"path":["added to a binary serving /metrics"],"name":"client_golang","cpu":"","values":[3.37],"shown":["3.37 MB"]},{"path":["added to a binary serving /metrics"],"name":"VictoriaMetrics","cpu":"","values":[0.46],"shown":["0.46 MB"]},{"path":["added to a binary serving /metrics"],"name":"OpenTelemetry","cpu":"","values":[9.61],"shown":["9.61 MB (OTLP/HTTP)"]}],"header":["","atomic","this","client_golang","VictoriaMetrics","OpenTelemetry"],"rows":[["a counter","5.0","5.0","5.1","5.0","45.2"],["a counter, from every CPU at once","23.1","23.6","23.6","23.8","61.8"],["two labels, resolved at each call","–","20.7","37.1","46.5, 1 allocation","278.7, 4 allocations"],["two labels, the series kept","–","5.0","5.1","5.0","100.8, 1 allocation"],["a histogram","–","13.7 (10.1 with Bounds)","17.5 (33.2 native)","11.1","61.7"],["a histogram, from every CPU at once","22.7","23.8 (55.0 with Bounds)","207.3","114.9","163.3"],["bytes a series of two labels takes","–","171","514","157","2,519"],["added to a binary serving /metrics","–","0.28 MB","3.37 MB","0.46 MB","9.61 MB (OTLP/HTTP)"]]},{"title":"Recording a value","against":"prometheus/client_golang, VictoriaMetrics/metrics, the OpenTelemetry Go SDK; a bare atomic add as the floor","legend":"counter: one Inc. histogram: one Observe; metrics-spec's histogram is exponential, explicit buckets are the other kind. 2 labels, looked up: CounterVec.With(route, status).Inc() at each call; kept: the series looked up once and reused. Every CPU: from all goroutines at once (b.RunParallel).","reproduce":"python3 tools/benchmarks.py telemetry --rounds 3 --cpu 1,4,14","source":"benchmarks/telemetry/results.md","ours":["goframework","goframework-bounds"],"names":{"atomic":"atomic.AddUint64","victoria":"VictoriaMetrics","otel":"OpenTelemetry","otel-exponential":"OpenTelemetry, exponential","client_golang-native":"client_golang, native histogram","goframework":"metrics-spec","goframework-bounds":"metrics-spec, explicit buckets"},"labels":{"counter":"counter","counter-parallel":"counter, every CPU","histogram":"histogram","histogram-parallel":"histogram, every CPU","labels":"2 labels, looked up","labels-kept":"2 labels, kept"},"kind":"explorer","measured":"telemetry: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 3 interleaved rounds of 200ms, cpu 1,4,14; load average during the runs 2.1 to 2.9. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Record","histogram-parallel"],"records":[{"path":["Record","counter"],"name":"atomic","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"atomic","cpu":"4","values":[4.9,0.0,0.0]},{"path":["Record","counter"],"name":"atomic","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"atomic","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"atomic","cpu":"4","values":[18.9,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"atomic","cpu":"14","values":[23.1,0.0,0.0]},{"path":["Record","labels"],"name":"atomic","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","labels"],"name":"atomic","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","labels"],"name":"atomic","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"atomic","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"atomic","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"atomic","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","histogram"],"name":"atomic","cpu":"1","values":[4.9,0.0,0.0]},{"path":["Record","histogram"],"name":"atomic","cpu":"4","values":[4.9,0.0,0.0]},{"path":["Record","histogram"],"name":"atomic","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"atomic","cpu":"1","values":[5.1,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"atomic","cpu":"4","values":[18.8,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"atomic","cpu":"14","values":[22.7,0.0,0.0]},{"path":["Record","counter"],"name":"goframework","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"goframework","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"goframework","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"goframework","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"goframework","cpu":"4","values":[19.7,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"goframework","cpu":"14","values":[23.6,0.0,0.0]},{"path":["Record","labels"],"name":"goframework","cpu":"1","values":[20.8,0.0,0.0]},{"path":["Record","labels"],"name":"goframework","cpu":"4","values":[20.8,0.0,0.0]},{"path":["Record","labels"],"name":"goframework","cpu":"14","values":[20.7,0.0,0.0]},{"path":["Record","labels-kept"],"name":"goframework","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"goframework","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"goframework","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework","cpu":"1","values":[13.9,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework","cpu":"4","values":[13.6,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework","cpu":"14","values":[13.7,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework","cpu":"1","values":[13.8,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework","cpu":"4","values":[20.8,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework","cpu":"14","values":[23.8,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework-bounds","cpu":"1","values":[10.1,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework-bounds","cpu":"4","values":[10.1,0.0,0.0]},{"path":["Record","histogram"],"name":"goframework-bounds","cpu":"14","values":[10.1,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework-bounds","cpu":"1","values":[9.4,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework-bounds","cpu":"4","values":[32.7,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"goframework-bounds","cpu":"14","values":[55.0,0.0,0.0]},{"path":["Record","counter"],"name":"client_golang","cpu":"1","values":[4.9,0.0,0.0]},{"path":["Record","counter"],"name":"client_golang","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"client_golang","cpu":"14","values":[5.1,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"client_golang","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"client_golang","cpu":"4","values":[17.2,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"client_golang","cpu":"14","values":[23.6,0.0,0.0]},{"path":["Record","labels"],"name":"client_golang","cpu":"1","values":[36.3,0.0,0.0]},{"path":["Record","labels"],"name":"client_golang","cpu":"4","values":[36.1,0.0,0.0]},{"path":["Record","labels"],"name":"client_golang","cpu":"14","values":[37.1,0.0,0.0]},{"path":["Record","labels-kept"],"name":"client_golang","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"client_golang","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"client_golang","cpu":"14","values":[5.1,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang","cpu":"1","values":[17.7,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang","cpu":"4","values":[17.6,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang","cpu":"14","values":[17.5,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang","cpu":"1","values":[17.2,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang","cpu":"4","values":[110.8,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang","cpu":"14","values":[207.3,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang-native","cpu":"1","values":[33.9,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang-native","cpu":"4","values":[32.5,0.0,0.0]},{"path":["Record","histogram"],"name":"client_golang-native","cpu":"14","values":[33.2,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang-native","cpu":"1","values":[32.5,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang-native","cpu":"4","values":[95.3,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"client_golang-native","cpu":"14","values":[206.9,0.0,0.0]},{"path":["Record","counter"],"name":"victoria","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"victoria","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","counter"],"name":"victoria","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"victoria","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"victoria","cpu":"4","values":[19.9,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"victoria","cpu":"14","values":[23.8,0.0,0.0]},{"path":["Record","labels"],"name":"victoria","cpu":"1","values":[46.0,32.0,1.0]},{"path":["Record","labels"],"name":"victoria","cpu":"4","values":[41.1,32.0,1.0]},{"path":["Record","labels"],"name":"victoria","cpu":"14","values":[46.5,32.0,1.0]},{"path":["Record","labels-kept"],"name":"victoria","cpu":"1","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"victoria","cpu":"4","values":[5.0,0.0,0.0]},{"path":["Record","labels-kept"],"name":"victoria","cpu":"14","values":[5.0,0.0,0.0]},{"path":["Record","histogram"],"name":"victoria","cpu":"1","values":[11.7,0.0,0.0]},{"path":["Record","histogram"],"name":"victoria","cpu":"4","values":[11.6,0.0,0.0]},{"path":["Record","histogram"],"name":"victoria","cpu":"14","values":[11.1,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"victoria","cpu":"1","values":[11.6,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"victoria","cpu":"4","values":[52.3,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"victoria","cpu":"14","values":[114.9,0.0,0.0]},{"path":["Record","counter"],"name":"otel","cpu":"1","values":[44.2,0.0,0.0]},{"path":["Record","counter"],"name":"otel","cpu":"4","values":[45.8,0.0,0.0]},{"path":["Record","counter"],"name":"otel","cpu":"14","values":[45.2,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"otel","cpu":"1","values":[44.2,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"otel","cpu":"4","values":[41.1,0.0,0.0]},{"path":["Record","counter-parallel"],"name":"otel","cpu":"14","values":[61.8,0.0,0.0]},{"path":["Record","labels"],"name":"otel","cpu":"1","values":[281.5,296.0,4.0]},{"path":["Record","labels"],"name":"otel","cpu":"4","values":[248.9,296.0,4.0]},{"path":["Record","labels"],"name":"otel","cpu":"14","values":[278.7,296.0,4.0]},{"path":["Record","labels-kept"],"name":"otel","cpu":"1","values":[105.5,16.0,1.0]},{"path":["Record","labels-kept"],"name":"otel","cpu":"4","values":[100.2,16.0,1.0]},{"path":["Record","labels-kept"],"name":"otel","cpu":"14","values":[100.8,16.0,1.0]},{"path":["Record","histogram"],"name":"otel","cpu":"1","values":[62.2,0.0,0.0]},{"path":["Record","histogram"],"name":"otel","cpu":"4","values":[61.6,0.0,0.0]},{"path":["Record","histogram"],"name":"otel","cpu":"14","values":[61.7,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel","cpu":"1","values":[60.6,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel","cpu":"4","values":[80.6,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel","cpu":"14","values":[163.3,0.0,0.0]},{"path":["Record","histogram"],"name":"otel-exponential","cpu":"1","values":[64.2,0.0,0.0]},{"path":["Record","histogram"],"name":"otel-exponential","cpu":"4","values":[61.4,0.0,0.0]},{"path":["Record","histogram"],"name":"otel-exponential","cpu":"14","values":[62.0,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel-exponential","cpu":"1","values":[59.6,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel-exponential","cpu":"4","values":[133.5,0.0,0.0]},{"path":["Record","histogram-parallel"],"name":"otel-exponential","cpu":"14","values":[204.6,0.0,0.0]}]},{"title":"Memory a series takes","against":"client_golang, VictoriaMetrics, OpenTelemetry","legend":"Bytes each series of a two-label counter takes, measured over 10,000 of them.","reproduce":"python3 tools/benchmarks.py telemetry --rounds 3 --cpu 1,4,14","source":"benchmarks/telemetry/results.md","ours":["goframework"],"names":{"victoria":"VictoriaMetrics","otel":"OpenTelemetry","goframework":"metrics-spec"},"labels":{},"kind":"explorer","measured":"telemetry: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 3 interleaved rounds of 200ms, cpu 1,4,14; load average during the runs 2.1 to 2.9. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes a series","bytes allocated an operation","allocations an operation"],"metric":1,"default":["SeriesMemory"],"records":[{"path":["SeriesMemory"],"name":"goframework","cpu":"1","values":[2826969.0,172.0,2033932.0,50944.0]},{"path":["SeriesMemory"],"name":"goframework","cpu":"4","values":[2685714.0,171.0,2029780.0,50943.0]},{"path":["SeriesMemory"],"name":"goframework","cpu":"14","values":[2844109.0,171.0,2034220.0,50949.0]},{"path":["SeriesMemory"],"name":"client_golang","cpu":"1","values":[6277970.0,514.0,6028816.0,120093.0]},{"path":["SeriesMemory"],"name":"client_golang","cpu":"4","values":[6677668.0,514.0,6028835.0,120093.0]},{"path":["SeriesMemory"],"name":"client_golang","cpu":"14","values":[6264171.0,514.0,6028822.0,120093.0]},{"path":["SeriesMemory"],"name":"victoria","cpu":"1","values":[5260272.0,157.0,2226168.0,30103.0]},{"path":["SeriesMemory"],"name":"victoria","cpu":"4","values":[4990018.0,157.0,2224634.0,30103.0]},{"path":["SeriesMemory"],"name":"victoria","cpu":"14","values":[5390946.0,157.0,2226170.0,30103.0]},{"path":["SeriesMemory"],"name":"otel","cpu":"1","values":[17120569.0,2518.0,26868206.0,103931.0]},{"path":["SeriesMemory"],"name":"otel","cpu":"4","values":[12447776.0,1047.0,12147152.0,103934.0]},{"path":["SeriesMemory"],"name":"otel","cpu":"14","values":[17845665.0,2519.0,26872435.0,103955.0]}]},{"title":"Added to a binary","against":"client_golang, VictoriaMetrics, OpenTelemetry","legend":"A small server exposing one counter the library's usual way, built stripped (go build -trimpath -ldflags='-s -w', CGO_ENABLED=0); the size it adds over net/http alone.","reproduce":"cd benchmarks && TELEMETRY_SIZES=1 go test -run TestBinarySizes ./telemetry","source":"benchmarks/telemetry/binary-sizes.md","ours":["goframework"],"names":{"baseline":"net/http alone","victoria":"VictoriaMetrics","otel":"OpenTelemetry (OTLP/HTTP)","goframework":"metrics-spec + Prometheus exporter"},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["Added over net/http"],"metric":0,"default":[],"records":[{"path":[],"name":"baseline","cpu":"","values":[0.0],"shown":["+0.00 MB"]},{"path":[],"name":"goframework","cpu":"","values":[0.28],"shown":["+0.28 MB"]},{"path":[],"name":"client_golang","cpu":"","values":[3.37],"shown":["+3.37 MB"]},{"path":[],"name":"victoria","cpu":"","values":[0.46],"shown":["+0.46 MB"]},{"path":[],"name":"otel","cpu":"","values":[9.61],"shown":["+9.61 MB"]}],"header":["Program","Size","Added over net/http"],"rows":[["baseline","5.83 MB","+0.00 MB"],["goframework","6.11 MB","+0.28 MB"],["client_golang","9.20 MB","+3.37 MB"],["victoria","6.29 MB","+0.46 MB"],["otel","15.44 MB","+9.61 MB"]]}],"checked":[{"kind":"measured","against":"a bare atomic, client_golang 1.25, VictoriaMetrics/metrics 1.44 and OpenTelemetry Go 1.47: a counter","result":"5.0 ns, as a bare atomic; client_golang 5.1, VictoriaMetrics 5.0, OpenTelemetry 45.2.","source":"metrics-spec/readme","quote":"| a counter | 5.0 | 5.0 | 5.1 | 5.0 | 45.2 |"},{"kind":"measured","against":"a bare atomic, client_golang 1.25, VictoriaMetrics/metrics 1.44 and OpenTelemetry Go 1.47: a histogram from every CPU at once","result":"23.8 ns, against 207.3 for client_golang, 114.9 for VictoriaMetrics and 163.3 for OpenTelemetry.","source":"metrics-spec/readme","quote":"| a histogram, from every CPU at once | 22.7 | 23.8 (55.0 with `Bounds`) | 207.3 | 114.9 | 163.3 |"},{"kind":"measured","against":"a bare atomic, client_golang 1.25, VictoriaMetrics/metrics 1.44 and OpenTelemetry Go 1.47: memory a series of two labels takes","result":"171 bytes, against 514 for client_golang, 157 for VictoriaMetrics and 2,519 for OpenTelemetry.","source":"metrics-spec/readme","quote":"| bytes a series of two labels takes | – | 171 | 514 | 157 | 2,519 |"},{"kind":"measured","against":"a bare atomic, client_golang 1.25, VictoriaMetrics/metrics 1.44 and OpenTelemetry Go 1.47: size added to a binary serving /metrics","result":"0.28 MB, against 3.37 MB for client_golang, 0.46 MB for VictoriaMetrics and 9.61 MB for OpenTelemetry.","source":"metrics-spec/readme","quote":"| added to a binary serving /metrics | – | 0.28 MB | 3.37 MB | 0.46 MB | 9.61 MB (OTLP/HTTP) |"},{"kind":"reference","against":"bounds computed to 200 bits with math/big","result":"","source":"metrics-spec/readme","quote":"exponential bucket placement against bounds computed to 200 bits with `math/big`, at every scale over the whole float64 range"},{"kind":"reference","against":"Prometheus' quantile computation","result":"Quantiles within a bucket of the true value; explicit ones as Prometheus computes them.","source":"metrics-spec/readme","quote":"quantiles within a bucket of the true value, negative values too, and explicit ones as Prometheus computes them"}]},"metrics-collectors-runtime":{"group":"metrics","line":"Go's own numbers: the garbage collector, heap, scheduler and goroutines.","tags":[],"keywords":"runtime/metrics gc telemetry","summary":"Go's own numbers, from `runtime/metrics`, in metrics-spec's snapshots: the garbage collector, the heap, the scheduler, goroutines, CPU classes, mutex waits, as the runtime reports them; nothing computed outside it.","snippet":"import runtime \"goframework.io/metrics-collectors-runtime\"\n\nregistry.Register(runtime.New()) // 66 metrics on Go 1.27, and go_info\nregistry.Register(runtime.New(runtime.WithGodebug())) // and the 42 /godebug/ counters","uses":["metrics-spec"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"client_golang's Go collector","notWhen":"","measures":[],"checked":[{"kind":"real servers","against":"the real receivers in a VM (benchmarks/interop)","result":"","source":"benchmarks/interop/readme","quote":"Go's runtime metrics, send them every way, read each receiver back and compare"},{"kind":"real servers","against":"Prometheus' scrape and its OTLP translation","result":"Three runtime metrics are named differently by the two paths; the collector follows the scrape's.","source":"metrics.md","quote":"Three runtime metrics are named differently by the two paths; ours follow the scrape's."},{"kind":"measured","against":"the time a collection takes","result":"About 76 µs.","source":"metrics-collectors-runtime/readme","quote":"from the buckets' middles. A collection takes about 76 µs."},{"kind":"tests","against":"the naming rule and every runtime metric","result":"","source":"metrics-collectors-runtime/readme","quote":"Checked: the naming rule; every runtime metric collected under a name of its own, of the right kind"},{"kind":"tests","against":"GC pauses after a collection, written by the Prometheus exporter","result":"","source":"metrics-collectors-runtime/readme","quote":"GC pauses with counts and a sum after a collection; written by the Prometheus exporter."}]},"metrics-exporters-prometheus":{"group":"metrics","line":"Prometheus text and OpenMetrics, served over HTTP for scraping.","tags":[],"keywords":"telemetry scrape openmetrics","summary":"Metrics-spec's snapshots in Prometheus' text format and in OpenMetrics, served over HTTP for Prometheus to scrape.","snippet":"import prometheus \"goframework.io/metrics-exporters-prometheus\"\n\nhttp.Handle(\"/metrics\", prometheus.Handler(registry))\n\nprometheus.Write(file, registry.Snapshot(), prometheus.OpenMetrics) // or to anything","uses":["metrics-spec"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"promhttp","notWhen":"","measures":[],"checked":[{"kind":"reference","against":"Prometheus' own parser (prometheus/common/expfmt)","result":"300 random snapshots read back to the same values.","source":"metrics-exporters-prometheus/readme","quote":"read back by Prometheus' own parser (prometheus/common/expfmt, in benchmarks/prometheusformat) to the same values"},{"kind":"real servers","against":"Prometheus 3.15 scraping, as text and as OpenMetrics, in a VM","result":"Every value, histogram bucket and overflow count read back in both formats.","source":"benchmarks/interop/readme","quote":"Prometheus, scraped: every value, histogram bucket and overflow count, in both formats; the same series both ways"},{"kind":"reference","against":"promtool check metrics","result":"Its only remarks: families without help text.","source":"benchmarks/interop/readme","quote":"`promtool check metrics` (its only remarks: families without help text)"},{"kind":"real servers","against":"VictoriaMetrics 1.154 scraping, in a VM","result":"Every value; exponential histograms as its vmrange buckets, quantiles near ours.","source":"benchmarks/interop/readme","quote":"VictoriaMetrics, scraped and OTLP: every value; exponential histograms as its vmrange buckets, quantiles near ours."},{"kind":"tests","against":"expected text, OpenMetrics lines, content negotiation and gzip over HTTP","result":"","source":"metrics-exporters-prometheus/readme","quote":"Checked: the text written exactly as expected; OpenMetrics' lines; negative values and scales; content negotiation and gzip over HTTP"}]},"metrics-exporters-otlp":{"group":"metrics","line":"Pushes to any OpenTelemetry receiver over OTLP/HTTP; protobuf written by hand.","tags":[],"keywords":"telemetry opentelemetry otel collector push","summary":"Metrics-spec's snapshots pushed to an OpenTelemetry receiver over OTLP/HTTP, in protobuf: the OpenTelemetry Collector, Prometheus' OTLP receiver, VictoriaMetrics, any vendor's. The protobuf is written by hand with codecs-data-protobuf: no generated code, no gRPC.","snippet":"import otlp \"goframework.io/metrics-exporters-otlp\"\n\nexporter := otlp.New(registry,\n\totlp.Endpoint(\"http://collector:4318\"),\n\totlp.Attribute(\"service.name\", \"shop\"),\n\totlp.Every(15*time.Second))\ndefer exporter.Close(ctx) // one last push","uses":["codecs-data-protobuf","metrics-spec"],"usedBy":[],"thirdParty":[],"flows":["metrics"],"maturity":"verified","newBecause":"","replaces":"the OpenTelemetry OTLP exporters, without gRPC","notWhen":"","measures":[],"checked":[{"kind":"reference","against":"OpenTelemetry's generated protobuf types (benchmarks/otlpformat)","result":"200 random snapshots, every value read back, no field unknown to them.","source":"metrics-exporters-otlp/readme","quote":"Against OpenTelemetry's generated types (benchmarks/otlpformat): 200 random snapshots, every value read back, no field unknown to them"},{"kind":"real servers","against":"Prometheus 3.15's OTLP receiver, in a VM","result":"Exponential histograms read as native ones, quantiles within a bucket of ours.","source":"metrics-exporters-otlp/readme","quote":"Prometheus 3.15's OTLP receiver (exponential histograms as native ones, quantiles within a bucket of ours)"},{"kind":"real servers","against":"the OpenTelemetry Collector 0.163, in a VM","result":"Every data point read back field by field, with requests split at 4 KiB.","source":"benchmarks/interop/readme","quote":"The Collector, OTLP: every data point, field by field, the requests split at 4 KiB; the units."},{"kind":"real servers","against":"VictoriaMetrics 1.154's OTLP receiver, in a VM","result":"Every value; exponential histograms as its vmrange buckets, quantiles near ours.","source":"benchmarks/interop/readme","quote":"VictoriaMetrics, scraped and OTLP: every value; exponential histograms as its vmrange buckets, quantiles near ours."},{"kind":"real servers","against":"the OpenTelemetry Collector stopped, then hung, 3 minutes each, with 2000 series pushed every second","result":"The heap stayed at 3 MB and the goroutines at 5; every failed push was counted; the last request held every final count.","source":"metrics-exporters-otlp/readme","quote":"the heap stayed at 3 MB and the goroutines at 5, every failed push was counted, and the Collector's last request held every final count"},{"kind":"measured","against":"a push of 1000 counters and 1000 histograms (BenchmarkEncode, 14 CPUs)","result":"Encoded in 0.13 ms with 4 allocations, 112 KB before gzip.","source":"metrics-exporters-otlp/readme","quote":"encoded in 0.13 ms with 4 allocations, into 112 KB before gzip"}]},"metrics-exporters-statsd":{"group":"metrics","line":"Pushes to StatsD over UDP: the Datadog agent, Telegraf, statsd_exporter.","tags":[],"keywords":"telemetry datadog dogstatsd telegraf","summary":"Metrics-spec's snapshots pushed to a StatsD server over UDP: the Datadog agent, Telegraf, Prometheus' statsd_exporter, the OpenTelemetry Collector's statsd receiver, StatsD itself.","snippet":"import statsd \"goframework.io/metrics-exporters-statsd\"\n\nexporter := statsd.New(registry, statsd.Address(\"127.0.0.1:8125\"), statsd.Prefix(\"shop.\"))\ndefer exporter.Close(ctx) // one last push","uses":["metrics-spec"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"datadog-go's statsd client, for counters, gauges and histograms","notWhen":"","measures":[],"checked":[{"kind":"real servers","against":"statsd_exporter 0.31, Telegraf 1.40 and the OpenTelemetry Collector 0.163's statsd receiver, in a VM","result":"Counters, gauges and histogram counts exact after two pushes, in both tag styles; sums as the middles sent.","source":"metrics-exporters-statsd/readme","quote":"statsd_exporter 0.31 and Telegraf 1.40, both tag styles, and the OpenTelemetry Collector 0.163's statsd receiver: counters, gauges and histograms' counts exact after two pushes"},{"kind":"real servers","against":"the OpenTelemetry Collector's statsd receiver","result":"The first run crashed the Collector; the exporter now sends only sample rates whose inverses are whole numbers.","source":"benchmarks/interop/readme","quote":"The first run crashed the Collector: its statsd receiver, observing timers as summaries"},{"kind":"real servers","against":"the receivers checked, for Graphite's tag style","result":"Graphite's tags were dropped: none of the receivers checked reads them.","source":"metrics.md","quote":"Graphite's `;tag=value` was dropped: none of the receivers checked reads it."},{"kind":"tests","against":"sample rates from 1 to 200,000","result":"They add up exactly.","source":"metrics-exporters-statsd/readme","quote":"sample rates from 1 to 200,000 adding up exactly"},{"kind":"tests","against":"datagrams within MaxPacket","result":"Every line sent once.","source":"metrics-exporters-statsd/readme","quote":"datagrams within `MaxPacket`, every line once"}]},"metrics-exporters-console":{"group":"metrics","line":"Snapshots for people to read while developing.","tags":[],"keywords":"telemetry debug","summary":"Metrics-spec's snapshots written for people to read while a program runs: for development, not for machines.","snippet":"import console \"goframework.io/metrics-exporters-console\"\n\nexporter := console.New(registry) // every 10 seconds, to standard error\ndefer exporter.Close(ctx)\n\nconsole.Write(os.Stdout, registry.Snapshot()) // once","uses":["metrics-spec","xtend-math"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"the expected layout and units","result":"","source":"metrics-exporters-console/readme","quote":"Checked: the layout and units exactly; numbers' rounding"},{"kind":"tests","against":"a fake clock, for rates and OnlyChanged","result":"","source":"metrics-exporters-console/readme","quote":"rates and `OnlyChanged` on a fake clock"}]},"metrics-exporters-memory":{"group":"metrics","line":"Snapshots kept in memory, for tests.","tags":[],"keywords":"telemetry testing","summary":"Metrics-spec's snapshots kept in memory, for tests: what was exported, and what changed between two snapshots.","snippet":"import memory \"goframework.io/metrics-exporters-memory\"\n\nexported := memory.New(registry)\nexported.Collect()\nhandler.ServeHTTP(recorder, request)\nexported.Collect()\nif exported.Increase(\"http_requests\", \"/items\", \"200\") != 1 {\n\tt.Error(\"the request was not counted\")\n}","uses":["metrics-spec"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"counters and histograms, Keep, and a series never made","result":"","source":"metrics-exporters-memory/readme","quote":"Checked: `Increase` and `Value` for counters and histograms, `Keep`, a series never made"},{"kind":"tests","against":"a fake clock, for Every and Close","result":"","source":"metrics-exporters-memory/readme","quote":"`Every` and `Close` on a fake clock."}]},"callstack":{"group":"callstack","line":"The current goroutine's call stack: file, line, function; no allocation for one frame.","tags":[],"keywords":"stack trace runtime.Callers","summary":"Reads the call stack of the current goroutine: file, line, package, function and receiver of each call; the fastest Go stack library measured, and zero allocations for one frame; no third-party dependencies.","snippet":"import \"goframework.io/callstack\"\n\nframe := callstack.Caller(0) // the function calling Caller; 1 is its caller, and so on\nframes := callstack.Stack(0) // the whole stack from there, innermost call first\nframes := callstack.PanicStack() // in a deferred recover: the stack where the panic was raised\nid := callstack.GoroutineID() // the current goroutine, for diagnostics","uses":[],"usedBy":["app-spec","crashproof","events","xtend-errors","xtend-sync"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"runtime.Callers with runtime.CallersFrames, go-stack/stack","notWhen":"You print one stack, once, in a crash report: runtime/debug.Stack is enough.","measures":[{"title":"Reading the call stack","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors, zap","legend":"Each library's own way to get the frames asked for; \"no API\" where it has none. The rows that are not numbers say what each gives.","reproduce":"python3 tools/update-callstack-comparison.py","source":"callstack/readme","ours":["callstack"],"names":{},"labels":{},"kind":"explorer","measured":"Measured 2026-10-11 with go1.27.1-X:nodwarf5 on Intel(R) Core(TM) Ultra 7 265U (load average 4.1 / 4.9 / 4.6); go-errors/errors v1.4.2, go-stack/stack v1.8.1, pkg/errors v0.9.1, zap v1.27.0.","how":"","metrics":["time · allocations"],"metric":0,"default":[],"records":[{"path":["One frame: the caller"],"name":"callstack","cpu":"","values":[95.0],"shown":["95 ns · 0"]},{"path":["One frame: the caller"],"name":"runtime (std)","cpu":"","values":[235.0],"shown":["235 ns · 2"]},{"path":["One frame: the caller"],"name":"go-stack/stack","cpu":"","values":[337.0],"shown":["337 ns · 2"]},{"path":["One frame: the caller"],"name":"go-errors/errors","cpu":"","values":[null],"shown":["no API"]},{"path":["One frame: the caller"],"name":"pkg/errors","cpu":"","values":[null],"shown":["no API"]},{"path":["One frame: the caller"],"name":"zap","cpu":"","values":[null],"shown":["no API"]},{"path":["Whole stack, 20 calls deep"],"name":"callstack","cpu":"","values":[1449.0],"shown":["1,449 ns · 1"]},{"path":["Whole stack, 20 calls deep"],"name":"runtime (std)","cpu":"","values":[3655.0],"shown":["3,655 ns · 3"]},{"path":["Whole stack, 20 calls deep"],"name":"go-stack/stack","cpu":"","values":[4024.0],"shown":["4,024 ns · 3"]},{"path":["Whole stack, 20 calls deep"],"name":"go-errors/errors","cpu":"","values":[5002.0],"shown":["5,002 ns · 51"]},{"path":["Whole stack, 20 calls deep"],"name":"pkg/errors","cpu":"","values":[6887.0],"shown":["6,887 ns · 35"]},{"path":["Whole stack, 20 calls deep"],"name":"zap","cpu":"","values":[3839.0],"shown":["3,839 ns · 2"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"callstack","cpu":"","values":[null],"shown":["yes"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"runtime (std)","cpu":"","values":[null],"shown":["no"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"go-stack/stack","cpu":"","values":[null],"shown":["no"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"go-errors/errors","cpu":"","values":[null],"shown":["no"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"pkg/errors","cpu":"","values":[null],"shown":["no"]},{"path":["Recovered panic: stack starts at the panicking line"],"name":"zap","cpu":"","values":[null],"shown":["no"]},{"path":["Package of a .v2-style path"],"name":"callstack","cpu":"","values":[null],"shown":["correct"]},{"path":["Package of a .v2-style path"],"name":"runtime (std)","cpu":"","values":[null],"shown":["escaped (%2e)"]},{"path":["Package of a .v2-style path"],"name":"go-stack/stack","cpu":"","values":[null],"shown":["escaped (%2e)"]},{"path":["Package of a .v2-style path"],"name":"go-errors/errors","cpu":"","values":[null],"shown":["escaped (%2e)"]},{"path":["Package of a .v2-style path"],"name":"pkg/errors","cpu":"","values":[null],"shown":["not reported"]},{"path":["Package of a .v2-style path"],"name":"zap","cpu":"","values":[null],"shown":["escaped (%2e)"]},{"path":["Package, function and receiver as fields"],"name":"callstack","cpu":"","values":[null],"shown":["yes"]},{"path":["Package, function and receiver as fields"],"name":"runtime (std)","cpu":"","values":[null],"shown":["full name only"]},{"path":["Package, function and receiver as fields"],"name":"go-stack/stack","cpu":"","values":[null],"shown":["format verbs"]},{"path":["Package, function and receiver as fields"],"name":"go-errors/errors","cpu":"","values":[null],"shown":["package and name"]},{"path":["Package, function and receiver as fields"],"name":"pkg/errors","cpu":"","values":[null],"shown":["format verbs"]},{"path":["Package, function and receiver as fields"],"name":"zap","cpu":"","values":[null],"shown":["text only"]},{"path":["Third-party dependencies"],"name":"callstack","cpu":"","values":[null],"shown":["none"]},{"path":["Third-party dependencies"],"name":"runtime (std)","cpu":"","values":[null],"shown":["none"]},{"path":["Third-party dependencies"],"name":"go-stack/stack","cpu":"","values":[null],"shown":["none"]},{"path":["Third-party dependencies"],"name":"go-errors/errors","cpu":"","values":[null],"shown":["none"]},{"path":["Third-party dependencies"],"name":"pkg/errors","cpu":"","values":[null],"shown":["none"]},{"path":["Third-party dependencies"],"name":"zap","cpu":"","values":[null],"shown":["multierr"]}],"header":["","callstack","runtime (std)","go-stack/stack","go-errors/errors","pkg/errors","zap"],"rows":[["One frame: the caller","95 ns · 0","235 ns · 2","337 ns · 2","no API","no API","no API"],["Whole stack, 20 calls deep","1,449 ns · 1","3,655 ns · 3","4,024 ns · 3","5,002 ns · 51","6,887 ns · 35","3,839 ns · 2"],["Recovered panic: stack starts at the panicking line","yes","no","no","no","no","no"],["Package of a .v2-style path","correct","escaped (%2e)","escaped (%2e)","escaped (%2e)","not reported","escaped (%2e)"],["Package, function and receiver as fields","yes","full name only","format verbs","package and name","format verbs","text only"],["Third-party dependencies","none","none","none","none","none","multierr"]]}],"checked":[{"kind":"measured","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors and zap: one frame, the caller","result":"95 ns and no allocation, against 235 ns and 2 allocations for the runtime.","source":"callstack/readme","quote":"| One frame: the caller | **95 ns · 0** | 235 ns · 2 | 337 ns · 2 | no API | no API | no API |"},{"kind":"measured","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors and zap: a whole stack 20 calls deep","result":"1,449 ns and 1 allocation, against 3,655 ns and 3 allocations for the runtime.","source":"callstack/readme","quote":"| Whole stack, 20 calls deep | **1,449 ns · 1** | 3,655 ns · 3 | 4,024 ns · 3 | 5,002 ns · 51 | 6,887 ns · 35 | 3,839 ns · 2 |"},{"kind":"other libraries","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors and zap, resolving the same stack","result":"","source":"callstack/readme","quote":"Every library resolves frames to function, file and line from the same stack"},{"kind":"other libraries","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors and zap: the stack of a recovered panic","result":"Only callstack starts it at the panicking line.","source":"callstack/readme","quote":"| Recovered panic: stack starts at the panicking line | yes | no | no | no | no | no |"},{"kind":"other libraries","against":"the runtime, go-stack/stack, go-errors/errors, pkg/errors and zap: the package of a .v2-style path","result":"callstack reports it correctly; the others report it escaped (%2e) or not at all.","source":"callstack/readme","quote":"| Package of a `.v2`-style path | correct | escaped (`%2e`) | escaped (`%2e`) | escaped (`%2e`) | not reported | escaped (`%2e`) |"},{"kind":"tests","against":"the other libraries, in the comparison test","result":"The yes/no rows are checked by the test, not written by hand.","source":"callstack/readme","quote":"Yes/no rows are checked by the comparison test, not written by hand."}]},"crashproof":{"group":"crashproof","line":"Keeps a panic from crashing the program and sends it to one handler.","tags":[],"keywords":"panic recover","summary":"Keeps a panic from crashing the program and sends it to one handler, typically to log it; no third-party dependencies; only the `callstack` framework module is required.","snippet":"func main() {\n\tcrashproof.OnPanic(func(p crashproof.Panic) {\n\t\tfmt.Fprintln(os.Stderr, p) // or log it\n\t})\n\tcrashproof.Run(runMain) // main's own code is protected too\n}\n\nfunc runMain() {\n\tfor _, conn := range connections {\n\t\tcrashproof.Go(func() { serve(conn) }) // instead of: go serve(conn)\n\t}\n\t...\n}","uses":["callstack"],"usedBy":["app-spec","config-cli","events","flow","system-info-cli","system-process-monitor","system-process-spec","system-service-cli","system-service-spec","terminal-progress-samples","terminal-progress-spec","terminal-ui-samples"],"thirdParty":[],"flows":["events"],"maturity":"verified","newBecause":"","replaces":"a deferred recover written in every goroutine","notWhen":"Your code is a library: it may call Go and Run, but OnPanic belongs to the application, and a library that calls it replaces the application's handler.","measures":[],"checked":[{"kind":"tests","against":"regression checks run with go test -race","result":"","source":"design.md","quote":"Regression checks cover exact argument values, panic completion, failed writes, shutdown, log retention, formatting, and concurrent access. Run them with `go test -race`"}]},"system-service-spec":{"group":"system-service","line":"A portable service definition, and the Manager that installs, controls and describes one.","tags":["contract"],"keywords":"daemon","summary":"The blueprints of system-service: a portable description of an OS service, and the `Manager` each service manager module returns to install, control and describe one.","snippet":"import spec \"goframework.io/system-service-spec\" // blueprints\nimport \"goframework.io/system-service-systemd\" // only systemd\nimport all \"goframework.io/system-service-all\" // whatever the system runs","uses":["crashproof","system-process-spec","vfs","vfs-source-disk"],"usedBy":["app-as-service","app-as-service-w-plugins","system-service-all","system-service-cli","system-service-freebsd","system-service-launchd","system-service-netbsd","system-service-openbsd","system-service-openrc","system-service-systemd","system-service-sysv","system-service-windows"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"system-service-all":{"group":"system-service","line":"The system's service manager, picked at compile time; on Linux, the running init.","tags":["picks"],"keywords":"daemon","summary":"Installs, controls and runs services with whichever service manager the system uses, from one portable definition (system-service-spec). Each platform compiles only its own service managers; Linux detects the running init. A program for one service manager includes that module instead, such as system-service-systemd.","snippet":"executable, _ := os.Executable()\nmanager, err := all.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: executable,\n\tArgs: []string{\"serve\"},\n\tStartAtBoot: true,\n\tRestart: spec.RestartOnFailure,\n})\nif err == nil {\n\terr = manager.Install() // creates or updates; enables at boot; does not start\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-freebsd","system-service-launchd","system-service-netbsd","system-service-openbsd","system-service-openrc","system-service-spec","system-service-systemd","system-service-sysv","system-service-windows"],"usedBy":["app-as-service","app-as-service-w-plugins","system-service-cli"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"kardianos/service, takama/daemon","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"throwaway QEMU VMs, as root","result":"Install, start, arguments, environment and working directory byte for byte, kill and restart, stop, enable, disable, update, opening system services, uninstall twice and start at boot across a reboot were checked.","source":"system-service-all/readme","quote":"The VM-verified implementations passed integration tests as root in throwaway QEMU VMs (`tools/vm.py`)"},{"kind":"other libraries","against":"kardianos/service and takama/daemon, their current code and issues","result":"","source":"system-service-all/readme","quote":"Checked on 2026-10-12 against the libraries' current code and issues."},{"kind":"measured","against":"a program running with systemd.Run alone","result":"A program that only runs is 1.9 MB stripped on Linux, the same as with systemd.Run alone.","source":"system-service-all/readme","quote":"on Linux it is 1.9 MB stripped, the same as running with `systemd.Run` alone."},{"kind":"tests","against":"fake system roots","result":"","source":"system-service-all/readme","quote":"detection of the running Linux init from fake system roots"},{"kind":"systems","against":"unit tests and a Windows Server 2025 VM","result":"Smoke test.","source":"system-service-all/platforms.md","quote":"system-service-all | unit tests; Windows Server 2025 | smoke test"}]},"system-service-systemd":{"group":"system-service","line":"systemd, system and user units.","tags":["Linux"],"keywords":"daemon unit","summary":"Installs and controls services with systemd, implementing the system-service-spec contract.","snippet":"manager, err := systemd.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/opt/acme/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/etc/acme/api config.json\"},\n\tStartAtBoot: true,\n\tRestart: spec.RestartOnFailure,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // writes the unit, reloads, enables it\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"reference","against":"systemd-analyze verify (systemd 262)","result":"It accepts every generated unit.","source":"system-service-all/platforms.md","quote":"`systemd-analyze verify` (systemd 262) accepts every generated unit."},{"kind":"tests","against":"written units and distribution units","result":"","source":"system-service-systemd/readme","quote":"round trips of written units and of distribution units (sshd, nginx)"},{"kind":"fuzzed","against":"the unit parser, and a fake systemctl for command sequences","result":"","source":"system-service-systemd/readme","quote":"command sequences against a fake `systemctl`, and a parser fuzz test"},{"kind":"tests","against":"a fake systemd notification socket","result":"Run tells it READY=1 and STOPPING=1.","source":"system-service-systemd/readme","quote":"`Run` tells a fake systemd socket READY=1 and STOPPING=1"},{"kind":"systems","against":"a Debian 13 VM, as root","result":"Install, start, kill and automatic restart, stop, enable, disable, update, open and remove, and a Type=notify start that waits for ready were checked.","source":"system-service-systemd/readme","quote":"a `Type=notify` unit whose start waits for a slow program to call `ready`; run in a Debian 13 VM with `tools/vm.py`"},{"kind":"systems","against":"Debian 13 with systemd 257","result":"The real install tests pass.","source":"system-service-all/platforms.md","quote":"| systemd | system-service-systemd | Debian 13, systemd 257 | pass |"}]},"system-service-sysv":{"group":"system-service","line":"SysV init on Debian-family systems.","tags":["Linux"],"keywords":"daemon init.d","summary":"Installs and controls services with SysV init on Debian-family systems (Debian with sysvinit-core, Devuan, MX Linux), implementing the system-service-spec contract.","snippet":"manager, err := sysv.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/opt/acme/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/etc/acme/api config.json\"},\n\tUser: \"acme\",\n\tStartAtBoot: true,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // writes /etc/init.d/acme-api, update-rc.d defaults and enable\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"fuzzed","against":"golden scripts read back into the same Service","result":"","source":"system-service-sysv/readme","quote":"golden scripts, read back into the same Service, also under fuzzing"},{"kind":"model","against":"every POSIX shell found, with a stand-in for start-stop-daemon","result":"Every argument, variable and path arrives exactly.","source":"system-service-sysv/readme","quote":"the scripts run in every POSIX shell found, with the test binary in the place of `start-stop-daemon`: every argument, variable and path arrives exactly"},{"kind":"tests","against":"Debian's own init scripts","result":"","source":"system-service-sysv/readme","quote":"best-effort reading of Debian's cron and ssh scripts, and a parser fuzz test"},{"kind":"tests","against":"a fake runner","result":"","source":"system-service-sysv/readme","quote":"command sequences against a fake runner, status from exit codes, pidfiles and runlevel links"},{"kind":"systems","against":"a Debian 13 VM switched to sysvinit-core, as root","result":"Install, start, kill and restart, stop, enable, disable, update, open and remove, Debian's own scripts and start at boot were checked.","source":"system-service-sysv/readme","quote":"open Debian's own scripts, and start at boot; run in a Debian 13 VM switched to sysvinit-core with `tools/vm.py`"},{"kind":"systems","against":"Debian 13 with sysvinit-core 3.14","result":"The real install tests pass.","source":"system-service-all/platforms.md","quote":"| SysV init | system-service-sysv | Debian 13 with sysvinit-core 3.14 | pass |"}]},"system-service-openrc":{"group":"system-service","line":"OpenRC: Alpine, Gentoo, Artix, Devuan.","tags":["Linux"],"keywords":"daemon","summary":"Installs and controls services with OpenRC (Alpine, Gentoo, Artix, Devuan with OpenRC), implementing the system-service-spec contract.","snippet":"manager, err := openrc.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/opt/acme/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/etc/acme/api config.json\"},\n\tUser: \"acme\",\n\tStartAtBoot: true,\n\tRestart: spec.RestartAlways,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // writes /etc/init.d/acme-api, rc-update add acme-api default\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"fuzzed","against":"golden scripts read back into the same Service","result":"","source":"system-service-openrc/readme","quote":"golden scripts, read back into the same Service, also under fuzzing"},{"kind":"reference","against":"the eval lines of OpenRC 0.63.2, in every POSIX shell found","result":"Every argument, variable and path arrives exactly.","source":"system-service-openrc/readme","quote":"the scripts are sourced and started with the very `eval` lines of OpenRC 0.63.2, in every POSIX shell found"},{"kind":"tests","against":"Alpine's own init scripts","result":"","source":"system-service-openrc/readme","quote":"best-effort reading of Alpine's sshd, chronyd and crond, and a parser fuzz test"},{"kind":"tests","against":"a fake runner","result":"","source":"system-service-openrc/readme","quote":"command sequences against a fake runner, status from exit codes, pidfiles and runlevels"},{"kind":"systems","against":"an Alpine VM, as root","result":"Install, start, kill, clean exit and automatic restart, crash and Start, stop, enable, disable, update, open and remove, and start at boot were checked.","source":"system-service-openrc/readme","quote":"scripts, and start at boot; run in an Alpine VM with `tools/vm.py`"},{"kind":"systems","against":"Alpine 3.24.1 with OpenRC 0.63.2","result":"Supervised and background lifecycles with kills and clean exits passed.","source":"system-service-all/platforms.md","quote":"Supervised (supervise-daemon) and background lifecycles with kills and clean exits"}]},"system-service-launchd":{"group":"system-service","line":"launchd on macOS.","tags":["macOS"],"keywords":"daemon plist","summary":"Installs and controls services with launchd on macOS, implementing the system-service-spec contract.","snippet":"manager, err := launchd.New(spec.Service{\n\tName: \"com.acme.api\",\n\tExecutable: \"/opt/acme/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/etc/acme/api config.json\"},\n\tStartAtBoot: true,\n\tRestart: spec.RestartOnFailure,\n})\nif err == nil {\n\terr = manager.Install() // writes the plist, enables the job; does not start it\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"reference","against":"xmllint and Python's plistlib","result":"Golden plists are accepted and read to the same values.","source":"system-service-launchd/readme","quote":"golden plists, accepted by `xmllint` and read to the same values by Python's `plistlib`"},{"kind":"tests","against":"foreign plists (Homebrew, Apple's sshd, an Apple-style daemon)","result":"Compared key by key as launchd reads them.","source":"system-service-launchd/readme","quote":"round trips of written plists and of foreign ones (Homebrew, Apple's sshd, an Apple-style daemon with every value type), compared key by key as launchd reads them"},{"kind":"fuzzed","against":"the plist reader, and reading and writing back","result":"","source":"system-service-launchd/readme","quote":"fuzz tests of the plist reader and of reading and writing back"},{"kind":"tests","against":"a fake launchctl","result":"","source":"system-service-launchd/readme","quote":"command sequences against a fake `launchctl`, status from `launchctl list` outputs"},{"kind":"systems","against":"macOS 15.8.1 (Intel), as a logged-in user, for agents","result":"Passed.","source":"system-service-launchd/readme","quote":"and Finder's agent opened read-only. Passed on macOS 15.8.1 (Intel)."},{"kind":"systems","against":"macOS 15.8.1 (Intel), as root, for daemons","result":"Passed.","source":"system-service-launchd/readme","quote":"with sshd opened read-only. Passed on macOS 15.8.1 (Intel)."}]},"system-service-windows":{"group":"system-service","line":"The Windows Service Control Manager.","tags":["Windows"],"keywords":"daemon scm","summary":"Installs and controls services with the Windows Service Control Manager (SCM), implementing the system-service-spec contract.","snippet":"manager, err := windows.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: `C:\\Program Files\\Acme\\acme-api.exe`,\n\tArgs: []string{\"--config\", `C:\\ProgramData\\Acme\\api config.json`},\n\tUser: \"NetworkService\",\n\tStartAtBoot: true,\n\tRestart: spec.RestartOnFailure,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // creates or updates it; does not start it\n}\nif err == nil {\n\terr = manager.Start() // waits until it runs\n}","uses":["system-native-windows","system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"a model of cmd.exe and the C runtime","result":"Each line of the golden scripts must hand sc.exe and reg.exe the exact values.","source":"system-service-windows/readme","quote":"each script line is read back through a cmd.exe and C runtime model and must hand sc.exe and reg.exe the exact values"},{"kind":"reference","against":"Microsoft's command line parsing rules and a copy of Go's os.Args parser","result":"","source":"system-service-windows/readme","quote":"command lines: every example of Microsoft's parsing rules, escaping read back by this package's parser and by a copy of Go's `os.Args` parser, and fuzz tests of both directions"},{"kind":"tests","against":"a fake Service Control Manager","result":"","source":"system-service-windows/readme","quote":"every Manager method against a fake SCM: the calls Install makes when it creates and when it updates"},{"kind":"systems","against":"Windows Server 2025 and Windows 11 26H2 in QEMU VMs, as an administrator","result":"Arguments and environment arrive exactly, Run reports ready and stops gracefully, failure actions restart a killed service, and the other controls work.","source":"system-service-windows/readme","quote":"run on Windows Server 2025 (10.0.26100) and Windows 11 26H2 (10.0.26300) in QEMU VMs"},{"kind":"systems","against":"cmd.exe running the batch script Render writes","result":"It recreates the same service.","source":"system-service-windows/readme","quote":"The batch script `Render` writes, run by cmd.exe, recreates the same service."},{"kind":"systems","against":"the real Service Control Manager on Windows Server 2025","result":"The run found that Open read the default restart delay of 1 second back as 0; it was fixed.","source":"system-service-all/platforms.md","quote":"`Open` read the default restart delay of 1 second back as 0."}]},"system-service-freebsd":{"group":"system-service","line":"FreeBSD's rc.d.","tags":["FreeBSD"],"keywords":"daemon rc.d","summary":"Installs and controls services with FreeBSD's rc.d, implementing the system-service-spec contract.","snippet":"manager, err := freebsd.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/usr/local/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/usr/local/etc/acme/api config.json\"},\n\tUser: \"www\",\n\tStartAtBoot: true,\n\tRestart: spec.RestartAlways,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // writes the script, sysrc acme_api_enable=YES\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"golden scripts read back into the same Service","result":"","source":"system-service-freebsd/readme","quote":"golden scripts, read back into the same Service"},{"kind":"model","against":"/bin/sh with a stand-in rc.subr","result":"","source":"system-service-freebsd/readme","quote":"/bin/sh runs each script against a stand-in rc.subr that evaluates it as the real one does, and prints the words daemon would get"},{"kind":"tests","against":"a fake service(8) and sysrc(8), and foreign scripts","result":"","source":"system-service-freebsd/readme","quote":"command sequences against a fake service(8) and sysrc(8), foreign scripts (FreeBSD's sshd, the node_exporter port), and a parser fuzz test"},{"kind":"systems","against":"a FreeBSD 15.1 VM, as root","result":"Install, start, arguments, environment, directory, user, log file, syslog, kill and automatic restart, stop, enable, disable, update, open, list, remove and start at boot across a reboot were checked.","source":"system-service-freebsd/readme","quote":"update, open, list, remove, and start at boot across a reboot; run in a FreeBSD 15.1 VM with `tools/vm.py`"},{"kind":"systems","against":"FreeBSD 15.1-RELEASE","result":"Lifecycle under daemon(8) with restart, output to a file and to syslog, and start at boot were verified.","source":"system-service-all/platforms.md","quote":"Lifecycle under daemon(8) with restart, output to a file and to syslog, `sshd` opened read-only, start at boot."}]},"system-service-openbsd":{"group":"system-service","line":"OpenBSD's rc.d.","tags":["OpenBSD"],"keywords":"daemon rc.d","summary":"Installs and controls services with OpenBSD's rc.d, implementing the system-service-spec contract.","snippet":"manager, err := openbsd.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/usr/local/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/etc/acme/api config.json\"},\n\tUser: \"_acme\",\n\tStartAtBoot: true,\n})\nif err == nil {\n\terr = manager.Install() // writes /etc/rc.d/acme_api, rcctl enable acme_api\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"golden scripts read back into the same Service","result":"The process pattern matches the command line, also as rc.subr reads it back.","source":"system-service-openbsd/readme","quote":"golden scripts, read back into the same Service; pexp matches the command line, also as rc.subr reads it back"},{"kind":"model","against":"/bin/sh with a stand-in rc.subr","result":"","source":"system-service-openbsd/readme","quote":"/bin/sh runs each script against a stand-in rc.subr that hands the command to sh -c as the real one does, and prints the words the program would get"},{"kind":"tests","against":"a fake rcctl(8) and pgrep(1), and foreign scripts","result":"","source":"system-service-openbsd/readme","quote":"command sequences against a fake rcctl(8) and pgrep(1), foreign scripts (OpenBSD's sshd, the redis package), and a parser fuzz test"},{"kind":"systems","against":"an OpenBSD 7.9 VM, as root","result":"Install, start, arguments, environment, directory, user, log file, syslog, a killed service seen stopped, restart, stop, enable, disable, update, open, list, remove and start at boot across a reboot were checked.","source":"system-service-openbsd/readme","quote":"and start at boot across a reboot; run in an OpenBSD 7.9 VM with `tools/vm.py`"},{"kind":"systems","against":"OpenBSD 7.9","result":"Lifecycle with rcctl, output to a file and to syslog, and start at boot were verified.","source":"system-service-all/platforms.md","quote":"Lifecycle with rcctl, output to a file and to syslog, `sshd` opened read-only, start at boot."}]},"system-service-netbsd":{"group":"system-service","line":"NetBSD's rc.d.","tags":["NetBSD"],"keywords":"daemon rc.d","summary":"Installs and controls services with NetBSD's rc.d, implementing the system-service-spec contract.","snippet":"manager, err := netbsd.New(spec.Service{\n\tName: \"acme-api\",\n\tExecutable: \"/usr/pkg/bin/acme-api\",\n\tArgs: []string{\"--config\", \"/usr/pkg/etc/acme/api config.json\"},\n\tUser: \"acme\",\n\tStartAtBoot: true,\n\tDependsOn: []string{spec.DependencyNetwork},\n})\nif err == nil {\n\terr = manager.Install() // writes the script and /etc/rc.conf.d/acme_api\n}\nif err == nil {\n\terr = manager.Start()\n}","uses":["system-service-spec"],"usedBy":["system-service-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"golden scripts read back into the same Service","result":"","source":"system-service-netbsd/readme","quote":"golden scripts, read back into the same Service"},{"kind":"model","against":"/bin/sh with stand-ins for chroot(8), id(1) and logger(1)","result":"","source":"system-service-netbsd/readme","quote":"/bin/sh runs each start function with stand-ins for chroot(8), id(1) and logger(1), and the program records its directory, arguments and environment"},{"kind":"tests","against":"a fake service(8), and a foreign script","result":"","source":"system-service-netbsd/readme","quote":"command sequences against a fake service(8), foreign scripts (the redis package), and a parser fuzz test"},{"kind":"systems","against":"a NetBSD 11.0 VM, as root","result":"Install, start, arguments, environment, directory, user and group, log file, syslog, a killed service seen stopped, restart, stop, enable, disable, update, open, list, remove and start at boot across a reboot were checked.","source":"system-service-netbsd/readme","quote":"start at boot across a reboot; run in a NetBSD 11.0 VM with `tools/vm.py`"},{"kind":"systems","against":"NetBSD 11.0","result":"Output to files and to syslog, and start at boot were verified.","source":"system-service-all/platforms.md","quote":"output to files and to syslog, `sshd` opened read-only, start at boot."}]},"system-service-cli":{"group":"system-service","line":"The system-service command: installs and controls services from a JSON definition.","tags":["command"],"keywords":"daemon","summary":"`system-service`: installs and controls services with the service manager of the system it runs on, from a portable JSON definition (system-service-spec). Built on system-service-all, cliflags and crashproof.","snippet":"","uses":["cliflags","crashproof","system-service-all","system-service-spec","terminal"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"systemctl, launchctl, sc.exe and service(8), for services described as JSON","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"a fake service manager","result":"","source":"system-service-cli/readme","quote":"Every command against a fake service manager: definitions read strictly, scope agreement, controls, status and not-installed"},{"kind":"systems","against":"a Windows Server 2025 VM","result":"The command was verified there: system, status, install, show, uninstall and render.","source":"system-service-all/platforms.md","quote":"the `system-service` command (system, status, install, show, uninstall, render)"}]},"system-process-spec":{"group":"system-process","line":"The contract: a Process, the Definition of one to start, how it exited.","tags":["contract"],"keywords":"exec pid","summary":"The contract of system-process: a `Process`, which is any process on the system, the `Definition` of one to start, and the `Exit` that says how a started one ended.","snippet":"import spec \"goframework.io/system-process-spec\" // the contract\nimport \"goframework.io/system-process-linux\" // only Linux\nimport all \"goframework.io/system-process-all\" // whatever the program is built for","uses":["crashproof","xtend-user"],"usedBy":["app-plugin","app-spec","system-process-all","system-process-freebsd","system-process-linux","system-process-macos","system-process-monitor","system-process-netbsd","system-process-openbsd","system-process-samples","system-process-windows","system-service-spec"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"throwaway QEMU VMs and a test Mac","result":"","source":"system-process-spec/readme","quote":"The integration test starts, signals and kills real processes, so it runs only in throwaway QEMU VMs (`tools/vm.py`) or on a test Mac"},{"kind":"systems","against":"sh on every Unix, and the system's shell on every OS","result":"sh reads back what Join wrote; both shells run a command line on every OS.","source":"system-process-spec/readme","quote":"Checked: `sh` reads back what `Join` wrote, on every Unix;"},{"kind":"fuzzed","against":"Split and Join of shell words","result":"Split(Join(w)) gives back w.","source":"system-process-spec/readme","quote":"`Split(Join(w))` is `w`, fuzzed; both shells run a command line on every OS."},{"kind":"tests","against":"a fake system, on any OS","result":"","source":"system-process-all/platforms.md","quote":"the ID text form, definitions and their checks, exits, the `/proc` stat parser, kit's start, stop, ask-then-kill, StopAll, cancelled runs and output handling against a fake system"}]},"system-process-all":{"group":"system-process","line":"The system's implementation, picked at compile time.","tags":["picks"],"keywords":"exec pid","summary":"Finds, starts and controls processes on whichever operating system the program is built for, so that one program works everywhere. It implements the system-process-spec contract by picking, when the program is compiled, one of system-process-linux, -macos, -windows, -freebsd, -openbsd or -netbsd; each build compiles only that one.","snippet":"import (\n\tall \"goframework.io/system-process-all\"\n\tspec \"goframework.io/system-process-spec\"\n)\n\nprocess, err := all.Start(spec.Definition{\n\tProgram: \"worker\",\n\tArgs: []string{\"--queue\", \"jobs\"},\n\tStdout: os.Stdout,\n})\n...\ninfo, err := process.Info()\nerr = process.Stop() // asks, waits, then kills its whole tree\nexit, err := process.Exit()\n...\ndefer all.StopAll() // the processes this program started end with it","uses":["system-process-freebsd","system-process-linux","system-process-macos","system-process-netbsd","system-process-openbsd","system-process-spec","system-process-windows"],"usedBy":["app-plugin","app-spec","system-process-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"unit tests","result":"","source":"system-process-all/platforms.md","quote":"| any | system-process-all, -monitor | unit tests | the monitor runs on fakes |"},{"kind":"tests","against":"builds and go vet for six systems on amd64 and arm64","result":"Every module builds and passes go vet; arm64 is compiled, not run.","source":"system-process-all/platforms.md","quote":"Every module builds and passes `go vet` for linux, darwin, windows, freebsd, openbsd and netbsd on amd64 and arm64"}]},"system-process-linux":{"group":"system-process","line":"Linux, through /proc and pidfds.","tags":["Linux"],"keywords":"exec pid","summary":"Finds, starts and controls processes on Linux, implementing the system-process-spec contract.","snippet":"processes, err := linux.List()\nprocess, err := linux.Start(spec.Definition{Program: \"worker\", Stdout: os.Stdout})\n...\nerr = linux.StopAll() // before the program ends","uses":["system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Debian 13 and Alpine 3.24 VMs, as root and as an unprivileged user","result":"The integration test passes.","source":"system-process-all/platforms.md","quote":"Debian 13 (glibc); Alpine 3.24 (musl, busybox) | pass, as root"},{"kind":"systems","against":"a Linux VM, with the integration test shared by every system","result":"","source":"system-process-linux/readme","quote":"and kills real processes, so it runs on a Linux VM, never on a developer's machine"}]},"system-process-macos":{"group":"system-process","line":"macOS.","tags":["macOS"],"keywords":"exec pid","summary":"Finds, starts and controls processes on macOS, implementing the system-process-spec contract.","snippet":"processes, err := macos.List()\nprocess, err := macos.Start(spec.Definition{Program: \"worker\", Stdout: os.Stdout})\n...\nerr = macos.StopAll() // before the program ends","uses":["system-native-macos","system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"a test Mac with macOS 15.8.1 (Intel), as a user and as root","result":"The integration test passes.","source":"system-process-all/platforms.md","quote":"| macOS | system-process-macos | macOS 15.8.1 (Intel) | pass, as a user and as root |"},{"kind":"systems","against":"macOS 15.8.1 on a test Mac","result":"The libSystem stubs (sysctl, proc_pidpath) work.","source":"system-process-all/platforms.md","quote":"The libSystem stubs (sysctl, proc_pidpath) work."}]},"system-process-windows":{"group":"system-process","line":"Windows, with Job Objects.","tags":["Windows"],"keywords":"exec pid","summary":"Finds, starts and controls processes on Windows, implementing the system-process-spec contract.","snippet":"processes, err := windows.List()\nprocess, err := windows.Start(spec.Definition{Program: \"worker.exe\", Stdout: os.Stdout})\n...\nerr = windows.StopAll() // before the program ends","uses":["system-native-windows","system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Windows Server 2025 and Windows 11 26H2 VMs","result":"The integration test passes on both.","source":"system-process-windows/readme","quote":"It passes on Windows Server 2025 and Windows 11 26H2"},{"kind":"systems","against":"Windows Server 2025 in a QEMU VM","result":"Processes are born inside their Job Object; Ctrl+Break stops a process sharing the console; the job ends the tree when its parent ends.","source":"system-process-all/platforms.md","quote":"Processes are born inside their Job Object; Ctrl+Break stops a process sharing the console; the job ends the tree when its parent ends."},{"kind":"systems","against":"cmd.exe on Windows","result":"A batch file receives its arguments; arguments cmd.exe would change are refused.","source":"system-process-all/platforms.md","quote":"a batch file receiving its arguments through cmd.exe, and the refusal of arguments cmd.exe would change"},{"kind":"systems","against":"Windows 11","result":"A security service keeps an ended process listed; it is gone all the same for Find, Info, List and Kill.","source":"system-process-windows/readme","quote":"On Windows 11 a security service holds handles to new processes, which keeps an ended process's object, and its PID, listed"}]},"system-process-freebsd":{"group":"system-process","line":"FreeBSD.","tags":["FreeBSD"],"keywords":"exec pid","summary":"Finds, starts and controls processes on FreeBSD, implementing the system-process-spec contract.","snippet":"processes, err := freebsd.List()\nprocess, err := freebsd.Start(spec.Definition{Program: \"worker\", Stdout: os.Stdout})\n...\nerr = freebsd.StopAll() // before the program ends","uses":["system-native-bsd","system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"a FreeBSD 15.1 VM, as root and as an unprivileged user","result":"The integration test passes.","source":"system-process-all/platforms.md","quote":"system-process-freebsd | FreeBSD 15.1-RELEASE | pass, as root"}]},"system-process-openbsd":{"group":"system-process","line":"OpenBSD.","tags":["OpenBSD"],"keywords":"exec pid","summary":"Finds, starts and controls processes on OpenBSD, implementing the system-process-spec contract.","snippet":"processes, err := openbsd.List()\nprocess, err := openbsd.Start(spec.Definition{Program: \"worker\", Stdout: os.Stdout})\n...\nerr = openbsd.StopAll() // before the program ends","uses":["system-native-bsd","system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"an OpenBSD 7.9 VM, as root and as an unprivileged user","result":"The integration test passes.","source":"system-process-all/platforms.md","quote":"system-process-openbsd | OpenBSD 7.9 | pass, as root"},{"kind":"systems","against":"OpenBSD 7.9","result":"The program path is always listed as missing: the kernel has no way to give it.","source":"system-process-all/platforms.md","quote":"The program path is always in Missing: the kernel has no way to give it."}]},"system-process-netbsd":{"group":"system-process","line":"NetBSD.","tags":["NetBSD"],"keywords":"exec pid","summary":"Finds, starts and controls processes on NetBSD, implementing the system-process-spec contract.","snippet":"processes, err := netbsd.List()\nprocess, err := netbsd.Start(spec.Definition{Program: \"worker\", Stdout: os.Stdout})\n...\nerr = netbsd.StopAll() // before the program ends","uses":["system-native-bsd","system-process-spec"],"usedBy":["system-process-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"a NetBSD 11.0 VM, as root and as an unprivileged user","result":"The integration test passes.","source":"system-process-all/platforms.md","quote":"system-process-netbsd | NetBSD 11.0 | pass, as root"},{"kind":"systems","against":"NetBSD 11.0","result":"NetBSD briefly reports a new process's start time as 0; the module reads the record again, so the process keeps its ID.","source":"system-process-all/platforms.md","quote":"For a moment after a process starts, NetBSD reports its start time as 0; the module reads such a record again until the start is there"}]},"system-process-monitor":{"group":"system-process","line":"Keeps named processes running: restarts by policy, with growing delays.","tags":[],"keywords":"supervisor restart","summary":"Keeps named processes running: starts them, restarts each by its policy with growing delays, and stops them together. It has no OS code: it starts processes with the function it is given, such as `all.Start` from system-process-all, one OS module's `Start`, or a fake in tests.","snippet":"processes := monitor.New(all.Start, func(event monitor.Event) {\n\tfmt.Println(event.Name, event.Kind, event.Exit)\n})\nerr := processes.Add( // all or nothing\n\tmonitor.Entry{Name: \"api\", Definition: api, Policy: monitor.Policy{Restart: monitor.OnFailure}},\n\tmonitor.Entry{Name: \"worker\", Definition: worker, Policy: monitor.Policy{Restart: monitor.Always, Limit: 5}},\n)\n...\nerr = processes.StopAll()","uses":["crashproof","system-process-spec"],"usedBy":["system-process-samples"],"thirdParty":[],"flows":[],"maturity":"new","newBecause":"tested against fake processes; not yet run on the six systems","replaces":"supervisord, or a restart loop written by hand","notWhen":"A service manager restarts your program already: let it.","measures":[],"checked":[{"kind":"tests","against":"fake processes","result":"","source":"system-process-all/platforms.md","quote":"the monitor's policies, delays, limits and events against fake processes"}]},"system-process-samples":{"group":"system-process","line":"Small programs showing system-process on six systems.","tags":["examples"],"keywords":"","summary":"Small programs that show system-process at work. Each one runs in its folder with `go run .`, on Linux, macOS, Windows, FreeBSD, OpenBSD or NetBSD. To show child processes without depending on other programs, each sample starts copies of itself, chosen by an argument.","snippet":"","uses":["system-process-all","system-process-monitor","system-process-spec","xtend-io"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Debian 13, FreeBSD 15.1, OpenBSD 7.9, NetBSD 11.0, Windows Server 2025 and macOS 15.8.1","result":"All six samples were run on each system.","source":"system-process-samples/readme","quote":"All six were run on Debian 13, FreeBSD 15.1, OpenBSD 7.9, NetBSD 11.0, Windows Server 2025 and macOS 15.8.1."},{"kind":"systems","against":"binaries built for each system","result":"Their output was checked.","source":"system-process-all/platforms.md","quote":"built for each system and run on every one of the systems below, their output checked"},{"kind":"tests","against":"expected output files, on the developer's machine","result":"","source":"system-process-samples/readme","quote":"`go test` on the developer's machine only checks that every sample folder has expected output; it starts nothing."}]},"system-info-spec":{"group":"system-info","line":"The contract: the machine's inventory by topic, as plain data.","tags":["contract"],"keywords":"hardware inventory","summary":"The contract of system-info: an inventory of the machine's hardware and operating system, by topic, as plain data.","snippet":"import spec \"goframework.io/system-info-spec\" // the contract\nimport all \"goframework.io/system-info-all\" // what applications call\n\ninventory, err := all.Inventory()\ncpu, err := all.CPU()\nidentity, err := all.Identity()","uses":[],"usedBy":["system-info-all","system-info-cli","system-info-source-freebsd","system-info-source-linux","system-info-source-macos","system-info-source-netbsd","system-info-source-openbsd","system-info-source-shared-bsd","system-info-source-shared-cpuid","system-info-source-shared-devicetree","system-info-source-shared-edid","system-info-source-shared-smbios","system-info-source-windows"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"unit, golden and fuzz tests that read nothing of the machine","result":"","source":"system-info-spec/readme","quote":"Unit, golden and fuzz tests run anywhere and read nothing of the machine."},{"kind":"tests","against":"unit tests of kit's merge, firmware placeholders and reasons","result":"","source":"system-info-all/platforms.md","quote":"kit's merge, placeholders and reasons, and lists read as empty;"},{"kind":"systems","against":"the real systems, through system-info-all's integration test","result":"","source":"system-info-all/platforms.md","quote":"that every empty field of a topic, disk, file system and interface says why, and that every reason is one of spec's kinds."},{"kind":"systems","against":"QEMU VMs and a test Mac","result":"","source":"system-info-spec/readme","quote":"addresses redacted and no identifier shown; it runs in QEMU VMs and on a test Mac."}]},"system-info-all":{"group":"system-info","line":"The inventory of the system the program is built for.","tags":["picks"],"keywords":"hardware inventory","summary":"Reads the inventory of whichever system the program is built for: what the machine is, its firmware, operating system, CPU, memory, storage, network, graphics, monitors, input devices, PCI and USB devices, batteries, sound devices and cameras, and, only when asked, its identifiers. It implements the system-info-spec contract by combining, when the program is compiled, the sources of that one system.","snippet":"import all \"goframework.io/system-info-all\"\n\ninventory, err := all.Inventory()\nfmt.Println(inventory.CPU.Model, inventory.Memory.Installed)\nif err := inventory.Machine.Missing.Lacks(\"Chassis\"); err != nil {\n\tfmt.Println(\"no chassis type:\", err)\n}\n\nidentity, err := all.Identity() // serial numbers, UUIDs, MAC addresses","uses":["system-info-source-freebsd","system-info-source-linux","system-info-source-macos","system-info-source-netbsd","system-info-source-openbsd","system-info-source-shared-cpuid","system-info-source-shared-devicetree","system-info-source-shared-smbios","system-info-source-windows","system-info-spec"],"usedBy":["system-info-cli"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"gopsutil's host, cpu, mem and disk packages for what is installed; jaypipes/ghw; elastic/go-sysinfo's host information","notWhen":"You need load, usage or temperatures: system-info reads none of them.","measures":[],"checked":[{"kind":"systems","against":"Debian 13 and Alpine 3.24 in QEMU VMs, and a Raspberry Pi 4 emulated in QEMU","result":"The integration test passed as root and as an unprivileged user; on the Pi as root.","source":"system-info-all/platforms.md","quote":"| Linux | Debian 13 (glibc) and Alpine 3.24 (musl) in QEMU; a Raspberry Pi 4 in QEMU (`raspi4b`) | pass, as root and as nobody; the Pi as root |"},{"kind":"systems","against":"macOS 15.8.1 on an Intel Mac","result":"The integration test passed as a user.","source":"system-info-all/platforms.md","quote":"| macOS | macOS 15.8.1, Intel (MacBookPro15,1) | pass, as a user |"},{"kind":"systems","against":"Windows Server 2025 and Windows 11 in QEMU VMs","result":"The integration test passed as an administrator and as a standard user.","source":"system-info-all/platforms.md","quote":"Windows Server 2025 (10.0.26100, BIOS) and Windows 11 Enterprise 26H2 (10.0.26300, UEFI with Secure Boot and a TPM 2.0) in QEMU | pass, as Administrator and as a standard user"},{"kind":"systems","against":"FreeBSD 15.1, OpenBSD 7.9 and NetBSD 11.0 in QEMU VMs","result":"The integration test passed on each as root and as an unprivileged user.","source":"system-info-all/platforms.md","quote":"| FreeBSD | FreeBSD 15.1-RELEASE in QEMU | pass, as root and as nobody | | OpenBSD | OpenBSD 7.9 in QEMU | pass, as root and as nobody | | NetBSD | NetBSD 11.0 in QEMU | pass, as root and as nobody |"},{"kind":"tests","against":"what the program knows of the running system: threads, architecture, host name, boot time","result":"","source":"system-info-all/platforms.md","quote":"reads every topic and checks that the values agree with what the program knows (threads, architecture, host name, boot time), that core types add up"},{"kind":"tests","against":"builds and go vet for six systems on amd64 and arm64","result":"Every module builds and passes go vet.","source":"system-info-all/platforms.md","quote":"Every module builds and passes `go vet` for linux, darwin, windows, freebsd, openbsd and netbsd on amd64 and arm64 (`tools/check-platforms.py`)."}]},"system-info-cli":{"group":"system-info","line":"The system-info command: the inventory, and why any value is missing.","tags":["command"],"keywords":"hardware","summary":"`system-info`: prints the inventory of the system it runs on, every value it read and why it could not read the others. Built on system-info-all, cliflags and crashproof.","snippet":"","uses":["cliflags","crashproof","system-info-all","system-info-spec","terminal"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"the output of lshw, dmidecode, system_profiler or msinfo32 pasted into a bug report; --redact leaves out the host name, addresses and identifiers","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"the topics of a made-up laptop","result":"","source":"system-info-cli/readme","quote":"Every command against the topics of a made-up laptop: text with units and reasons, JSON in topic order, redaction, identity, system and help."}]},"system-info-source-linux":{"group":"system-info","line":"Linux.","tags":["Linux"],"keywords":"hardware","summary":"Reads the inventory of a Linux system for system-info-spec, without running any program.","snippet":"cpu := linux.CPU()\nidentity := linux.Identity()","uses":["system-info-source-shared-edid","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Debian 13 and Alpine 3.24 in QEMU VMs, and a Raspberry Pi 4 emulated in QEMU","result":"The integration test passed as root and as an unprivileged user; on the Pi as root.","source":"system-info-all/platforms.md","quote":"| Linux | Debian 13 (glibc) and Alpine 3.24 (musl) in QEMU; a Raspberry Pi 4 in QEMU (`raspi4b`) | pass, as root and as nobody; the Pi as root |"},{"kind":"tests","against":"fake sysfs and procfs trees: a laptop with hybrid cores, a Raspberry Pi, a Docker container","result":"","source":"system-info-all/platforms.md","quote":"the Linux source against fake sysfs and procfs trees: a laptop with hybrid cores, a Raspberry Pi, a Docker container;"},{"kind":"tests","against":"a fake tree of devices, GPUs and monitors","result":"","source":"system-info-all/platforms.md","quote":"the Linux devices, GPUs and monitors on a fake tree"},{"kind":"tests","against":"a fake tree of input devices and power supplies","result":"","source":"system-info-all/platforms.md","quote":"the Linux input devices and power supplies on a fake tree (an ACPI battery in mWh, a fuel gauge in mAh, a mouse's battery, a kernel without the ACPI battery driver)"},{"kind":"tests","against":"a fake tree of sound cards and cameras","result":"","source":"system-info-all/platforms.md","quote":"the Linux sound cards and cameras on a fake tree (HD Audio, a USB headset, snd-aloop, a UVC camera, a codec that is not a camera, sound hardware without a driver)"},{"kind":"systems","against":"Alpine 3.24 in QEMU with emulated input devices, a touchscreen and a battery","result":"All of them were read, the touchscreen included.","source":"system-info-all/platforms.md","quote":"Alpine 3.24, as root and as nobody: all of them, the touchscreen included"}]},"system-info-source-macos":{"group":"system-info","line":"macOS, Intel and Apple silicon, without cgo.","tags":["macOS"],"keywords":"hardware","summary":"Reads the inventory of a Mac, Intel or Apple silicon, for system-info-spec, without running any program and without cgo.","snippet":"","uses":["system-info-source-shared-edid","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec","system-native-macos"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"macOS 15.8.1 on an Intel Mac","result":"The integration test passed as a user.","source":"system-info-all/platforms.md","quote":"| macOS | macOS 15.8.1, Intel (MacBookPro15,1) | pass, as a user |"},{"kind":"systems","against":"an Intel Mac's GPUs, built-in panel and USB devices","result":"","source":"system-info-all/platforms.md","quote":"the Intel Mac: Intel UHD 630, Radeon Pro 560X with 4 GB, the built-in panel, the T2's USB devices"},{"kind":"systems","against":"an Intel Mac's keyboard, trackpad, Touch Bar and battery","result":"","source":"system-info-all/platforms.md","quote":"the Intel Mac: the built-in keyboard and trackpad, the Touch Bar (a touch pad, as it declares) and its virtual keys, the battery"},{"kind":"systems","against":"an Intel Mac's speakers, microphones and FaceTime HD camera","result":"","source":"system-info-all/platforms.md","quote":"the Intel Mac: its speakers and microphone, a nearby iPhone's microphone (Continuity), the FaceTime HD camera, which the T2 puts on USB"}]},"system-info-source-windows":{"group":"system-info","line":"Windows, through its own interfaces, without WMI.","tags":["Windows"],"keywords":"hardware","summary":"Reads the inventory of a Windows system for system-info-spec, through Windows' own interfaces, without WMI and without running any program.","snippet":"","uses":["system-info-source-shared-edid","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec","system-native-windows"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"Windows Server 2025 and Windows 11 in QEMU VMs","result":"The integration test passed as an administrator and as a standard user.","source":"system-info-all/platforms.md","quote":"Windows Server 2025 (10.0.26100, BIOS) and Windows 11 Enterprise 26H2 (10.0.26300, UEFI with Secure Boot and a TPM 2.0) in QEMU | pass, as Administrator and as a standard user"},{"kind":"systems","against":"Windows 11 in a QEMU VM with UEFI, Secure Boot keys enrolled and a software TPM","result":"It reported interface uefi, Secure Boot on and TPM 2.0.","source":"system-info-all/platforms.md","quote":"verified on Windows 11 (`tools/vm.py up windows11`: OVMF with Microsoft's keys enrolled, swtpm), as Administrator and as a standard user: interface `uefi`, Secure Boot `on`, TPM `2.0`."},{"kind":"systems","against":"devices, GPUs and monitors on Windows Server 2025 and Windows 11 VMs","result":"The Windows 11 VM's screen has no EDID, and every monitor field says so.","source":"system-info-all/platforms.md","quote":"Windows Server 2025 and Windows 11, as Administrator and as a standard user; on Windows 11 the VM's screen is Windows' \"Default Monitor\", which has no EDID and says so of every field"},{"kind":"systems","against":"Windows 11 in a QEMU VM with an HD Audio device and a USB speaker","result":"The HD Audio device and the USB speaker were read.","source":"system-info-all/platforms.md","quote":"Windows 11, as Administrator and as a standard user: the HD Audio device (output and input) and the USB speaker;"},{"kind":"systems","against":"an ACPI battery table loaded into the QEMU VMs","result":"","source":"system-info-all/platforms.md","quote":"QEMU emulates no battery, so `--battery` loads an ACPI table that describes one (`tools/vm-battery.asl`); every system's own ACPI battery driver reads it."},{"kind":"tests","against":"unit tests of Windows' sound and camera interface paths and buses","result":"","source":"system-info-all/platforms.md","quote":"and NetBSD boot logs, and the Windows interface paths and buses."}]},"system-info-source-freebsd":{"group":"system-info","line":"FreeBSD.","tags":["FreeBSD"],"keywords":"hardware","summary":"Reads the inventory of a FreeBSD system for system-info-spec, without running any program.","snippet":"","uses":["system-info-source-shared-bsd","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec","system-native-bsd"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"FreeBSD 15.1 in a QEMU VM","result":"The integration test passed as root and as an unprivileged user.","source":"system-info-all/platforms.md","quote":"| FreeBSD | FreeBSD 15.1-RELEASE in QEMU | pass, as root and as nobody |"},{"kind":"systems","against":"PCI and USB devices of a FreeBSD 15.1 VM","result":"","source":"system-info-all/platforms.md","quote":"| FreeBSD | PCI and USB from the device tree, named from the base system's lists | FreeBSD 15.1, as root and as nobody |"},{"kind":"systems","against":"input devices and a battery in a FreeBSD 15.1 VM","result":"","source":"system-info-all/platforms.md","quote":"| FreeBSD | evdev's devices, classified as on Linux; batteries in mWh and mAh, with everything | FreeBSD 15.1, as root and as nobody |"},{"kind":"systems","against":"an HD Audio device and a USB speaker in a FreeBSD 15.1 VM","result":"The HD Audio and USB devices were read.","source":"system-info-all/platforms.md","quote":"FreeBSD 15.1, as root and as nobody: the HD Audio and USB devices"},{"kind":"systems","against":"an ACPI battery table loaded into the QEMU VMs","result":"","source":"system-info-all/platforms.md","quote":"QEMU emulates no battery, so `--battery` loads an ACPI table that describes one (`tools/vm-battery.asl`); every system's own ACPI battery driver reads it."}]},"system-info-source-openbsd":{"group":"system-info","line":"OpenBSD.","tags":["OpenBSD"],"keywords":"hardware","summary":"Reads the inventory of an OpenBSD system for system-info-spec, without running any program.","snippet":"","uses":["system-info-source-shared-bsd","system-info-source-shared-cpuid","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec","system-native-bsd"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"OpenBSD 7.9 in a QEMU VM","result":"The integration test passed as root and as an unprivileged user.","source":"system-info-all/platforms.md","quote":"| OpenBSD | OpenBSD 7.9 in QEMU | pass, as root and as nobody |"},{"kind":"tests","against":"unit tests of the boot-log parsers","result":"","source":"system-info-all/platforms.md","quote":"the Linux devices, GPUs and monitors on a fake tree, and the boot-log parsers of OpenBSD and NetBSD;"},{"kind":"systems","against":"PCI and USB devices and GPUs of an OpenBSD 7.9 VM","result":"","source":"system-info-all/platforms.md","quote":"| OpenBSD | PCI, USB and GPUs by the kernel's names, from the boot log; root adds the PCI IDs and classes | OpenBSD 7.9, as root and as nobody |"},{"kind":"systems","against":"input devices and a battery in an OpenBSD 7.9 VM","result":"","source":"system-info-all/platforms.md","quote":"batteries from the sensors, with model, maker and chemistry from the boot log | OpenBSD 7.9, as root and as nobody |"},{"kind":"systems","against":"an HD Audio device and a USB speaker in an OpenBSD 7.9 VM","result":"The USB speaker was read; the HD Audio driver knows no QEMU codec.","source":"system-info-all/platforms.md","quote":"OpenBSD 7.9, as root and as nobody: the USB speaker (its HD Audio driver knows no QEMU codec)"},{"kind":"systems","against":"an ACPI battery table loaded into the QEMU VMs","result":"","source":"system-info-all/platforms.md","quote":"QEMU emulates no battery, so `--battery` loads an ACPI table that describes one (`tools/vm-battery.asl`); every system's own ACPI battery driver reads it."}]},"system-info-source-netbsd":{"group":"system-info","line":"NetBSD.","tags":["NetBSD"],"keywords":"hardware","summary":"Reads the inventory of a NetBSD system for system-info-spec, without running any program.","snippet":"","uses":["system-info-source-shared-bsd","system-info-source-shared-cpuid","system-info-source-shared-edid","system-info-source-shared-ids","system-info-source-shared-input","system-info-spec","system-native-bsd"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"NetBSD 11.0 in a QEMU VM","result":"The integration test passed as root and as an unprivileged user.","source":"system-info-all/platforms.md","quote":"| NetBSD | NetBSD 11.0 in QEMU | pass, as root and as nobody |"},{"kind":"tests","against":"unit tests of the boot-log parsers","result":"","source":"system-info-all/platforms.md","quote":"the Linux devices, GPUs and monitors on a fake tree, and the boot-log parsers of OpenBSD and NetBSD;"},{"kind":"systems","against":"PCI and USB devices and GPUs of a NetBSD 11.0 VM","result":"","source":"system-info-all/platforms.md","quote":"| NetBSD | the same, with USB IDs; root and wheel add the PCI IDs and classes | NetBSD 11.0, as root and as nobody |"},{"kind":"systems","against":"input devices and a battery in a NetBSD 11.0 VM","result":"","source":"system-info-all/platforms.md","quote":"batteries from envsys, with maker and chemistry from the boot log | NetBSD 11.0, as root and as nobody |"},{"kind":"systems","against":"an HD Audio device and a USB speaker in a NetBSD 11.0 VM","result":"The HD Audio and USB devices were read.","source":"system-info-all/platforms.md","quote":"NetBSD 11.0, as root and as nobody: the HD Audio and USB devices"},{"kind":"systems","against":"an ACPI battery table loaded into the QEMU VMs","result":"","source":"system-info-all/platforms.md","quote":"QEMU emulates no battery, so `--battery` loads an ACPI table that describes one (`tools/vm-battery.asl`); every system's own ACPI battery driver reads it."}]},"system-info-source-shared-bsd":{"group":"system-info","line":"What the three BSDs report the same way.","tags":["BSDs"],"keywords":"hardware","summary":"Reads what FreeBSD, OpenBSD and NetBSD report the same way, for the three BSD sources of system-info-spec, through system-native-bsd.","snippet":"","uses":["system-info-spec","system-native-bsd"],"usedBy":["system-info-source-freebsd","system-info-source-netbsd","system-info-source-openbsd"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[]},"system-info-source-shared-cpuid":{"group":"system-info","line":"What x86 processors say about themselves through CPUID.","tags":[],"keywords":"cpu","summary":"Reads what x86 processors say about themselves through the CPUID instruction, for system-info-spec: no privileges, the same on every operating system.","snippet":"","uses":["system-info-spec"],"usedBy":["system-info-all","system-info-source-netbsd","system-info-source-openbsd"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"klauspost/cpuid, for the vendor, model, features, caches and hypervisor","notWhen":"Applications call system-info-all, which uses it on x86.","measures":[],"checked":[{"kind":"tests","against":"tables of Intel, AMD and hypervisor CPUID answers","result":"","source":"system-info-all/platforms.md","quote":"CPUID decoding against tables of Intel, AMD and hypervisor answers;"},{"kind":"tests","against":"recorded CPUID answers","result":"","source":"system-info-source-shared-cpuid/readme","quote":"`Decode` and the other `Decode*` functions take any CPUID function, so tests feed recorded answers"}]},"system-info-source-shared-devicetree":{"group":"system-info","line":"Device trees: boards such as the Raspberry Pi.","tags":[],"keywords":"raspberry pi arm","summary":"Reads device trees, the hardware description of boards such as the Raspberry Pi, for system-info-spec.","snippet":"","uses":["system-info-spec"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"fuzzed","against":"malformed device trees","result":"Malformed trees give an error, never a panic.","source":"system-info-source-shared-devicetree/readme","quote":"malformed trees are an error, never a panic: the reader is fuzzed"},{"kind":"tests","against":"built blobs and Raspberry Pi revision codes","result":"","source":"system-info-all/platforms.md","quote":"the device tree reader against built blobs, Raspberry Pi revision codes, and a fuzzer;"},{"kind":"systems","against":"a Raspberry Pi 4 emulated in QEMU, with the Raspberry Pi kernel 6.18 and its device tree","result":"The model, installed memory, serial number and Cortex core names were read.","source":"system-info-all/platforms.md","quote":"the serial number, the Cortex core names; no SMBIOS. Verified in QEMU's `raspi4b` with the Raspberry Pi kernel 6.18 and its device tree"}]},"system-info-source-shared-edid":{"group":"system-info","line":"EDID: what a monitor says about itself.","tags":[],"keywords":"monitor display","summary":"Decodes EDID, the description a monitor gives of itself (VESA E-EDID 1.3 and 1.4), for system-info-spec. It reads no system: the OS sources hand it the bytes from sysfs, the registry or the IORegistry.","snippet":"","uses":["system-info-spec"],"usedBy":["system-info-source-linux","system-info-source-macos","system-info-source-netbsd","system-info-source-windows"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"fuzzed","against":"malformed EDID bytes","result":"Malformed bytes give an error, never a panic.","source":"system-info-source-shared-edid/readme","quote":"malformed bytes are an error, never a panic: the parser is fuzzed"},{"kind":"systems","against":"a laptop panel whose preferred mode is in a DisplayID 2.0 extension","result":"The panel is reported with no native mode, since only the EDID base block is read.","source":"todo.md","quote":"system-info-source-shared-edid reads only the EDID base block: a panel whose preferred mode is in a DisplayID 2.0 extension (this laptop's) is reported as having no native mode."}]},"system-info-source-shared-smbios":{"group":"system-info","line":"SMBIOS: the firmware's description of the machine.","tags":[],"keywords":"bios dmi firmware","summary":"Decodes SMBIOS tables, written from the DMTF standard (DSP0134), for system-info-spec. It reads no system: the OS sources hand it the raw tables.","snippet":"","uses":["system-info-spec"],"usedBy":["system-info-all"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"dmidecode, run as root and its output parsed","notWhen":"Applications call system-info-all, which hands it the tables wherever the system gives them.","measures":[],"checked":[{"kind":"tests","against":"synthetic tables and two tables captured from QEMU VMs, compared with golden files","result":"","source":"system-info-all/platforms.md","quote":"the SMBIOS decoder against synthetic tables and two tables captured from QEMU VMs (a SeaBIOS entry point and table, and Windows' RSMB blob), compared with golden files;"},{"kind":"fuzzed","against":"malformed SMBIOS tables","result":"Malformed tables give an error, never a panic.","source":"system-info-source-shared-smbios/readme","quote":"malformed tables are an error, never a panic: the parser is fuzzed"}]},"system-info-source-shared-ids":{"group":"system-info","line":"Names PCI and USB devices and monitor makers from the system's own lists.","tags":[],"keywords":"pci usb vendor","summary":"Names PCI and USB devices and monitor makers for system-info-spec from the lists the system itself keeps, so that no program carries a database of names.","snippet":"","uses":[],"usedBy":["system-info-source-freebsd","system-info-source-linux","system-info-source-macos","system-info-source-netbsd","system-info-source-openbsd","system-info-source-windows"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a database of PCI and USB names compiled into the program","notWhen":"On macOS and Windows: they keep no such lists, and their sources name devices by what drivers and devices say.","measures":[],"checked":[{"kind":"fuzzed","against":"a fuzzer on the pci.ids reader, and tests of the PCI and USB class names","result":"","source":"system-info-all/platforms.md","quote":"the EDID decoder and the pci.ids reader, both fuzzed, the PCI and USB class names"}]},"system-info-source-shared-input":{"group":"system-info","line":"Tells keyboards, mice, touchpads, touchscreens, tablets and gamepads apart.","tags":[],"keywords":"keyboard mouse","summary":"Tells what kind of input device a device is (keyboard, mouse, touchpad, touchscreen, tablet, gamepad) for system-info-spec, from what the system says of it. It reads no system: the OS sources hand it what they read.","snippet":"","uses":[],"usedBy":["system-info-source-freebsd","system-info-source-linux","system-info-source-macos","system-info-source-netbsd","system-info-source-openbsd","system-info-source-windows"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"tests","against":"evdev capabilities captured on QEMU and on real touchpads, gamepads, pens and touchscreens","result":"","source":"system-info-all/platforms.md","quote":"the input device kinds, from evdev capabilities captured on QEMU and on real hardware (a Synaptics touchpad, an Xbox pad, a Wacom pen, ELAN and eGalax touchscreens)"},{"kind":"tests","against":"the HID usages of a MacBook Pro and of Windows' collections","result":"","source":"system-info-all/platforms.md","quote":"and from HID usages (a MacBook Pro's, Windows' collections)"}]},"terminal":{"group":"terminal","line":"The terminal itself: raw mode, size, keys, mouse, paste and focus, on six systems.","tags":[],"keywords":"tty console raw","summary":"Talks to the terminal a program runs in, on Linux, macOS, Windows, FreeBSD, OpenBSD and NetBSD: the foundation of the terminal-* modules and of the programs that draw on terminals, from progress lines to full-screen applications.","snippet":"if terminal.IsTerminal(os.Stdout) {\n\tsize, err := terminal.SizeOf(os.Stdout) // columns, rows, and pixels when told\n}\n\nstate, err := terminal.MakeRaw(os.Stdin) // every key at once, no echo, no signals\ndefer state.Restore()\n\nfmt.Print(\"Password: \")\npassword, err := terminal.ReadPassword(os.Stdin) // hidden; Ctrl-C gives ErrInterrupted\nfmt.Println()\n\nsizes, err := terminal.Resizes(ctx, os.Stdout) // the size now, then each new one\ninput, output, err := terminal.Open() // the terminal, when the streams are redirected","uses":["system-native-windows"],"usedBy":["config-cli","system-info-cli","system-service-cli","terminal-colors","terminal-progress-line","terminal-progress-spec","terminal-progress-tree","terminal-ui","terminal-ui-widgets"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/term (raw mode, size, passwords); rivo/uniseg and go-runewidth for widths","notWhen":"","measures":[],"checked":[{"kind":"test suite","against":"Unicode's grapheme cluster tests (UAX #29)","result":"All of Unicode's test cases pass.","source":"terminal/readme","quote":"`cells` finds them by Unicode's rules (UAX #29, all of Unicode's test cases pass)"},{"kind":"reference","against":"Unicode's data files and their SHA-256 sums","result":"A test fails when Go moves to a newer Unicode.","source":"terminal/readme","quote":"The tables are generated by `cells/gen.go` from Unicode's files, checked against their SHA-256 sums"},{"kind":"measured","against":"measuring the width of printable ASCII","result":"No allocation, and about half a nanosecond a character.","source":"terminal/readme","quote":"Measuring printable ASCII allocates nothing and takes about half a nanosecond a character."},{"kind":"fuzzed","against":"Strip, Width, Truncate, Stripper and the parser held to each other","result":"","source":"terminal/readme","quote":"a fuzz test that holds `Strip`, `Width`, `Truncate`, `Stripper` and the parser to each other"},{"kind":"systems","against":"Debian 13, FreeBSD 15.1, OpenBSD 7.9, NetBSD 11.0, Windows Server 2025 and Windows 11 in QEMU VMs, and macOS 15 (Intel)","result":"All of it passed, with and without a terminal.","source":"terminal/readme","quote":"All of it passed on Debian 13, FreeBSD 15.1, OpenBSD 7.9, NetBSD 11.0, Windows Server 2025 and Windows 11 in QEMU VMs (`tools/vm.py`), and on macOS 15 (Intel)"},{"kind":"systems","against":"each system's terminal driver, and ConPTY and console records on Windows","result":"A typed password is handled with Backspace, nothing echoed and the mode restored; keys, Esc, paste and answers reach the reader; Close interrupts a read everywhere.","source":"terminal/readme","quote":"plus a password typed into each terminal driver (Backspace handled, nothing echoed, the mode restored)"}]},"terminal-colors":{"group":"terminal","line":"Styles text, fitted to what the terminal shows: 16 colours, 256, RGB or none.","tags":[],"keywords":"ansi color","summary":"Styles text for terminals, and fits every style to what the terminal shows: the 16 ANSI colours, the 256 of the xterm palette, RGB, or nothing at all for output that is no terminal. Built on terminal.","snippet":"profile := colors.Detect(os.Stdout)\nwarning := colors.Style{Foreground: colors.RGB(255, 128, 0), Attributes: colors.Bold}\nfmt.Println(warning.Render(profile, \"disk almost full\"))\n\nbuffer = style.AppendSequence(buffer, profile) // no allocation, for loggers\nfmt.Fprint(colors.NewWriter(os.Stdout, profile), text) // strips sequences for Plain","uses":["terminal"],"usedBy":["logging-persisters-consolewcolors","logging-samples","terminal-progress-line","terminal-progress-spec","terminal-progress-tree","terminal-ui","terminal-ui-widgets"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"fatih/color; escape sequences written by hand","notWhen":"Your program draws with Bubble Tea and Lip Gloss, which fit colours themselves.","measures":[],"checked":[{"kind":"tests","against":"known nearest colours","result":"","source":"terminal-colors/readme","quote":"Colours, names, hex, fitting with known nearest colours, sequences for every profile"},{"kind":"tests","against":"allocation counts and the detection table","result":"","source":"terminal-colors/readme","quote":"`Render` across lines, no allocation in `AppendSequence`, the detection table"},{"kind":"model","against":"terminal/terminaltest's terminal in memory","result":"","source":"terminal-colors/readme","quote":"styles drawn on terminal/terminaltest's terminal in memory and read back cell by cell"}]},"terminal-progress-spec":{"group":"terminal","line":"The contract: the tasks a program reports on, and the display that shows them.","tags":["contract"],"keywords":"progress bar","summary":"The contract of terminal-progress: the `Task`s a program reports on and the `Display` that shows them, drawn in place on a terminal and as plain lines anywhere else.","snippet":"display := tree.New(spec.Options{}) // standard error; detected\ndefer display.Close()\nlog.SetOutput(display) // log lines go above\n\nbuild := display.Add(\"build\")\ncompile := build.Add(\"compile\") // starting a subtask starts its parents\ncompile.Progress(12, 40) // a bar and 30%\ncompile.Message(\"main.go\")\ncompile.Succeed()\nbuild.Fail(err) // the reason is shown\ndisplay.Add(\"publish\").Skip(\"tests failed\")","uses":["crashproof","terminal","terminal-colors"],"usedBy":["terminal-progress-line","terminal-progress-samples","terminal-progress-tree"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"terminal/terminaltest's terminal in memory","result":"","source":"terminal-progress-spec/readme","quote":"The drawing in place on terminal/terminaltest's terminal in memory: the screen after each change, the cursor, its visibility, the synchronized update closed"},{"kind":"tests","against":"eight goroutines under the race detector","result":"","source":"terminal-progress-spec/readme","quote":"eight goroutines changing tasks at once under the race detector"},{"kind":"tests","against":"the glyphs of both themes","result":"Every glyph takes one cell.","source":"terminal-progress-spec/readme","quote":"and that every glyph of both themes takes one cell"},{"kind":"systems","against":"Linux, the BSDs, Windows Server 2025, Windows 11 through ConPTY and macOS 15 (Intel), on amd64","result":"Verified.","source":"todo.md","quote":"verified on amd64: Linux, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, macOS 15 Intel"}]},"terminal-progress-line":{"group":"terminal","line":"Progress on one line, drawn in place.","tags":[],"keywords":"progress bar spinner","summary":"Shows progress on one line, drawn in place: the task that started last among those running, after the tasks it is part of, its message, a bar, and how many others run.","snippet":"display := line.New(spec.Options{})\ndefer display.Close()\ntask := display.Add(\"Downloading\")\ntask.Progress(done, size)","uses":["terminal","terminal-colors","terminal-progress-spec"],"usedBy":["terminal-progress-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"schollz/progressbar","notWhen":"You need the time left: neither display computes one; vbauerster/mpb does.","measures":[],"checked":[{"kind":"model","against":"a terminal in memory","result":"","source":"terminal-progress-line/readme","quote":"The line on a terminal in memory as tasks start, nest, run side by side, fail and finish, and the plain lines."},{"kind":"systems","against":"Linux, the BSDs, Windows Server 2025, Windows 11 through ConPTY and macOS 15 (Intel), on amd64","result":"Verified.","source":"todo.md","quote":"verified on amd64: Linux, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, macOS 15 Intel"}]},"terminal-progress-tree":{"group":"terminal","line":"Progress as a tree of tasks and subtasks, drawn in place.","tags":[],"keywords":"progress","summary":"Shows progress as a tree drawn in place: every task not yet done, its subtasks under it, each with its state, message and progress, as parallel work runs.","snippet":"display := tree.New(spec.Options{})\ndefer display.Close()\nlinux := display.Add(\"linux\")\namd64 := linux.Add(\"amd64\")","uses":["terminal","terminal-colors","terminal-progress-spec"],"usedBy":["terminal-progress-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"vbauerster/mpb's several bars, for parallel tasks","notWhen":"You need the time left: neither display computes one; vbauerster/mpb does.","measures":[],"checked":[{"kind":"model","against":"a terminal in memory","result":"","source":"terminal-progress-tree/readme","quote":"The tree on a terminal in memory as tasks start, fail, are skipped and finish, and on a terminal too short for it."},{"kind":"systems","against":"Linux, the BSDs, Windows Server 2025, Windows 11 through ConPTY and macOS 15 (Intel), on amd64","result":"Verified.","source":"todo.md","quote":"verified on amd64: Linux, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, macOS 15 Intel"}]},"terminal-progress-samples":{"group":"terminal","line":"Programs showing both progress displays.","tags":["examples"],"keywords":"","summary":"Programs that show terminal-progress-line and terminal-progress-tree at work.","snippet":"","uses":["crashproof","terminal-progress-line","terminal-progress-spec","terminal-progress-tree"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"the terminal package's terminal in memory","result":"","source":"terminal/readme","quote":"the progress samples replayed on this package's terminal in memory"}]},"terminal-ui":{"group":"terminal","line":"Terminal applications from a tree of widgets, full screen or inline under the prompt.","tags":[],"keywords":"tui","summary":"Builds terminal applications from a tree of widgets, full screen or inline under the shell's prompt, on Linux, macOS, Windows, FreeBSD, OpenBSD and NetBSD. Built on terminal and terminal-colors; the standard library only.","snippet":"app := ui.New(ui.Options{Mouse: ui.MouseClicks})\nname := widgets.NewInput(\"Your name\")\ngreeting := widgets.NewLabel(\"\")\nname.OnSubmit(func(text string) { greeting.SetText(\"Hello, \" + text) })\nroot := ui.Column(name, greeting, widgets.NewKeyHints())\nerr := app.Run(root) // until Ctrl+C or app.Quit()","uses":["dsa-collections-queue-priority","terminal","terminal-colors"],"usedBy":["terminal-ui-samples","terminal-ui-widgets"],"thirdParty":[],"flows":[],"maturity":"new","newBecause":"the widget toolkit's first milestone; work on it is paused","replaces":"Bubble Tea with Bubbles and Lip Gloss; rivo/tview","notWhen":"You need a text editor, a file picker, Markdown, forms or charts, or images: not built yet (terminal-ui.md).","measures":[],"checked":[{"kind":"model","against":"terminal/terminaltest's terminal, with a clock the test moves, also as an older terminal","result":"","source":"terminal-ui/readme","quote":"`uitest` runs the app on terminal/terminaltest's terminal, with a clock the test moves; `uitest.Legacy()` makes it an older terminal."},{"kind":"tests","against":"layouts, the renderer, focus, input, layers and a legacy terminal","result":"","source":"terminal-ui/readme","quote":"The package's tests check the layouts, the box model, joined lines, text wrapping, the renderer (only changes written, wide characters and emoji, the cursor, inline regions with prints above)"},{"kind":"systems","against":"Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15","result":"Verified.","source":"terminal-ui.md","quote":"Verified on Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15."}]},"terminal-ui-widgets":{"group":"terminal","line":"The standard widgets, themed, with the keys and mouse users expect.","tags":[],"keywords":"tui widgets","summary":"The standard widgets of terminal-ui: each draws with the app's theme, takes the keys and mouse a user expects, and is laid out like any widget (its box: width, height, margin, border, padding).","snippet":"list := widgets.NewList(100000, func(painter *ui.Painter, index int, style ui.Style) {\n\tpainter.Text(1, 0, items[index].Name, style)\n})\nlist.OnActivate(open)\ntable := widgets.NewTable([]widgets.Column{{Title: \"Name\", Width: ui.Auto()}, {Title: \"Size\", Width: ui.Cells(8), Align: ui.End}},\n\tlen(files), func(row, column int) string { return cell(files[row], column) })\nwidgets.Confirm(app, \"Delete\", \"Delete it for good?\", []string{\"Cancel\", \"Delete\"}, func(choice int) { ... })\nwidgets.AddCommandPalette(app) // Ctrl+P","uses":["terminal","terminal-colors","terminal-ui"],"usedBy":["terminal-ui-samples"],"thirdParty":[],"flows":[],"maturity":"new","newBecause":"the widget toolkit's first milestone; work on it is paused","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"terminal-ui's terminal in memory (uitest)","result":"","source":"terminal-ui-widgets/readme","quote":"Each widget on terminal-ui's terminal in memory (uitest): drawing, keys, clicks, drags and the wheel"},{"kind":"systems","against":"Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15","result":"Verified.","source":"terminal-ui.md","quote":"Verified on Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15."}]},"terminal-ui-samples":{"group":"terminal","line":"Programs built on terminal-ui.","tags":["examples"],"keywords":"tui","summary":"Programs built on terminal-ui and terminal-ui-widgets.","snippet":"","uses":["crashproof","terminal-ui","terminal-ui-widgets"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"new","newBecause":"the widget toolkit's first milestone; work on it is paused","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"systems","against":"real terminals","result":"The gallery and inline samples ran.","source":"terminal-ui/readme","quote":"The gallery and inline samples (terminal-ui-samples) ran in real terminals."},{"kind":"systems","against":"Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15","result":"Verified.","source":"terminal-ui.md","quote":"Verified on Debian, the BSDs, Windows Server 2025 and Windows 11 through ConPTY, and macOS 15."}]},"system-native-windows":{"group":"system-native","line":"Win32 and native Windows calls beyond syscall, without cgo or x/sys.","tags":["Windows"],"keywords":"win32 ntdll","summary":"Calls the parts of the Win32 and native Windows API that the standard library's syscall package does not, without cgo and without golang.org/x/sys, for the goframework modules that need Windows: system-process, system-service, system-info and the Windows Event Log persister. What syscall offers (handles, processes, tokens, CreateProcess, CreateFile, DeviceIoControl, basic registry reads) is used from there.","snippet":"","uses":[],"usedBy":["app-spec","logging-persisters-wineventlog","system-info-source-windows","system-process-windows","system-service-windows","terminal","vfs-source-disk"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/sys/windows, for the calls goframework's modules make","notWhen":"You need a Win32 call none of goframework's modules needed: golang.org/x/sys/windows covers more.","measures":[],"checked":[{"kind":"systems","against":"Windows Server 2025 in a QEMU VM, with an emulated battery","result":"","source":"system-native-windows/readme","quote":"They ran on Windows Server 2025 in a QEMU VM (`tools/vm.py up windows2025`, with `--battery` for a battery to read)"},{"kind":"tests","against":"the system they run on, read without changing anything","result":"","source":"system-native-windows/readme","quote":"The tests read the system they run on and change nothing on it: layouts, the registry, version and firmware, processes, command lines round-tripped, events and jobs"},{"kind":"other libraries","against":"golang.org/x/sys's structure sizes, field offsets and constants for amd64, arm64 and 386","result":"","source":"system-native-windows/readme","quote":"every structure's size and field offsets and every constant were checked at compile time against golang.org/x/sys's, for amd64, arm64 and 386."}]},"system-native-macos":{"group":"system-native","line":"libSystem, IOKit and CoreFoundation, without cgo.","tags":["macOS"],"keywords":"darwin iokit","summary":"Calls macOS's libSystem, IOKit and CoreFoundation without cgo, for the goframework modules that need the kernel or the IORegistry: system-process and system-info. Core Audio is loaded only when asked for.","snippet":"","uses":[],"usedBy":["system-info-source-macos","system-process-macos"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"cgo calls to IOKit, CoreFoundation and Core Audio","notWhen":"You need an Objective-C framework such as AVFoundation: it calls libSystem, IOKit, CoreFoundation and Core Audio.","measures":[],"checked":[{"kind":"fuzzed","against":"malformed XML property lists","result":"It never panics on malformed input.","source":"system-native-macos/readme","quote":"`DecodePlist`: XML property lists into Go values; it never panics on malformed input (fuzzed)"}]},"system-native-bsd":{"group":"system-native","line":"The FreeBSD, OpenBSD and NetBSD kernels, without cgo.","tags":["BSDs"],"keywords":"sysctl","summary":"Reads the kernels of FreeBSD, OpenBSD and NetBSD without cgo, for the goframework modules that need them: system-process and system-info.","snippet":"","uses":[],"usedBy":["system-info-source-freebsd","system-info-source-netbsd","system-info-source-openbsd","system-info-source-shared-bsd","system-process-freebsd","system-process-netbsd","system-process-openbsd"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/sys/unix's sysctl calls, and sysctl, kenv or drvctl run and their output parsed","notWhen":"","measures":[],"checked":[]},"vfs":{"group":"vfs","line":"The contract every file system implements, with helpers, decorators and io/fs bridges.","tags":["contract"],"keywords":"filesystem fs io/fs","summary":"Goframework's file systems: one contract, `vfs.FS`, that every source implements (the disk, memory, archives, remote stores), and helpers and decorators over any FS. Standard library only.","snippet":"import (\n\t\"goframework.io/vfs\"\n\t\"goframework.io/vfs-source-disk\"\n)\n\nlocal, err := disk.Open(\"/var/lib/app\") // a folder: nothing done through it leaves it\ndata, err := vfs.ReadFile(ctx, local, \"state.json\", vfs.Limit(1<<20))\nerr = vfs.WriteFile(ctx, local, \"state.json\", data) // atomic where Caps.Atomic\n\nfor entry, err := range vfs.Walk(ctx, local, \"src\", vfs.Only(\"**/*.go\"), vfs.Exclude(\"vendor/\")) {\n\t// entry.Path, Kind, Size, Mode, Modified, Created, ID, Version\n}\nerr = vfs.CopyFile(ctx, local, \"a.txt\", bucket, \"backup/a.txt\") // natively on one FS, streamed between two\nlayered := vfs.Overlay(local, vfs.FromIOFS(embeddedDefaults)) // defaults compiled in, overridden on disk\nhttp.Handle(\"/\", http.FileServerFS(vfs.IOFS(ctx, layered))) // any FS for io/fs's consumers","uses":[],"usedBy":["config-source-file","system-service-spec","vfs-device-partitions","vfs-device-qcow2","vfs-device-vhd","vfs-device-vmdk","vfs-operations","vfs-source-archive-7z","vfs-source-archive-rar","vfs-source-archive-tar","vfs-source-archive-zip","vfs-source-disk","vfs-source-filesystem-exfat","vfs-source-filesystem-ext4","vfs-source-filesystem-fat","vfs-source-filesystem-iso9660","vfs-source-filesystem-ntfs","vfs-source-filesystem-udf","vfs-source-memory","vfs-source-remote-ftp","vfs-source-remote-gdrive","vfs-source-remote-http","vfs-source-remote-nfs","vfs-source-remote-s3","vfs-source-remote-sftp","vfs-source-remote-smb","vfs-source-remote-webdav","vfs-watch"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"io/fs where writes, a context, ranged reads or capabilities are needed; spf13/afero","notWhen":"You only read embedded files or one folder: io/fs and os.DirFS are enough.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the conformance suite in vfs-tests","result":"Every source passes it.","source":"vfs.md","quote":"Every source passes it, the way config's formats passed toml-test and yaml-test-suite."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes vfstest and its own tests on all six.","source":"vfs.md","quote":"Step 1 done on 2026-10-15: vfs, vfs-source-memory, vfs-source-disk and vfs-watch, passing vfstest and their own tests on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"tests","against":"its own tests of names, SafeName, options and patterns","result":"","source":"vfs-tests/readme","quote":"`core` | vfs: names, SafeName, options, patterns"},{"kind":"measured","against":"binary size with crypto/rand naming temporary files","result":"crypto/rand's start-up added 170 KB to every program importing them, so temporary names use math/rand/v2.","source":"vfs.md","quote":"vfs and vfs-source-disk name temporary files with math/rand/v2: crypto/rand's start-up added 170 KB to every program importing them"}]},"vfs-operations":{"group":"vfs","line":"Copy, move, mirror, delete and compare trees between any two file systems, planned before run.","tags":[],"keywords":"copy sync rsync mirror","summary":"Operations on trees across any two vfs file systems: copy, move, mirror, delete, compare. Each is planned first, a walk of both trees listing each folder once on each side, so what is there already and the conflicts are decided before anything changes; then run, files in parallel.","snippet":"job := operations.Copy(ctx, local, \"photos\", bucket, \"backup/photos\",\n\toperations.Parallel(8), // files at once; a large file's parts at once\n\toperations.OnConflict(operations.KeepNewer), // files the target holds, different\n\toperations.Journal(state, \"photos.journal\"), // run again, it resumes\n\toperations.Verify(), // each copy read back, by SHA-256\n)\nfor event := range job.Events() {\n\t// event.Progress: files and bytes done of the plan's, the rate, what is left\n}\nreport, err := job.Wait() // *FailedError: the steps that failed, the rest done","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"os.CopyFS, and copy and sync loops written by hand","notWhen":"One file: vfs.CopyFile is enough.","measures":[],"checked":[{"kind":"tests","against":"copies, conflicts, mirrors, moves, deletes, failures and retries, verification, pausing, bandwidth, parts, journals","result":"","source":"vfs-tests/readme","quote":"vfs-operations: copies, conflicts, mirrors, moves, deletes, failures and retries, verification, pausing, bandwidth, parts, journals"},{"kind":"systems","against":"every OS in the VMs and the Mac","result":"","source":"vfs.md","quote":"Each step on every OS in the VMs and the Mac"}]},"vfs-watch":{"group":"vfs","line":"Watches any file system; polls those that give no hints.","tags":[],"keywords":"watch notify inotify","summary":"Watching any vfs FS. The source's hints say where to look, and the watcher's snapshot of the tree is the truth: reading where a hint points and comparing it with the snapshot says what changed. A source without hints is polled through the same comparison.","snippet":"local, _ := disk.Open(\"~/projects/site\")\nwatcher, err := watch.Start(ctx, local, \".\",\n\twatch.Recursive(),\n\twatch.Exclude(\".git\", \"node_modules/\", \"*.swp\"), // never read, recorded or told\n\twatch.Only(\"**/*.{html,css,js}\"), // told; the rest still watched\n\twatch.Since(state, \"site.snapshot\"), // what changed while not running comes first\n)\ndefer watcher.Close()\n<-watcher.Ready()\nfor batch := range watcher.Changes() {\n\tfor _, change := range batch.Changes {\n\t\t// change.Kind: watch.Created, Changed, Removed, Renamed (change.From)\n\t\t// change.Path relative to the watched folder; change.Entry a vfs.Entry\n\t}\n}","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"fsnotify, and the walking and rescanning its users write","notWhen":"You want each raw event the OS sends, not batches of what changed: fsnotify gives those.","measures":[{"title":"Noticing changes","against":"fsnotify, Rust's notify","legend":"From vfs.md, which calls vfs-watch by its earlier name, files-watch: how soon each reports a change, how long each took to tell a burst and churn of changes, and how many it got wrong.","reproduce":"","source":"vfs.md","ours":["files-watch"],"names":{"files-watch":"vfs-watch"},"labels":{},"kind":"table","header":["tool","latency min / median / p99","burst: told in, wrong","churn: told in, wrong","CPU, burst and churn","peak memory","start on 100k files, memory"],"rows":[["files-watch, Settle(0)","0.1 / 0.3 / 0.6 ms","165 ms, 0","330 ms, 0","506 ms","15 MB","187 ms, 27 MB"],["files-watch, Settle(50ms)","50.4 / 50.5 / 50.8 ms","172 ms, 0","416 ms, 0","333 ms","12 MB","195 ms, 27 MB"],["fsnotify v1.10.1","0.1 / 0.2 / 0.3 ms","164 ms, 0","424 ms, 0","474 ms","10 MB","91 ms, 18 MB"],["notify 8.2.0 (Rust)","0.1 / 0.2 / 0.2 ms","never, 13","never, 8","338 ms","3 MB","266 ms, 8 MB"]]}],"checked":[{"kind":"measured","against":"fsnotify v1.10.1 and Rust's notify 8.2.0, in the Debian VM, five interleaved rounds","result":"At Settle(0) it told the burst in 165 ms and the churn in 330 ms, none wrong.","source":"vfs.md","quote":"The engine as files-watch, measured on 2026-10-15 against fsnotify and Rust's notify: the Debian VM (2 vCPU, 1 GB, ext4), five interleaved rounds"},{"kind":"measured","against":"Rust's notify 8.2.0","result":"notify misses files made in a new folder before it watches that folder; its users cannot close the gap without rescanning.","source":"vfs.md","quote":"notify misses files made in a new folder before it adds that folder's watch, which it adds itself; its users cannot close the gap without rescanning"},{"kind":"measured","against":"fsnotify v1.10.1","result":"The snapshot costs 9 MB more on 100 000 files and twice fsnotify's time to start.","source":"vfs.md","quote":"files-watch's cost is the snapshot: 9 MB more on 100 000 files, and twice fsnotify's time to start, reading every entry"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes vfstest and its own tests on all six.","source":"vfs.md","quote":"vfs-watch, passing vfstest and their own tests on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"tests","against":"the disk and memory: editors' saves, links swapped, renames, Since, Only, Stable, falling back to polling, ten thousand files churned","result":"","source":"vfs-tests/readme","quote":"vfs-watch on the disk and in memory: editors' saves, links swapped, renames, Since, Only, Stable, falling back to polling, ten thousand files churned"},{"kind":"systems","against":"a network share polled, in a VM with a share","result":"Not done yet: open in todo.md.","source":"todo.md","quote":"vfs-watch: a network share polled, in a VM with a share"}]},"vfs-source-disk":{"group":"vfs","line":"A folder of the OS's file systems, sandboxed through os.Root.","tags":[],"keywords":"disk local","summary":"The vfs source of the OS's file systems: a folder as a `vfs.FS`, through an os.Root, so nothing done through it leaves the folder, by `..`, absolute names or symlinks (`ErrEscapes`).","snippet":"local, err := disk.Open(\"~/projects/site\") // or disk.Create(path, 0o700)\nerr = vfs.WriteFile(ctx, local, \"out/index.html\", page) // temporary file, sync, rename, sync the folder\nlock, err := local.Lock(ctx, \"build.lock\", vfs.Wait()) // flock, LockFileEx\nplaces, err := disk.UserPlaces(\"editor\") // XDG, ~/Library, Known Folders\nsingle, err := disk.Single(ctx, \"editor\") // ErrLocked: another runs\nerr = disk.WriteFile(\"/etc/editor/config.yaml\", data) // on a plain path, for scripts","uses":["system-native-windows","vfs"],"usedBy":["config-source-file","system-service-spec"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"os and path/filepath calls on a root folder, os.Root alone","notWhen":"A program that only reads one config file: os.ReadFile is enough.","measures":[],"checked":[{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes vfstest and its own tests on all six.","source":"vfs.md","quote":"Step 1 done on 2026-10-15: vfs, vfs-source-memory, vfs-source-disk and vfs-watch, passing vfstest and their own tests on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"tests","against":"the conformance suite, escapes, links, places, locks, Single; locks across processes and a write killed half-way","result":"","source":"vfs-tests/readme","quote":"vfs-source-disk: the suite, escapes, links, places, locks, `Single`; with `-tags integration`, locks across processes and a write killed half-way"},{"kind":"systems","against":"Windows Server 2025","result":"Go's os.Root on Windows kept a folder held open from being removed or renamed, so there each call opens the root.","source":"vfs.md","quote":"On Windows, Go opens an os.Root without sharing its deletion: a folder held open could not be removed or renamed."},{"kind":"systems","against":"OpenBSD's FFS","result":"OpenBSD's FFS keeps no births, so births are claimed per folder.","source":"vfs.md","quote":"Births are claimed per folder: OpenBSD's FFS keeps none."},{"kind":"systems","against":"NetBSD","result":"NetBSD's syscall package lacks statvfs, so it is called directly.","source":"vfs.md","quote":"NetBSD's statvfs is called directly, for `Usage` and for telling network file systems; its syscall package has neither"}]},"vfs-source-memory":{"group":"vfs","line":"A file system in memory with every capability, for tests, staging and caches.","tags":[],"keywords":"memfs ram","summary":"A vfs source in memory, with every capability: atomic writes, versions, identities and births, symlinks and hard links, locks, exact hints for watching, a limit of bytes. For tests, staging and caches; it is the reference vfstest is written against.","snippet":"fsys := memory.New(memory.Limit(64 << 20)) // ErrNoSpace past 64 MiB\nerr := vfs.WriteFile(ctx, fsys, \"a/b.txt\", data)","uses":["vfs"],"usedBy":["config-source-file"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"fstest.MapFS, which is read-only; afero's MemMapFs","notWhen":"","measures":[],"checked":[{"kind":"reference","against":"vfstest, which is written against it","result":"","source":"vfs-source-memory/readme","quote":"it is the reference vfstest is written against"},{"kind":"tests","against":"the conformance suite and the byte limit","result":"","source":"vfs-tests/readme","quote":"vfs-source-memory: the suite, the limit"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes vfstest and its own tests on all six.","source":"vfs.md","quote":"Step 1 done on 2026-10-15: vfs, vfs-source-memory, vfs-source-disk and vfs-watch, passing vfstest and their own tests on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-archive-zip":{"group":"vfs","line":"Zip archives: read, and written as new ones.","tags":[],"keywords":"zip archive","summary":"A vfs source of zip archives: an archive in any vfs FS, read-only; and new archives written through `Create`, a write-only FS.","snippet":"archive, err := zip.Open(ctx, fsys, \"site.zip\", zip.Password(\"secret\"))\ndata, err := vfs.ReadFile(ctx, archive, \"site/index.html\")\n\nwriter, err := zip.Create(ctx, fsys, \"out.zip\")\n_, err = operations.Copy(ctx, disk, \"site\", writer, \"site\").Wait()\nerr = writer.Close() // the archive appears whole now","uses":["codecs-compress-lzma","cryptography-cipher-zipcrypto","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"archive/zip, which reads no encrypted entries and no LZMA","notWhen":"Plain zips, deflated, read whole: archive/zip is enough. Encrypted zips are read, not written.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"archives made by zip and 7z","result":"","source":"vfs.md","quote":"Fixtures from real tools: zip and 7z, GNU tar and bsdtar, xorriso"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-archive-tar":{"group":"vfs","line":"Tar, plain or with gzip, bzip2, xz or zstd: read, and written as new ones.","tags":[],"keywords":"tar archive tgz","summary":"A vfs source of tar archives, plain or compressed with gzip, bzip2, xz or zstd: an archive in any vfs FS, read-only; and new archives written through `Create`, a write-only FS.","snippet":"archive, err := tar.Open(ctx, fsys, \"backup.tar.zst\") // the compression is told from its bytes\ndata, err := vfs.ReadFile(ctx, archive, \"etc/hosts\")\n\nwriter, err := tar.Create(ctx, fsys, \"site.tar.xz\", tar.Compress(tar.Xz))\n_, err = operations.Copy(ctx, disk, \"site\", writer, \"site\").Wait()\nerr = writer.Close() // the archive appears whole now","uses":["vfs"],"usedBy":[],"thirdParty":["github.com/klauspost/compress","github.com/ulikunitz/xz"],"flows":[],"maturity":"verified","newBecause":"","replaces":"archive/tar with compress/gzip, and an index of it written by hand","notWhen":"You read one tar from start to end once: archive/tar does that.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"archives made by GNU tar and bsdtar","result":"","source":"vfs.md","quote":"Fixtures from real tools: zip and 7z, GNU tar and bsdtar, xorriso"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-archive-7z":{"group":"vfs","line":"7z archives, read-only, read in ranges from any file system.","tags":[],"keywords":"7zip archive","summary":"A vfs source of 7z archives, read-only: an archive in any vfs FS that reads at offsets, so one on a remote is read in ranges.","snippet":"archive, err := sevenzip.Open(ctx, fsys, \"backup.7z\", sevenzip.Prompter(ask))\n_, err = operations.Copy(ctx, archive, \"photos\", disk, \"photos\").Wait()","uses":["codecs-compress-bcj","codecs-compress-delta","codecs-compress-lzma","codecs-compress-ppmd","cryptography-cipher-aes-cbc","cryptography-kdf-7z","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"bodgit/sevenzip, or the 7z program","notWhen":"Archives in volumes (.7z.001), IA64, RISC-V, Deflate64 and ZSTD: ErrUnsupported. 7z is not written.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"archives made by 7-Zip 25.01 in each method and filter","result":"","source":"vfs.md","quote":"Fixtures from real tools: 7-Zip 25.01 in each method and filter"},{"kind":"reference","against":"a PPMd stream 7-Zip encoded with a 1 MiB model that starts again 34 times","result":"Decodes what 7-Zip encoded.","source":"vfs.md","quote":"a model of 1 MiB that starts again 34 times decodes what 7-Zip encoded"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"every OS in the VMs and the Mac","result":"","source":"vfs.md","quote":"Each step on every OS in the VMs and the Mac"}]},"vfs-source-archive-rar":{"group":"vfs","line":"RAR archives, read-only.","tags":[],"keywords":"rar archive","summary":"A vfs source of RAR archives, read-only: an archive in any vfs FS that reads at offsets, so one on a remote is read in ranges. RAR is never written: its licence forbids it.","snippet":"archive, err := rar.Open(ctx, fsys, \"photos.rar\", rar.Prompter(ask))\n_, err = operations.Copy(ctx, archive, \".\", disk, \"photos\").Wait()","uses":["codecs-compress-rar","codecs-text-charsets","cryptography-cipher-aes-cbc","cryptography-kdf-rar","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"nwaples/rardecode, or the unrar program","notWhen":"RAR 1.5 and 2.0 archives, and files split across volumes: ErrUnsupported. RAR is never written: its licence forbids it.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"archives RAR 6.24 for Linux wrote, in RAR 4's and RAR 5's formats","result":"","source":"vfs.md","quote":"RAR 6.24 for Linux in RAR 4's and 5's formats (RAR 7 writes no RAR 4)"},{"kind":"test suite","against":"libarchive's RAR samples, vendored in vfs-tests with its licence","result":"","source":"vfs-source-archive-rar/readme","quote":"Tested against archives RAR 6.24 for Linux wrote, and libarchive's samples (testdata/libarchive in vfs-tests, with its licence)"},{"kind":"reference","against":"a sample archive for each filter","result":"The Itanium filter, which no tool here makes, and RAR 5's E8E9 are not tested against a sample.","source":"todo.md","quote":"the Itanium filter, which no tool here makes, and RAR 5's E8E9 untested against a sample"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"every OS in the VMs and the Mac","result":"","source":"vfs.md","quote":"Each step on every OS in the VMs and the Mac"}]},"vfs-source-remote-s3":{"group":"vfs","line":"S3 and its kind: AWS, Cloudflare R2, Backblaze B2, MinIO, Wasabi, Google Cloud Storage.","tags":[],"keywords":"s3 aws r2 minio object storage","summary":"A vfs source of S3 and the services that speak its API: AWS, Cloudflare R2, Backblaze B2, MinIO, Wasabi, Google Cloud Storage's XML API. Requests are signed in-house (Signature Version 4).","snippet":"bucket, err := s3.Open(ctx, \"s3://backups/hosts\") // AWS: keys and region from the environment or ~/.aws\nminio, err := s3.Open(ctx, \"s3://data\", s3.Endpoint(\"http://127.0.0.1:9000\"), s3.Credentials(key, secret, \"\"))\n_, err = operations.Mirror(ctx, disk, \"site\", bucket, \"site\", operations.Parallel(16)).Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"aws-sdk-go-v2's S3 client, minio-go","notWhen":"You manage S3 beyond files (bucket policies, lifecycle rules, presigned URLs): use the AWS SDK.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"MinIO in the Debian VM","result":"","source":"vfs.md","quote":"the remote ones against Apache, nginx, lighttpd, Caddy, vsftpd, pyftpdlib and MinIO in the Debian VM"},{"kind":"reference","against":"AWS's worked examples of Signature Version 4","result":"","source":"vfs.md","quote":"In-house SigV4 signing, checked against AWS's worked examples."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"real servers","against":"AWS, R2 and B2","result":"Not done yet: waits for an account.","source":"todo.md","quote":"against AWS, R2 and B2 once an account is given"},{"kind":"other libraries","against":"rclone's way of keeping a modification time (x-amz-meta-mtime)","result":"","source":"vfs-source-remote-s3/readme","quote":"keep the time in x-amz-meta-mtime, as rclone does"}]},"vfs-source-remote-smb":{"group":"vfs","line":"SMB shares: Windows, Samba, NAS boxes; signing and encryption.","tags":[],"keywords":"smb cifs samba windows share","summary":"A vfs source of SMB shares: Windows, Samba, NAS boxes. SMB 2.1 to 3.1.1 in-house: NTLMv2 in SPNEGO, signing (HMAC-SHA256, AES-CMAC), encryption (AES-GCM, AES-CCM); the algorithms Go lacks from the cryptography-* modules (hash-md4, mac-aes-cmac, aead-aes-ccm, kdf-sp800-108).","snippet":"share, err := smb.Open(ctx, \"smb://nas/media/photos\", smb.Login(`HOME\\me`, password))\n_, err = operations.Mirror(ctx, disk, \"photos\", share, \".\").Wait()","uses":["cryptography-aead-aes-ccm","cryptography-hash-md4","cryptography-kdf-sp800-108","cryptography-mac-aes-cmac","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"hirochachacha/go-smb2, or mounting the share","notWhen":"You need Kerberos, DFS referrals or ACLs: not yet here.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"Samba 4.22 (2.1, 3.0, 3.0.2, 3.1.1, encrypted, guest) and Windows Server 2025's SMB server","result":"","source":"vfs.md","quote":"smb against Samba 4.22 (2.1, 3.0, 3.0.2, 3.1.1, encrypted, guest) and Windows Server 2025's own server"},{"kind":"reference","against":"published vectors: NTLMv2 (MS-NLMP 4.2.4), MD4 (RFC 1320), AES-CMAC (RFC 4493), AES-CCM (SP 800-38C)","result":"","source":"vfs.md","quote":"Each protocol's crypto is checked against published vectors: MD4 (RFC 1320), AES-CMAC (RFC 4493), AES-CCM (SP 800-38C), NTLMv2 (MS-NLMP 4.2.4)"},{"kind":"systems","against":"the six test systems","result":"Passes on all six.","source":"vfs.md","quote":"Step 4 done on 2026-10-15: vfs-source-remote-sftp and -smb, passing on the six systems"},{"kind":"real servers","against":"Samba","result":"Samba can't replace a file held open; the tests name it as a skip.","source":"vfs.md","quote":"A server's own limits are named skips: Samba and Windows' OpenSSH can't replace a file held open."},{"kind":"real servers","against":"Samba's locks","result":"Samba turns SMB locks into fcntl locks, which flock on the server doesn't see.","source":"vfs.md","quote":"Samba turns them into fcntl locks on the server, and disk's flock doesn't see those: a program on the server itself doesn't see an SMB client's lock"}]},"vfs-source-remote-sftp":{"group":"vfs","line":"SFTP servers.","tags":[],"keywords":"ssh sftp","summary":"A vfs source of SFTP servers: SSH's transport from golang.org/x/crypto/ssh, the SFTP protocol (version 3, with OpenSSH's extensions) in-house.","snippet":"build, err := sftp.Open(ctx, \"sftp://build-box/~/releases\", sftp.Prompter(ask)) // an ssh_config alias\n_, err = operations.Mirror(ctx, disk, \"dist\", build, \"v2.4\").Wait()\n\nfor _, host := range sftp.Hosts() { menu.Add(host.Alias, host.HostName) }","uses":["vfs"],"usedBy":[],"thirdParty":["golang.org/x/crypto"],"flows":[],"maturity":"verified","newBecause":"","replaces":"pkg/sftp, and the ssh_config and known_hosts handling written around it","notWhen":"","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"Debian's OpenSSH 10 and Windows Server 2025's OpenSSH","result":"","source":"vfs.md","quote":"sftp against Debian's OpenSSH 10 and Windows Server 2025's"},{"kind":"systems","against":"the six test systems","result":"Passes on all six.","source":"vfs.md","quote":"Step 4 done on 2026-10-15: vfs-source-remote-sftp and -smb, passing on the six systems"},{"kind":"real servers","against":"Windows' OpenSSH","result":"Windows' OpenSSH can't replace a file held open; the tests name it as a skip.","source":"vfs.md","quote":"A server's own limits are named skips: Samba and Windows' OpenSSH can't replace a file held open."},{"kind":"reference","against":"ssh(1)'s reading of ssh_config","result":"Read as ssh(1) reads it: first value wins, Match, Include, tokens.","source":"vfs.md","quote":"ssh_config is read as ssh(1) reads it (first value wins, Match, Include, tokens), not as commander did (last value, no patterns)"},{"kind":"real servers","against":"servers other than OpenSSH","result":"Not done yet: open in todo.md.","source":"todo.md","quote":"the agent forwarded; servers other than OpenSSH"}]},"vfs-source-remote-ftp":{"group":"vfs","line":"FTP, plain or with TLS.","tags":[],"keywords":"ftp ftps","summary":"A vfs source of FTP servers: plain, with explicit TLS (AUTH TLS) or implicit TLS (ftps://).","snippet":"site, err := ftp.Open(ctx, \"ftp://deploy@host/www\", ftp.TLS(nil), ftp.Prompter(askPassword))\n_, err = operations.Mirror(ctx, disk, \"public\", site, \".\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"jlaffaye/ftp","notWhen":"","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"vsftpd and pyftpdlib in the Debian VM","result":"","source":"vfs.md","quote":"the remote ones against Apache, nginx, lighttpd, Caddy, vsftpd, pyftpdlib and MinIO in the Debian VM"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"real servers","against":"vsftpd","result":"vsftpd lacks MLSD, and its lock_upload_files makes a write wait for the transfers reading the file.","source":"vfs-source-remote-ftp/readme","quote":"vsftpd lacks MLSD; its lock_upload_files makes a write wait for the transfers reading the file"},{"kind":"real servers","against":"vsftpd over TLS","result":"Data connections under TLS resume the control connection's session, as vsftpd requires.","source":"vfs.md","quote":"Data connections under TLS resume the control's session (vsftpd requires it)"},{"kind":"reference","against":"samples of a Windows (IIS) server's LIST","result":"No IIS server tested yet; its LIST shape only from samples.","source":"todo.md","quote":"a Windows (IIS) server, so far its LIST shape only from samples"}]},"vfs-source-remote-webdav":{"group":"vfs","line":"WebDAV: Apache, nginx, IIS, Nextcloud, ownCloud.","tags":[],"keywords":"webdav dav nextcloud","summary":"A vfs source of WebDAV servers: Apache's mod_dav, nginx with dav-ext, IIS, Nextcloud and ownCloud, sabre/dav.","snippet":"dav, err := webdav.Open(ctx, \"https://cloud.example.org/remote.php/dav/files/me/\", webdav.Login(\"me\", password))\nerr = vfs.WriteFile(ctx, dav, \"notes/today.md\", data)\n_, err = operations.Mirror(ctx, disk, \"photos\", dav, \"photos\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"studio-b12/gowebdav","notWhen":"","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"Apache's mod_dav and nginx's dav-ext","result":"Their own limits are named skips in the tests: Apache's GET racing a replace, nginx's dot names.","source":"vfs.md","quote":"A server's own limits are named in the tests (`vfstest.Skip`), not hidden: Apache's GET racing a replace, nginx's dot names."},{"kind":"real servers","against":"Apache's MOVE","result":"Apache's MOVE deletes the target first, so a write is a direct PUT instead of a PUT and a MOVE.","source":"vfs.md","quote":"Apache's MOVE deletes the target first, and a reader meets nothing for a moment"},{"kind":"reference","against":"published vectors for Digest (RFC 7616)","result":"","source":"vfs.md","quote":"Each protocol's crypto is checked against published vectors: MD4 (RFC 1320), AES-CMAC (RFC 4493), AES-CCM (SP 800-38C), NTLMv2 (MS-NLMP 4.2.4), SigV4 (AWS's examples), Digest (RFC 7616)"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"real servers","against":"IIS and sabre/dav","result":"Not tested against yet.","source":"todo.md","quote":"IIS and sabre/dav, not yet tested against"}]},"vfs-source-remote-nfs":{"group":"vfs","line":"NFS version 3 exports.","tags":[],"keywords":"nfs","summary":"A vfs source of NFS version 3 exports, in-house: ONC RPC over TCP and XDR, AUTH_SYS's credentials, the portmapper and MOUNT.","snippet":"fsys, err := nfs.Open(ctx, \"nfs://nas/srv/media/photos\", nfs.User(1000, 1000))\n_, err = operations.Copy(ctx, fsys, \"2024\", disk, \"2024\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"mounting the export, which takes root; vmware/go-nfs-client","notWhen":"Servers that speak only NFS 4: this speaks version 3. No locks: NFS 3 has none.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"Linux's NFS server in the Debian VM","result":"","source":"vfs.md","quote":"vfs-source-remote-nfs, against Linux's server in the Debian VM"},{"kind":"real servers","against":"an async export on Linux's server","result":"COMMIT is used.","source":"vfs.md","quote":"The test export is async, so COMMIT is used."},{"kind":"systems","against":"every OS in the VMs and the Mac","result":"","source":"vfs.md","quote":"Each step on every OS in the VMs and the Mac"},{"kind":"real servers","against":"FreeBSD's NFS server and Windows Server's Server for NFS","result":"Not done yet: open in todo.md.","source":"todo.md","quote":"servers other than Linux's (FreeBSD's, Windows Server's Server for NFS)"}]},"vfs-source-remote-gdrive":{"group":"vfs","line":"Google Drive: My Drive or a shared drive.","tags":[],"keywords":"google drive gdrive","summary":"A vfs source of Google Drive: My Drive, or a shared drive, by its REST API (v3), in-house.","snippet":"drive, err := gdrive.Open(ctx, \"gdrive:///Photos\", gdrive.Client(id, secret),\n\tgdrive.RefreshToken(saved, save), gdrive.Prompter(ask))\n_, err = operations.Copy(ctx, drive, \"2024\", disk, \"2024\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"new","newBecause":"checked against a fake of Drive's API, not yet against Google's","replaces":"google.golang.org/api/drive/v3","notWhen":"You manage Drive beyond files (sharing, permissions): use Google's client.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"Drive's shortcuts are not symlinks as vfstest means them.","source":"vfs.md","quote":"Drive's shortcuts are not symlinks as vfstest means them"},{"kind":"model","against":"a fake of Drive's API in vfs-tests","result":"Not tested against Google's Drive: no account given.","source":"vfs-source-remote-gdrive/readme","quote":"Tested against a fake of Drive's API in vfs-tests, not against Google's."},{"kind":"systems","against":"every OS in the VMs and the Mac","result":"","source":"vfs.md","quote":"Each step on every OS in the VMs and the Mac"}]},"vfs-source-remote-http":{"group":"vfs","line":"What web servers list: Apache's, nginx's, lighttpd's and Caddy's indexes, read-only.","tags":[],"keywords":"http index","summary":"A vfs source of what web servers list, read-only: Apache's, nginx's, lighttpd's and Caddy's index pages, and nginx's and Caddy's JSON listings. Package `web`.","snippet":"site, err := web.Open(ctx, \"https://mirror.example.org/debian/dists/\")\n_, err = operations.Mirror(ctx, site, \"trixie\", disk, \"trixie\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"wget -r, or index pages parsed by hand","notWhen":"The server speaks WebDAV or S3: use that source; this one reads listings made for people.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"real servers","against":"Apache, nginx, lighttpd and Caddy in the Debian VM","result":"","source":"vfs.md","quote":"the remote ones against Apache, nginx, lighttpd, Caddy, vsftpd, pyftpdlib and MinIO in the Debian VM"},{"kind":"reference","against":"listings as Apache, nginx, lighttpd and Caddy wrote them, replayed","result":"","source":"vfs.md","quote":"web listings as Apache, nginx, lighttpd and Caddy wrote them, replayed"},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"real servers","against":"lighttpd","result":"lighttpd serves a file for a name under it; here such a name is no name.","source":"vfs.md","quote":"A name under a file is no name, though lighttpd serves the file for it."},{"kind":"other libraries","against":"rclone's default of a HEAD for each file listed","result":"","source":"vfs-source-remote-http/readme","quote":"each file in it has a HEAD too, for its exact size, time and ETag (`Version`), as rclone does by default"}]},"vfs-device-partitions":{"group":"vfs","line":"A disk's partition table, MBR or GPT.","tags":[],"keywords":"partition mbr gpt disk","summary":"A disk's partition table, MBR or GPT: each partition a `vfs.Device`, which a file system format reads.","snippet":"file, _ := disk.Open(ctx, \"vm.img\") // or an image's device: qcow2, vhd\ntable, err := partitions.Read(vfs.DeviceOf(file))\nfor _, partition := range table.Partitions {\n\tfmt.Println(partition.Number, partition.Kind, partition.Name, partition.Length)\n}\nwindows, err := ntfs.New(table.Partitions[2], \"C:\")","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"fdisk -l and sgdisk run as programs; diskfs/go-diskfs, for reading","notWhen":"You write or change a partition table: this reads them only.","measures":[],"checked":[{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-device-vhd":{"group":"vfs","line":"Microsoft's VHD and VHDX virtual disks.","tags":[],"keywords":"vhd vhdx hyper-v disk image","summary":"Microsoft's virtual disks, VHD and VHDX, as the disk they hold: a `vfs.Device`, which partitions and file system formats then read. Hyper-V's, Windows' own (Disk Management, diskpart, backups), Disk2vhd's, Azure's.","snippet":"disk, err := vhd.Open(ctx, share, \"Hyper-V/server.vhdx\") // parents beside it\ndefer disk.Close()\ntable, _ := partitions.Read(disk)\nwindows, err := ntfs.New(table.Partitions[len(table.Partitions)-1], \"server.vhdx!/C:\")","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"qemu-nbd and mount, which take root, or a Windows machine to attach it","notWhen":"A VHDX detached uncleanly, its log not replayed: ErrUnsupported; attach it once to Windows or qemu.","measures":[],"checked":[{"kind":"reference","against":"disks made by qemu-img and qemu-io","result":"","source":"vfs.md","quote":"qemu-img and qemu-io for qcow2, VHD, VHDX and VMDK"},{"kind":"reference","against":"differencing VHD and VHDX made by Windows Server's diskpart","result":"","source":"vfs.md","quote":"differencing VHD and VHDX by diskpart, written raw"},{"kind":"tests","against":"one disk in every image format","result":"Checked byte for byte.","source":"vfs.md","quote":"every image format one disk, checked byte for byte"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-device-vmdk":{"group":"vfs","line":"VMware's virtual disks.","tags":[],"keywords":"vmdk vmware disk image","summary":"VMware's virtual disks as the disk they hold: a `vfs.Device`, which partitions and file system formats then read. Workstation's, Fusion's, ESXi's, OVA and OVF exports'.","snippet":"disk, err := vmdk.Open(ctx, datastore, \"vm/vm.vmdk\") // its extents beside it\ndefer disk.Close()\ntable, _ := partitions.Read(disk)\nroot, err := ext4.New(table.Partitions[0], \"vm.vmdk!/1\")","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"qemu-nbd and mount, or vmware-mount","notWhen":"ESXi's sparse formats (COWD, SESPARSE) and raw device mappings: ErrUnsupported.","measures":[],"checked":[{"kind":"reference","against":"disks made by qemu-img and qemu-io","result":"","source":"vfs.md","quote":"qemu-img and qemu-io for qcow2, VHD, VHDX and VMDK"},{"kind":"tests","against":"one disk in every image format","result":"Checked byte for byte.","source":"vfs.md","quote":"every image format one disk, checked byte for byte"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-device-qcow2":{"group":"vfs","line":"QEMU's qcow2 images.","tags":[],"keywords":"qcow2 qemu disk image","summary":"QEMU's qcow2 disk images as the disk they hold: a `vfs.Device`, which partitions and file system formats then read.","snippet":"image, err := qcow2.Open(ctx, server, \"vms/debian.qcow2\") // backing files beside it\ndefer image.Close()\ntable, _ := partitions.Read(image)\nroot, err := ext4.New(table.Partitions[0], \"debian.qcow2!/1\")","uses":["vfs"],"usedBy":[],"thirdParty":["github.com/klauspost/compress"],"flows":[],"maturity":"verified","newBecause":"","replaces":"qemu-nbd and mount, or qemu-img convert to raw first","notWhen":"Encrypted images (AES, LUKS), external data files, subclusters: ErrUnsupported. Snapshots are not read.","measures":[],"checked":[{"kind":"reference","against":"images made by qemu-img and qemu-io","result":"","source":"vfs.md","quote":"qemu-img and qemu-io for qcow2, VHD, VHDX and VMDK"},{"kind":"tests","against":"one disk in every image format","result":"Checked byte for byte.","source":"vfs.md","quote":"every image format one disk, checked byte for byte"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-filesystem-ntfs":{"group":"vfs","line":"NTFS, read-only.","tags":[],"keywords":"ntfs windows disk","summary":"A vfs source of NTFS file systems, read-only: Windows disks and partitions, from a `vfs.Device` (`New`), or an image file in any FS that reads at offsets (`Open`).","snippet":"table, _ := partitions.Read(disk)\nwindows, err := ntfs.New(table.Partitions[2], \"C:\")\n_, err = operations.Copy(ctx, windows, \"Users/me/Documents\", backup, \"documents\").Wait()","uses":["codecs-compress-lznt1","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"ntfs-3g and a mount","notWhen":"Alternate data streams, owners, EFS-encrypted and WOF-compressed files: not read.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"file systems made by mkntfs and ntfs-3g","result":"","source":"vfs.md","quote":"mkfs.fat and mtools, mkfs.exfat, mke2fs, mkntfs and ntfs-3g, mkudffs"},{"kind":"reference","against":"NTFS made by Windows Server, with its symlinks, junctions, LZNT1 and WOF","result":"","source":"vfs.md","quote":"Windows Server makes what only it makes (vfs-tests/fstree/windows.ps1): NTFS with its symlinks, junctions, LZNT1 and WOF"},{"kind":"tests","against":"one shared tree of files (vfs-tests/fstree)","result":"Every file system format reads it, checked whole.","source":"vfs.md","quote":"Every file system format reads one tree (vfs-tests/fstree), checked whole"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"Found a bug in NTFS's attribute offsets, fixed.","source":"vfs.md","quote":"Found and fixed: sizes past 2^63 read as negative (ext4, UDF; guarded in every reader since), NTFS's attribute offsets."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-filesystem-ext4":{"group":"vfs","line":"ext2, ext3 and ext4, read-only.","tags":[],"keywords":"ext4 linux disk","summary":"A vfs source of ext2, ext3 and ext4 file systems, read-only: Linux disks and partitions, from a `vfs.Device` (`New`), or an image file in any FS that reads at offsets (`Open`).","snippet":"table, _ := partitions.Read(disk)\nroot, err := ext4.New(table.Partitions[1], \"root\")\ndata, err := vfs.ReadFile(ctx, root, \"etc/fstab\")","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"debugfs, or a loop mount","notWhen":"Encrypted files: ErrUnsupported; meta_bg's layout is not read.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"file systems made by mke2fs","result":"","source":"vfs.md","quote":"mkfs.fat and mtools, mkfs.exfat, mke2fs, mkntfs and ntfs-3g, mkudffs"},{"kind":"tests","against":"one shared tree of files (vfs-tests/fstree)","result":"Every file system format reads it, checked whole.","source":"vfs.md","quote":"Every file system format reads one tree (vfs-tests/fstree), checked whole"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"Found sizes past 2^63 read as negative, fixed and guarded in every reader since.","source":"vfs.md","quote":"Found and fixed: sizes past 2^63 read as negative (ext4, UDF; guarded in every reader since), NTFS's attribute offsets."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-filesystem-fat":{"group":"vfs","line":"FAT12, FAT16 and FAT32 with long names, read-only.","tags":[],"keywords":"fat vfat usb sd card","summary":"A vfs source of FAT file systems: FAT12, FAT16 and FAT32 with long names (VFAT), read-only. USB sticks, SD cards, EFI system partitions, floppy images; from a `vfs.Device` (`New`: a partition, an image's device) or an image file in any FS that reads at offsets (`Open`).","snippet":"table, _ := partitions.Read(disk)\nefi, err := fat.New(table.Partitions[0], \"EFI\")\ndata, err := vfs.ReadFile(ctx, efi, \"EFI/BOOT/BOOTX64.EFI\")","uses":["codecs-text-charsets","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"mtools, or a loop mount","notWhen":"You write to the card or the stick: read-only.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"file systems made by mkfs.fat and mtools","result":"","source":"vfs.md","quote":"mkfs.fat and mtools, mkfs.exfat, mke2fs, mkntfs and ntfs-3g, mkudffs"},{"kind":"reference","against":"FAT32 made by Windows Server's formatter","result":"","source":"vfs.md","quote":"Windows Server makes what only it makes (vfs-tests/fstree/windows.ps1): NTFS with its symlinks, junctions, LZNT1 and WOF; UDF 2.50 and 2.01; exFAT and FAT32 by its formatter"},{"kind":"tests","against":"one shared tree of files (vfs-tests/fstree)","result":"Every file system format reads it, checked whole.","source":"vfs.md","quote":"Every file system format reads one tree (vfs-tests/fstree), checked whole"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-filesystem-exfat":{"group":"vfs","line":"exFAT: SD cards, large USB sticks, cameras; read-only.","tags":[],"keywords":"exfat sd card usb","summary":"A vfs source of exFAT file systems, read-only: SDXC cards, large USB sticks, cameras' storage; from a `vfs.Device` (`New`), or an image file in any FS that reads at offsets (`Open`).","snippet":"card, err := exfat.New(table.Partitions[0], \"SD\")\n_, err = operations.Copy(ctx, card, \"DCIM\", disk, \"photos\").Wait()","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"a loop mount with exfat-fuse","notWhen":"You write to the card: read-only.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"file systems made by mkfs.exfat","result":"","source":"vfs.md","quote":"mkfs.fat and mtools, mkfs.exfat, mke2fs, mkntfs and ntfs-3g, mkudffs"},{"kind":"reference","against":"exFAT made by Windows Server's formatter","result":"","source":"vfs.md","quote":"Windows Server makes what only it makes (vfs-tests/fstree/windows.ps1): NTFS with its symlinks, junctions, LZNT1 and WOF; UDF 2.50 and 2.01; exFAT and FAT32 by its formatter"},{"kind":"tests","against":"one shared tree of files (vfs-tests/fstree)","result":"Every file system format reads it, checked whole.","source":"vfs.md","quote":"Every file system format reads one tree (vfs-tests/fstree), checked whole"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"","source":"vfs.md","quote":"Every parser is fuzzed, seeded with small images made for it."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"vfs-source-filesystem-iso9660":{"group":"vfs","line":"ISO 9660 images, read-only.","tags":[],"keywords":"iso cd image","summary":"A vfs source of ISO 9660 images, read-only: from a file in any vfs FS that reads at offsets, or from any `vfs.Device`.","snippet":"image, err := iso9660.Open(ctx, fsys, \"debian.iso\")\n_, err = operations.Copy(ctx, image, \".\", usb, \".\").Wait()\n\nseed, err := iso9660.Open(ctx, fsys, \"seed.iso\")\nif seed.Label() == \"cidata\" { userData, err := vfs.ReadFile(ctx, seed, \"user-data\") }","uses":["vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"kdomanski/iso9660, 7z x, or a loop mount","notWhen":"zisofs-compressed files: ErrUnsupported.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"images made by xorriso","result":"","source":"vfs.md","quote":"Fixtures from real tools: zip and 7z, GNU tar and bsdtar, xorriso"},{"kind":"fuzzed","against":"corrupt images","result":"A corrupt image is an error, never a panic or a hang.","source":"vfs.md","quote":"Fuzzed: a corrupt image is an error, never a panic or a hang."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-source-archive-zip, -tar, vfs-source-filesystem-iso9660, vfs-source-remote-http, -webdav, -s3, -ftp, passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"},{"kind":"other libraries","against":"7-Zip's way of showing El Torito boot images","result":"","source":"vfs-source-filesystem-iso9660/readme","quote":"`Boot` shows the El Torito boot images as files in `[BOOT]`, as 7-Zip does"}]},"vfs-source-filesystem-udf":{"group":"vfs","line":"UDF: DVDs, Blu-ray discs and their images; read-only.","tags":[],"keywords":"udf dvd bluray","summary":"A vfs source of UDF file systems, read-only: DVDs, Blu-ray discs, UDF hard disks and their images; from a `vfs.Device` (`New`), or an image file in any FS that reads at offsets (`Open`).","snippet":"disc, err := udf.Open(ctx, fsys, \"movie.iso\")\nentries, err := vfs.ReadDir(ctx, disc, \"BDMV\")","uses":["codecs-text-charsets","vfs"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"7z x, or a loop mount","notWhen":"CD-R's virtual partitions (VAT) and named streams: not read.","measures":[],"checked":[{"kind":"test suite","against":"vfstest, the family's conformance suite","result":"","source":"vfs-tests/readme","quote":"the conformance suite every source passes: `vfstest.Run` for a writable source, `vfstest.RunReadOnly` for a read-only one"},{"kind":"reference","against":"file systems made by mkudffs","result":"","source":"vfs.md","quote":"mkfs.fat and mtools, mkfs.exfat, mke2fs, mkntfs and ntfs-3g, mkudffs"},{"kind":"reference","against":"UDF 2.50 and 2.01 made by Windows Server","result":"","source":"vfs.md","quote":"Windows Server makes what only it makes (vfs-tests/fstree/windows.ps1): NTFS with its symlinks, junctions, LZNT1 and WOF; UDF 2.50 and 2.01"},{"kind":"tests","against":"one shared tree of files (vfs-tests/fstree)","result":"Every file system format reads it, checked whole.","source":"vfs.md","quote":"Every file system format reads one tree (vfs-tests/fstree), checked whole"},{"kind":"fuzzed","against":"fuzzing seeded with small images made for it","result":"Found sizes past 2^63 read as negative, fixed and guarded in every reader since.","source":"vfs.md","quote":"Found and fixed: sizes past 2^63 read as negative (ext4, UDF; guarded in every reader since), NTFS's attribute offsets."},{"kind":"systems","against":"Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS","result":"Passes on all six.","source":"vfs.md","quote":"vfs-device-partitions, -qcow2, -vhd, -vmdk; vfs-source-filesystem-fat, -exfat, -ext4, -ntfs, -udf; passing on Debian, FreeBSD, OpenBSD, NetBSD, Windows Server 2025 and macOS"}]},"codecs-compress-lzma":{"group":"codecs","line":"LZMA and LZMA2, as 7z, xz and zip store them.","tags":[],"keywords":"compression xz 7z","summary":"LZMA and LZMA2, Igor Pavlov's, decoded, in-house: a plain `io.Reader`, nothing of vfs. 7z keeps its streams so (vfs-source-archive-7z), xz and zip do too.","snippet":"reader, err := lzma.NewReader(file) // the .lzma format\nreader, err = lzma.NewRawReader(input, properties, size) // raw LZMA: 7z's, zip's\nreader, err = lzma.NewReader2(input, dictionary, size) // raw LZMA2: 7z's, xz's","uses":[],"usedBy":["vfs-source-archive-7z","vfs-source-archive-zip"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"ulikunitz/xz's lzma package","notWhen":"You compress, or read .xz files: neither is here yet (vfs-source-archive-tar reads .xz through ulikunitz/xz).","measures":[],"checked":[{"kind":"reference","against":"streams xz and 7-Zip wrote (testdata/make.sh)","result":"","source":"codecs-compress-lzma/readme","quote":"Checked against what xz and 7-Zip wrote (testdata/make.sh); fuzzed."},{"kind":"fuzzed","against":"the decoder, fuzzed","result":"","source":"codecs-compress-lzma/readme","quote":"Checked against what xz and 7-Zip wrote (testdata/make.sh); fuzzed."}]},"codecs-compress-ppmd":{"group":"codecs","line":"PPMd variant H, as 7z and RAR use it.","tags":[],"keywords":"compression","summary":"PPMd variant H, Dmitry Shkarin's, decoded, in-house, as 7z and RAR compress with it: one model, read through 7z's range decoder or RAR's. Nothing of vfs.","snippet":"reader, err := ppmd.NewReader7z(input, order, memory, size) // 7z's PPMd, an io.Reader\n\nmodel, err := ppmd.NewModel(16 << 20) // RAR's: blocks of a stream,\nerr = model.Init(6) // a model kept across them\ndecoder, err := ppmd.NewRAR(input, model)\nsymbol, err := decoder.Symbol() // a byte, or -1: an escape from the root","uses":[],"usedBy":["codecs-compress-rar","vfs-source-archive-7z"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"Variant I (PPMd8, zip's method 98), or compressing: not here.","measures":[],"checked":[{"kind":"reference","against":"7-Zip's PPMd streams (testdata/make.sh), a 1 MiB model among them that starts again many times","result":"","source":"codecs-compress-ppmd/readme","quote":"Checked against 7-Zip's streams (testdata/make.sh), a model of 1 MiB among them that starts again many times"},{"kind":"reference","against":"what 7-Zip encoded, with a model of 1 MiB that starts again 34 times","result":"decodes what 7-Zip encoded, its allocator emulated exactly","source":"vfs.md","quote":"PPMd variant H is one module, its allocator emulated exactly: a model of 1 MiB that starts again 34 times decodes what 7-Zip encoded."},{"kind":"reference","against":"RAR's PPMd streams, in codecs-compress-rar's checks","result":"","source":"codecs-compress-ppmd/readme","quote":"starts again many times; RAR's in codecs-compress-rar; fuzzed, there too."},{"kind":"fuzzed","against":"corrupt data, through codecs-compress-rar's fuzzing","result":"two crashes found and fixed: a range scaled to 0, a model used after an error","source":"todo.md","quote":"Fuzzing -rar found two crashes in -ppmd on corrupt data, fixed: a range scaled to 0, a model used after an error"}]},"codecs-compress-rar":{"group":"codecs","line":"RAR's compression: RAR 3 and 4 archives, and RAR 5.","tags":[],"keywords":"compression unrar","summary":"RAR's compression decoded, in-house: a plain `io.Reader` of a file's packed data, nothing of vfs. RAR 2.9's, of RAR 3 and 4 archives (LZ, PPMd blocks through codecs-compress-ppmd, and the standard filters its virtual machine runs, known by their code: E8, E8E9, Itanium, Delta, RGB, Audio), and RAR 5's (its LZ and its filters Delta, E8, E8E9, ARM; RAR 7's larger distances). The archive around them is vfs-source-archive-rar's.","snippet":"decoder, err := rar.NewDecoder(29, dictionary, total) // 29, RAR 2.9's; 50, RAR 5's; 70, RAR 7's\nerr = decoder.Start(packed, size, false) // a file: its packed data and size\ndata, err := io.ReadAll(decoder)\nerr = decoder.Start(next, nextSize, true) // solid: goes on from the file before\nerr = decoder.Finish() // the rest of a file, unread","uses":["codecs-compress-ppmd"],"usedBy":["vfs-source-archive-rar"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"nwaples/rardecode's decoders, or unrar run as a program","notWhen":"RAR 1.5's and 2.0's compression: errors.ErrUnsupported.","measures":[],"checked":[{"kind":"reference","against":"RAR 6.24's streams cut out of its archives (tools/rar-streams.py): LZ, best, solid, each filter, PPMd alone and solid","result":"","source":"codecs-compress-rar/readme","quote":"Checked against RAR 6.24's streams, cut out of its archives by tools/rar-streams.py (testdata/make.sh): LZ, best, solid, each filter, PPMd alone and solid; fuzzed."},{"kind":"fuzzed","against":"corrupt data","result":"two crashes found in codecs-compress-ppmd, fixed","source":"todo.md","quote":"Fuzzing -rar found two crashes in -ppmd on corrupt data, fixed: a range scaled to 0, a model used after an error"},{"kind":"reference","against":"RAR 7's own archives; a sample of RAR 5's ARM filter","result":"RAR 7's own archives untested, no RAR here writing them; RAR 5's ARM filter untested against a sample","source":"codecs-compress-rar/readme","quote":"RAR 7's own archives, which no RAR here writes, untested); RAR 5's ARM filter untested against a sample."}]},"codecs-compress-bcj":{"group":"codecs","line":"Undoes the filters 7z and xz apply to machine code before compressing.","tags":[],"keywords":"compression filter","summary":"Branch filters undone, in-house: plain `io.Reader`s, nothing of vfs. Machine code compresses better with its calls' and jumps' relative targets made absolute, so 7z and xz filter it before compressing; these undo it after.","snippet":"reader, err := bcj.NewReader(input, bcj.X86, 0) // x86 (BCJ), ARM, ARMThumb, ARM64, PowerPC, SPARC\nreader, err = bcj.NewBCJ2Reader(main, call, jump, rc, size) // BCJ2: four streams","uses":[],"usedBy":["vfs-source-archive-7z"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"IA64 and RISC-V code, or encoding: not yet.","measures":[],"checked":[{"kind":"reference","against":"what 7-Zip's branch filters wrote (testdata/make.sh)","result":"","source":"codecs-compress-bcj/readme","quote":"Checked against what 7-Zip's filters wrote (testdata/make.sh); fuzzed."},{"kind":"fuzzed","against":"the filters, fuzzed","result":"","source":"codecs-compress-bcj/readme","quote":"Checked against what 7-Zip's filters wrote (testdata/make.sh); fuzzed."}]},"codecs-compress-delta":{"group":"codecs","line":"Undoes the delta filter used for sound and pictures.","tags":[],"keywords":"compression filter","summary":"Delta undone, in-house: a plain `io.Reader`, nothing of vfs. Sounds and pictures compress better stored as each byte's difference from the one some bytes before (a sample's, a pixel's), so 7z and xz filter them before compressing; this undoes it after.","snippet":"reader, err := delta.NewReader(input, 4) // distance 1 to 256; 7z's property is the distance less one","uses":[],"usedBy":["vfs-source-archive-7z"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"Encoding: not yet.","measures":[],"checked":[{"kind":"reference","against":"what 7-Zip's Delta wrote at distances 1, 4 and 256 (testdata/make.sh)","result":"","source":"codecs-compress-delta/readme","quote":"Checked against what 7-Zip's Delta wrote at distances 1, 4 and 256 (testdata/make.sh)."}]},"codecs-compress-lznt1":{"group":"codecs","line":"Windows' NTFS compression.","tags":[],"keywords":"compression ntfs","summary":"LZNT1 decompressed, in-house: Windows' compression of NTFS's compressed files (and of RtlCompressBuffer's callers), a plain `io.Reader` or a buffer, nothing of vfs. Chunks of 4 KiB, each stored or compressed, its back references' split of offset and length growing with the position in the chunk.","snippet":"data, err := lznt1.Decompress(unit, 65536) // as RtlDecompressBuffer: up to a size\nreader := lznt1.NewReader(input) // a stream of chunks","uses":[],"usedBy":["vfs-source-filesystem-ntfs"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"RtlDecompressBuffer, which only Windows has","notWhen":"Xpress and LZX, WOF's compact files, or compressing: not yet.","measures":[],"checked":[{"kind":"reference","against":"what Windows' own RtlCompressBuffer made and RtlDecompressBuffer read, both its engines (testdata/make.ps1)","result":"","source":"codecs-compress-lznt1/readme","quote":"Checked against what Windows' own RtlCompressBuffer made and RtlDecompressBuffer read (testdata/make.ps1), both its engines; fuzzed."},{"kind":"fuzzed","against":"the decompressor, fuzzed","result":"","source":"codecs-compress-lznt1/readme","quote":"RtlDecompressBuffer read (testdata/make.ps1), both its engines; fuzzed."}]},"codecs-data-protobuf":{"group":"codecs","line":"Protocol Buffers' wire format, read and written without generated code.","tags":[],"keywords":"protobuf proto grpc","summary":"Protocol Buffers' wire format, written and read without code generation or a schema: what an exporter needs to send OTLP, without google.golang.org/protobuf or gRPC.","snippet":"import protobuf \"goframework.io/codecs-data-protobuf\"\n\nb = protobuf.AppendString(b, 1, \"service.name\")\nb = protobuf.AppendMessage(b, 2, func(b []byte) []byte { // the length goes before it once known\n\tb = protobuf.AppendUint(b, 1, 42)\n\treturn protobuf.AppendDouble(b, 2, 0.125)\n})\n\nfor r := protobuf.NewReader(b); r.Next(); {\n\tswitch r.Field() {\n\tcase 1:\n\t\tname := string(r.Bytes())\n\tcase 2:\n\t\tinner := protobuf.NewReader(r.Bytes()) // a nested message\n\tdefault:\n\t\tr.Skip()\n\t}\n}","uses":[],"usedBy":["metrics-exporters-otlp"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"google.golang.org/protobuf and protoc's generated code, for messages written and read by hand","notWhen":"Many messages from .proto files that change: generated code keeps them in step and checks their types.","measures":[],"checked":[{"kind":"test suite","against":"the Protocol Buffers encoding guide's examples","result":"","source":"codecs-data-protobuf/readme","quote":"Checked: the encoding guide's examples; random fields written and read back; against Google's protowire (benchmarks/protobuf)"},{"kind":"reference","against":"Google's protowire, 20,000 random messages (benchmarks/protobuf)","result":"the same bytes; ours reads every field protowire's parser finds","source":"codecs-data-protobuf/readme","quote":"against Google's protowire (benchmarks/protobuf): 20,000 random messages encode to the same bytes, ours reads every field protowire's parser finds"},{"kind":"fuzzed","against":"fuzzed input, read by both this reader and protowire's","result":"both agree whether it parses","source":"codecs-data-protobuf/readme","quote":"and on fuzzed input both agree whether it parses; the reader fuzzed on its own."},{"kind":"tests","against":"random fields written and read back","result":"","source":"codecs-data-protobuf/readme","quote":"random fields written and read back"}]},"codecs-text-charsets":{"group":"codecs","line":"Old 8-bit code pages to and from Go strings.","tags":[],"keywords":"encoding charset codepage cp437","summary":"The old 8-bit code pages, decoded into Go's strings and encoded back, in-house: nothing of vfs. Bytes below 0x80 are ASCII; each above is one character of the page. And OSTA's compressed Unicode, CS0.","snippet":"name := charsets.CP437.Decode(raw) // FAT's short names, old archives' names\nraw, whole := charsets.CP437.Encode(name) // whole false: a character the page has not, written \"?\"\nname = charsets.CS0.Decode(raw) // UDF's names and labels","uses":[],"usedBy":["vfs-source-archive-rar","vfs-source-filesystem-fat","vfs-source-filesystem-udf"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/text's charmap, for CP437","notWhen":"Code pages other than 437, and Japanese, Chinese or Korean ones: not yet.","measures":[],"checked":[{"kind":"reference","against":"Python's codecs, for CP437 (testdata/make.sh)","result":"","source":"codecs-text-charsets/readme","quote":"Checked against Python's codecs (testdata/make.sh)."},{"kind":"reference","against":"the labels mkudffs wrote, for CS0 in 8 and 16 bits (testdata/make.sh)","result":"","source":"codecs-text-charsets/readme","quote":"Checked against the labels mkudffs wrote, in both (testdata/make.sh)."}]},"cryptography-aead-aes-ccm":{"group":"cryptography","line":"AES-CCM, authenticated encryption, as SMB 3 uses it.","tags":[],"keywords":"aead aes ccm","summary":"AES-CCM (NIST SP 800-38C). An AEAD, authenticated encryption: it hides data and detects any change to it at once, and also covers \"additional\" data it leaves readable (a header). Counter mode to encrypt, a CBC-MAC for the tag. A nonce must never be used twice with one key. SMB 3.0 encrypts with it (vfs-source-remote-smb). Go's standard library has GCM, not CCM; this is a `cipher.AEAD` like its GCM.","snippet":"aead, err := ccm.New(key, 11, 16) // nonces of 7 to 13 bytes, tags of 4 to 16\nsealed := aead.Seal(nil, nonce, plain, header)\nplain, err = aead.Open(nil, nonce, sealed, header) // ccm.ErrOpen: altered, or the wrong key","uses":[],"usedBy":["vfs-source-remote-smb"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You choose the AEAD yourself: take the standard library's AES-GCM.","measures":[],"checked":[{"kind":"test suite","against":"NIST SP 800-38C's examples","result":"","source":"cryptography-aead-aes-ccm/readme","quote":"Checked against SP 800-38C's examples."},{"kind":"test suite","against":"published vectors: AES-CCM (SP 800-38C)","result":"","source":"vfs.md","quote":"Each protocol's crypto is checked against published vectors: MD4 (RFC 1320), AES-CMAC (RFC 4493), AES-CCM (SP 800-38C)"}]},"cryptography-mac-aes-cmac":{"group":"cryptography","line":"AES-CMAC, a MAC, as SMB 3 signs with it.","tags":[],"keywords":"mac cmac aes","summary":"AES-CMAC (RFC 4493, NIST SP 800-38B). A MAC: a fingerprint of data only a holder of the key can make, so it proves who wrote the data and that it is unchanged. It hides nothing: the data stays readable. SMB 3 signs its messages with it (vfs-source-remote-smb). Go's standard library has HMAC, not CMAC.","snippet":"code, err := cmac.Sum(key, message) // key: 16, 24 or 32 bytes","uses":[],"usedBy":["vfs-source-remote-smb"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You choose the MAC yourself: the standard library's HMAC-SHA256 serves.","measures":[],"checked":[{"kind":"test suite","against":"RFC 4493's examples","result":"","source":"cryptography-mac-aes-cmac/readme","quote":"Checked against RFC 4493's examples."},{"kind":"test suite","against":"published vectors: AES-CMAC (RFC 4493)","result":"","source":"vfs.md","quote":"Each protocol's crypto is checked against published vectors: MD4 (RFC 1320), AES-CMAC (RFC 4493), AES-CCM (SP 800-38C)"}]},"cryptography-cipher-aes-cbc":{"group":"cryptography","line":"AES-CBC over a stream, as 7z and RAR encrypt.","tags":[],"keywords":"aes cbc cipher","summary":"AES in CBC mode, as a stream. A cipher: it hides data under a key, and detects no change to it (an AEAD does). Go's standard library has AES and CBC block by block; this decrypts what an `io.Reader` gives, whole blocks as they come, as 7z and RAR encrypt (vfs-source-archive-7z, -rar).","snippet":"reader, err := aescbc.NewReader(input, key, iv) // bytes past the last whole block dropped\nerr = aescbc.Decrypt(data, key, iv) // whole blocks, in place","uses":[],"usedBy":["vfs-source-archive-7z","vfs-source-archive-rar"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You encrypt new data: CBC detects no change to it; take an AEAD, such as the standard library's AES-GCM.","measures":[],"checked":[{"kind":"reference","against":"OpenSSL","result":"","source":"cryptography-cipher-aes-cbc/readme","quote":"whole blocks, in place ``` Checked against OpenSSL."}]},"cryptography-kdf-sp800-108":{"group":"cryptography","line":"NIST SP 800-108 key derivation, as SMB 3 uses it.","tags":[],"keywords":"kdf hmac","summary":"NIST SP 800-108's key derivation in counter mode, with HMAC. A KDF: it turns one key into others, each for its own purpose, named by a label and a context, so that no key serves two. (A KDF from a password is made slow on purpose; this one starts from a key already strong, so it is not.) SMB 3 derives its signing and encryption keys with it, HMAC-SHA256 (vfs-source-remote-smb).","snippet":"key := sp800108.Counter(sha256.New, sessionKey, []byte(\"SMBSigningKey\\x00\"), preauthHash, 16)","uses":[],"usedBy":["vfs-source-remote-smb"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You derive keys of your own design: the standard library's crypto/hkdf serves.","measures":[],"checked":[{"kind":"reference","against":"OpenSSL 3's KBKDF","result":"","source":"cryptography-kdf-sp800-108/readme","quote":"Checked against OpenSSL 3's KBKDF."}]},"cryptography-kdf-7z":{"group":"cryptography","line":"7-Zip's key from a password.","tags":[],"keywords":"kdf password 7zip","summary":"7-Zip's key derivation. A KDF from a password: it turns a password into a key, slowly on purpose, so that guessing passwords is slow too. SHA-256 over the salt, the password in UTF-16 and a counter, 2^cycles times (2^19 by default): the key of 7z's AES-256 (vfs-source-archive-7z).","snippet":"key := kdf7z.Key(password, salt, cycles) // salt and cycles from 7z's AES properties","uses":[],"usedBy":["vfs-source-archive-7z"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You turn passwords into keys of your own: take the standard library's crypto/pbkdf2.","measures":[],"checked":[{"kind":"reference","against":"a file 7-Zip encrypted","result":"","source":"cryptography-kdf-7z/readme","quote":"Checked against a file 7-Zip encrypted."}]},"cryptography-kdf-rar":{"group":"cryptography","line":"RAR's keys from a password.","tags":[],"keywords":"kdf password","summary":"RAR's key derivations. KDFs from a password: they turn a password into a key, slowly on purpose, so that guessing passwords is slow too. RAR 3's (RAR 4 archives): SHA-1 over the password in UTF-16, the salt and a counter, 2^18 times, the IV taken from digests along the way, for AES-128; its SHA-1 writes into the input as it hashes, which passwords past 28 characters meet, and which this keeps. RAR 5's: PBKDF2 with HMAC-SHA256, 2^count times, then 16 more for the key of its checksums' MACs and 16 more for its password check, for AES-256 (vfs-source-archive-rar).","snippet":"key, iv := kdfrar.Key3(password, salt)\nkey, hashKey, check := kdfrar.Key5(password, salt, count)","uses":[],"usedBy":["vfs-source-archive-rar"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"You turn passwords into keys of your own: take the standard library's crypto/pbkdf2.","measures":[],"checked":[{"kind":"reference","against":"archives RAR 6.24 encrypted","result":"","source":"cryptography-kdf-rar/readme","quote":"Checked against archives RAR 6.24 encrypted."}]},"cryptography-cipher-zipcrypto":{"group":"cryptography","line":"ZipCrypto, to read old zips.","tags":["weak"],"keywords":"zip password","summary":"PKWARE's traditional zip encryption, ZipCrypto. A cipher: it hides data under a password, and detects no change to it. It is weak: a few known bytes of the data break it in minutes. Read old zips with it (vfs-source-archive-zip); write new ones with AES.","snippet":"cipher := zipcrypto.New([]byte(password))\nreader := zipcrypto.NewReader(raw, cipher) // or cipher.Decrypt(data), cipher.Encrypt(data)","uses":[],"usedBy":["vfs-source-archive-zip"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"Writing new zips: ZipCrypto is weak; encrypt with AES.","measures":[],"checked":[{"kind":"reference","against":"what Info-ZIP's zip encrypted","result":"","source":"cryptography-cipher-zipcrypto/readme","quote":"Checked against what Info-ZIP's zip encrypted."}]},"cryptography-hash-md4":{"group":"cryptography","line":"MD4, for NTLM only.","tags":["broken"],"keywords":"hash ntlm","summary":"MD4 (RFC 1320). A hash: a fingerprint of data, of a fixed size, made with no key; the data can't be had back from it. Two messages of one fingerprint should be impossible to make. MD4 is broken: they are easy. Use it only to speak a protocol that needs it: NTLM hashes passwords with it (vfs-source-remote-smb). For fingerprints, take SHA-256 from Go's standard library, which has no MD4.","snippet":"sum := md4.Sum([]byte(\"message\"))","uses":[],"usedBy":["vfs-source-remote-smb"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"golang.org/x/crypto/md4, which Go marks deprecated","notWhen":"Anything but NTLM: MD4 is broken. For fingerprints, take SHA-256 from the standard library.","measures":[],"checked":[{"kind":"test suite","against":"RFC 1320's test suite","result":"","source":"cryptography-hash-md4/readme","quote":"Checked against RFC 1320's test suite."},{"kind":"test suite","against":"RFC 1320's 7 vectors","result":"the test has 6 of the 7","source":"todo.md","quote":"cryptography-hash-md4's test has 6 of RFC 1320's 7 vectors"}]},"flow":{"group":"flow","line":"Structured concurrency: steps and parallel blocks, nothing left running behind your back.","tags":[],"keywords":"errgroup goroutine waitgroup parallel","summary":"Structured concurrency: blocks of code run one after another or at the same time, and the call that runs them returns only when every one of them has ended. The first error cancels the rest, every error comes back, a panic in a block becomes that block's error. Nothing is left running behind your back.","snippet":"err := flow.Step(prepare). // A\n\tParallel(copyA, copyB).Limit(8).Timeout(5 * time.Second). // then B and C at once\n\tStep(report). // then D: a block, or another plan\n\tFinally(func(err error) { cleanup() }). // always, with the plan's error\n\tRun(ctx) // runs it all, and waits","uses":["crashproof"],"usedBy":["events"],"thirdParty":[],"flows":["flow"],"maturity":"verified","newBecause":"","replaces":"errgroup, sourcegraph/conc, WaitGroup and recover written by hand","notWhen":"One goroutine and a channel say it all.","measures":[{"title":"In short, from its readme","against":"WaitGroup.Go, errgroup, sourcegraph/conc, alitto/pond","legend":"","reproduce":"","source":"flow/readme","ours":["flow"],"names":{},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["time · bytes a block"],"metric":0,"default":[],"records":[{"path":["all at once"],"name":"flow","cpu":"","values":[312.0],"shown":["312 ns, 51 B"]},{"path":["all at once"],"name":"WaitGroup.Go","cpu":"","values":[255.0],"shown":["255 ns, 25 B"]},{"path":["all at once"],"name":"errgroup","cpu":"","values":[263.0],"shown":["263 ns, 26 B"]},{"path":["all at once"],"name":"sourcegraph/conc","cpu":"","values":[273.0],"shown":["273 ns, 24 B"]},{"path":["all at once"],"name":"alitto/pond","cpu":"","values":[631.0],"shown":["631 ns, 164 B"]},{"path":["at most 8 at once"],"name":"flow","cpu":"","values":[513.0],"shown":["513 ns, 53 B (ForEach 534 ns)"]},{"path":["at most 8 at once"],"name":"WaitGroup.Go","cpu":"","values":[null],"shown":["–"]},{"path":["at most 8 at once"],"name":"errgroup","cpu":"","values":[409.0],"shown":["409 ns, 27 B"]},{"path":["at most 8 at once"],"name":"sourcegraph/conc","cpu":"","values":[425.0],"shown":["425 ns, 56 B"]},{"path":["at most 8 at once"],"name":"alitto/pond","cpu":"","values":[390.0],"shown":["390 ns, 152 B"]}],"header":["Per block","flow","WaitGroup.Go","errgroup","sourcegraph/conc","alitto/pond"],"rows":[["all at once","312 ns, 51 B","255 ns, 25 B","263 ns, 26 B","273 ns, 24 B","631 ns, 164 B"],["at most 8 at once","513 ns, 53 B (ForEach 534 ns)","–","409 ns, 27 B","425 ns, 56 B","390 ns, 152 B"]]},{"title":"Running blocks","against":"golang.org/x/sync/errgroup, sourcegraph/conc, alitto/pond, sync.WaitGroup.Go","legend":"Tiny blocks, so this is the machinery's own cost: about 20 to 25% over errgroup a block (flow.md). For blocks that do I/O it does not show.","reproduce":"python3 tools/benchmarks.py flow --rounds 8","source":"benchmarks/flow/results.md","ours":["flow.Scope","flow.ForEach"],"names":{},"labels":{"limit-8":"at most 8 at once","unlimited":"all at once"},"kind":"explorer","measured":"flow: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 8 interleaved rounds of 200ms; load average during the runs 6.9 to 7.2. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Blocks","limit-8"],"records":[{"path":["Blocks","unlimited"],"name":"WaitGroup.Go","cpu":"","values":[25466.0,2462.0,101.0]},{"path":["Blocks","unlimited"],"name":"errgroup","cpu":"","values":[26331.0,2560.0,103.0]},{"path":["Blocks","unlimited"],"name":"conc","cpu":"","values":[27272.0,2426.0,101.0]},{"path":["Blocks","unlimited"],"name":"pond","cpu":"","values":[63141.5,16370.0,417.0]},{"path":["Blocks","unlimited"],"name":"flow.Scope","cpu":"","values":[31208.5,5072.0,104.0]},{"path":["Blocks","limit-8"],"name":"errgroup","cpu":"","values":[40866.0,2672.0,104.0]},{"path":["Blocks","limit-8"],"name":"conc","cpu":"","values":[42546.5,5572.0,221.0]},{"path":["Blocks","limit-8"],"name":"pond","cpu":"","values":[39029.0,15151.0,329.0]},{"path":["Blocks","limit-8"],"name":"flow.Scope","cpu":"","values":[51291.0,5296.0,106.0]},{"path":["Blocks","limit-8"],"name":"flow.ForEach","cpu":"","values":[53434.5,8568.0,208.0]}]}],"checked":[{"kind":"other libraries","against":"WaitGroup.Go, errgroup, sourcegraph/conc and alitto/pond, 100 blocks a run (benchmarks/flow)","result":"about a fifth more than WaitGroup.Go and errgroup: 312 ns a block against 255 and 263; 513 ns with at most 8 at once, against 409 for errgroup","source":"flow/readme","quote":"flow costs about a fifth more than `WaitGroup.Go` and errgroup, for what they leave to the caller"},{"kind":"other libraries","against":"alitto/pond, with a limit (benchmarks/flow)","result":"pond is the quickest with a limit, at six times the memory","source":"flow/readme","quote":"pond is the quickest with a limit, from workers kept alive, at six times the memory."},{"kind":"tests","against":"goroutines counted and Go's leak profile after every test; Limit; cancellation causes","result":"Limit never exceeded","source":"flow/readme","quote":"Checked: goroutines counted and Go's leak profile empty after every test; `Limit` never exceeded; cancellation causes"},{"kind":"tests","against":"panics with a handler and, in a child process, without one; timeouts under testing/synctest; -race","result":"","source":"flow/readme","quote":"panics with a handler and, in a child process, without one; timeouts under `testing/synctest`; `-race`."},{"kind":"tests","against":"the readme's examples, compiled against flow and errgroup","result":"every example compiles","source":"flow.md","quote":"Every example compiles (checked against flow and errgroup)."}]},"events":{"group":"events","line":"A typed in-process event bus, with ordered, bounded delivery.","tags":[],"keywords":"pubsub publish subscribe bus","summary":"An in-process event bus: parts of one program telling each other that something happened, typed, with delivery that stays ordered and bounded when a subscriber is slow or panics.","snippet":"import \"goframework.io/events\"\n\ntype Order struct{ ID string }\n\nvar OrderPlaced = events.NewTopic[Order](\"orders.placed\")\n\nbus := events.New()\ndefer bus.Close(ctx)\n\nsub := OrderPlaced.Subscribe(bus, func(ctx context.Context, o Order) error {\n\treturn notify(ctx, o)\n}, events.Async(1024, events.DropOldest), events.Workers(1))\ndefer sub.Close()\n\nerr := OrderPlaced.Publish(ctx, bus, Order{ID: \"42\"})","uses":["callstack","crashproof","dsa-collections-queue-concurrent","flow"],"usedBy":[],"thirdParty":[],"flows":["events"],"maturity":"verified","newBecause":"","replaces":"EventBus, kelindar/event, cskr/pubsub, a channel per subscriber","notWhen":"Events must cross processes or survive a restart: use a broker.","measures":[{"title":"In short, from its readme","against":"plain channels, kelindar/event, cskr/pubsub, asaskevich/EventBus, watermill's GoChannel","legend":"kelindar/event publishes fastest because its subscribers wake every 500 µs, not per event: a millisecond from publish to handler. The last row counts allocations.","reproduce":"","source":"events/readme","ours":["this"],"names":{"this":"events","kelindar":"kelindar/event","cskr":"cskr/pubsub","EventBus":"asaskevich/EventBus","watermill":"watermill GoChannel"},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["ns"],"metric":0,"default":[],"records":[{"path":["a publish, 1 subscriber"],"name":"this","cpu":"","values":[59.0],"shown":["59 ns"]},{"path":["a publish, 1 subscriber"],"name":"channels","cpu":"","values":[66.0],"shown":["66"]},{"path":["a publish, 1 subscriber"],"name":"kelindar","cpu":"","values":[22.0],"shown":["22"]},{"path":["a publish, 1 subscriber"],"name":"cskr","cpu":"","values":[301.0],"shown":["301"]},{"path":["a publish, 1 subscriber"],"name":"EventBus","cpu":"","values":[436.0],"shown":["436"]},{"path":["a publish, 1 subscriber"],"name":"watermill","cpu":"","values":[2774.0],"shown":["2774"]},{"path":["1 publisher, 10 subscribers"],"name":"this","cpu":"","values":[450.0],"shown":["450"]},{"path":["1 publisher, 10 subscribers"],"name":"channels","cpu":"","values":[473.0],"shown":["473"]},{"path":["1 publisher, 10 subscribers"],"name":"kelindar","cpu":"","values":[45.0],"shown":["45"]},{"path":["1 publisher, 10 subscribers"],"name":"cskr","cpu":"","values":[953.0],"shown":["953"]},{"path":["1 publisher, 10 subscribers"],"name":"EventBus","cpu":"","values":[3913.0],"shown":["3913"]},{"path":["1 publisher, 10 subscribers"],"name":"watermill","cpu":"","values":[11858.0],"shown":["11858"]},{"path":["10 publishers, 10 subscribers"],"name":"this","cpu":"","values":[591.0],"shown":["591"]},{"path":["10 publishers, 10 subscribers"],"name":"channels","cpu":"","values":[759.0],"shown":["759"]},{"path":["10 publishers, 10 subscribers"],"name":"kelindar","cpu":"","values":[120.0],"shown":["120"]},{"path":["10 publishers, 10 subscribers"],"name":"cskr","cpu":"","values":[1094.0],"shown":["1094"]},{"path":["10 publishers, 10 subscribers"],"name":"EventBus","cpu":"","values":[3965.0],"shown":["3965"]},{"path":["10 publishers, 10 subscribers"],"name":"watermill","cpu":"","values":[12625.0],"shown":["12625"]},{"path":["1 publisher, 100 subscribers"],"name":"this","cpu":"","values":[7188.0],"shown":["7188"]},{"path":["1 publisher, 100 subscribers"],"name":"channels","cpu":"","values":[7398.0],"shown":["7398"]},{"path":["1 publisher, 100 subscribers"],"name":"kelindar","cpu":"","values":[685.0],"shown":["685"]},{"path":["1 publisher, 100 subscribers"],"name":"cskr","cpu":"","values":[15351.0],"shown":["15351"]},{"path":["1 publisher, 100 subscribers"],"name":"EventBus","cpu":"","values":[36431.0],"shown":["36431"]},{"path":["1 publisher, 100 subscribers"],"name":"watermill","cpu":"","values":[93938.0],"shown":["93938"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"this","cpu":"","values":[36.0],"shown":["36 / 288 / 3375"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"channels","cpu":"","values":[null],"shown":["–"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"kelindar","cpu":"","values":[null],"shown":["–"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"cskr","cpu":"","values":[null],"shown":["–"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"EventBus","cpu":"","values":[187.0],"shown":["187 / 1531 / 13948 (its sync)"]},{"path":["Sync(), 1 / 10 / 100 subscribers"],"name":"watermill","cpu":"","values":[null],"shown":["–"]},{"path":["publish to handler, median / 99th"],"name":"this","cpu":"","values":[289.0],"shown":["289 / 545 ns"]},{"path":["publish to handler, median / 99th"],"name":"channels","cpu":"","values":[181.0],"shown":["181 / 447"]},{"path":["publish to handler, median / 99th"],"name":"kelindar","cpu":"","values":[1060000.0],"shown":["1.06 / 1.12 ms"]},{"path":["publish to handler, median / 99th"],"name":"cskr","cpu":"","values":[373.0],"shown":["373 / 1295"]},{"path":["publish to handler, median / 99th"],"name":"EventBus","cpu":"","values":[876.0],"shown":["876 / 2988"]},{"path":["publish to handler, median / 99th"],"name":"watermill","cpu":"","values":[1527.0],"shown":["1527 / 12247"]},{"path":["allocations a publish"],"name":"this","cpu":"","values":[0.0],"shown":["0"]},{"path":["allocations a publish"],"name":"channels","cpu":"","values":[0.0],"shown":["0"]},{"path":["allocations a publish"],"name":"kelindar","cpu":"","values":[0.0],"shown":["0"]},{"path":["allocations a publish"],"name":"cskr","cpu":"","values":[1.0],"shown":["1"]},{"path":["allocations a publish"],"name":"EventBus","cpu":"","values":[4.0],"shown":["4–203"]},{"path":["allocations a publish"],"name":"watermill","cpu":"","values":[26.0],"shown":["26–773"]}],"header":["","this","channels","kelindar","cskr","EventBus","watermill"],"rows":[["a publish, 1 subscriber","59 ns","66","22","301","436","2774"],["1 publisher, 10 subscribers","450","473","45","953","3913","11858"],["10 publishers, 10 subscribers","591","759","120","1094","3965","12625"],["1 publisher, 100 subscribers","7188","7398","685","15351","36431","93938"],["Sync(), 1 / 10 / 100 subscribers","36 / 288 / 3375","–","–","–","187 / 1531 / 13948 (its sync)","–"],["publish to handler, median / 99th","289 / 545 ns","181 / 447","1.06 / 1.12 ms","373 / 1295","876 / 2988","1527 / 12247"],["allocations a publish","0","0","0","1","4–203","26–773"]]},{"title":"Publishing","against":"kelindar/event, cskr/pubsub, asaskevich/EventBus, watermill's GoChannel, plain channels","legend":"kelindar/event is faster at publishing: it keeps no order per subscriber and has no policy for a full queue. One subscriber slow: what one publish costs while a subscriber falls behind, by each library's behaviour when its queue is full.","reproduce":"python3 tools/benchmarks.py events --rounds 5","source":"benchmarks/events/results.md","ours":["dsa-events","dsa-events-sync","dsa-events-Block","dsa-events-DropOldest","dsa-events-Reject"],"names":{"dsa-events":"events","dsa-events-sync":"events, handlers inside Publish","dsa-events-Block":"events, Block when full","dsa-events-DropOldest":"events, DropOldest when full","dsa-events-Reject":"events, Reject when full","eventbus-async":"EventBus, async","eventbus-sync":"EventBus","kelindar-event":"kelindar/event","cskr-pubsub":"cskr/pubsub","watermill-gochannel":"watermill GoChannel"},"labels":{"Publish":"publish","Latency":"publish to handler","SlowSubscriber":"one subscriber slow","1x1":"1 publisher, 1 subscriber","1x10":"1 publisher, 10 subscribers","1x100":"1 publisher, 100 subscribers","10x10":"10 publishers, 10 subscribers"},"kind":"explorer","measured":"events: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 5 interleaved rounds of 200ms; load average during the runs 3.4 to 4.7. Written by tools/benchmarks.py.","metrics":["ns an operation","latency, median ns","latency, 99th percentile ns","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Publish","1x10"],"records":[{"path":["Publish","1x1"],"name":"cskr-pubsub","cpu":"","values":[300.8,null,null,16.0,1.0]},{"path":["Publish","1x1"],"name":"eventbus-async","cpu":"","values":[436.4,null,null,120.0,4.0]},{"path":["Publish","1x1"],"name":"watermill-gochannel","cpu":"","values":[2774.0,null,null,2065.0,26.0]},{"path":["Publish","1x1"],"name":"channels","cpu":"","values":[65.8,null,null,0.0,0.0]},{"path":["Publish","1x1"],"name":"dsa-events-sync","cpu":"","values":[36.0,null,null,0.0,0.0]},{"path":["Publish","1x1"],"name":"eventbus-sync","cpu":"","values":[187.0,null,null,56.0,3.0]},{"path":["Publish","1x1"],"name":"dsa-events","cpu":"","values":[58.6,null,null,0.0,0.0]},{"path":["Publish","1x1"],"name":"kelindar-event","cpu":"","values":[22.1,null,null,0.0,0.0]},{"path":["Publish","1x10"],"name":"cskr-pubsub","cpu":"","values":[952.6,null,null,16.0,1.0]},{"path":["Publish","1x10"],"name":"eventbus-async","cpu":"","values":[3913.0,null,null,992.0,23.0]},{"path":["Publish","1x10"],"name":"watermill-gochannel","cpu":"","values":[11858.0,null,null,8646.0,98.0]},{"path":["Publish","1x10"],"name":"channels","cpu":"","values":[472.9,null,null,0.0,0.0]},{"path":["Publish","1x10"],"name":"dsa-events-sync","cpu":"","values":[287.6,null,null,0.0,0.0]},{"path":["Publish","1x10"],"name":"eventbus-sync","cpu":"","values":[1531.0,null,null,352.0,13.0]},{"path":["Publish","1x10"],"name":"dsa-events","cpu":"","values":[450.1,null,null,0.0,0.0]},{"path":["Publish","1x10"],"name":"kelindar-event","cpu":"","values":[44.8,null,null,17.0,0.0]},{"path":["Publish","10x10"],"name":"dsa-events","cpu":"","values":[590.9,null,null,0.0,0.0]},{"path":["Publish","10x10"],"name":"kelindar-event","cpu":"","values":[120.1,null,null,15.0,0.0]},{"path":["Publish","10x10"],"name":"cskr-pubsub","cpu":"","values":[1094.0,null,null,16.0,1.0]},{"path":["Publish","10x10"],"name":"eventbus-async","cpu":"","values":[3965.0,null,null,992.0,23.0]},{"path":["Publish","10x10"],"name":"watermill-gochannel","cpu":"","values":[12625.0,null,null,7012.0,93.0]},{"path":["Publish","10x10"],"name":"channels","cpu":"","values":[758.8,null,null,0.0,0.0]},{"path":["Publish","10x10"],"name":"dsa-events-sync","cpu":"","values":[428.4,null,null,0.0,0.0]},{"path":["Publish","10x10"],"name":"eventbus-sync","cpu":"","values":[1967.0,null,null,352.0,13.0]},{"path":["Publish","1x100"],"name":"eventbus-async","cpu":"","values":[36431.0,null,null,9728.0,203.0]},{"path":["Publish","1x100"],"name":"watermill-gochannel","cpu":"","values":[93938.0,null,null,61962.0,773.0]},{"path":["Publish","1x100"],"name":"channels","cpu":"","values":[7398.0,null,null,0.0,0.0]},{"path":["Publish","1x100"],"name":"dsa-events-sync","cpu":"","values":[3375.0,null,null,0.0,0.0]},{"path":["Publish","1x100"],"name":"eventbus-sync","cpu":"","values":[13948.0,null,null,3328.0,103.0]},{"path":["Publish","1x100"],"name":"dsa-events","cpu":"","values":[7188.0,null,null,0.0,0.0]},{"path":["Publish","1x100"],"name":"kelindar-event","cpu":"","values":[685.1,null,null,181.0,0.0]},{"path":["Publish","1x100"],"name":"cskr-pubsub","cpu":"","values":[15351.0,null,null,16.0,1.0]},{"path":["Latency"],"name":"channels","cpu":"","values":[388.1,181.0,447.0,0.0,0.0]},{"path":["Latency"],"name":"dsa-events","cpu":"","values":[547.2,289.0,545.0,0.0,0.0]},{"path":["Latency"],"name":"kelindar-event","cpu":"","values":[1035057.0,1061785.0,1118329.0,1.0,0.0]},{"path":["Latency"],"name":"cskr-pubsub","cpu":"","values":[567.7,373.0,1295.0,16.0,1.0]},{"path":["Latency"],"name":"eventbus-async","cpu":"","values":[1232.0,876.0,2988.0,120.0,4.0]},{"path":["Latency"],"name":"watermill-gochannel","cpu":"","values":[3626.0,1527.0,12247.0,1776.0,26.0]},{"path":["SlowSubscriber"],"name":"dsa-events-Block","cpu":"","values":[943831.0,306.0,33847502.0,0.0,0.0]},{"path":["SlowSubscriber"],"name":"dsa-events-DropOldest","cpu":"","values":[565.4,323.0,596.0,0.0,0.0]},{"path":["SlowSubscriber"],"name":"dsa-events-Reject","cpu":"","values":[540.2,298.0,573.0,0.0,0.0]},{"path":["SlowSubscriber"],"name":"kelindar-event","cpu":"","values":[1035362.0,1059198.0,1087480.0,9.0,0.0]},{"path":["SlowSubscriber"],"name":"cskr-pubsub","cpu":"","values":[947569.0,1059277.0,2131176.0,16.0,1.0]},{"path":["SlowSubscriber"],"name":"channels","cpu":"","values":[946703.0,1058486.0,1075682.0,0.0,0.0]}]}],"checked":[{"kind":"other libraries","against":"kelindar/event, cskr/pubsub, asaskevich/EventBus, watermill's GoChannel and a channel per subscriber (benchmarks/events, 14 CPUs, medians)","result":"a publish to 1 subscriber 59 ns, against 66 for channels, 22 for kelindar, 301 for cskr, 436 for EventBus and 2774 for watermill; no allocation a publish","source":"events/readme","quote":"Measured (benchmarks/events, 14 CPUs, medians), against kelindar/event, cskr/pubsub, asaskevich/EventBus, watermill's GoChannel and a channel per subscriber"},{"kind":"other libraries","against":"kelindar/event's latency from publish to handler","result":"kelindar/event publishes fastest, its subscribers waking every 500 µs: about a millisecond from publish to handler","source":"events/readme","quote":"kelindar/event publishes fastest because its subscribers wake every 500 µs, not per event: a millisecond from publish to handler here."},{"kind":"measured","against":"a subscriber taking a millisecond an event beside a fast one","result":"with DropOldest or Reject, the fast subscriber's 99th percentile stays at 596 and 573 ns","source":"events/readme","quote":"with `DropOldest` or `Reject`, the fast subscriber's 99th percentile stays at 596 and 573 ns."},{"kind":"tests","against":"order per subscription; every policy against a subscriber that never keeps up, under testing/synctest","result":"","source":"events/readme","quote":"Checked: order per subscription, every policy against a subscriber that never keeps up (under `testing/synctest`)"},{"kind":"tests","against":"closing while four goroutines publish to five kinds of subscription, under -race","result":"no handler runs once Close has returned, the counts add up, no goroutine is left","source":"events/readme","quote":"no handler runs once `Close` has returned, the counts add up, no goroutine is left."}]},"dsa-collections-set":{"group":"dsa-collections","line":"A generic set.","tags":[],"keywords":"set generic","summary":"A generic set of comparable values, named as Go's proposed collections name things, so code can move over if Go ships one.","snippet":"import set \"goframework.io/dsa-collections-set\"\n\nvar seen set.Set[string] // the zero value is an empty set\nseen.Insert(\"a\") // true: it was not there\nseen.Contains(\"a\")\nboth := set.Of(1, 2, 3).Intersection(set.Of(2, 3, 4)) // {2 3}\nfor value := range both.All() {}","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"a plain map, over random operation sequences","result":"","source":"dsa-collections-set/readme","quote":"Checked against a plain map over random operation sequences, the set algebra's laws on random sets, and fuzzing."},{"kind":"tests","against":"the set algebra's laws on random sets","result":"","source":"dsa-collections-set/readme","quote":"the set algebra's laws on random sets, and fuzzing."},{"kind":"fuzzed","against":"the set, fuzzed","result":"","source":"dsa-collections-set/readme","quote":"the set algebra's laws on random sets, and fuzzing."}]},"dsa-collections-map-sorted":{"group":"dsa-collections","line":"A sorted map and set (a B-tree): ranges, floor and ceiling, a clone in constant time.","tags":[],"keywords":"btree ordered treemap","summary":"A map and a set kept in order by a comparison: a B-tree with ranges, floor and ceiling, iteration both ways, and `Clone` in constant time (nodes are copied only when one of the copies changes them).","snippet":"import sorted \"goframework.io/dsa-collections-map-sorted\"\n\nages := sorted.NewMap[string, int](strings.Compare)\nages.Set(\"bob\", 41)\nage, ok := ages.Get(\"bob\")\nfor name, age := range ages.Range(\"a\", \"c\") {} // from \"a\", before \"c\"\nfor name, age := range ages.Backward() {} // descending\nname, _, ok := ages.Floor(\"carl\") // the greatest key not above\nsnapshot := ages.Clone() // constant time\n\nseen := sorted.NewSet(cmp.Compare[int])\nseen.Insert(7)","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"google/btree, tidwall/btree","notWhen":"","measures":[{"title":"A sorted map of 100,000 keys","against":"google/btree, tidwall/btree","legend":"","reproduce":"python3 tools/benchmarks.py sortedmap --rounds 6","source":"benchmarks/sortedmap/results.md","ours":["dsa-sorted"],"names":{"dsa-sorted":"map-sorted","google-btree":"google/btree","tidwall-btree":"tidwall/btree"},"labels":{"get":"get a random key","set":"set","delete+set":"delete, then set","scan-100":"100 keys in order"},"kind":"explorer","measured":"sortedmap: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 6 interleaved rounds of 200ms; load average during the runs 10.7 to 12.3. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Sorted","get"],"records":[{"path":["Sorted","get"],"name":"dsa-sorted","cpu":"","values":[149.0,0.0,0.0]},{"path":["Sorted","get"],"name":"google-btree","cpu":"","values":[139.1,0.0,0.0]},{"path":["Sorted","get"],"name":"tidwall-btree","cpu":"","values":[128.4,0.0,0.0]},{"path":["Sorted","set"],"name":"dsa-sorted","cpu":"","values":[139.1,0.0,0.0]},{"path":["Sorted","set"],"name":"google-btree","cpu":"","values":[141.1,0.0,0.0]},{"path":["Sorted","set"],"name":"tidwall-btree","cpu":"","values":[126.4,0.0,0.0]},{"path":["Sorted","delete+set"],"name":"google-btree","cpu":"","values":[304.2,0.0,0.0]},{"path":["Sorted","delete+set"],"name":"tidwall-btree","cpu":"","values":[253.9,0.0,0.0]},{"path":["Sorted","delete+set"],"name":"dsa-sorted","cpu":"","values":[314.3,0.0,0.0]},{"path":["Sorted","scan-100"],"name":"google-btree","cpu":"","values":[455.6,0.0,0.0]},{"path":["Sorted","scan-100"],"name":"tidwall-btree","cpu":"","values":[359.5,0.0,0.0]},{"path":["Sorted","scan-100"],"name":"dsa-sorted","cpu":"","values":[439.6,0.0,0.0]}]}],"checked":[{"kind":"other libraries","against":"google/btree and tidwall/btree, 100,000 keys (benchmarks/sortedmap)","result":"on par with google/btree; tidwall/btree, which compares keys with <, 15 to 25% quicker","source":"dsa-collections-map-sorted/readme","quote":"Measured against google/btree and tidwall/btree on 100000 keys (benchmarks/sortedmap): on par with google/btree"},{"kind":"model","against":"a sorted model, every query; clones changed independently","result":"","source":"dsa-collections-map-sorted/readme","quote":"every query against a sorted model, clones changed independently"},{"kind":"tests","against":"the B-tree's invariants after random operations: node sizes, order, every leaf at one depth","result":"","source":"dsa-collections-map-sorted/readme","quote":"Checked: the B-tree's invariants (node sizes, order, every leaf at one depth) after random operations"},{"kind":"fuzzed","against":"a tree four levels deep emptied in random order; fuzzing","result":"","source":"dsa-collections-map-sorted/readme","quote":"a tree four levels deep emptied in random order, fuzzing."}]},"dsa-collections-map-concurrent":{"group":"dsa-collections","line":"A concurrent hash map: reads take no lock, a write locks one bucket.","tags":[],"keywords":"sync.Map concurrent hashmap","summary":"A hash map safe for concurrent use, for what `sync.Map` is used for: reads take no lock, writes lock one bucket. A cache-line hash table (CLHT): buckets of five entries fill a 64-byte line, a byte of each key's hash is matched eight at a time, the table doubles as it fills.","snippet":"import concurrent \"goframework.io/dsa-collections-map-concurrent\"\n\nvar sessions concurrent.Map[string, *Session] // the zero value is ready\nsessions.Store(id, session)\nsession, ok := sessions.Load(id)\nuser, loaded := users.LoadOrCompute(id, fetch) // fetch runs once per absent key\nhits.Compute(path, func(old int, _ bool) (int, concurrent.Action) {\n\treturn old + 1, concurrent.Store\n})\nfor id, session := range sessions.All() {}","uses":[],"usedBy":["metrics-spec"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"sync.Map, xsync.Map","notWhen":"Few goroutines and rare writes: a map and a mutex.","measures":[{"title":"In short, from its readme","against":"xsync v4, llxisdsh/pb, sync.Map","legend":"","reproduce":"","source":"dsa-collections-map-concurrent/readme","ours":["this"],"names":{"this":"dsa-collections-map-concurrent"},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["ns an operation, at 14 CPUs"],"metric":0,"default":[],"records":[{"path":["1,000 int keys, reads only"],"name":"this","cpu":"","values":[3.1],"shown":["3.1"]},{"path":["1,000 int keys, reads only"],"name":"xsync v4","cpu":"","values":[3.0],"shown":["3.0"]},{"path":["1,000 int keys, reads only"],"name":"llxisdsh/pb","cpu":"","values":[2.3],"shown":["2.3"]},{"path":["1,000 int keys, reads only"],"name":"sync.Map","cpu":"","values":[5.5],"shown":["5.5"]},{"path":["1,000 random int keys, 90% reads"],"name":"this","cpu":"","values":[7.3],"shown":["7.3"]},{"path":["1,000 random int keys, 90% reads"],"name":"xsync v4","cpu":"","values":[7.1],"shown":["7.1"]},{"path":["1,000 random int keys, 90% reads"],"name":"llxisdsh/pb","cpu":"","values":[6.6],"shown":["6.6"]},{"path":["1,000 random int keys, 90% reads"],"name":"sync.Map","cpu":"","values":[12.3],"shown":["12.3"]},{"path":["1,000 string keys, 75% reads"],"name":"this","cpu":"","values":[12.3],"shown":["12.3"]},{"path":["1,000 string keys, 75% reads"],"name":"xsync v4","cpu":"","values":[13.1],"shown":["13.1"]},{"path":["1,000 string keys, 75% reads"],"name":"llxisdsh/pb","cpu":"","values":[11.5],"shown":["11.5"]},{"path":["1,000 string keys, 75% reads"],"name":"sync.Map","cpu":"","values":[20.2],"shown":["20.2"]},{"path":["1,000,000 random int keys, 90% reads"],"name":"this","cpu":"","values":[25.2],"shown":["25.2"]},{"path":["1,000,000 random int keys, 90% reads"],"name":"xsync v4","cpu":"","values":[23.8],"shown":["23.8"]},{"path":["1,000,000 random int keys, 90% reads"],"name":"llxisdsh/pb","cpu":"","values":[23.9],"shown":["23.9"]},{"path":["1,000,000 random int keys, 90% reads"],"name":"sync.Map","cpu":"","values":[57.3],"shown":["57.3"]},{"path":["1,000,000 string keys, 90% reads"],"name":"this","cpu":"","values":[48.8],"shown":["48.8"]},{"path":["1,000,000 string keys, 90% reads"],"name":"xsync v4","cpu":"","values":[47.0],"shown":["47.0"]},{"path":["1,000,000 string keys, 90% reads"],"name":"llxisdsh/pb","cpu":"","values":[48.5],"shown":["48.5"]},{"path":["1,000,000 string keys, 90% reads"],"name":"sync.Map","cpu":"","values":[77.5],"shown":["77.5"]},{"path":["inserting random int keys from empty"],"name":"this","cpu":"","values":[92.0],"shown":["92"]},{"path":["inserting random int keys from empty"],"name":"xsync v4","cpu":"","values":[77.0],"shown":["77"]},{"path":["inserting random int keys from empty"],"name":"llxisdsh/pb","cpu":"","values":[75.0],"shown":["75"]},{"path":["inserting random int keys from empty"],"name":"sync.Map","cpu":"","values":[69.0],"shown":["69"]},{"path":["iterating while one goroutine writes, per entry"],"name":"this","cpu":"","values":[49.0],"shown":["49"]},{"path":["iterating while one goroutine writes, per entry"],"name":"xsync v4","cpu":"","values":[59.0],"shown":["59"]},{"path":["iterating while one goroutine writes, per entry"],"name":"llxisdsh/pb","cpu":"","values":[43.0],"shown":["43"]},{"path":["iterating while one goroutine writes, per entry"],"name":"sync.Map","cpu":"","values":[178.0],"shown":["178"]},{"path":["a million int keys and values, bytes an entry"],"name":"this","cpu":"","values":[50.0],"shown":["50"]},{"path":["a million int keys and values, bytes an entry"],"name":"xsync v4","cpu":"","values":[50.0],"shown":["50"]},{"path":["a million int keys and values, bytes an entry"],"name":"llxisdsh/pb","cpu":"","values":[33.0],"shown":["33"]},{"path":["a million int keys and values, bytes an entry"],"name":"sync.Map","cpu":"","values":[122.0],"shown":["122"]}],"header":["","this","xsync v4","llxisdsh/pb","sync.Map"],"rows":[["1,000 int keys, reads only","3.1","3.0","2.3","5.5"],["1,000 random int keys, 90% reads","7.3","7.1","6.6","12.3"],["1,000 string keys, 75% reads","12.3","13.1","11.5","20.2"],["1,000,000 random int keys, 90% reads","25.2","23.8","23.9","57.3"],["1,000,000 string keys, 90% reads","48.8","47.0","48.5","77.5"],["inserting random int keys from empty","92","77","75","69"],["iterating while one goroutine writes, per entry","49","59","43","178"],["a million int keys and values, bytes an entry","50","50","33","122"]]},{"title":"Reads and writes from many goroutines","against":"puzpuzpuz/xsync v4, llxisdsh/pb, sync.Map, alphadose/haxmap, cornelk/hashmap, orcaman/concurrent-map","legend":"Every goroutine reads, stores and deletes at random over the keys, in the share chosen. cornelk/hashmap is left out at 1,000,000 keys: it does not finish.","reproduce":"python3 tools/benchmarks.py maps --rounds 3 --cpu 1,4,14","source":"benchmarks/maps/results.md","ours":["dsa"],"names":{"dsa":"dsa-collections-map-concurrent","xsync":"puzpuzpuz/xsync v4","pb":"llxisdsh/pb","haxmap":"alphadose/haxmap","cornelk":"cornelk/hashmap","orcaman":"orcaman/concurrent-map"},"labels":{"Mix":"reads and writes","Insert":"inserting","Range":"ranging","Memory":"memory","int":"int keys, in order","randomint":"int keys, random","string":"string keys","1000":"1,000 keys","1000000":"1,000,000 keys","r100":"reads only","r99":"99% reads","r90":"90% reads","r75":"75% reads"},"kind":"explorer","measured":"maps: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 3 interleaved rounds of 200ms, cpu 1,4,14; load average during the runs 10.3 to 21.4. Written by tools/benchmarks.py.","metrics":["ns an operation","ns an entry","bytes an entry","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Mix","int","1000","r90"],"records":[{"path":["Mix","int","1000","r100"],"name":"dsa","cpu":"1","values":[16.1,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"dsa","cpu":"4","values":[5.4,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"dsa","cpu":"14","values":[3.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"dsa","cpu":"1","values":[15.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"dsa","cpu":"4","values":[5.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"dsa","cpu":"14","values":[3.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"dsa","cpu":"1","values":[24.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"dsa","cpu":"4","values":[8.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"dsa","cpu":"14","values":[4.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"xsync","cpu":"1","values":[13.6,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"xsync","cpu":"4","values":[5.0,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"xsync","cpu":"14","values":[3.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"xsync","cpu":"1","values":[13.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"xsync","cpu":"4","values":[4.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"xsync","cpu":"14","values":[2.9,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"xsync","cpu":"1","values":[24.2,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"xsync","cpu":"4","values":[7.9,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"xsync","cpu":"14","values":[4.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"pb","cpu":"1","values":[13.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"pb","cpu":"4","values":[4.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"pb","cpu":"14","values":[2.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"pb","cpu":"1","values":[14.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"pb","cpu":"4","values":[4.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"pb","cpu":"14","values":[2.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"pb","cpu":"1","values":[22.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"pb","cpu":"4","values":[8.0,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"pb","cpu":"14","values":[4.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"sync.Map","cpu":"1","values":[28.1,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"sync.Map","cpu":"4","values":[10.1,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"sync.Map","cpu":"14","values":[5.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"sync.Map","cpu":"1","values":[27.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"sync.Map","cpu":"4","values":[10.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"sync.Map","cpu":"14","values":[5.8,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"sync.Map","cpu":"1","values":[39.0,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"sync.Map","cpu":"4","values":[13.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"sync.Map","cpu":"14","values":[7.4,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"haxmap","cpu":"1","values":[18.4,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"haxmap","cpu":"4","values":[6.6,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"haxmap","cpu":"14","values":[3.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"haxmap","cpu":"1","values":[21.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"haxmap","cpu":"4","values":[6.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"haxmap","cpu":"14","values":[3.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"haxmap","cpu":"1","values":[33.0,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"haxmap","cpu":"4","values":[11.9,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"haxmap","cpu":"14","values":[6.5,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"cornelk","cpu":"1","values":[18.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"cornelk","cpu":"4","values":[7.0,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"cornelk","cpu":"14","values":[3.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"cornelk","cpu":"1","values":[19.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"cornelk","cpu":"4","values":[7.2,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"cornelk","cpu":"14","values":[3.8,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"cornelk","cpu":"1","values":[47.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"cornelk","cpu":"4","values":[13.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"cornelk","cpu":"14","values":[6.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"orcaman","cpu":"1","values":[28.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"orcaman","cpu":"4","values":[19.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r100"],"name":"orcaman","cpu":"14","values":[13.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"orcaman","cpu":"1","values":[29.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"orcaman","cpu":"4","values":[19.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r100"],"name":"orcaman","cpu":"14","values":[13.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"orcaman","cpu":"1","values":[65.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"orcaman","cpu":"4","values":[33.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r100"],"name":"orcaman","cpu":"14","values":[18.2,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"dsa","cpu":"1","values":[22.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"dsa","cpu":"4","values":[7.7,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"dsa","cpu":"14","values":[4.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"dsa","cpu":"1","values":[24.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"dsa","cpu":"4","values":[8.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"dsa","cpu":"14","values":[4.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"dsa","cpu":"1","values":[34.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"dsa","cpu":"4","values":[11.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"dsa","cpu":"14","values":[6.0,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"xsync","cpu":"1","values":[22.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"xsync","cpu":"4","values":[7.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"xsync","cpu":"14","values":[4.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"xsync","cpu":"1","values":[22.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"xsync","cpu":"4","values":[7.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"xsync","cpu":"14","values":[4.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"xsync","cpu":"1","values":[36.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"xsync","cpu":"4","values":[10.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"xsync","cpu":"14","values":[6.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"pb","cpu":"1","values":[20.7,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"pb","cpu":"4","values":[6.5,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"pb","cpu":"14","values":[3.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"pb","cpu":"1","values":[22.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"pb","cpu":"4","values":[7.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"pb","cpu":"14","values":[3.8,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"pb","cpu":"1","values":[29.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"pb","cpu":"4","values":[9.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"pb","cpu":"14","values":[5.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"sync.Map","cpu":"1","values":[37.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"sync.Map","cpu":"4","values":[11.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"sync.Map","cpu":"14","values":[7.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"sync.Map","cpu":"1","values":[36.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"sync.Map","cpu":"4","values":[11.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"sync.Map","cpu":"14","values":[7.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"sync.Map","cpu":"1","values":[43.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"sync.Map","cpu":"4","values":[14.7,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"sync.Map","cpu":"14","values":[9.2,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"haxmap","cpu":"1","values":[44.0,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"haxmap","cpu":"4","values":[12.5,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"haxmap","cpu":"14","values":[10.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"haxmap","cpu":"1","values":[34.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"haxmap","cpu":"4","values":[13.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"haxmap","cpu":"14","values":[10.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"haxmap","cpu":"1","values":[70.8,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"haxmap","cpu":"4","values":[20.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"haxmap","cpu":"14","values":[13.2,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"cornelk","cpu":"1","values":[30.1,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"cornelk","cpu":"4","values":[10.2,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"cornelk","cpu":"14","values":[5.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"cornelk","cpu":"1","values":[31.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"cornelk","cpu":"4","values":[10.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"cornelk","cpu":"14","values":[6.0,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"cornelk","cpu":"1","values":[45.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"cornelk","cpu":"4","values":[16.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"cornelk","cpu":"14","values":[8.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"orcaman","cpu":"1","values":[32.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"orcaman","cpu":"4","values":[32.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r99"],"name":"orcaman","cpu":"14","values":[29.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"orcaman","cpu":"1","values":[32.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"orcaman","cpu":"4","values":[31.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r99"],"name":"orcaman","cpu":"14","values":[28.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"orcaman","cpu":"1","values":[86.8,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"orcaman","cpu":"4","values":[44.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r99"],"name":"orcaman","cpu":"14","values":[36.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"dsa","cpu":"1","values":[29.0,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"dsa","cpu":"4","values":[10.1,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"dsa","cpu":"14","values":[7.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"dsa","cpu":"1","values":[26.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"dsa","cpu":"4","values":[10.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"dsa","cpu":"14","values":[7.3,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"dsa","cpu":"1","values":[35.5,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"dsa","cpu":"4","values":[13.9,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"dsa","cpu":"14","values":[9.0,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"xsync","cpu":"1","values":[29.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"xsync","cpu":"4","values":[10.2,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"xsync","cpu":"14","values":[7.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"xsync","cpu":"1","values":[28.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"xsync","cpu":"4","values":[10.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"xsync","cpu":"14","values":[7.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"xsync","cpu":"1","values":[39.5,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"xsync","cpu":"4","values":[14.1,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"xsync","cpu":"14","values":[10.0,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"pb","cpu":"1","values":[25.6,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"pb","cpu":"4","values":[9.3,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"pb","cpu":"14","values":[6.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"pb","cpu":"1","values":[27.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"pb","cpu":"4","values":[9.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"pb","cpu":"14","values":[6.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"pb","cpu":"1","values":[32.6,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"pb","cpu":"4","values":[13.1,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"pb","cpu":"14","values":[8.7,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"sync.Map","cpu":"1","values":[46.8,null,null,3.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"sync.Map","cpu":"4","values":[16.6,null,null,3.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"sync.Map","cpu":"14","values":[12.8,null,null,3.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"sync.Map","cpu":"1","values":[41.1,null,null,3.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"sync.Map","cpu":"4","values":[16.7,null,null,3.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"sync.Map","cpu":"14","values":[12.3,null,null,3.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"sync.Map","cpu":"1","values":[48.4,null,null,4.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"sync.Map","cpu":"4","values":[19.3,null,null,4.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"sync.Map","cpu":"14","values":[15.8,null,null,4.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"haxmap","cpu":"1","values":[44.2,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"haxmap","cpu":"4","values":[20.8,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"haxmap","cpu":"14","values":[32.4,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"haxmap","cpu":"1","values":[41.3,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"haxmap","cpu":"4","values":[20.5,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"haxmap","cpu":"14","values":[36.3,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"haxmap","cpu":"1","values":[60.5,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"haxmap","cpu":"4","values":[27.6,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"haxmap","cpu":"14","values":[34.6,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"cornelk","cpu":"1","values":[42.4,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"cornelk","cpu":"4","values":[21.0,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"cornelk","cpu":"14","values":[17.8,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"cornelk","cpu":"1","values":[47.6,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"cornelk","cpu":"4","values":[21.5,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"cornelk","cpu":"14","values":[18.1,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"cornelk","cpu":"1","values":[70.4,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"cornelk","cpu":"4","values":[28.4,null,null,1.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"cornelk","cpu":"14","values":[20.1,null,null,1.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"orcaman","cpu":"1","values":[37.5,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"orcaman","cpu":"4","values":[50.7,null,null,0.0,0.0]},{"path":["Mix","int","1000","r90"],"name":"orcaman","cpu":"14","values":[43.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"orcaman","cpu":"1","values":[33.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"orcaman","cpu":"4","values":[55.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r90"],"name":"orcaman","cpu":"14","values":[45.2,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"orcaman","cpu":"1","values":[86.4,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"orcaman","cpu":"4","values":[85.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r90"],"name":"orcaman","cpu":"14","values":[61.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"dsa","cpu":"1","values":[32.7,null,null,2.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"dsa","cpu":"4","values":[13.9,null,null,1.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"dsa","cpu":"14","values":[10.2,null,null,2.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"dsa","cpu":"1","values":[33.8,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"dsa","cpu":"4","values":[14.3,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"dsa","cpu":"14","values":[9.5,null,null,2.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"dsa","cpu":"1","values":[42.5,null,null,3.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"dsa","cpu":"4","values":[18.3,null,null,2.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"dsa","cpu":"14","values":[12.3,null,null,2.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"xsync","cpu":"1","values":[32.2,null,null,2.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"xsync","cpu":"4","values":[14.5,null,null,2.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"xsync","cpu":"14","values":[10.1,null,null,2.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"xsync","cpu":"1","values":[33.1,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"xsync","cpu":"4","values":[13.8,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"xsync","cpu":"14","values":[9.6,null,null,2.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"xsync","cpu":"1","values":[41.3,null,null,3.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"xsync","cpu":"4","values":[18.6,null,null,3.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"xsync","cpu":"14","values":[13.1,null,null,3.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"pb","cpu":"1","values":[27.8,null,null,0.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"pb","cpu":"4","values":[12.5,null,null,0.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"pb","cpu":"14","values":[9.0,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"pb","cpu":"1","values":[28.4,null,null,1.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"pb","cpu":"4","values":[13.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"pb","cpu":"14","values":[9.7,null,null,1.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"pb","cpu":"1","values":[38.3,null,null,1.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"pb","cpu":"4","values":[16.8,null,null,1.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"pb","cpu":"14","values":[11.5,null,null,1.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"sync.Map","cpu":"1","values":[59.1,null,null,8.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"sync.Map","cpu":"4","values":[23.2,null,null,8.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"sync.Map","cpu":"14","values":[17.6,null,null,8.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"sync.Map","cpu":"1","values":[56.9,null,null,9.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"sync.Map","cpu":"4","values":[23.9,null,null,9.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"sync.Map","cpu":"14","values":[19.1,null,null,9.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"sync.Map","cpu":"1","values":[76.7,null,null,10.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"sync.Map","cpu":"4","values":[27.8,null,null,10.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"sync.Map","cpu":"14","values":[20.2,null,null,10.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"haxmap","cpu":"1","values":[53.5,null,null,3.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"haxmap","cpu":"4","values":[34.7,null,null,4.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"haxmap","cpu":"14","values":[55.9,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"haxmap","cpu":"1","values":[55.6,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"haxmap","cpu":"4","values":[34.4,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"haxmap","cpu":"14","values":[59.4,null,null,4.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"haxmap","cpu":"1","values":[69.1,null,null,3.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"haxmap","cpu":"4","values":[38.7,null,null,4.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"haxmap","cpu":"14","values":[61.6,null,null,3.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"cornelk","cpu":"1","values":[69.3,null,null,4.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"cornelk","cpu":"4","values":[39.2,null,null,3.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"cornelk","cpu":"14","values":[36.1,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"cornelk","cpu":"1","values":[71.5,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"cornelk","cpu":"4","values":[39.3,null,null,4.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"cornelk","cpu":"14","values":[37.1,null,null,4.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"cornelk","cpu":"1","values":[94.3,null,null,4.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"cornelk","cpu":"4","values":[47.0,null,null,4.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"cornelk","cpu":"14","values":[40.1,null,null,4.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"orcaman","cpu":"1","values":[36.7,null,null,0.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"orcaman","cpu":"4","values":[67.9,null,null,0.0,0.0]},{"path":["Mix","int","1000","r75"],"name":"orcaman","cpu":"14","values":[58.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"orcaman","cpu":"1","values":[39.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"orcaman","cpu":"4","values":[64.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000","r75"],"name":"orcaman","cpu":"14","values":[59.2,null,null,0.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"orcaman","cpu":"1","values":[92.5,null,null,0.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"orcaman","cpu":"4","values":[103.9,null,null,0.0,0.0]},{"path":["Mix","string","1000","r75"],"name":"orcaman","cpu":"14","values":[80.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"dsa","cpu":"1","values":[167.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"dsa","cpu":"4","values":[57.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"dsa","cpu":"14","values":[26.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"dsa","cpu":"1","values":[188.2,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"dsa","cpu":"4","values":[55.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"dsa","cpu":"14","values":[27.0,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"dsa","cpu":"1","values":[319.0,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"dsa","cpu":"4","values":[110.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"dsa","cpu":"14","values":[46.1,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"xsync","cpu":"1","values":[158.1,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"xsync","cpu":"4","values":[59.6,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"xsync","cpu":"14","values":[24.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"xsync","cpu":"1","values":[156.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"xsync","cpu":"4","values":[59.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"xsync","cpu":"14","values":[26.2,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"xsync","cpu":"1","values":[321.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"xsync","cpu":"4","values":[97.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"xsync","cpu":"14","values":[45.3,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"pb","cpu":"1","values":[152.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"pb","cpu":"4","values":[51.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"pb","cpu":"14","values":[22.2,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"pb","cpu":"1","values":[167.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"pb","cpu":"4","values":[52.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"pb","cpu":"14","values":[23.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"pb","cpu":"1","values":[422.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"pb","cpu":"4","values":[98.3,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"pb","cpu":"14","values":[45.7,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"sync.Map","cpu":"1","values":[410.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"sync.Map","cpu":"4","values":[135.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"sync.Map","cpu":"14","values":[60.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"sync.Map","cpu":"1","values":[375.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"sync.Map","cpu":"4","values":[123.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"sync.Map","cpu":"14","values":[67.0,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"sync.Map","cpu":"1","values":[536.5,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"sync.Map","cpu":"4","values":[179.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"sync.Map","cpu":"14","values":[85.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"haxmap","cpu":"1","values":[251.2,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"haxmap","cpu":"4","values":[92.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"haxmap","cpu":"14","values":[43.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"haxmap","cpu":"1","values":[244.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"haxmap","cpu":"4","values":[94.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"haxmap","cpu":"14","values":[41.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"haxmap","cpu":"1","values":[421.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"haxmap","cpu":"4","values":[136.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"haxmap","cpu":"14","values":[61.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"orcaman","cpu":"1","values":[172.1,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"orcaman","cpu":"4","values":[55.3,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r100"],"name":"orcaman","cpu":"14","values":[31.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"orcaman","cpu":"1","values":[178.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"orcaman","cpu":"4","values":[57.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r100"],"name":"orcaman","cpu":"14","values":[30.7,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"orcaman","cpu":"1","values":[356.0,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"orcaman","cpu":"4","values":[120.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r100"],"name":"orcaman","cpu":"14","values":[56.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"dsa","cpu":"1","values":[177.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"dsa","cpu":"4","values":[55.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"dsa","cpu":"14","values":[29.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"dsa","cpu":"1","values":[248.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"dsa","cpu":"4","values":[66.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"dsa","cpu":"14","values":[35.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"dsa","cpu":"1","values":[325.6,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"dsa","cpu":"4","values":[112.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"dsa","cpu":"14","values":[47.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"xsync","cpu":"1","values":[169.6,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"xsync","cpu":"4","values":[51.6,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"xsync","cpu":"14","values":[24.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"xsync","cpu":"1","values":[156.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"xsync","cpu":"4","values":[60.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"xsync","cpu":"14","values":[25.0,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"xsync","cpu":"1","values":[319.4,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"xsync","cpu":"4","values":[96.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"xsync","cpu":"14","values":[47.7,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"pb","cpu":"1","values":[155.2,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"pb","cpu":"4","values":[54.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"pb","cpu":"14","values":[23.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"pb","cpu":"1","values":[166.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"pb","cpu":"4","values":[66.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"pb","cpu":"14","values":[23.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"pb","cpu":"1","values":[313.7,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"pb","cpu":"4","values":[97.3,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"pb","cpu":"14","values":[45.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"sync.Map","cpu":"1","values":[415.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"sync.Map","cpu":"4","values":[131.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"sync.Map","cpu":"14","values":[61.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"sync.Map","cpu":"1","values":[411.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"sync.Map","cpu":"4","values":[132.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"sync.Map","cpu":"14","values":[59.4,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"sync.Map","cpu":"1","values":[551.3,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"sync.Map","cpu":"4","values":[171.5,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"sync.Map","cpu":"14","values":[82.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"haxmap","cpu":"1","values":[254.3,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"haxmap","cpu":"4","values":[98.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"haxmap","cpu":"14","values":[45.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"haxmap","cpu":"1","values":[260.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"haxmap","cpu":"4","values":[92.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"haxmap","cpu":"14","values":[44.3,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"haxmap","cpu":"1","values":[428.3,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"haxmap","cpu":"4","values":[142.6,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"haxmap","cpu":"14","values":[64.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"orcaman","cpu":"1","values":[189.7,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"orcaman","cpu":"4","values":[85.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r99"],"name":"orcaman","cpu":"14","values":[58.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"orcaman","cpu":"1","values":[166.6,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"orcaman","cpu":"4","values":[85.5,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r99"],"name":"orcaman","cpu":"14","values":[57.6,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"orcaman","cpu":"1","values":[358.7,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"orcaman","cpu":"4","values":[135.4,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r99"],"name":"orcaman","cpu":"14","values":[87.6,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"dsa","cpu":"1","values":[178.6,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"dsa","cpu":"4","values":[56.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"dsa","cpu":"14","values":[27.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"dsa","cpu":"1","values":[201.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"dsa","cpu":"4","values":[60.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"dsa","cpu":"14","values":[25.2,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"dsa","cpu":"1","values":[330.9,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"dsa","cpu":"4","values":[113.1,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"dsa","cpu":"14","values":[48.8,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"xsync","cpu":"1","values":[170.5,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"xsync","cpu":"4","values":[51.1,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"xsync","cpu":"14","values":[23.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"xsync","cpu":"1","values":[168.0,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"xsync","cpu":"4","values":[60.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"xsync","cpu":"14","values":[23.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"xsync","cpu":"1","values":[350.1,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"xsync","cpu":"4","values":[100.9,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"xsync","cpu":"14","values":[47.0,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"pb","cpu":"1","values":[166.5,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"pb","cpu":"4","values":[50.3,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"pb","cpu":"14","values":[20.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"pb","cpu":"1","values":[233.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"pb","cpu":"4","values":[58.9,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"pb","cpu":"14","values":[23.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"pb","cpu":"1","values":[326.8,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"pb","cpu":"4","values":[112.2,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"pb","cpu":"14","values":[48.5,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"sync.Map","cpu":"1","values":[461.9,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"sync.Map","cpu":"4","values":[138.5,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"sync.Map","cpu":"14","values":[61.2,null,null,3.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"sync.Map","cpu":"1","values":[462.6,null,null,3.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"sync.Map","cpu":"4","values":[130.3,null,null,3.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"sync.Map","cpu":"14","values":[57.3,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"sync.Map","cpu":"1","values":[565.3,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"sync.Map","cpu":"4","values":[165.3,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"sync.Map","cpu":"14","values":[77.5,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"haxmap","cpu":"1","values":[291.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"haxmap","cpu":"4","values":[108.3,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"haxmap","cpu":"14","values":[71.0,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"haxmap","cpu":"1","values":[280.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"haxmap","cpu":"4","values":[118.7,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"haxmap","cpu":"14","values":[67.3,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"haxmap","cpu":"1","values":[472.7,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"haxmap","cpu":"4","values":[148.5,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"haxmap","cpu":"14","values":[95.5,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"orcaman","cpu":"1","values":[183.4,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"orcaman","cpu":"4","values":[157.0,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r90"],"name":"orcaman","cpu":"14","values":[88.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"orcaman","cpu":"1","values":[175.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"orcaman","cpu":"4","values":[151.4,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r90"],"name":"orcaman","cpu":"14","values":[89.4,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"orcaman","cpu":"1","values":[381.8,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"orcaman","cpu":"4","values":[233.6,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r90"],"name":"orcaman","cpu":"14","values":[132.9,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"dsa","cpu":"1","values":[186.8,null,null,2.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"dsa","cpu":"4","values":[54.2,null,null,2.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"dsa","cpu":"14","values":[25.3,null,null,2.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"dsa","cpu":"1","values":[184.9,null,null,2.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"dsa","cpu":"4","values":[61.9,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"dsa","cpu":"14","values":[25.6,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"dsa","cpu":"1","values":[353.0,null,null,2.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"dsa","cpu":"4","values":[107.2,null,null,2.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"dsa","cpu":"14","values":[48.4,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"xsync","cpu":"1","values":[194.4,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"xsync","cpu":"4","values":[55.9,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"xsync","cpu":"14","values":[25.7,null,null,2.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"xsync","cpu":"1","values":[180.9,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"xsync","cpu":"4","values":[58.7,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"xsync","cpu":"14","values":[25.7,null,null,2.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"xsync","cpu":"1","values":[337.5,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"xsync","cpu":"4","values":[98.8,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"xsync","cpu":"14","values":[44.4,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"pb","cpu":"1","values":[161.3,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"pb","cpu":"4","values":[47.6,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"pb","cpu":"14","values":[20.5,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"pb","cpu":"1","values":[166.0,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"pb","cpu":"4","values":[53.2,null,null,1.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"pb","cpu":"14","values":[26.4,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"pb","cpu":"1","values":[312.1,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"pb","cpu":"4","values":[103.4,null,null,1.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"pb","cpu":"14","values":[43.9,null,null,1.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"sync.Map","cpu":"1","values":[462.3,null,null,8.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"sync.Map","cpu":"4","values":[125.4,null,null,8.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"sync.Map","cpu":"14","values":[55.4,null,null,8.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"sync.Map","cpu":"1","values":[426.2,null,null,8.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"sync.Map","cpu":"4","values":[126.1,null,null,8.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"sync.Map","cpu":"14","values":[55.6,null,null,9.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"sync.Map","cpu":"1","values":[617.7,null,null,9.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"sync.Map","cpu":"4","values":[166.1,null,null,9.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"sync.Map","cpu":"14","values":[75.9,null,null,9.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"haxmap","cpu":"1","values":[342.2,null,null,2.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"haxmap","cpu":"4","values":[119.7,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"haxmap","cpu":"14","values":[89.3,null,null,3.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"haxmap","cpu":"1","values":[358.1,null,null,2.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"haxmap","cpu":"4","values":[126.2,null,null,3.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"haxmap","cpu":"14","values":[85.7,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"haxmap","cpu":"1","values":[528.9,null,null,2.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"haxmap","cpu":"4","values":[167.3,null,null,3.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"haxmap","cpu":"14","values":[102.0,null,null,3.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"orcaman","cpu":"1","values":[180.8,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"orcaman","cpu":"4","values":[188.5,null,null,0.0,0.0]},{"path":["Mix","int","1000000","r75"],"name":"orcaman","cpu":"14","values":[104.3,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"orcaman","cpu":"1","values":[199.8,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"orcaman","cpu":"4","values":[190.1,null,null,0.0,0.0]},{"path":["Mix","randomint","1000000","r75"],"name":"orcaman","cpu":"14","values":[102.1,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"orcaman","cpu":"1","values":[369.7,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"orcaman","cpu":"4","values":[296.9,null,null,0.0,0.0]},{"path":["Mix","string","1000000","r75"],"name":"orcaman","cpu":"14","values":[165.7,null,null,0.0,0.0]},{"path":["Insert","int"],"name":"dsa","cpu":"1","values":[235.7,null,null,89.0,1.0]},{"path":["Insert","int"],"name":"dsa","cpu":"4","values":[120.5,null,null,78.0,1.0]},{"path":["Insert","int"],"name":"dsa","cpu":"14","values":[91.1,null,null,75.0,1.0]},{"path":["Insert","randomint"],"name":"dsa","cpu":"1","values":[223.4,null,null,89.0,1.0]},{"path":["Insert","randomint"],"name":"dsa","cpu":"4","values":[110.5,null,null,83.0,1.0]},{"path":["Insert","randomint"],"name":"dsa","cpu":"14","values":[92.2,null,null,75.0,1.0]},{"path":["Insert","int"],"name":"xsync","cpu":"1","values":[209.0,null,null,89.0,1.0]},{"path":["Insert","int"],"name":"xsync","cpu":"4","values":[95.7,null,null,71.0,1.0]},{"path":["Insert","int"],"name":"xsync","cpu":"14","values":[75.4,null,null,66.0,1.0]},{"path":["Insert","randomint"],"name":"xsync","cpu":"1","values":[201.3,null,null,89.0,1.0]},{"path":["Insert","randomint"],"name":"xsync","cpu":"4","values":[92.1,null,null,70.0,1.0]},{"path":["Insert","randomint"],"name":"xsync","cpu":"14","values":[77.2,null,null,70.0,1.0]},{"path":["Insert","int"],"name":"pb","cpu":"1","values":[51.7,null,null,48.0,1.0]},{"path":["Insert","int"],"name":"pb","cpu":"4","values":[74.7,null,null,37.0,1.0]},{"path":["Insert","int"],"name":"pb","cpu":"14","values":[91.2,null,null,42.0,1.0]},{"path":["Insert","randomint"],"name":"pb","cpu":"1","values":[238.5,null,null,72.0,1.0]},{"path":["Insert","randomint"],"name":"pb","cpu":"4","values":[120.3,null,null,66.0,1.0]},{"path":["Insert","randomint"],"name":"pb","cpu":"14","values":[74.7,null,null,71.0,1.0]},{"path":["Insert","int"],"name":"sync.Map","cpu":"1","values":[355.6,null,null,113.0,2.0]},{"path":["Insert","int"],"name":"sync.Map","cpu":"4","values":[124.8,null,null,118.0,2.0]},{"path":["Insert","int"],"name":"sync.Map","cpu":"14","values":[70.3,null,null,114.0,2.0]},{"path":["Insert","randomint"],"name":"sync.Map","cpu":"1","values":[367.0,null,null,113.0,2.0]},{"path":["Insert","randomint"],"name":"sync.Map","cpu":"4","values":[127.9,null,null,118.0,2.0]},{"path":["Insert","randomint"],"name":"sync.Map","cpu":"14","values":[68.9,null,null,114.0,2.0]},{"path":["Insert","int"],"name":"haxmap","cpu":"1","values":[808.2,null,null,93.0,2.0]},{"path":["Insert","int"],"name":"haxmap","cpu":"4","values":[389.8,null,null,89.0,2.0]},{"path":["Insert","int"],"name":"haxmap","cpu":"14","values":[627.2,null,null,89.0,2.0]},{"path":["Insert","randomint"],"name":"haxmap","cpu":"1","values":[804.0,null,null,89.0,2.0]},{"path":["Insert","randomint"],"name":"haxmap","cpu":"4","values":[329.7,null,null,89.0,2.0]},{"path":["Insert","randomint"],"name":"haxmap","cpu":"14","values":[528.5,null,null,89.0,2.0]},{"path":["Insert","int"],"name":"orcaman","cpu":"1","values":[163.4,null,null,48.0,0.0]},{"path":["Insert","int"],"name":"orcaman","cpu":"4","values":[130.0,null,null,71.0,0.0]},{"path":["Insert","int"],"name":"orcaman","cpu":"14","values":[103.2,null,null,68.0,0.0]},{"path":["Insert","randomint"],"name":"orcaman","cpu":"1","values":[163.9,null,null,46.0,0.0]},{"path":["Insert","randomint"],"name":"orcaman","cpu":"4","values":[152.0,null,null,50.0,0.0]},{"path":["Insert","randomint"],"name":"orcaman","cpu":"14","values":[110.2,null,null,66.0,0.0]},{"path":["Range","int"],"name":"dsa","cpu":"1","values":[1074216.0,22.0,null,66339.0,4146.0]},{"path":["Range","int"],"name":"dsa","cpu":"4","values":[249448.0,20.0,null,13881.0,867.0]},{"path":["Range","int"],"name":"dsa","cpu":"14","values":[173905.0,49.0,null,5301.0,331.0]},{"path":["Range","int"],"name":"xsync","cpu":"1","values":[1459616.0,29.0,null,125065.0,7816.0]},{"path":["Range","int"],"name":"xsync","cpu":"4","values":[384816.0,31.0,null,32052.0,2003.0]},{"path":["Range","int"],"name":"xsync","cpu":"14","values":[211191.0,59.0,null,8850.0,553.0]},{"path":["Range","int"],"name":"pb","cpu":"1","values":[663048.0,13.0,null,22350.0,1396.0]},{"path":["Range","int"],"name":"pb","cpu":"4","values":[153458.0,12.0,null,5300.0,331.0]},{"path":["Range","int"],"name":"pb","cpu":"14","values":[152051.0,43.0,null,2156.0,134.0]},{"path":["Range","int"],"name":"sync.Map","cpu":"1","values":[9921700.0,151.0,null,391248.0,16932.0]},{"path":["Range","int"],"name":"sync.Map","cpu":"4","values":[1251285.0,95.0,null,70600.0,2991.0]},{"path":["Range","int"],"name":"sync.Map","cpu":"14","values":[664946.0,178.0,null,30418.0,1254.0]},{"path":["Range","int"],"name":"haxmap","cpu":"1","values":[16984379.0,274.0,null,371998.0,19086.0]},{"path":["Range","int"],"name":"haxmap","cpu":"4","values":[2001983.0,156.0,null,73031.0,3504.0]},{"path":["Range","int"],"name":"haxmap","cpu":"14","values":[638111.0,178.0,null,15260.0,727.0]},{"path":["Range","int"],"name":"orcaman","cpu":"1","values":[752188.0,8.0,null,0.0,0.0]},{"path":["Range","int"],"name":"orcaman","cpu":"4","values":[245504.0,10.0,null,0.0,0.0]},{"path":["Range","int"],"name":"orcaman","cpu":"14","values":[178875.0,26.0,null,1.0,0.0]},{"path":["Memory","int"],"name":"dsa","cpu":"1","values":[879826385.0,null,50.0,89564680.0,1100634.0]},{"path":["Memory","int"],"name":"dsa","cpu":"4","values":[1308117509.0,null,50.0,89556104.0,1100648.0]},{"path":["Memory","int"],"name":"dsa","cpu":"14","values":[900532813.0,null,50.0,89580360.0,1100879.0]},{"path":["Memory","int"],"name":"xsync","cpu":"1","values":[839064608.0,null,50.0,89583968.0,1100925.0]},{"path":["Memory","int"],"name":"xsync","cpu":"4","values":[1225949592.0,null,50.0,89572704.0,1100749.0]},{"path":["Memory","int"],"name":"xsync","cpu":"14","values":[830932445.0,null,50.0,89584480.0,1100933.0]},{"path":["Memory","int"],"name":"pb","cpu":"1","values":[680312594.0,null,33.0,50018960.0,1007246.0]},{"path":["Memory","int"],"name":"pb","cpu":"4","values":[1121442708.0,null,33.0,50038112.0,1007554.0]},{"path":["Memory","int"],"name":"pb","cpu":"14","values":[668590843.0,null,33.0,50112400.0,1008706.0]},{"path":["Memory","int"],"name":"sync.Map","cpu":"1","values":[1071757390.0,null,122.0,121573872.0,3359361.0]},{"path":["Memory","int"],"name":"sync.Map","cpu":"4","values":[1606233600.0,null,122.0,121581056.0,3359406.0]},{"path":["Memory","int"],"name":"sync.Map","cpu":"14","values":[1033463794.0,null,122.0,121596736.0,3359504.0]},{"path":["Memory","int"],"name":"haxmap","cpu":"1","values":[1454127888.0,null,73.0,89560336.0,2000054.0]},{"path":["Memory","int"],"name":"haxmap","cpu":"4","values":[1934665890.0,null,73.0,89560336.0,2000054.0]},{"path":["Memory","int"],"name":"haxmap","cpu":"14","values":[1437412520.0,null,73.0,89560336.0,2000054.0]},{"path":["Memory","int"],"name":"orcaman","cpu":"1","values":[780618195.0,null,38.0,75422848.0,8819.0]},{"path":["Memory","int"],"name":"orcaman","cpu":"4","values":[1258184862.0,null,38.0,75570560.0,8835.0]},{"path":["Memory","int"],"name":"orcaman","cpu":"14","values":[793070815.0,null,38.0,75681344.0,8847.0]}]},{"title":"What Go had before it","against":"sync.Map, xsync.Map, a map behind an RWMutex, a map sharded over 64 locks","legend":"Measured before dsa-collections-map-concurrent was written, to decide whether it should be.","reproduce":"python3 tools/benchmarks.py syncmap --rounds 6 --benchtime 100ms --cpu 1,4,14","source":"benchmarks/syncmap/results.md","ours":[],"names":{},"labels":{"100%-reads":"reads only","99%-reads":"99% reads","75%-reads":"75% reads","50%-reads":"50% reads"},"kind":"explorer","measured":"syncmap: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 6 interleaved rounds of 100ms, cpu 1,4,14; load average during the runs 8.6 to 10.0. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Map","99%-reads"],"records":[{"path":["Map","100%-reads"],"name":"sync.Map","cpu":"1","values":[34.8,0.0,0.0]},{"path":["Map","100%-reads"],"name":"sync.Map","cpu":"4","values":[12.6,0.0,0.0]},{"path":["Map","100%-reads"],"name":"sync.Map","cpu":"14","values":[7.9,0.0,0.0]},{"path":["Map","100%-reads"],"name":"xsync.Map","cpu":"1","values":[13.3,0.0,0.0]},{"path":["Map","100%-reads"],"name":"xsync.Map","cpu":"4","values":[4.8,0.0,0.0]},{"path":["Map","100%-reads"],"name":"xsync.Map","cpu":"14","values":[2.6,0.0,0.0]},{"path":["Map","100%-reads"],"name":"RWMutex","cpu":"1","values":[25.7,0.0,0.0]},{"path":["Map","100%-reads"],"name":"RWMutex","cpu":"4","values":[96.8,0.0,0.0]},{"path":["Map","100%-reads"],"name":"RWMutex","cpu":"14","values":[102.8,0.0,0.0]},{"path":["Map","100%-reads"],"name":"sharded","cpu":"1","values":[23.4,0.0,0.0]},{"path":["Map","100%-reads"],"name":"sharded","cpu":"4","values":[18.8,0.0,0.0]},{"path":["Map","100%-reads"],"name":"sharded","cpu":"14","values":[27.9,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sync.Map","cpu":"1","values":[47.0,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sync.Map","cpu":"4","values":[13.9,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sync.Map","cpu":"14","values":[10.5,0.0,0.0]},{"path":["Map","99%-reads"],"name":"xsync.Map","cpu":"1","values":[15.2,0.0,0.0]},{"path":["Map","99%-reads"],"name":"xsync.Map","cpu":"4","values":[5.5,0.0,0.0]},{"path":["Map","99%-reads"],"name":"xsync.Map","cpu":"14","values":[3.4,0.0,0.0]},{"path":["Map","99%-reads"],"name":"RWMutex","cpu":"1","values":[27.2,0.0,0.0]},{"path":["Map","99%-reads"],"name":"RWMutex","cpu":"4","values":[58.4,0.0,0.0]},{"path":["Map","99%-reads"],"name":"RWMutex","cpu":"14","values":[77.1,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sharded","cpu":"1","values":[21.4,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sharded","cpu":"4","values":[23.4,0.0,0.0]},{"path":["Map","99%-reads"],"name":"sharded","cpu":"14","values":[24.4,0.0,0.0]},{"path":["Map","75%-reads"],"name":"sync.Map","cpu":"1","values":[103.3,15.0,0.0]},{"path":["Map","75%-reads"],"name":"sync.Map","cpu":"4","values":[30.6,15.0,0.0]},{"path":["Map","75%-reads"],"name":"sync.Map","cpu":"14","values":[23.5,15.0,0.0]},{"path":["Map","75%-reads"],"name":"xsync.Map","cpu":"1","values":[42.3,3.0,0.0]},{"path":["Map","75%-reads"],"name":"xsync.Map","cpu":"4","values":[11.9,4.0,0.0]},{"path":["Map","75%-reads"],"name":"xsync.Map","cpu":"14","values":[9.6,4.0,0.0]},{"path":["Map","75%-reads"],"name":"RWMutex","cpu":"1","values":[32.1,0.0,0.0]},{"path":["Map","75%-reads"],"name":"RWMutex","cpu":"4","values":[54.4,0.0,0.0]},{"path":["Map","75%-reads"],"name":"RWMutex","cpu":"14","values":[54.7,0.0,0.0]},{"path":["Map","75%-reads"],"name":"sharded","cpu":"1","values":[27.9,0.0,0.0]},{"path":["Map","75%-reads"],"name":"sharded","cpu":"4","values":[46.3,0.0,0.0]},{"path":["Map","75%-reads"],"name":"sharded","cpu":"14","values":[39.8,0.0,0.0]},{"path":["Map","50%-reads"],"name":"sync.Map","cpu":"1","values":[145.4,31.0,1.0]},{"path":["Map","50%-reads"],"name":"sync.Map","cpu":"4","values":[44.8,31.0,1.0]},{"path":["Map","50%-reads"],"name":"sync.Map","cpu":"14","values":[32.4,31.0,1.0]},{"path":["Map","50%-reads"],"name":"xsync.Map","cpu":"1","values":[55.7,8.0,0.0]},{"path":["Map","50%-reads"],"name":"xsync.Map","cpu":"4","values":[16.3,8.0,0.0]},{"path":["Map","50%-reads"],"name":"xsync.Map","cpu":"14","values":[13.6,8.0,0.0]},{"path":["Map","50%-reads"],"name":"RWMutex","cpu":"1","values":[41.4,0.0,0.0]},{"path":["Map","50%-reads"],"name":"RWMutex","cpu":"4","values":[65.8,0.0,0.0]},{"path":["Map","50%-reads"],"name":"RWMutex","cpu":"14","values":[64.8,0.0,0.0]},{"path":["Map","50%-reads"],"name":"sharded","cpu":"1","values":[28.8,0.0,0.0]},{"path":["Map","50%-reads"],"name":"sharded","cpu":"4","values":[55.1,0.0,0.0]},{"path":["Map","50%-reads"],"name":"sharded","cpu":"14","values":[49.5,0.0,0.0]}]}],"checked":[{"kind":"other libraries","against":"xsync v4 and llxisdsh/pb (benchmarks/maps, 14 CPUs, the method of puzpuzpuz/go-concurrent-map-bench)","result":"on par with xsync; llxisdsh/pb ahead in most mixes by 5 to 20%","source":"dsa-collections-map-concurrent/readme","quote":"On par with xsync, its design's nearest kin; llxisdsh/pb is ahead in most mixes by 5 to 20%"},{"kind":"other libraries","against":"sync.Map, haxmap, cornelk/hashmap and orcaman/concurrent-map (benchmarks/maps)","result":"sync.Map takes about twice as long; haxmap, cornelk/hashmap and orcaman/concurrent-map fall far behind once there are writes","source":"dsa-collections-map-concurrent/readme","quote":"sync.Map, a hash trie since Go 1.24, takes about twice as long; haxmap, cornelk/hashmap and orcaman/concurrent-map fall far behind once there are writes"},{"kind":"model","against":"Go's map, on random sequences, for keys of every hashing path","result":"","source":"dsa-collections-map-concurrent/readme","quote":"Checked: every operation against Go's map on random sequences, for keys of every hashing path"},{"kind":"tests","against":"counters incremented from 8 goroutines through Compute while the map grows, under -race at 1 to 16 CPUs","result":"every increment counted","source":"dsa-collections-map-concurrent/readme","quote":"under `-race` at 1 to 16 CPUs: counters incremented from 8 goroutines through `Compute` while the map grows, every increment counted"},{"kind":"tests","against":"readers while four writers add 200,000 keys, the table doubling twelve times","result":"keys stored before never missed","source":"dsa-collections-map-concurrent/readme","quote":"keys stored before never missed by readers while four writers add 200,000 more, the table doubling twelve times"},{"kind":"fuzzed","against":"LoadOrCompute racing on the same keys; Clear among writers; fuzzing","result":"LoadOrCompute computes once","source":"dsa-collections-map-concurrent/readme","quote":"`LoadOrCompute` racing on the same keys, computing once; `Clear` among writers; fuzzed."}]},"dsa-collections-queue-priority":{"group":"dsa-collections","line":"A priority queue (a binary heap), with the API Go has proposed.","tags":[],"keywords":"heap priority","summary":"A priority queue: a binary heap with the API Go's proposal for a generic heap gives (issue 77397), so code can move over if Go ships one.","snippet":"import priority \"goframework.io/dsa-collections-queue-priority\"\n\nq := priority.New(cmp.Compare[int])\nq.Insert(5)\nq.Insert(2)\nleast, ok := q.TakeMin() // 2\n\n// Values that know their place, to change their priority or take them out:\ntasks := priority.NewIndexed(byDeadline, func(task *Task, index int) { task.index = index }, priority.Stable())\ntasks.Changed(task.index)\ntasks.Delete(task.index)\n\n// For several goroutines: TakeMin waits for a value, its context or Close.\njobs := priority.NewBlocking(byPriority)\njob, err := jobs.TakeMin(ctx)","uses":[],"usedBy":["terminal-ui"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"","notWhen":"","measures":[],"checked":[{"kind":"model","against":"a sorted slice over random operations, every index verified","result":"","source":"dsa-collections-queue-priority/readme","quote":"Checked against a sorted slice over random operations with every index verified"},{"kind":"tests","against":"first-in-first-out among equals; producers and consumers under -race","result":"exactly-once delivery","source":"dsa-collections-queue-priority/readme","quote":"first-in-first-out among equals, exactly-once delivery with producers and consumers under `-race`"},{"kind":"fuzzed","against":"the queue, fuzzed","result":"","source":"dsa-collections-queue-priority/readme","quote":"exactly-once delivery with producers and consumers under `-race`, and fuzzing."},{"kind":"measured","against":"Insert plus TakeMin on a queue of 1000","result":"about 130 ns, no allocation","source":"dsa-collections-queue-priority/readme","quote":"Insert plus TakeMin on a queue of 1000 costs about 130 ns, with no allocation."}]},"dsa-collections-queue-ring":{"group":"dsa-collections","line":"A fixed-size FIFO for one goroutine, refusing or overwriting when full.","tags":[],"keywords":"ring buffer circular","summary":"A first-in, first-out queue of a fixed size for one goroutine: `Push` refuses when it is full, `PushOverwrite` drops the oldest value instead, for the last N lines of a log or the recent samples of a meter. For several goroutines, see dsa-collections-queue-concurrent.","snippet":"import ring \"goframework.io/dsa-collections-queue-ring\"\n\nrecent := ring.New[string](100)\nrecent.PushOverwrite(line) // the oldest goes when full\nfor line := range recent.All() {} // oldest first\nfor line := range recent.Backward() {}\noldest, ok := recent.Pop()","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"gammazero/deque, for a fixed size","notWhen":"","measures":[{"title":"Push and pop on one goroutine","against":"gammazero/deque, a buffered channel","legend":"","reproduce":"python3 tools/benchmarks.py queues --rounds 6 --cpu 4,14","source":"benchmarks/queues/results.md","ours":["dsa-ring"],"names":{"dsa-ring":"queue-ring","gammazero-deque":"gammazero/deque","channel":"buffered channel"},"labels":{},"kind":"explorer","measured":"queues: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 6 interleaved rounds of 200ms, cpu 4,14; load average during the runs 4.2 to 4.8. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Ring"],"records":[{"path":["Ring"],"name":"dsa-ring","cpu":"4","values":[2.6,0.0,0.0]},{"path":["Ring"],"name":"dsa-ring","cpu":"14","values":[2.5,0.0,0.0]},{"path":["Ring"],"name":"gammazero-deque","cpu":"4","values":[5.9,0.0,0.0]},{"path":["Ring"],"name":"gammazero-deque","cpu":"14","values":[5.9,0.0,0.0]},{"path":["Ring"],"name":"channel","cpu":"4","values":[31.8,0.0,0.0]},{"path":["Ring"],"name":"channel","cpu":"14","values":[30.8,0.0,0.0]}]}],"checked":[{"kind":"other libraries","against":"gammazero/deque and a buffered channel, keeping 512 values (benchmarks/queues)","result":"2.6 to 2.9 ns a push and pop, against 5.7 for gammazero/deque and 33 for a buffered channel","source":"dsa-collections-queue-ring/readme","quote":"(benchmarks/queues), keeping 512 values and pushing and popping one: about 2.6 to 2.9 ns, against 5.7 for gammazero/deque and 33 for a buffered channel."},{"kind":"model","against":"a slice, after random operations","result":"","source":"dsa-collections-queue-ring/readme","quote":"Checked against a slice after random operations, and fuzzed."},{"kind":"fuzzed","against":"the ring, fuzzed","result":"","source":"dsa-collections-queue-ring/readme","quote":"Checked against a slice after random operations, and fuzzed."}]},"dsa-collections-queue-concurrent":{"group":"dsa-collections","line":"A bounded FIFO for many producers and consumers.","tags":[],"keywords":"mpmc queue channel","summary":"A bounded first-in, first-out queue for any number of producers and consumers: a sequence per slot (Vyukov's algorithm), a power-of-two size, padded counters. Calls that never wait, calls that wait with a context, `Close`, batches, and a policy for a full queue: the vocabulary events uses for a slow subscriber.","snippet":"import concurrent \"goframework.io/dsa-collections-queue-concurrent\"\n\njobs := concurrent.New[Job](256, concurrent.Block)\n\ngo func() {\n\tfor {\n\t\tjob, err := jobs.Pop(ctx) // ErrClosed once closed and empty\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tjob.Run()\n\t}\n}()\n\nerr := jobs.Push(ctx, job) // waits for room, or ctx\njobs.Close()\n\nrecent := concurrent.New[Sample](1024, concurrent.DropOldest, concurrent.OnDrop(release))","uses":[],"usedBy":["events"],"thirdParty":[],"flows":["events"],"maturity":"verified","newBecause":"","replaces":"a buffered channel, when the producer must not wait","notWhen":"Producers may wait: a channel.","measures":[{"title":"In short, from its readme","against":"xsync v4, a channel","legend":"","reproduce":"","source":"dsa-collections-queue-concurrent/readme","ours":["this"],"names":{"this":"queue-concurrent"},"labels":{},"kind":"explorer","measured":"","how":"","metrics":["ns a value"],"metric":0,"default":[],"records":[{"path":["one producer, one consumer"],"name":"this","cpu":"","values":[20.0],"shown":["20–21 ns"]},{"path":["one producer, one consumer"],"name":"xsync v4","cpu":"","values":[23.0],"shown":["23 (its MPMC), 40 (its SPSC)"]},{"path":["one producer, one consumer"],"name":"channel","cpu":"","values":[57.0],"shown":["57"]},{"path":["four producers, four consumers"],"name":"this","cpu":"","values":[38.0],"shown":["38–45 waiting, 31–52 spinning"]},{"path":["four producers, four consumers"],"name":"xsync v4","cpu":"","values":[30.0],"shown":["30–49"]},{"path":["four producers, four consumers"],"name":"channel","cpu":"","values":[83.0],"shown":["83–92"]},{"path":["batches of 16, one and one"],"name":"this","cpu":"","values":[11.0],"shown":["11"]},{"path":["batches of 16, one and one"],"name":"xsync v4","cpu":"","values":[null],"shown":["–"]},{"path":["batches of 16, one and one"],"name":"channel","cpu":"","values":[null],"shown":["–"]},{"path":["every goroutine pushes then pops"],"name":"this","cpu":"","values":[104.0],"shown":["104–131"]},{"path":["every goroutine pushes then pops"],"name":"xsync v4","cpu":"","values":[94.0],"shown":["94–125"]},{"path":["every goroutine pushes then pops"],"name":"channel","cpu":"","values":[72.0],"shown":["72–76"]}],"header":["","this","xsync v4","channel"],"rows":[["one producer, one consumer","20–21 ns","23 (its MPMC), 40 (its SPSC)","57"],["four producers, four consumers","38–45 waiting, 31–52 spinning","30–49","83–92"],["batches of 16, one and one","11","–","–"],["every goroutine pushes then pops","104–131","94–125","72–76"]]},{"title":"Moving values between goroutines","against":"puzpuzpuz/xsync's queues, a buffered channel","legend":"Waits: sleeps on a signal when the queue is empty or full; spins: retries instead.","reproduce":"python3 tools/benchmarks.py queues --rounds 6 --cpu 4,14","source":"benchmarks/queues/results.md","ours":["dsa-blocking","dsa-spinning","dsa-batch16"],"names":{"dsa-blocking":"queue-concurrent, waits","dsa-spinning":"queue-concurrent, spins","dsa-batch16":"queue-concurrent, batches of 16","xsync-mpmc":"xsync MPMC queue","xsync-spsc":"xsync SPSC queue","channel":"buffered channel"},"labels":{"Move":"moving values","RoundTrip":"there and back","Batches":"in batches","1x1":"1 producer, 1 consumer","4x4":"4 producers, 4 consumers"},"kind":"explorer","measured":"queues: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 6 interleaved rounds of 200ms, cpu 4,14; load average during the runs 4.2 to 4.8. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Move","4x4"],"records":[{"path":["Move","1x1"],"name":"dsa-blocking","cpu":"4","values":[22.7,0.0,0.0]},{"path":["Move","1x1"],"name":"dsa-blocking","cpu":"14","values":[20.5,0.0,0.0]},{"path":["Move","1x1"],"name":"dsa-spinning","cpu":"4","values":[24.3,0.0,0.0]},{"path":["Move","1x1"],"name":"dsa-spinning","cpu":"14","values":[25.7,0.0,0.0]},{"path":["Move","1x1"],"name":"channel","cpu":"4","values":[52.5,0.0,0.0]},{"path":["Move","1x1"],"name":"channel","cpu":"14","values":[53.2,0.0,0.0]},{"path":["Move","1x1"],"name":"xsync-mpmc","cpu":"4","values":[23.6,0.0,0.0]},{"path":["Move","1x1"],"name":"xsync-mpmc","cpu":"14","values":[24.1,0.0,0.0]},{"path":["Move","1x1"],"name":"xsync-spsc","cpu":"4","values":[39.0,0.0,0.0]},{"path":["Move","1x1"],"name":"xsync-spsc","cpu":"14","values":[37.6,0.0,0.0]},{"path":["Move","4x4"],"name":"dsa-blocking","cpu":"4","values":[35.6,0.0,0.0]},{"path":["Move","4x4"],"name":"dsa-blocking","cpu":"14","values":[43.8,0.0,0.0]},{"path":["Move","4x4"],"name":"dsa-spinning","cpu":"4","values":[31.3,0.0,0.0]},{"path":["Move","4x4"],"name":"dsa-spinning","cpu":"14","values":[49.5,0.0,0.0]},{"path":["Move","4x4"],"name":"channel","cpu":"4","values":[77.8,0.0,0.0]},{"path":["Move","4x4"],"name":"channel","cpu":"14","values":[86.3,0.0,0.0]},{"path":["Move","4x4"],"name":"xsync-mpmc","cpu":"4","values":[26.9,0.0,0.0]},{"path":["Move","4x4"],"name":"xsync-mpmc","cpu":"14","values":[48.6,0.0,0.0]},{"path":["Batches","1x1"],"name":"dsa-batch16","cpu":"4","values":[11.5,0.0,0.0]},{"path":["Batches","1x1"],"name":"dsa-batch16","cpu":"14","values":[11.1,0.0,0.0]},{"path":["RoundTrip"],"name":"dsa-blocking","cpu":"4","values":[102.2,0.0,0.0]},{"path":["RoundTrip"],"name":"dsa-blocking","cpu":"14","values":[124.7,0.0,0.0]},{"path":["RoundTrip"],"name":"dsa-spinning","cpu":"4","values":[106.0,0.0,0.0]},{"path":["RoundTrip"],"name":"dsa-spinning","cpu":"14","values":[133.6,0.0,0.0]},{"path":["RoundTrip"],"name":"channel","cpu":"4","values":[73.9,0.0,0.0]},{"path":["RoundTrip"],"name":"channel","cpu":"14","values":[73.6,0.0,0.0]},{"path":["RoundTrip"],"name":"xsync-mpmc","cpu":"4","values":[92.4,0.0,0.0]},{"path":["RoundTrip"],"name":"xsync-mpmc","cpu":"14","values":[119.2,0.0,0.0]}]}],"checked":[{"kind":"model","against":"a slice after random operations, batches and policies included","result":"","source":"dsa-collections-queue-concurrent/readme","quote":"Checked: against a slice after random operations, batches and policies included"},{"kind":"tests","against":"one to eight producers and consumers under -race, capacities 1 to 64, blocking, spinning and in batches","result":"every value popped exactly once (or dropped once), each producer's values in order; closing loses nothing","source":"dsa-collections-queue-concurrent/readme","quote":"asserting every value queued is popped exactly once (or dropped, once) and each producer's values reach each consumer in order; closing while pushing and popping loses nothing"},{"kind":"systems","against":"arm64 under user-mode emulation (its atomics, not its weaker memory ordering)","result":"","source":"dsa-collections-queue-concurrent/readme","quote":"on arm64 under user-mode emulation (which runs arm64's atomics but not its weaker memory ordering"},{"kind":"other libraries","against":"xsync v4's queues (benchmarks/queues, 4 and 14 CPUs)","result":"on par with xsync's queue; 20 to 21 ns a value one to one, against 23 for xsync's MPMC queue and 40 for its SPSC","source":"dsa-collections-queue-concurrent/readme","quote":"On par with xsync's queue, which retries nothing (its `TryEnqueue` fails when it loses a race"},{"kind":"other libraries","against":"a channel (benchmarks/queues)","result":"twice as fast as a channel between producers and consumers; slower than one when each goroutine pushes and pops in turn","source":"dsa-collections-queue-concurrent/readme","quote":"twice as fast as a channel between producers and consumers, and slower than one when each goroutine pushes and pops in turn"},{"kind":"measured","against":"padded slots against unpadded, under contention","result":"padding 10 to 30% faster","source":"dsa-collections-queue-concurrent/readme","quote":"A slot takes the value's size plus 64 bytes (padding measured 10 to 30% faster under contention)."}]},"xtend-errors":{"group":"xtend","line":"Errors with a stack, attributes and a code; they work with errors.Is and As.","tags":[],"keywords":"errors stack","summary":"What Go's `errors` leaves out: a stack kept where an error is made, attributes for logs, a code for mapping errors to statuses, and walking the trees `errors.Join` and several `%w` build. Every error here works with `errors.Is`, `errors.As` and `errors.Unwrap`.","snippet":"import errorsx \"goframework.io/xtend-errors\"\n\nerr := errorsx.New(\"no route\") // with the stack here\nerr = errorsx.Errorf(\"reading %s: %w\", path, err) // as fmt.Errorf, %w included\nerr = errorsx.Wrap(err, \"loading the configuration\") // \"loading the configuration: reading ...: no route\"\nerr = errorsx.With(err, \"path\", path, \"attempt\", 2) // attributes, as slog takes them\nerr = errorsx.WithCode(err, \"config.unreadable\")\n\nfmt.Printf(\"%+v\\n\", err) // the message, then the stack: function, then file:line\nerrorsx.Stack(err) // []callstack.Frame, where the innermost error was made\nerrorsx.Attrs(err) // every attribute in the tree, outermost first\nerrorsx.Code(err) // \"config.unreadable\"\nfor each := range errorsx.All(err) {} // every error in the tree\nfor pathErr := range errorsx.AllAs[*fs.PathError](err) {} // those of a type","uses":["callstack"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"pkg/errors, frozen since 2020; the stacks of cockroachdb/errors","notWhen":"You need a message for users kept apart from the internal one: Public is not built yet (xtend-errors/readme).","measures":[],"checked":[{"kind":"tests","against":"errors.Is and errors.As through every wrapper and through joined trees","result":"","source":"xtend-errors/readme","quote":"Checked: `Is` and `As` through every wrapper and through joined trees"},{"kind":"other libraries","against":"pkg/errors' %+v layout","result":"","source":"xtend-errors/readme","quote":"`%+v` laid out as pkg/errors prints it."},{"kind":"measured","against":"the cost of keeping a stack","result":"about 210 ns and one allocation an error","source":"xtend-errors/readme","quote":"A stack is kept as program counters (about 210 ns and one allocation an error)"}]},"xtend-io":{"group":"xtend","line":"Lines ending in \\n, \\r\\n or \\r; copies that stop with a context; counting readers; a file in memory.","tags":[],"keywords":"io lines copy","summary":"What Go's `io` lacks: lines that end at `\\n`, `\\r\\n` or a lone `\\r`, for writers and for `bufio.Scanner`; a copy that stops when its context ends; readers and writers that count; a file in memory that behaves as an `*os.File`.","snippet":"writer := iox.NewLineWriter(func(line []byte) {\n\tfmt.Printf(\"worker: %s\\n\", line)\n}, 0)\nprocess, err := all.Start(spec.Definition{Program: \"worker\", Stdout: writer})\n\nlines := bufio.NewScanner(output)\nlines.Split(iox.ScanLines) // \"\\r\" ends a line too: progress bars\n\nn, err := iox.CopyContext(ctx, dst, src) // stops between reads once ctx ends\ncounted := iox.NewCountingReader(body) // counted.Count(), from any goroutine\n\nwav := iox.NewFile(nil) // Write, then WriteAt(header, 0)","uses":[],"usedBy":["system-process-samples"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"bufio.Scanner on programs' output; an io.Copy loop that checks a context; dsa-seekablebuffer","notWhen":"A Read already blocked: CopyContext stops between reads and does not interrupt one (xtend-io/readme).","measures":[],"checked":[{"kind":"reference","against":"Python's universal newlines, 3,000 random texts (testdata/python.py), for LineWriter and ScanLines","result":"","source":"xtend-io/readme","quote":"Checked: `LineWriter` and `ScanLines` against Python's universal newlines on 3,000 random texts (testdata/python.py)"},{"kind":"reference","against":"a real file in a temporary folder, 20 rounds of 2,000 random operations, on every OS, for File","result":"","source":"xtend-io/readme","quote":"`File` against a real file in a temporary folder, 20 rounds of 2,000 random operations, every result and error compared"},{"kind":"fuzzed","against":"LineWriter, fuzzed; the scanner reading a byte at a time","result":"","source":"xtend-io/readme","quote":"the scanner reading a byte at a time; `LineWriter` fuzzed"}]},"xtend-time":{"group":"xtend","line":"Calendar arithmetic: months that do not overflow, starts of periods in a time zone, ISO weeks.","tags":[],"keywords":"time date calendar","summary":"The calendar arithmetic Go's `time` leaves to its callers: months that do not overflow into the next, the starts of days, weeks, months, quarters and years in a time zone, calendar days across DST, ISO weeks back to dates.","snippet":"import timex \"goframework.io/xtend-time\"\n\ntimex.AddMonths(jan31, 1) // the end of February; time's AddDate gives 3 March\ntimex.AddYears(feb29, 1) // 28 February\ntimex.StartOfWeek(t, time.Monday) // in t's location; also StartOfDay, -Month, -Quarter, -Year\ntimex.DaysBetween(a, b) // calendar days, a 23-hour day counting as one\ntimex.ISOWeekStart(2026, 53, time.Local) // the Monday of 2026-W53","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"AddDate with the day clamped by hand; jinzhu/now's BeginningOf functions","notWhen":"You need durations in days and weeks, ISO 8601 periods or business days: they are planned, not built (xtend.md).","measures":[],"checked":[{"kind":"reference","against":"java.time: 51,000 answers (java 27, tz database 2026b) for random times from 1980 to 2022 and around every DST change of 22 zones","result":"","source":"xtend-time/readme","quote":"Checked against java.time: 51,000 answers it gave (testdata/Generate.java, java 27 with tz database 2026b)"},{"kind":"reference","against":"Go's own ISOWeek, every day from 1600 to 2400","result":"","source":"xtend-time/readme","quote":"ISO weeks against Go's own `ISOWeek` for every day from 1600 to 2400."}]},"xtend-strings":{"group":"xtend","line":"Words and case conversion that keep acronyms; natural sort order.","tags":[],"keywords":"camelcase snakecase strings","summary":"What Go's `strings` lacks: words and case conversion that keep acronyms, and natural order.","snippet":"import stringsx \"goframework.io/xtend-strings\"\n\nstringsx.Words(\"XMLHttpRequest\") // XML, Http, Request\nstringsx.Snake(\"userID\") // user_id\nstringsx.Pascal(\"http_server_url\") // HTTPServerURL\nstringsx.Camel(\"User ID\") // userID\nslices.SortFunc(names, stringsx.NaturalCompare) // img2.png before img10.png","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"iancoleman/strcase and ettle/strcase; maruel/natural","notWhen":"You need Unicode normalization (NFC, NFD): planned, not built (xtend.md); golang.org/x/text has it.","measures":[{"title":"Sorting 1,000 strings in natural order","against":"maruel/natural","legend":"Natural order: file2 before file10, digits compared as numbers.","reproduce":"python3 tools/benchmarks.py strings --rounds 6","source":"benchmarks/strings/results.md","ours":["xtend-strings"],"names":{"maruel-natural":"maruel/natural"},"labels":{},"kind":"explorer","measured":"strings: measured 2026-10-17","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 6 interleaved rounds of 200ms; load average during the runs 1.1 to 1.1. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Natural","sort1000"],"records":[{"path":["Natural","sort1000"],"name":"xtend-strings","cpu":"","values":[153442.0,16387.0,1.0]},{"path":["Natural","sort1000"],"name":"maruel-natural","cpu":"","values":[273385.0,16384.0,1.0]}]}],"checked":[{"kind":"test suite","against":"heck's own case tables, 113 cases (testdata/heck.txt)","result":"","source":"xtend-strings/readme","quote":"Checked: heck's own case tables (113 cases, testdata/heck.txt); fuzzed"},{"kind":"model","against":"a model, 200,000 random pairs, for NaturalCompare","result":"","source":"xtend-strings/readme","quote":"`NaturalCompare` against a model on 200,000 random pairs, fuzzed as a total order"},{"kind":"other libraries","against":"maruel/natural, 500,000 random names (benchmarks/strings)","result":"sorts 1,000 names in 228 µs where maruel/natural takes 499 µs, without allocating","source":"xtend-strings/readme","quote":"against maruel/natural on 500,000 random names (benchmarks/strings), which sorts 1,000 names in 499 µs where this takes 228 µs, without allocating."},{"kind":"other libraries","against":"maruel/natural, where both are defined (no leading zeros)","result":"","source":"xtend.md","quote":"maruel/natural, which orders them by text and has equal distinct strings, was compared where both are defined (no leading zeros)"},{"kind":"fuzzed","against":"NaturalCompare, fuzzed as a total order","result":"","source":"xtend-strings/readme","quote":"random pairs, fuzzed as a total order"}]},"xtend-math":{"group":"xtend","line":"Integer arithmetic and conversions that report overflow; numbers written for people.","tags":[],"keywords":"overflow humanize bytes","summary":"Integer arithmetic and conversions that report overflow, which Go leaves to callers (and its issues 24853 and 60858 declined), and numbers written for people.","snippet":"import mathx \"goframework.io/xtend-math\"\n\nsum, ok := mathx.Add(a, b) // false where a+b wraps\nn32, ok := mathx.Convert[int32](length) // false where it does not fit: what gosec's G115 asks for\nmathx.FormatBytes(1536) // 1.5 KiB\nsize, err := mathx.ParseBytes(\"1.5 GB\") // 1500000000\nmathx.Scale(2_400_000_000, 1000, \"Hz\", \"kHz\", \"MHz\", \"GHz\") // 2.4 GHz\nmathx.FormatCount(1234567) // 1,234,567\nmathx.Ordinal(22) // 22nd","uses":[],"usedBy":["metrics-exporters-console"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"dustin/go-humanize for sizes, counts and ordinals; overflow checks written by hand","notWhen":"Sizes in a configuration file: config-spec's Size reads and writes them exactly, so a file saved again keeps its number (xtend-math/readme).","measures":[],"checked":[{"kind":"reference","against":"math/big, for int64 and uint64 arithmetic, fuzzed","result":"","source":"xtend-math/readme","quote":"int64 and uint64 arithmetic fuzzed against `math/big`"},{"kind":"tests","against":"every pair of int8 and of uint8 values against arithmetic in int; every int16 and uint16 converted to 8 and 16 bits","result":"","source":"xtend-math/readme","quote":"Checked: every pair of int8 and of uint8 values against arithmetic in `int`; every int16 and uint16 converted to the types of 8 and 16 bits"},{"kind":"other libraries","against":"go-humanize: its own table and 300,000 random sizes for ParseBytes; FormatCount and Ordinal (benchmarks/sizes)","result":"","source":"xtend-math/readme","quote":"`ParseBytes` on go-humanize's own table and against go-humanize on 300,000 random sizes, `FormatCount` and `Ordinal` against it too (benchmarks/sizes)"},{"kind":"fuzzed","against":"what FormatBytes writes, read back","result":"","source":"xtend-math/readme","quote":"what `FormatBytes` writes reads back, fuzzed."}]},"xtend-json":{"group":"xtend","line":"JSON Pointer, Patch and Merge Patch on encoding/json/v2.","tags":[],"keywords":"json patch pointer rfc6902","summary":"Reading and changing JSON documents without structs: JSON Pointer (RFC 6901), JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7396), on `encoding/json/v2`'s `jsontext.Value`. Go has the pointer only as a type, `jsontext.Pointer`, with nothing to look one up.","snippet":"import jsonx \"goframework.io/xtend-json\"\n\nname, err := jsonx.Get(doc, \"/users/0/name\")\ndoc, err = jsonx.Set(doc, \"/users/0/name\", jsontext.Value(`\"Ada\"`))\ndoc, err = jsonx.Remove(doc, \"/users/0/draft\")\ndoc, err = jsonx.Patch(doc, jsontext.Value(`[{\"op\":\"test\",\"path\":\"/v\",\"value\":1},{\"op\":\"replace\",\"path\":\"/v\",\"value\":2}]`))\ndoc, err = jsonx.MergePatch(doc, jsontext.Value(`{\"title\":\"Hi\",\"draft\":null}`))\npatch, err := jsonx.CreateMergePatch(before, after)","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"evanphx/json-patch, for JSON Patch and Merge Patch","notWhen":"Your documents map onto Go types: encoding/json/v2 with structs is simpler. JSONPath queries and Diff are planned, not built (xtend.md).","measures":[],"checked":[{"kind":"test suite","against":"the json-patch-tests suite: the RFC's examples and the community's cases, errors included","result":"","source":"xtend-json/readme","quote":"Checked: the json-patch-tests suite (testdata/json-patch-tests, Apache 2.0; the RFC's examples and the community's cases, errors included)"},{"kind":"test suite","against":"RFC 6901's and RFC 7396's own examples","result":"","source":"xtend-json/readme","quote":"RFC 6901's and RFC 7396's own examples"},{"kind":"tests","against":"MergePatch(a, CreateMergePatch(a, b)) on 20,000 random documents","result":"gives b","source":"xtend-json/readme","quote":"`MergePatch(a, CreateMergePatch(a, b))` is b on 20,000 random documents"},{"kind":"fuzzed","against":"Patch, fuzzed","result":"","source":"xtend-json/readme","quote":"is b on 20,000 random documents; `Patch` fuzzed."}]},"xtend-random":{"group":"xtend","line":"Random strings without bias, tokens, IDs that sort by time, UUIDs.","tags":[],"keywords":"random uuid ulid token","summary":"Random text and IDs: strings over any alphabet without modulo bias, tokens of a given strength, IDs that sort by time, and what Go 1.27's `uuid` lacks.","snippet":"import randomx \"goframework.io/xtend-random\"\n\nrandomx.String(12, randomx.Unambiguous) // from crypto/rand, each symbol equally likely\nrandomx.Token(128) // 22 characters of base64url\nrandomx.SortableID() // a UUID v7 written as a ULID: 01K7... (26 characters)\nrandomx.NewV5(randomx.NamespaceDNS, \"example.com\")\nat, ok := randomx.Time(id) // when a v1, v6 or v7 was made","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"google/uuid for v3, v5 and the time of a v7; oklog/ulid; alphabet[b % n] written by hand","notWhen":"You need a v4 or v7 UUID: Go 1.27's uuid package has New, NewV4 and NewV7.","measures":[],"checked":[{"kind":"test suite","against":"RFC 9562's examples for versions 1, 3, 5, 6 and 7","result":"","source":"xtend-random/readme","quote":"RFC 9562's examples for versions 1, 3, 5, 6 and 7"},{"kind":"reference","against":"Python's uuid: v3 and v5 of 2,000 random names, the times of 300 UUIDs each of v1, v6 and v7 (testdata/python.py)","result":"","source":"xtend-random/readme","quote":"Python's `uuid` (testdata/python.py): v3 and v5 of 2,000 random names and the times of 300 UUIDs each of v1, v6 and v7"},{"kind":"other libraries","against":"oklog/ulid, 100,000 values (benchmarks/ids)","result":"both read each other's text","source":"xtend-random/readme","quote":"the ULID text against oklog/ulid on 100,000 values, both reading each other's (benchmarks/ids)"},{"kind":"tests","against":"a chi-square test of 400,000 draws from a 57-symbol alphabet","result":"","source":"xtend-random/readme","quote":"Checked: a chi-square test of 400,000 draws from a 57-symbol alphabet"},{"kind":"fuzzed","against":"fuzzing","result":"","source":"xtend-random/readme","quote":"both reading each other's (benchmarks/ids); fuzzed."}]},"xtend-channel":{"group":"xtend","line":"Merges, fans out, batches, debounces and throttles channels without leaking goroutines.","tags":[],"keywords":"channel debounce throttle","summary":"Channel plumbing: merging, fanning out, batching, debouncing and throttling, written once rather than in every program, where it often leaks a goroutine. For structured concurrency, see flow.","snippet":"import channelx \"goframework.io/xtend-channel\"\n\nfor value := range channelx.Merge(ctx, a, b, c) {}\nfor batch := range channelx.Batch(ctx, events, 100, time.Second) {} // 100 at a time, or a second after the first\nsaves := channelx.Debounce(ctx, edits, 500*time.Millisecond) // the last of each burst\nframes := channelx.Throttle(ctx, updates, time.Second/30)\ncopies := channelx.Tee(ctx, in, 2)\nfor value := range channelx.OrDone(ctx, in) {} // a range that ends with ctx","uses":[],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"merge, batch, debounce and throttle loops written in each program","notWhen":"Work in groups of tasks that start, fail and end together: use flow (xtend-channel/readme).","measures":[],"checked":[{"kind":"other libraries","against":"destel/rill's Batch, 300 random schedules (benchmarks/channels)","result":"the same batches at the same times","source":"xtend-channel/readme","quote":"`Batch` against destel/rill on 300 random schedules, the same batches at the same times (benchmarks/channels)"},{"kind":"tests","against":"every function under testing/synctest: exact times, a goroutine left blocked fails the test, also when ctx ends with nobody reading","result":"","source":"xtend-channel/readme","quote":"Checked: every function under `testing/synctest`, so times are exact and a goroutine left blocked fails the test"},{"kind":"tests","against":"-race","result":"","source":"xtend-channel/readme","quote":"also when ctx ends with nobody reading; `-race`"}]},"xtend-sync":{"group":"xtend","line":"Drop-in Mutex and RWMutex that report lock-order inversions before they deadlock.","tags":[],"keywords":"mutex deadlock lock","summary":"Mutexes that find deadlocks before they happen. `Mutex` and `RWMutex` are drop-in for sync's: they keep a graph of the order locks are taken in, and report an inversion (A then B here, B then A there, or a longer cycle) with the stacks that took them, even when the timing never made it hang; a goroutine locking what it holds, reading included; and a wait for a lock past a limit, with the holder's stack.","snippet":"import syncx \"goframework.io/xtend-sync\"\n\nvar accounts syncx.Mutex // as a sync.Mutex\nsyncx.OnReport(func(r syncx.Report) { panic(r) }) // by default, reports go to standard error\nsyncx.SetWaitLimit(10 * time.Second) // 30 seconds by default; 0 for none","uses":["callstack"],"usedBy":[],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"sasha-s/go-deadlock","notWhen":"In release builds: checking costs 3.8 µs a lock and unlock, against 12 ns for sync's (xtend-sync/readme). Build them with -tags syncx_plain.","measures":[{"title":"What checking lock order costs","against":"sync.Mutex, sasha-s/go-deadlock","legend":"Most of xtend-sync's cost is finding which goroutine locks, by parsing the runtime's stack header, since Go has no supported way; go-deadlock reads the runtime's private structure instead. Releases are built with the tag syncx_plain, which makes the types sync's own.","reproduce":"python3 tools/benchmarks.py locks --rounds 5 --cpu 8","source":"benchmarks/locks/results.md","ours":["xtend-sync"],"names":{"sync":"sync.Mutex","go-deadlock":"sasha-s/go-deadlock"},"labels":{"alone":"lock and unlock","nested":"two locks, nested","eight-goroutines":"8 goroutines, one lock"},"kind":"explorer","measured":"locks: measured 2026-10-16","how":"go version go1.27.2-X:nodwarf5 linux/amd64; Intel(R) Core(TM) Ultra 7 265U; 5 interleaved rounds of 200ms, cpu 8; load average during the runs 3.3 to 3.5. Written by tools/benchmarks.py.","metrics":["ns an operation","bytes allocated an operation","allocations an operation"],"metric":0,"default":["Locks","alone"],"records":[{"path":["Locks","alone"],"name":"sync","cpu":"8","values":[11.8,0.0,0.0]},{"path":["Locks","nested"],"name":"sync","cpu":"8","values":[20.6,0.0,0.0]},{"path":["Locks","eight-goroutines"],"name":"sync","cpu":"8","values":[78.5,0.0,0.0]},{"path":["Locks","alone"],"name":"xtend-sync","cpu":"8","values":[3771.0,448.0,5.0]},{"path":["Locks","nested"],"name":"xtend-sync","cpu":"8","values":[7582.0,912.0,10.0]},{"path":["Locks","eight-goroutines"],"name":"xtend-sync","cpu":"8","values":[5820.0,449.0,5.0]},{"path":["Locks","alone"],"name":"go-deadlock","cpu":"8","values":[555.9,24.0,1.0]},{"path":["Locks","nested"],"name":"go-deadlock","cpu":"8","values":[1465.0,144.0,4.0]},{"path":["Locks","eight-goroutines"],"name":"go-deadlock","cpu":"8","values":[634.3,18.0,0.0]}]}],"checked":[{"kind":"other libraries","against":"sync and sasha-s/go-deadlock (benchmarks/locks)","result":"a lock and unlock 3.8 µs alone and 7.6 µs two nested while checking, against 12 and 21 ns for sync and 0.56 and 1.5 µs for go-deadlock","source":"xtend-sync/readme","quote":"a lock and unlock takes 3.8 µs alone, 7.6 µs two nested (benchmarks/locks, against 12 and 21 ns for sync and 0.56 and 1.5 µs for sasha-s/go-deadlock)"},{"kind":"tests","against":"ABBA in two goroutines one after the other; five dining philosophers; recursive locking","result":"","source":"xtend-sync/readme","quote":"Checked: ABBA in two goroutines one after the other; five dining philosophers, a cycle of five; recursive locking"},{"kind":"tests","against":"a recursive read lock with a writer waiting; a long wait","result":"the long wait reported with where the holder took the lock","source":"xtend-sync/readme","quote":"a recursive read lock with a writer waiting, which would hang for good; a long wait, reported with where the holder took the lock"},{"kind":"tests","against":"eight goroutines locking in one order under -race; the syncx_plain build","result":"nothing reported; the tagged build's types are sync's","source":"xtend-sync/readme","quote":"eight goroutines in one order under `-race`, nothing reported; the tagged build's types are sync's"}]},"xtend-user":{"group":"xtend","line":"Who the process runs as: privileges, names, ids and groups.","tags":[],"keywords":"user uid root admin","summary":"Who this process runs as: whether it is privileged or could become so, and the user's names, ids and groups.","snippet":"import userx \"goframework.io/xtend-user\"\n\nif !userx.Privileged() && userx.Elevatable() {\n\tfmt.Println(\"run it again as an administrator\")\n}\nme, err := userx.Current() // Name, DisplayName, Home, UID, EffectiveID, GID, Groups\nname, ok := userx.UserName(uid) // remembered: a lookup can be slow","uses":[],"usedBy":["system-process-spec"],"thirdParty":[],"flows":[],"maturity":"verified","newBecause":"","replaces":"os/user, which without cgo reads only /etc/passwd and /etc/group on Linux and the BSDs; os.Geteuid() == 0 written by hand","notWhen":"You need to know whether the user may use sudo: Elevatable does not guess sudo rights (xtend-user/readme).","measures":[],"checked":[{"kind":"systems","against":"the system's own tools on every OS: id -u, id -ru, id -run and id -G on Unix; whoami and whoami /groups on Windows","result":"","source":"xtend-user/readme","quote":"Checked against the system's own tools on every OS: `id -u`, `id -ru`, `id -run` and `id -G` on Unix, `whoami` and `whoami /groups`"},{"kind":"systems","against":"the VMs as root, as a user, through a setuid-root copy and through sudo on Debian; Windows as an elevated administrator and through scheduled tasks","result":"","source":"xtend-user/readme","quote":"In the VMs as root, as a user, as a user through a setuid-root copy (from `/` on OpenBSD, whose /home is nosuid) and through sudo on Debian"},{"kind":"systems","against":"a filtered administrator's token on Windows; the Mac under sudo","result":"not exercised","source":"xtend-user/readme","quote":"Not exercised: a filtered administrator's token on Windows (the test VM's scheduled tasks at the LIMITED run level got full tokens) and the Mac under sudo"}]}},"about":{"draft":false,"title":"How goframework.io runs","intro":"One small Linux server and four programs, all written by the owner: simple-http-router in front (HTTPS with Let's Encrypt, at most 10 requests a second from each visitor), simple-http-fileserver for this site, simple-git-server for every module's code, `go get` and pushes over SSH, and simple-git-server-repo-viewer to browse that code, read only. A push to the website's dev branch is its deployment.","configsTitle":"The configuration","boxes":[{"id":"visitors","label":"Visitors","note":"browsers, go get, git clone","column":1,"row":1,"kind":"outside"},{"id":"owner","label":"The owner","note":"git push","column":1,"row":3,"kind":"outside"},{"id":"router","label":"simple-http-router","note":":80 to HTTPS, :443 TLS","column":2,"row":1,"kind":"service"},{"id":"fileserver","label":"simple-http-fileserver","note":"127.0.0.1:64080","column":3,"row":1,"kind":"service"},{"id":"viewer","label":"simple-git-server-repo-viewer","note":"127.0.0.1:64280, read only","column":3,"row":2,"kind":"service"},{"id":"git","label":"simple-git-server","note":"127.0.0.1:64180, SSH :2222","column":3,"row":3,"kind":"service"},{"id":"www","label":"/srv/www","note":"this website","column":4,"row":1,"kind":"store"},{"id":"repos","label":"/srv/git/repos","note":"every repository, bare","column":4,"row":3,"kind":"store"}],"edges":[{"from":"visitors","to":"router","label":"80, 443"},{"from":"router","to":"fileserver","label":"goframework.io"},{"from":"router","to":"viewer","label":"src.goframework.io"},{"from":"router","to":"git","label":"/git/, ?go-get=1"},{"from":"owner","to":"git","label":"SSH :2222"},{"from":"fileserver","to":"www","label":"serves"},{"from":"viewer","to":"repos","label":"reads"},{"from":"git","to":"repos","label":"reads, writes"},{"from":"repos","to":"www","label":"a push to dev deploys"}],"configs":[{"title":"Router config","path":"/etc/simple-http-router/config.json","note":"As on the server, but for the Let's Encrypt account's e-mail address, left out.","text":"{\n \"listeners\": {\n \"all-on-80\": {\n \"addr\": \":80\",\n \"redirect-to-https\": true\n },\n \"all-on-443\": {\n \"addr\": \":443\",\n \"tls\": true\n }\n },\n \"routing\": {\n \"all-on-80\": [\n {\n \"host\": \"goframework.io\",\n \"to\": \"127.0.0.1:64080\"\n },\n {\n \"host\": \"www.goframework.io\",\n \"to\": \"127.0.0.1:64080\"\n },\n {\n \"host\": \"src.goframework.io\",\n \"to\": \"127.0.0.1:64280\"\n }\n ],\n \"all-on-443\": {\n \"goframework.io\": [\n {\n \"path\": \"/git/\",\n \"to\": \"127.0.0.1:64180\"\n },\n {\n \"path\": \"/\",\n \"match-query\": {\n \"go-get\": \"1\"\n },\n \"to\": \"127.0.0.1:64180\"\n },\n {\n \"path\": \"/\",\n \"to\": \"127.0.0.1:64080\"\n }\n ],\n \"www.goframework.io\": [\n {\n \"path\": \"/\",\n \"to\": \"127.0.0.1:64080\"\n }\n ],\n \"src.goframework.io\": [\n {\n \"path\": \"/\",\n \"to\": \"127.0.0.1:64280\"\n }\n ]\n }\n },\n \"sites-config-defaults\": {\n \"cert\": {\n \"renew\": true,\n \"renew-account\": {\n \"email\": \"<the owner's address>\",\n \"cache-dir\": \"/var/lib/simple-http-router\",\n \"use-staging\": false\n }\n }\n },\n \"sites-config-overrides\": {\n \"goframework.io\": {\n \"rate-limit-rps\": 10,\n \"rate-limit-burst\": 50,\n \"redirects\": [\n {\n \"from\": \"/src/\",\n \"to\": \"https://src.goframework.io/\",\n \"status\": 301\n }\n ]\n },\n \"www.goframework.io\": {\n \"redirects\": [\n {\n \"from\": \"/\",\n \"to\": \"https://goframework.io/\",\n \"status\": 301\n }\n ]\n },\n \"src.goframework.io\": {\n \"rate-limit-rps\": 10,\n \"rate-limit-burst\": 50\n }\n }\n}"},{"title":"simple-http-router","path":"/etc/systemd/system/simple-http-router.service","note":"","text":"[Unit]\nDescription=simple-http-router (goframework.io front proxy, TLS)\nDocumentation=https://github.com/codemodify/simple-http-router\nAfter=network-online.target simple-http-fileserver.service simple-git-server.service\nWants=network-online.target\n\n[Service]\nType=notify\nUser=www-data\nGroup=www-data\nAmbientCapabilities=CAP_NET_BIND_SERVICE\nCapabilityBoundingSet=CAP_NET_BIND_SERVICE\nNoNewPrivileges=true\nExecStart=/usr/local/bin/simple-http-router -config /etc/simple-http-router/config.json\nExecReload=/bin/kill -HUP $MAINPID\nWorkingDirectory=/var/lib/simple-http-router\nRestart=on-failure\nRestartSec=5\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target"},{"title":"simple-git-server","path":"/etc/systemd/system/simple-git-server.service","note":"","text":"[Unit]\nDescription=simple-git-server (HTTP read + go-import + built-in SSH read/write)\nDocumentation=https://github.com/codemodify/simple-git-server\nAfter=network.target\n\n[Service]\nType=simple\nUser=git\nGroup=git\nWorkingDirectory=/srv/git/repos\nEnvironment=PATH=/usr/local/bin:/usr/bin:/bin\nExecStart=/usr/local/bin/simple-git-server \\\n --git-repos-folder /srv/git/repos \\\n --enable-http-read --http-on 127.0.0.1:64180 \\\n --http-git-repos-prefix /git/ \\\n --enable-http-write=false \\\n --enable-go-import --go-import-domain goframework.io \\\n --enable-ssh-read --enable-ssh-write --ssh-on :2222 \\\n --ssh-host-key /srv/git/keys/host_key \\\n --ssh-authorized-keys /srv/git/authorized_keys\nRestart=on-failure\nRestartSec=10\nAmbientCapabilities=\nNoNewPrivileges=true\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target"},{"title":"simple-git-server-repo-viewer","path":"/etc/systemd/system/simple-git-server-repo-viewer.service","note":"","text":"[Unit]\nDescription=simple-git-server-repo-viewer (read-only web view of /srv/git/repos at https://goframework.io/src/)\nDocumentation=https://github.com/codemodify/simple-git-server-repo-viewer\nAfter=network.target\n\n[Service]\nType=simple\n# Runs as the repo owner because git refuses repos owned by another user\n# (the viewer ignores system/user git config, so safe.directory cannot help);\n# the sandbox below makes the repos read-only and hides the SSH keys.\nUser=git\nGroup=git\nWorkingDirectory=/srv/git/repos\nEnvironment=PATH=/usr/local/bin:/usr/bin:/bin\nExecStart=/usr/local/bin/simple-git-server-repo-viewer \\\n --git-repos-folder /srv/git/repos \\\n --http-on 127.0.0.1:64280 \\\n --http-prefix / \\\n --title goframework.io \\\n --clone-url \"https://goframework.io/git/{repo}.git\" \\\n --clone-url \"ssh://git@goframework.io:2222/{repo}.git\"\nRestart=on-failure\nRestartSec=10\nStandardOutput=journal\nStandardError=journal\n\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nPrivateTmp=true\nPrivateDevices=true\nReadOnlyPaths=/srv/git/repos\nInaccessiblePaths=/srv/git/keys /srv/git/authorized_keys\nCapabilityBoundingSet=\nAmbientCapabilities=\nRestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX\nRestrictNamespaces=true\nRestrictSUIDSGID=true\nRestrictRealtime=true\nLockPersonality=true\nProtectKernelTunables=true\nProtectKernelModules=true\nProtectKernelLogs=true\nProtectControlGroups=true\nProtectClock=true\nProtectHostname=true\nSystemCallArchitectures=native\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target"},{"title":"simple-http-fileserver","path":"/etc/systemd/system/simple-http-fileserver.service","note":"","text":"[Unit]\nDescription=simple-http-fileserver (goframework.io static site)\nDocumentation=https://github.com/codemodify/simple-http-fileserver\nAfter=network.target\n\n[Service]\nType=simple\nUser=www\nGroup=www\nWorkingDirectory=/srv/www\nExecStart=/usr/local/bin/simple-http-fileserver --folder /srv/www --http-on 127.0.0.1:64080\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target"},{"title":"Deploy hook","path":"/srv/git/repos/goframework-io-www.git/hooks/post-receive","note":"","text":"#!/bin/sh\n# Deploy pushes to dev into /srv/www (served by simple-http-fileserver)\nwhile read old new ref; do\n if [ \"$ref\" = \"refs/heads/dev\" ]; then\n git --work-tree=/srv/www --git-dir=\"$GIT_DIR\" checkout -f dev\n git --work-tree=/srv/www --git-dir=\"$GIT_DIR\" clean -fdq\n echo \"Deployed dev to https://goframework.io/\"\n fi\ndone"}]}};