feat(thread-dump): Thread Dump Compare – follow-up to #389 - #394
Open
zachelnet wants to merge 34 commits into
Open
feat(thread-dump): Thread Dump Compare – follow-up to #389#394zachelnet wants to merge 34 commits into
zachelnet wants to merge 34 commits into
Conversation
…pring Boot 3 Backend (analysis/thread-dump): - New diagnoser: ThreadDumpDiagnoser, Diagnostic, ThreadDumpAnalysisConfig - ThreadDumpAnalyzer: diagnose(), blockingThreads(), cpuConsumingThreads(), cpuConsumingThreadsCompare(), searchThreads(), threads() with state/id filter - Converter: robust locale-aware number parsing - New VOs: SearchHit, VBlockingThread, extended VThread - Tests: TestDiagnoser, TestConverter, TestAnalyzer updated Frontend (Vue 3): - New components: Diagnose, BlockedThreads (D3 tree), CpuConsumingThreads (ECharts bar), ThreadDumpSearch, ThreadDumpSearchForm, ThreadDumpOverview - ThreadDump.vue: thread summary with clickable colored el-tags per state - ThreadDumpOverview.vue: ECharts Doughnut + Bar charts with dark mode, resize handler and onUnmounted cleanup - STATE_COLORS covers all JavaThreadState + OSTreadState enum values - i18n: en.ts + zh.ts for all new keys Assisted-by: GitHub Copilot (Claude Sonnet 4.5)
…iagnosis (Basic Information) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Backend (ThreadDumpAnalyzer): - remove cpuConsumingThreadsCompare API whose parameter cannot be marshalled through the analysis API layer and which has no consumer - cpuConsumingThreads: exclude threads without cpu data (cpu <= 0) - searchThreads: apply name/state filters before any file I/O and read matched thread contents in a single sequential pass; propagate IOException instead of swallowing it Frontend: - ThreadDump: drop the state distribution pie chart; build the Total row state tags by merging per-type distributions (Java states for Java threads, OS states otherwise) so counts and tag clicks are consistent with backend thread filtering - thread-state-colors: soft tinted tag style (light background, colored text/border) with a semantic, less saturated palette; works in light and dark themes - ThreadDumpSearch: render results in a table (expandable stack rows, sortable CPU/elapsed columns) with an inline summary bar; stack content style matches Thread.vue - ThreadDumpSearchForm: remove card wrapper, inline search input with appended button and 'More Options' toggle, left-aligned layout, aligned labels and section titles - CpuConsumingThreads: query JIT/GC/VM types separately for the non-Java chart; center x-axis name to avoid clipped label - Diagnose: shared table header style and icon/message alignment - i18n: rename search section to 'Find Threads', clearer placeholder, rename keys (matchFields/matchFieldXxx/otherOptions) Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Add VThreadDelta VO and cpuConsumingThreadsCompare() method to ThreadDumpAnalyzer. Matches threads between two dumps by native ID, computes the CPU-time delta and returns the top-N results sorted descending by delta. Uses @ApiParameterMeta(comparisonTargetPath = true) to resolve the second dump's uniqueName to a Path via the existing ComparisonPathResolver infrastructure. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Add a dedicated /thread-dump-compare route accessible via the sidebar. The page lets the user pick two THREAD_DUMP files and shows: - Basic Information side-by-side (timestamp delta, VM info match/mismatch) - Thread Summary table with Dump1 | Δ | Dump2 columns (▲/▼ highlighted) - Thread Group Summary table with delta highlighting and new-group markers - CPU Time Delta chart (reuses CpuConsumingThreadsCompare component) CpuConsumingThreadsCompare.vue refactored from standalone dropdown component to a pure chart receiving file1/file2 as props, using requestWithTarget() instead of direct axios. Remove the temporary cpuConsumingThreadsCompare collapse-item from the single-file ThreadDump view. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…omponent Vite build failed because el-collapse-item-like is not a real Element Plus component. Merge all sections into a single el-collapse with four named el-collapse-item children. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
The condition used || instead of &&, causing any string value (including valid uniqueNames) to be rejected with 'must be the name of a comparison target'. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Add /thread-dump-compare to the Spring MVC view controller list so direct URL access and browser refresh forward to index.html instead of returning 404 NoResourceFoundException. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
All three tables (Basic Info, Thread Summary, Thread Groups) now use the same 4-column layout with identical widths: - Col 1 label/type: min-width 200px - Col 2 Dump1: min-width 220px - Col 3 Delta: fixed 130px, centered - Col 4 Dump2: min-width 220px Remove separate col-header div, use table headers consistently. Remove extra icon column from Basic Info, integrate icon into label cell. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
The values in the 'Basic Information' table were still left-aligned. This commit applies 'align="right"' to the value columns in that table as well, making the alignment consistent across all three comparison tables. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Show upload prompt with icon, title and hint text when no thread dump files are available, with a direct link to the files page - Show a simple hint when files exist but none are selected yet - Add i18n keys: noFilesAvailable, noFilesAvailableHint, uploadNow (en + zh) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…hanges and persistent blockers sections - Add Diagnosis section to compare page showing both dumps side-by-side Extend Diagnose.vue with optional target prop for use outside analysis store context - Add StateDistributionCompare component: Java thread state table with Dump1/Δ/Dump2 columns, consistent with Thread Summary and Thread Group Summary UX pattern - Add ThreadStateChanges component: summary table (changed/disappeared/new) plus state transition breakdown grouped by from→to pairs - Add PersistentBlockers component: summary table with resolved/introduced metrics plus list of threads blocked in both dumps with inspect dialog - Add VThreadStateChange VO and two new backend API methods: threadStateChanges(Path other) and persistentBlockers(Path other) - Align all new table column widths (min-width 200/220) to match existing sections - Add i18n keys for all new sections in en.ts Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Remove duplicate interface ThreadDelta block rendered as raw text after
</template> in CpuConsumingThreadsCompare.vue (B1)
- Fix paging parameters in PersistentBlockers: send flat {page, pageSize}
instead of nested {paging: {page, pageSize}} to match server API (B2)
- Pass actual file names as column headers to ThreadStateChanges and
PersistentBlockers (were showing generic 'Dump 1'/'Dump 2') (D1)
- Remove 'State changed' summary row: count1=count2=N with delta=0 was
confusing; transition breakdown table conveys the same info (D2)
- Gate 'State transition breakdown' h4 header behind v-if to avoid empty
heading when no threads changed state (D3)
- Gate 'Threads blocked in both dumps' h4 header behind v-if when no
persistent blockers exist (D4)
- Remove dead CSS classes from ThreadDumpCompare.vue (T4)
Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…ntax and tests - Fix Examine button in DiagnoseCompare: add optional target prop to Thread.vue so it uses requestWithTarget when called from compare page, avoiding TypeError on missing analysis store (B3) - Fix State Distribution label column width: 260 -> 200 to match all other compare table sections (D6) - Fix zh.ts syntax error: missing closing } for threadDumpCompare block causing build failure - Add --add-opens=java.base/java.lang=ALL-UNNAMED to thread-dump test JVM args to fix cglib incompatibility with Java 17 in new tests (T5) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…components - Move deltaClass(), deltaText() and PageView<T> into thread-state-colors.ts as shared exports; remove 4 identical local copies across compare components - Replace local stateType() in ThreadStateChanges with stateTagStyle() from thread-state-colors.ts for full state color coverage (17 states vs 4) - Remove duplicate :deep(.delta-*) CSS from child components; delta classes are defined once in ThreadDumpCompare parent via :deep and cascade down Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…ntBlockers - testThreadStateChanges_sameDump: same dump yields no changes - testThreadStateChanges_differentDumps: different JVMs yield disappeared/new - testPersistentBlockers_sameDump: all 4 blocked threads are persistent - testPersistentBlockers_noOverlap: no overlap across different JVM dumps - testCpuConsumingThreadsCompare_sameDump: delta is 0 for all threads Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Reorder collapse sections: State Distribution, Thread State Changes and Persistent Blockers before CPU Delta (CPU is often empty, should not block visible content above the fold) (Dx3) - Gate el-divider in ThreadStateChanges: only show when there are state changes or non-empty results to separate (Dx1) - Add error notification to loadFiles() when API call fails (Ex2) - Add 'Compare with another dump' button to single-dump view (ThreadDump.vue) that navigates to compare page with file1 pre-selected via query param (Po1) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
… | Δ | Dump2) Diagnosis table had 3 columns while all other compare tables use 4. - Reduce Dump1/Dump2 min-width from 300 to 220 to fit Δ column - Add Δ column (width 130): New tag / Fixed tag / = for consistent visual Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
New endpoint compareMultiple(other1, other2?, other3?) returns a unified
VMultiDumpComparison with:
- DumpSummary per dump: name, threadCount, deadLockCount, stateCounts map
- ThreadRow per unique NID: name, states[n] (null = not present), alwaysBlocked, stateChanged
Rows sorted: persistently blocked first, then state-changed, then rest.
Supports 2–4 dumps in a single API call.
Usage:
POST /jifa-api/analysis
{ namespace: 'thread-dump', api: 'compareMultiple',
target: 'uuid1',
parameters: { other1: 'uuid2', other2: 'uuid3' } }
Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
The resolver was hardcoded with required=true and ignored the @ApiParameterMeta(required = false) annotation. This caused optional comparison paths (other2, other3 in compareMultiple) to always fail with 'is required'. Pass the required flag through from buildLeafResolver and return null when the key is absent and required=false. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
AbstractApiExecutor.buildApiParameters() forced required=true for all Path parameters with comparisonTargetPath=true, overriding any @ApiParameterMeta(required=false) annotation. Only targetPath parameters should be unconditionally required; comparisonTargetPath parameters must respect the required flag from the annotation. This fixes compareMultiple(other1, other2?, other3?) where other2 and other3 are optional. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Fix CpuConsumingThreadsCompare Thread dialog: pass target prop so click-to-inspect works on compare page (Bug eclipse-jifa#6) - Add JavaThreadState and LinkedHashMap imports to ThreadDumpAnalyzer; remove fully-qualified usages (Code Quality eclipse-jifa#2, eclipse-jifa#4) - Fix threadStateChanges sort order comment: 'changed, new, disappeared' not 'changed, disappeared, new' (Comment eclipse-jifa#3) - Remove unused isStateChanged() from VThreadStateChange (Dead Code eclipse-jifa#5) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…compare tables Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…arations Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…eaders, empty states) Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…ct, rename CPU label Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Correct threadGroupSummary from thread pool to thread group - Use precise Java monitor and call site tree terminology - Replace CPU delta/increment wording with neutral change wording - Align noCpuData terminology with thread dump naming Assisted-by: GitHub Copilot (Kimi K2.7 Code)
Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…t DecimalFormatSymbols Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
…e file picker - threadStateChanges: use second.getId() instead of -1 for threads only in the second dump, so the frontend can open the thread detail view - ThreadDumpCompare: fetch all pages instead of hardcoded pageSize=200 Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Contributor
Author
…format DecimalFormat with only decimalSeparator=',' could not parse '1.234,56' because the dot was not recognised as a grouping separator. Fix: set groupingSeparator='.' and use pattern '#,##0.##' with ParsePosition to ensure the full string is consumed. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
- Pagination: use Infinity fallback instead of 0 when totalSize missing, preventing early termination after the first page - Empty state: show loading spinner while filesLoading, not el-empty - onMounted: capture preselect query param before async loadFiles() - groupRows: use optional chaining for threadGroupStat[k].counts Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
Also auto-triggers comparison when both file1 and file2 are preset via URL. Assisted-by: GitHub Copilot (Claude Sonnet 4.6)
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.




Overview
This PR adds a Thread Dump Compare page as the follow-up to #389, as agreed with @D-D-H (see comment in #389: "Let's handle it in a separate PR").
It follows the same snapshot-diff methodology already established in Jifa for heap dump histogram comparison and GC log comparison: threads are matched across dumps by their native thread ID (NID), and changes in state, CPU time, and blocking relationships are surfaced.
Closes part of #231 (comparing a series of thread dumps).
Changes
Backend (
analysis/thread-dump)New API endpoints in
ThreadDumpAnalyzer:cpuConsumingThreadsCompare(other, type, max)threadStateChanges(other)persistentBlockers(other)BLOCKED_ON_MONITOR_ENTERin both dumpscompareMultiple(other1, other2?, other3?)New VOs:
VThreadDelta,VThreadStateChange,VMultiDumpComparisonInfrastructure fix in
AbstractApiExecutor+AnalysisApiArgumentResolverFactory:comparisonTargetPathparameters can now berequired = false(needed for optionalother2/other3incompareMultiple)ComparisonPathResolver:!element.isJsonPrimitive() || !element.getAsJsonPrimitive().isString()Frontend (Vue 3 / TypeScript)
New route
/thread-dump-comparewithThreadDumpCompare.vueas the main page, containing:ThreadDumpCompare.vueStateDistributionCompare.vueCpuConsumingThreadsCompare.vueThreadStateChanges.vuePersistentBlockers.vueDiagnoseCompare.vueUI conventions:
Tests
New unit tests in
TestAnalyzer:testThreadStateChanges_sameDump– comparing a dump to itself yields no changestestThreadStateChanges_differentDumps– different JVMs produce disappeared + new entriestestPersistentBlockers_sameDump– all 4 blocked threads are persistenttestPersistentBlockers_noOverlap– no blocked threads → empty resultNote on
compareMultipleThe
compareMultipleAPI (up to 4 dumps, matrix view) is implemented in the backend and ready for use. The matrix UI is intentionally left out of this PR to keep the scope focused. It can be added in a follow-up.AI Disclosure
This contribution was developed with AI assistance.
Assisted-by: GitHub Copilot (Claude Sonnet 4.6)