v1.1.3 β report readability: three charts and drill-downs on every ranking - #46
Merged
Conversation
The overview KPI printed "0 CGLIB / 0 JDK" as fact while the JSON already recorded notInstantiatedSkipped β on the petclinic run, 3 beans that were never instantiated and therefore never inspected. Renders only when the count is > 0.
lazySuggestions had zero references in the template: the only actionable advice WireDoctor produces was console- and JSON-only since 0.3.0. The HTML showed cycles as a number and a graph filter, never the bean names. New sidebar tab (third, with a red count badge), one card per cycle: - the chain, first bean repeated so it reads as a loop - every member clickable through to that node in the graph - the @lazy cut with the lowest downstreamImpact, alternatives dimmed below - no suggestion -> says only that, never that the cycle is unbreakable The overview KPI now links to the tab.
startupSlowestSteps carries tags {beanName, beanType, threadName}; the table
used none of them, so the twelve slowest rows read "spring.beans.instantiate"
twelve times while the tag beside each one named the actual bean.
- Bean column, present only when at least one row is tagged
- beanType and threadName behind a native <details> (long, and thread only
matters when it is not main)
- off-main thread also shown as a pill on the row itself
- untagged steps (spring.context.refresh, ...) render exactly as before
Adds the shared .drill style used by the remaining drill-downs.
smellTable took the strings 'dependents' and 'dependencies' as column labels while the arrays with those exact names sat unused in the same objects β the table said a bean has 4 dependents and never which 4. Click the bean name to expand them, capped at 30 with a pointer to the JSON for the rest.
cumulativeMs had zero references. A 369ms bean at the end of a 1111ms chain means something different from the same 369ms at the start, and the chain chips showed only ownMs. Dim running total on each chip, cumulative in the timeline row tooltip, and the explaining note renders only when the field is present β pre-1.1.3 reports keep exactly the chain they had.
orphanBeans, cglibBeans and jdkBeans were read only to colour graph nodes and produce a count β the names never appeared as text anywhere in the report. Both are now expandable rows in the composition card, capped at 30 names with a pointer to the JSON. Orphans keep their heuristic caveat, spelled out where the list is: nothing declares a dependency on them, which is not the same as unused. nameList/NAME_LIST_CAP moved up to the shared helpers β the overview renders before the smells tab, so declaring them there was a TDZ error on any report with at least one orphan.
node --check only proves the template's JS parses. This runs it against a real wiredoctor-report.json and fails on any runtime error or empty pane β it is what caught the NAME_LIST_CAP TDZ error in the previous commit. Renders without the vis-network bundle, so the no-library degrade path is covered every run. Skips cleanly when jsdom is absent, so it never becomes a build dependency.
The slow-bean table answers "which bean is slowest". It does not answer the
question a developer actually has before deciding to spend an afternoon on it:
is the cost concentrated in a handful of beans, or spread thin across hundreds?
startupPareto() sorts beanTimings descending and plots the cumulative share.
The knee β the bean where the running total crosses 80% β is marked with a
dashed line and labelled, so the count is readable without counting pixels.
On petclinic that is 33 of 274 beans.
The caption does not present the sum of beanTimings as wall-clock time.
spring.beans.instantiate steps nest, and the analyzer keeps max-per-bean, so a
bean's number includes the beans its constructor triggered and the sum counts
nested work more than once. The share is a valid ranking of where cost
concentrates; the total is not a budget, and a dim line under the curve says so.
Degrades to the empty state below 5 timed beans (a curve through 3 points
claims a distribution that isn't there) β verified against a report with
beanTimings: {}.
highFanIn and highFanOut are ten rows each. They name the extremes and say nothing about the distribution, which is the question an architect actually asks: is coupling concentrated in three god beans, or is this a flat field of leaves? One dot per (fan-out, fan-in) position answers it. Bucketed by position, because 95 petclinic beans sit at fan-out 0 / fan-in 1 and 95 overlapping identical dots would be a lie told with ink. Dot area is how many beans share the spot; the tooltip names the first six. Square-root axes rather than the clamped edge band the plan called for. Clamping the top decile puts a fan-in 25 god bean in the same band as a fan-in 4 bean and destroys the only reading worth having. Every tick prints its real count, so the scale is visible instead of silently applied. The dashed curve is I = Ce/(Ca+Ce) = 0.8 β the same threshold the unstable table uses, so chart and table cannot disagree on screen. Framework beans are dimmed, not dropped, with one checkbox to hide them; the caption states the heuristic's blind spot (a camelCase framework bean reads as user-defined). isFw moves up to the shared helpers: the smells tab renders long before the graph section, so reading it in place was the NAME_LIST_CAP TDZ error again. The render check now exercises the toggle, which is the template's only interactive re-render.
trendHistory carried timestamp, totalStartupMs and slowBeanCount. That is enough to draw a line and not enough to read it: a run that got 800ms slower because someone added forty beans looks identical to one that got 800ms slower for no reason, and only the second is a bug. beanCount is the number the dependencies section already reports, so the two cannot disagree. Carried forward from prior entries only when present β a baseline written before 1.1.3 keeps its entries and simply has no count on them, rather than being back-filled with a number nobody measured. Prepares the trend chart's explained / unexplained verdict. 259 tests.
The sparkline plotted one line and could not answer the only question worth asking about it. An app that added forty beans and an app that added none draw the identical rising curve, and only the second one is a bug. Bean count now shares the plot on its own labelled axis (dashed, faint), and the signal is the band between two consecutive runs rather than the points: red regressed past the gate's thresholds with bean growth under 1% amber regressed, and the app grew β or one endpoint has no bean count green improved past the thresholds plain inside the thresholds Thresholds come from gates.config in the report, not from a constant, so the chart cannot call something a regression that the gate lets pass. The fallback for a pre-0.7.1 report without that section is WireDoctorProperties' own default (500ms / 20%) β guessing lower would paint bands the gate would have passed. Red needs a bean count on BOTH endpoints. A pre-1.1.3 entry can only reach amber, because "nothing was added" is a claim its data cannot support. The bean line is drawn as segments over consecutive counted runs, so it never joins run 2 to run 5 across a hole as though the runs between had been measured. The caption says "unexplained by bean count", never "regression detected". A flat bean count with rising startup does not prove a code regression β a bigger dataset, a slower runner or a cold page cache draw the same line. The band says where to look. Verified on six real wiredoctor-test runs with the regression induced on purpose (AlphaBean's sleep 50 -> 450ms, bean count held at 67): the four band states came out amber, green, red, green in the order constructed. Entries with no totalStartupMs are dropped rather than read as zero. 259 tests.
The distribution caption read "N over 100ms β the slow-bean threshold". The last histogram bucket is fixed at 100ms+; the threshold is configurable, so on the demo app (50ms) the sentence named the wrong number and read as if the tool had measured against it. Found by re-running the JSON-key-vs-template diff: slowBeanThreshold was in the report and nowhere in the HTML. Now the bucket and the threshold are stated separately, the threshold read from the report β "66 beans measured Β· 0 at 100ms or more Β· slow-bean threshold 50ms".
Parent version bumped in all three child poms as well β a bump that misses one child breaks the reactor build in CI.
Report tour gains the coupling quadrant and the Pareto curve, and states that its 0.7.1 screenshots predate them. The trend guide gets the verdict-band table, the beanCount field and the two claims the chart deliberately does not make.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Report readability pass. 1.1.2 fixed what the report claimed; this release fixes what it shows.
Three charts
I = 0.8line, dot size = beans sharing a position, framework beans dimmed with a toggle, single-bean dot opens it in the graph. Square-root axes so a 400-bean tail stays readable.startup-timegate's own thresholds (default 500ms AND 20%). Red = slowdown unexplained by bean count, amber = the app also grew (or one run predatesbeanCount), green = faster.Drill-downs β fan-in / fan-out rows expand to the beans on the other end; slow startup steps name the bean they instantiated; critical-path chips show how far into the chain each bean sits; orphan and proxied beans are listed, not just counted; Cycles tab lists cycle members and the
@Lazyedge that breaks it.Fixes β the proxy card no longer implies it inspected beans that were never instantiated; the histogram caption no longer calls its fixed 100ms bucket "the slow-bean threshold" (that value is configurable, and was 50ms on the run described).
New:
beanCountin eachtrendHistory[]entry β without it the trend chart cannot tell a regression from an app that simply grew. Pre-1.1.3 entries stay valid and are handled, not guessed.Tested
mvn -pl wiredoctor-autoconfigure test), including two new baseline tests forbeanCountpersistence across writes.tools/render-check.jsβ new jsdom harness. The template is one top-down script, so aconstdeclared below a tab's render is in TDZ for it andnode --checkpasses such code; the harness caught three of these during this release. Run across 13 report JSONs (empty graph, no timings, single-bean graph, flat fan-in, five trend shapes, two real apps).ForkJoinPoolthread tags, 2-entry trend). Screenshotted per tab in headless chromium β no console errors.Not in this PR
No API, schema (
schemaVersion: 1) or configuration changes. Chart animation was considered and deliberately skipped β the charts are static SVG built in one pass; adding motion to the same file as this diff doubles the review surface. Install snippets stay at1.1.0because that is what Maven Central serves.