Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,9 @@
import org.eclipse.jifa.common.domain.vo.PageView;
import org.eclipse.jifa.common.util.PageViewBuilder;
import org.eclipse.jifa.tda.enums.MonitorState;
import org.eclipse.jifa.tda.enums.OSTreadState;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo

We should submit a separate PR upstream to rename it.

import org.eclipse.jifa.tda.enums.ThreadType;
import org.eclipse.jifa.tda.model.CallSiteTree;
import org.eclipse.jifa.tda.model.Frame;
import org.eclipse.jifa.tda.model.IdentityPool;
import org.eclipse.jifa.tda.model.JavaThread;
import org.eclipse.jifa.tda.model.Monitor;
import org.eclipse.jifa.tda.model.RawMonitor;
import org.eclipse.jifa.tda.model.Snapshot;
import org.eclipse.jifa.tda.model.*;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we undo this please?

import org.eclipse.jifa.tda.model.Thread;
import org.eclipse.jifa.tda.parser.ParserFactory;
import org.eclipse.jifa.tda.util.CollectionUtil;
Expand All @@ -43,11 +38,8 @@
import java.io.IOException;
import java.io.LineNumberReader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
Comment on lines +41 to +42

Copilot AI Jun 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid wildcard imports; explicitly import only the needed classes to improve readability and prevent accidental dependencies.

Suggested change
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Collectors;

Copilot uses AI. Check for mistakes.

/**
* Thread dump analyzer
Expand Down Expand Up @@ -305,4 +297,31 @@ public Map<MonitorState, Integer> threadCountsByMonitor(int id) {
map.forEach((s, l) -> counts.put(s, l.size()));
return counts;
}

/**
* @return rows of a thread dump table
*/
public List<ThreadDumpRow> rows(PagingRequest paging) {

Copilot AI Jun 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The paging parameter is currently unused. Either implement pagination logic inside this method or remove the parameter to keep the API signature accurate.

Copilot uses AI. Check for mistakes.

List<ThreadDumpRow> threadDumpRows = new ArrayList<>(snapshot.getJavaThreads().stream().map(t -> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style:

We can add static functions and/or local functions somewhere to do this conversion. For example, a ThreadDumpRow(JavaThread javaThread) constructor or a static ThreadDumpRow fromJavaThread(JavaThread javaThread).

Though, it's a bit clunky having that in the Model, so maybe somewhere here.


Frame[] frames = null;
int length = 0;

if (t.getTrace() != null) {
frames = t.getTrace().getFrames();
length = frames.length;
}

return new ThreadDumpRow(t.getName(), t.getId(), t.getOsThreadState(), length, frames, t.getElapsed(), t.getCpu(), t.getLineStart(), t.getLineEnd());
}).toList());

List<ThreadDumpRow> nonJavaThreadDumpRows = snapshot.getNonJavaThreads().stream().map(t -> {
return new ThreadDumpRow(t.getName(), t.getId(), t.getOsThreadState(), 0, new Frame[]{}, t.getElapsed(), t.getCpu(), t.getLineStart(), t.getLineEnd());
}).toList();

threadDumpRows.addAll(nonJavaThreadDumpRows);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to simply construct the paging component using PageViewer, see the function buildVThreadPageView for an example.

Comment on lines +306 to +323

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach generates quite a bit of intermediary list garbage. Consider something like this instead:

Stream<ThreadDumpRow> javaRows = snapshot.getJavaThreads().stream().map(....);
Stream<ThreadDumpRow> nonJavaRows = snapshot.getNonJavaThreads().stream().map(....);
Stream<ThreadDumpRow> allRows = Stream.concat(javaRows, nonJavaRows);
// consider implementing paging here on the stream
List<ThreadRumpRow> threadDumpRows = allRows.toList();


return threadDumpRows;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.eclipse.jifa.tda.model;

import org.eclipse.jifa.tda.enums.OSTreadState;

import lombok.Data;

@Data
Comment on lines +5 to +7

Copilot AI Jun 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there's an explicit all-args constructor, Lombok won’t generate a no-argument constructor. Add @NoArgsConstructor (or a manual default constructor) if your JSON serialization framework requires it.

Suggested change
import lombok.Data;
@Data
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor

Copilot uses AI. Check for mistakes.
public class ThreadDumpRow {

private String name;

private int tid;

private OSTreadState state;

private int stackDepth;

private Frame[] frames;

private double elapsedTime;

private double cpuTime;

private int lineNumberStart;

private int lineNumberEnd;

public ThreadDumpRow(String name, int id, OSTreadState osThreadState, int length, Frame[] frames, double elapsed, double cpu, int lineStart, int lineEnd) {
this.name = name;
this.tid = id;
this.state = osThreadState;
this.stackDepth = length;
this.frames = frames;
this.elapsedTime = elapsed;
this.cpuTime = cpu;
this.lineNumberStart = lineStart;
this.lineNumberEnd = lineEnd;
}
}
58 changes: 57 additions & 1 deletion frontend/src/components/threaddump/ThreadDump.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Content from '@/components/threaddump/Content.vue';
import Thread from '@/components/threaddump/Thread.vue';
import Monitor from '@/components/threaddump/Monitor.vue';
import CallSiteTree from '@/components/threaddump/CallSiteTree.vue';
import ThreadViewerGrid from '@/components/threaddump/ThreadViewerGrid.vue';

const { request } = useAnalysisApiRequester();

Expand All @@ -36,7 +37,9 @@ const activeNames = ref<string[]>([
'threadSummary',
'threadGroupSummary',
'javaMonitors',
'callSiteTree'
'callSiteTree',
'Table View',

Copilot AI Jun 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeNames includes the display title 'Table View', but the corresponding <el-collapse-item> uses the name 'emptyBox'. This mismatch prevents the grid tab from activating. Update activeNames to use 'emptyBox' or rename the collapse-item to 'Table View'.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Table View doesn't have the java camelCase approach.

'fileContent'
]);

const deadLockCount = ref(0);
Expand Down Expand Up @@ -66,6 +69,10 @@ const threadDialogVisible = ref(false);
const selectedThreadType = ref();
const selectedThreadGroup = ref();

const threadsArray = ref([]);
const selectedThread = ref<any>(null);
const selectedThreadContent = ref('');

function sum(arr) {
return arr.reduce((l, r) => l + r);
}
Expand All @@ -92,6 +99,20 @@ function showThreadsOfGroup(group) {
threadDialogVisible.value = true;
}

function onThreadSelected(thread) {
selectedThread.value = thread;
const lineNumberStart = thread.lineNumberStart;
const lineNumberEnd = thread.lineNumberEnd;
if (lineNumberStart && lineNumberEnd) {

Copilot AI Jun 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking these values with a truthiness test fails when lineNumberStart is 0 (falsy). Use explicit checks like if (lineNumberStart != null && lineNumberEnd != null) { to handle zero-based lines.

Suggested change
if (lineNumberStart && lineNumberEnd) {
if (lineNumberStart != null && lineNumberEnd != null) {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good bot

// Calculate limit as end - start + 1
const limit = lineNumberEnd - lineNumberStart + 1;
request('content', { lineNo: lineNumberStart, lineLimit: limit }).then((data) => {
// data.content is assumed to be a list of strings
selectedThreadContent.value = data.content.join('\n');
});
}
}

onMounted(() => {
loading.value = true;
request('overview').then((overview) => {
Expand Down Expand Up @@ -178,6 +199,21 @@ onMounted(() => {
threadGroupStats.value = _threadGroupStats;
loading.value = false;
});

request('rows').then((threadsData) => {
console.log(threadsData);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is this required?

threadsArray.value = threadsData.map((t) => ({
tid: t.tid,
name: t.name,
state: t.state,
stackDepth: t.stackDepth,
frames: t.frames,
cpuTime: t.cpuTime,
elapsedTime: t.elapsedTime,
lineNumberStart: t.lineNumberStart,
lineNumberEnd: t.lineNumberEnd
}));
});
});
</script>
<template>
Expand Down Expand Up @@ -298,6 +334,26 @@ onMounted(() => {
<CallSiteTree />
</el-collapse-item>

<el-collapse-item name="emptyBox" :title="tdt('emptyBox')">
<div style="display: flex;">
<div style="width: 60%; height: 1200px; overflow: auto;">
<ThreadViewerGrid :threads="threadsArray" @row-click="onThreadSelected" />
</div>
<div
style="width: 40%; height: 1200px; overflow-y: auto; background-color: #fff; margin-left: 20px; padding: 16px; border: 1px solid #ccc;"
>
<template v-if="selectedThread">
<h3>{{ selectedThread.name }}</h3>
<pre>{{ selectedThreadContent }}</pre>
</template>
<template v-else>
<h3>{{ tdt('noThreadSelected') }}</h3>
<p>{{ tdt('pleaseSelectAThread') }}</p>
</template>
</div>
</div>
</el-collapse-item>

<el-collapse-item name="fileContent" :title="tdt('fileContent')">
<Content />
</el-collapse-item>
Expand Down
63 changes: 63 additions & 0 deletions frontend/src/components/threaddump/ThreadViewerGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!--
Copyright (c) 2023, 2024 Contributors to the Eclipse Foundation

See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.

This program and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0

SPDX-License-Identifier: EPL-2.0
-->
<script setup lang="ts">
import { ref } from 'vue';

const props = defineProps<{
threads: {
tid: string;
name: string;
state: string;
stackDepth: number;
frames: any[];
cpuTime: string;
elapsedTime: string;
}[];
}>();

const emit = defineEmits(['row-click']);

const selectedThread = ref(null);

function onRowClick(row) {
emit('row-click', row);
selectedThread.value = row;
}

function rowClassName({ row, rowIndex }) {
if (selectedThread.value) {
return row === selectedThread.value ? 'selected-row' : '';
} else {
return rowIndex === 0 ? 'selected-row' : '';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not clear what the intent is here, are we defaulting to first-row selected deliberately?

}
}
</script>

<template>
<el-table :data="props.threads" stripe @row-click="onRowClick" :row-class-name="rowClassName">
<el-table-column prop="name" label="Name" sortable />
<el-table-column prop="state" label="State" sortable />
<el-table-column prop="stackDepth" label="Stack Depth" sortable />
<el-table-column prop="cpuTime" label="CPU Time (ms)" sortable />
<el-table-column prop="elapsedTime" label="Elapsed Time (ms)" sortable />
</el-table>

<div v-if="selectedThread" class="thread-details">
</div>
</template>

<style scoped>
.selected-row {
border: 2px solid #409EFF;
}
</style>