v1.1.0 β Thread Distribution & Startup Time Trend
Two new diagnostics, one architecture migration that fixes a real bug, and a UI pass on the HTML report. Purely additive β schemaVersion stays 1, no config property renamed, no new dependency.
π§΅ Thread Distribution β did anything actually leave main?
spring.beans.instantiate startup steps now carry a threadName tag captured at step-creation time β the only point where the instantiating thread is still on the stack. Reading Thread.currentThread() at analysis time, as the old code did, always returned main.
The report gains a threadDistribution section mapping each thread to its beans plus a per-thread count. The Timing tab renders it as a donut chart and a table ordered by bean count.
"threadDistribution": {
"perThread": { "main": ["dataSource", "entityManagerFactory"], "app-bg-1": ["warmCacheBean"] },
"counts": { "main": 2, "app-bg-1": 1 }
}The honest framing matters here: Spring does not parallelise bean initialisation by default, and no Boot version has. Background init is opt-in per bean and needs both @Bean(bootstrap = Bean.Bootstrap.BACKGROUND) (Framework 6.2+ / Boot 3.4+) and a bootstrapExecutor. Without both, a 600-bean application legitimately reports 100% on main β the card shows a parallel init not active pill rather than implying something is wrong. The one case where this chart really earns its place: you marked a slow bean BACKGROUND and gave it an executor β did it actually move off main, or is it silently still blocking startup?
Validated on Spring Cloud Config Server (616 beans, Boot 4.1): 610 on main, 1 on configserver-bg-1 β the single @Bean(bootstrap = BACKGROUND).
π Startup Time Trend β catch creep before the gate trips
Every run with wiredoctor.baseline-write=true appends {timestamp, totalStartupMs, slowBeanCount} to trendHistory[] in wiredoctor-baseline.json, capped by wiredoctor.trend-history-size (default 30, 0 = unlimited). The Timing tab renders a sparkline, so a +50ms/week drift becomes visible long before the startup-time gate trips on a single bad run.
Worth knowing: the sparkline appears only in reports from a baseline-write=true run, and only from the second write onward β trendHistory[] lives in the baseline file, not in the per-run report. A nightly baseline-refresh job is the natural place to look.
π Report UI
Still plain HTML/CSS/JS in one self-contained file β no libraries added.
- Overview charts β a 5-bucket bean instantiation-time histogram (
0/1β9/10β49/50β99/β₯100ms), a bean-role stacked bar (ROLE_APPLICATION/SUPPORT/INFRASTRUCTURE), and an autoconfiguration-outcome stacked bar (matched / unconditional / notMatched / excluded). Every column carries its exact count, so the slow tail β always the smallest bucket and always the one you care about β never renders as an invisible sliver. Palettes are colour-blind validated against the dark surface, and every series is legend-labelled so identity is never colour-alone. - Responsive sidebar β under 860px the sticky nav becomes an off-canvas drawer (hamburger, backdrop,
Escape, auto-close on tab select). KPIs, cards, and header chips reflow at 860px/560px instead of overflowing. Motion respectsprefers-reduced-motion. - Table polish β sticky uppercase headers, zebra rows, tabular-aligned numeric columns, hover row highlight with an accent edge marker, and horizontal scroll confined to the card instead of the page.
π§ Changed
WireDoctorStartupListener(ApplicationListener) βWireDoctorEnvironmentPostProcessor(EnvironmentPostProcessor) +WireDoctorBufferingApplicationStartup(extendsBufferingApplicationStartup). The processor now runs before context preparation, soApplicationStartupis installed at the correct lifecycle phase; the subclass keeps Actuator/actuator/startuptype checks intact.beanThreadMapis nowMap<String, Set<String>>, so a prototype instantiated on several threads keeps all of them instead of overwriting.
π Fixed
- Politeness contract restored. The processor only replaces
ApplicationStartup.DEFAULTornull. A plainBufferingApplicationStartupis transparently upgraded (same type, extra thread tagging); a foreign implementation is never overwritten βthreadDistributionis omitted with a warning and every other analysis still runs. - Compat fix: a new test used
JsonNode.values(), which only exists on the Jackson shipped with Boot 3.5+/4.x. Now iteratesJsonNodedirectly, which works on Jackson 2.x and 3.x alike. - Report layout: fan-in counts no longer wrap one digit per line; two-column rows no longer stretch the shorter card.
- Docs: five false claims removed from
docs/thread-distribution.md(including a non-existent donut hover interaction and inventedtask-1/task-2thread names);wiredoctor.trend.history-sizecorrected towiredoctor.trend-history-size.
β Verification
- 253 tests green β 242 autoconfigure + 11 actuator, 0 failures. 7 new tests cover trend-history carry-forward and capping, thread tagging, and the EnvironmentPostProcessor politeness paths.
- Compatibility matrix green across Boot 2.7.18 / 3.3.13 / 3.5.15 / 4.0.1 on Java 17 / 21 / 25 (12 combinations).
- Report UI checked at 1440px / 430px / 390px on every tab.
π¦ Install
<dependency>
<groupId>io.github.ddsha441981</groupId>
<artifactId>wiredoctor-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>π Documentation
Full docs are live at https://ddsha441981.github.io/wiredoctor/ β including the two new v1.1.0 guides, Thread Distribution and Startup Time Trend.
A set of links on the docs site was broken at the moment this release was published and was fixed immediately after in #42: doc-to-doc links pointed at .md, which serves raw Markdown source instead of the rendered page, and the sample-report link 404'd because sample/ lives at the repo root while Pages publishes only docs/. The start.spring.io sample report is now published and browsable: open it live.
Full changelog: v1.0.0...v1.1.0