2121import org .eclipse .jifa .common .domain .request .PagingRequest ;
2222import org .eclipse .jifa .common .domain .vo .PageView ;
2323import org .eclipse .jifa .common .util .PageViewBuilder ;
24+ import org .eclipse .jifa .tda .diagnoser .Diagnostic ;
25+ import org .eclipse .jifa .tda .diagnoser .ThreadDumpAnalysisConfig ;
26+ import org .eclipse .jifa .tda .diagnoser .ThreadDumpDiagnoser ;
2427import org .eclipse .jifa .tda .enums .MonitorState ;
2528import org .eclipse .jifa .tda .enums .ThreadType ;
2629import org .eclipse .jifa .tda .model .CallSiteTree ;
3538import org .eclipse .jifa .tda .util .CollectionUtil ;
3639import org .eclipse .jifa .tda .vo .Content ;
3740import org .eclipse .jifa .tda .vo .Overview ;
41+ import org .eclipse .jifa .tda .vo .SearchHit ;
42+ import org .eclipse .jifa .tda .vo .VBlockingThread ;
3843import org .eclipse .jifa .tda .vo .VFrame ;
3944import org .eclipse .jifa .tda .vo .VMonitor ;
4045import org .eclipse .jifa .tda .vo .VThread ;
4449import java .io .LineNumberReader ;
4550import java .nio .file .Path ;
4651import java .util .ArrayList ;
52+ import java .util .Arrays ;
4753import java .util .Collections ;
54+ import java .util .Comparator ;
4855import java .util .HashMap ;
4956import java .util .List ;
5057import java .util .Map ;
58+ import java .util .Map .Entry ;
59+ import java .util .regex .Pattern ;
60+ import java .util .stream .Collectors ;
61+ import java .util .stream .Stream ;
5162
5263/**
5364 * Thread dump analyzer
@@ -176,13 +187,17 @@ private PageView<VThread> buildVThreadPageView(List<Thread> threads, PagingReque
176187 }
177188
178189 /**
179- * @param name the thread name
180- * @param type the thread type
181- * @param paging paging request
182- * @return the threads filtered by name and type
190+ * @param name the thread name filter (substring match, optional)
191+ * @param type the thread type filter (optional)
192+ * @param threadState the Java or OS thread state to filter by (optional)
193+ * @param ids explicit list of thread ids to include (optional)
194+ * @param paging paging request
195+ * @return the threads filtered by name, type, state and/or id
183196 */
184197 public PageView <VThread > threads (@ ApiParameterMeta (required = false ) String name ,
185198 @ ApiParameterMeta (required = false ) ThreadType type ,
199+ @ ApiParameterMeta (required = false ) String threadState ,
200+ @ ApiParameterMeta (required = false ) List <Integer > ids ,
186201 PagingRequest paging ) {
187202 List <Thread > threads = new ArrayList <>();
188203 CollectionUtil .forEach (t -> {
@@ -192,12 +207,29 @@ public PageView<VThread> threads(@ApiParameterMeta(required = false) String name
192207 if (StringUtils .isNotBlank (name ) && !t .getName ().contains (name )) {
193208 return ;
194209 }
210+ if (StringUtils .isNotBlank (threadState ) && !getThreadState (t ).equals (threadState )) {
211+ return ;
212+ }
213+ if (ids != null && !ids .isEmpty () && !ids .contains (t .getId ())) {
214+ return ;
215+ }
195216 threads .add (t );
196217 }, snapshot .getJavaThreads (), snapshot .getNonJavaThreads ());
197218
198219 return buildVThreadPageView (threads , paging );
199220 }
200221
222+ /** Returns the most specific state string for a thread (Java state if available, else OS state). */
223+ private String getThreadState (Thread t ) {
224+ if (t instanceof JavaThread ) {
225+ JavaThread jt = (JavaThread ) t ;
226+ if (jt .getJavaThreadState () != null ) {
227+ return String .valueOf (jt .getJavaThreadState ());
228+ }
229+ }
230+ return String .valueOf (t .getOsThreadState ());
231+ }
232+
201233 /**
202234 * @param groupName the thread group name
203235 * @param paging paging request
@@ -305,4 +337,256 @@ public Map<MonitorState, Integer> threadCountsByMonitor(int id) {
305337 map .forEach ((s , l ) -> counts .put (s , l .size ()));
306338 return counts ;
307339 }
340+
341+ /**
342+ * Returns all threads that are blocking at least one other thread via monitor
343+ * ownership, sorted descending by number of blocked threads, then by blocker
344+ * thread name.
345+ *
346+ * @return list of blocking threads with their blocked threads and held monitor
347+ */
348+ public List <VBlockingThread > blockingThreads () {
349+ List <VBlockingThread > result = new ArrayList <>();
350+
351+ Map <Integer , Map <MonitorState , List <Thread >>> allMonitors = snapshot .getMonitorThreads ();
352+ for (Entry <Integer , Map <MonitorState , List <Thread >>> monitorEntry : allMonitors .entrySet ()) {
353+ Map <MonitorState , List <Thread >> monitorMap = monitorEntry .getValue ();
354+ if (!monitorMap .containsKey (MonitorState .LOCKED )) {
355+ continue ;
356+ }
357+ Thread blockingThread = monitorMap .get (MonitorState .LOCKED ).stream ().findFirst ().orElse (null );
358+ List <Thread > blockedThreads = new ArrayList <>();
359+ if (monitorMap .containsKey (MonitorState .WAITING_TO_LOCK )) {
360+ blockedThreads .addAll (monitorMap .get (MonitorState .WAITING_TO_LOCK ));
361+ }
362+ if (monitorMap .containsKey (MonitorState .WAITING_TO_RE_LOCK )) {
363+ blockedThreads .addAll (monitorMap .get (MonitorState .WAITING_TO_RE_LOCK ));
364+ }
365+ if (!blockedThreads .isEmpty () && blockingThread != null ) {
366+ VBlockingThread r = new VBlockingThread ();
367+ r .setBlockedThreads (blockedThreads .stream ()
368+ .map (this ::convertToVThread )
369+ .collect (Collectors .toList ()));
370+ r .setBlockingThread (convertToVThread (blockingThread ));
371+ Monitor mon = findBlockingMonitor (blockedThreads .get (0 ));
372+ if (mon != null ) {
373+ r .setHeldLock (new VMonitor (
374+ mon .getRawMonitor ().getId (),
375+ mon .getRawMonitor ().getAddress (),
376+ mon .getRawMonitor ().isClassInstance (),
377+ mon .getRawMonitor ().getClazz (),
378+ mon .getState ()));
379+ }
380+ result .add (r );
381+ }
382+ }
383+
384+ result .sort (Comparator
385+ .<VBlockingThread >comparingInt (m -> m .getBlockedThreads ().size ())
386+ .reversed ()
387+ .thenComparing (m -> m .getBlockingThread ().getName ()));
388+ return result ;
389+ }
390+
391+ /**
392+ * Returns the threads with the highest CPU usage, in descending order.
393+ *
394+ * @param type limit to threads of this type; {@code null} means all types
395+ * @param max maximum number of results; {@code -1} means unlimited
396+ * @return list of threads sorted from most to least CPU-intensive
397+ */
398+ public List <VThread > cpuConsumingThreads (@ ApiParameterMeta (required = false ) ThreadType type ,
399+ int max ) {
400+ Stream <Thread > stream = snapshot .getThreadMap ().values ().stream ()
401+ .filter (t -> type == null || t .getType () == type );
402+ stream = stream .sorted (Comparator .comparingDouble (Thread ::getCpu ).reversed ())
403+ .limit (max < 0 ? Integer .MAX_VALUE : max );
404+ return stream .map (this ::convertToVThread ).collect (Collectors .toList ());
405+ }
406+
407+ /**
408+ * Computes which threads consumed the most CPU <em>between</em> two thread
409+ * dumps by matching threads via their native thread id ({@code tid}).
410+ *
411+ * @param other the second (later) thread dump analyzer
412+ * @param max maximum number of results; {@code -1} means unlimited
413+ * @param type limit to threads of this type; {@code null} means all types
414+ * @return threads sorted by delta-CPU descending
415+ */
416+ public List <VThread > cpuConsumingThreadsCompare (ThreadDumpAnalyzer other , int max ,
417+ @ ApiParameterMeta (required = false ) ThreadType type ) {
418+ int limit = max < 0 ? Integer .MAX_VALUE : max ;
419+ Map <Thread , Double > cpuDelta = new HashMap <>();
420+ for (Thread first : snapshot .getThreadMap ().values ()) {
421+ if (type != null && first .getType () != type ) {
422+ continue ;
423+ }
424+ Thread second = other .snapshot .getThreadMap ().values ().stream ()
425+ .filter (t -> t .getTid () == first .getTid ())
426+ .findFirst ().orElse (null );
427+ if (second != null && second .getCpu () > 0 ) {
428+ cpuDelta .put (first , second .getCpu () - first .getCpu ());
429+ }
430+ }
431+ List <VThread > result = new ArrayList <>();
432+ cpuDelta .entrySet ().stream ()
433+ .sorted (Collections .reverseOrder (Map .Entry .comparingByValue ()))
434+ .limit (limit )
435+ .forEach (e -> {
436+ VThread vt = convertToVThread (e .getKey ());
437+ vt .setCpu (e .getValue ());
438+ result .add (vt );
439+ });
440+ return result ;
441+ }
442+
443+ /**
444+ * Diagnoses the thread dump for potential issues based on the given
445+ * configuration and returns any issues found.
446+ *
447+ * @param config the configuration to use; a default config is used if {@code null}
448+ * @return potentially empty list of diagnostic issues
449+ */
450+ public List <Diagnostic > diagnose (
451+ @ ApiParameterMeta (required = false ) ThreadDumpAnalysisConfig config ) {
452+ return new ThreadDumpDiagnoser ().analyze (
453+ snapshot ,
454+ config != null ? config : new ThreadDumpAnalysisConfig ());
455+ }
456+
457+ /**
458+ * Searches through all threads and returns those whose name, state or
459+ * stack trace match all of the given terms.
460+ *
461+ * @param term search terms; each term must match at least one of the
462+ * enabled search fields (AND semantics across terms)
463+ * @param searchName include the thread name in the search (default: true)
464+ * @param searchState include the thread state in the search (default: true)
465+ * @param searchStack include the stack trace in the search (default: true)
466+ * @param regex treat terms as regular expressions (default: false)
467+ * @param matchCase perform a case-sensitive search (default: false)
468+ * @param allowedJavaStates if non-empty, only include threads whose Java state is
469+ * one of these values
470+ * @return matching threads together with their raw content lines
471+ */
472+ public List <SearchHit > searchThreads (
473+ @ ApiParameterMeta (required = false ) List <String > term ,
474+ @ ApiParameterMeta (required = false ) Boolean searchName ,
475+ @ ApiParameterMeta (required = false ) Boolean searchState ,
476+ @ ApiParameterMeta (required = false ) Boolean searchStack ,
477+ @ ApiParameterMeta (required = false ) Boolean regex ,
478+ @ ApiParameterMeta (required = false ) Boolean matchCase ,
479+ @ ApiParameterMeta (required = false ) List <String > allowedJavaStates ) {
480+ if (term == null || term .isEmpty ()) {
481+ return Collections .emptyList ();
482+ }
483+
484+ boolean doSearchName = !Boolean .FALSE .equals (searchName );
485+ boolean doSearchState = !Boolean .FALSE .equals (searchState );
486+ boolean doSearchStack = !Boolean .FALSE .equals (searchStack );
487+ boolean doRegex = Boolean .TRUE .equals (regex );
488+ int flags = Boolean .TRUE .equals (matchCase ) ? 0 : Pattern .CASE_INSENSITIVE ;
489+
490+ List <Pattern > patterns = term .stream ()
491+ .map (t -> Pattern .compile (doRegex ? t : Pattern .quote (t ), flags ))
492+ .collect (Collectors .toList ());
493+
494+ List <SearchHit > results = new ArrayList <>();
495+
496+ CollectionUtil .forEach (t -> {
497+ // Optional state pre-filter
498+ if (allowedJavaStates != null && !allowedJavaStates .isEmpty ()) {
499+ if (!(t instanceof JavaThread )) return ;
500+ JavaThread jt = (JavaThread ) t ;
501+ String state = jt .getJavaThreadState () != null
502+ ? String .valueOf (jt .getJavaThreadState ()) : "" ;
503+ if (!allowedJavaStates .contains (state )) return ;
504+ }
505+
506+ List <String > rawLines ;
507+ try {
508+ rawLines = rawContentOfThread (t .getId ());
509+ } catch (IOException e ) {
510+ rawLines = Collections .emptyList ();
511+ }
512+
513+ String nameStr = t .getName () != null ? t .getName () : "" ;
514+ String stateStr = getThreadState (t );
515+ String stackStr = rawLines .size () > 1
516+ ? String .join ("\n " , rawLines .subList (1 , rawLines .size ()))
517+ : "" ;
518+
519+ boolean matches = patterns .stream ().allMatch (p ->
520+ (doSearchName && p .matcher (nameStr ).find ())
521+ || (doSearchState && p .matcher (stateStr ).find ())
522+ || (doSearchStack && p .matcher (stackStr ).find ())
523+ );
524+
525+ if (matches ) {
526+ SearchHit hit = new SearchHit ();
527+ hit .setId (t .getId ());
528+ hit .setName (t .getName ());
529+ hit .setOsState (String .valueOf (t .getOsThreadState ()));
530+ if (t instanceof JavaThread ) {
531+ JavaThread jt = (JavaThread ) t ;
532+ if (jt .getJavaThreadState () != null ) {
533+ hit .setJavaState (String .valueOf (jt .getJavaThreadState ()));
534+ }
535+ }
536+ if (t .getCpu () > 0 ) hit .setCpu (t .getCpu ());
537+ if (t .getElapsed () > 0 ) hit .setElapsed (t .getElapsed ());
538+ hit .setLines (rawLines );
539+ results .add (hit );
540+ }
541+ }, snapshot .getJavaThreads (), snapshot .getNonJavaThreads ());
542+
543+ return results ;
544+ }
545+
546+ // ------------------------------------------------------------------
547+ // private helpers
548+ // ------------------------------------------------------------------
549+
550+ /**
551+ * Converts a model {@link Thread} to a lightweight {@link VThread} VO,
552+ * copying cpu and elapsed times when available ({@code > 0}).
553+ */
554+ private VThread convertToVThread (Thread thread ) {
555+ VThread vt = new VThread ();
556+ vt .setId (thread .getId ());
557+ vt .setName (thread .getName ());
558+ if (thread .getCpu () > 0 ) {
559+ vt .setCpu (thread .getCpu ());
560+ }
561+ if (thread .getElapsed () > 0 ) {
562+ vt .setElapsed (thread .getElapsed ());
563+ }
564+ return vt ;
565+ }
566+
567+ /**
568+ * Finds the monitor that a blocked thread is waiting to acquire by inspecting
569+ * the thread-level monitor list and the first frame that carries monitor
570+ * information.
571+ */
572+ private Monitor findBlockingMonitor (Thread thread ) {
573+ List <Monitor > candidates = new ArrayList <>();
574+ if (thread instanceof JavaThread ) {
575+ JavaThread blockedThread = (JavaThread ) thread ;
576+ if (blockedThread .getTrace () != null && blockedThread .getTrace ().getFrames () != null ) {
577+ for (Frame frame : blockedThread .getTrace ().getFrames ()) {
578+ if (frame .getMonitors () != null ) {
579+ Arrays .stream (frame .getMonitors ()).forEach (candidates ::add );
580+ // only the first frame with monitors is relevant
581+ break ;
582+ }
583+ }
584+ }
585+ }
586+ return candidates .stream ()
587+ .filter (m -> m .getState () == MonitorState .WAITING_TO_LOCK
588+ || m .getState () == MonitorState .WAITING_TO_RE_LOCK )
589+ .findFirst ()
590+ .orElse (null );
591+ }
308592}
0 commit comments