Skip to content
Merged
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
1 change: 1 addition & 0 deletions spectre-frontend/src/pages/channel/[channelId]/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const Header: React.FC<ToolbarProps> = (props) => {
<Input
label="Classloader Hash"
name="hash"
defaultValue={classloaderHash}
labelPlacement="outside-top"
/>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
} from '@heroui/react'
import React, { useMemo } from 'react'
import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts'
import PercentageData from '@/pages/channel/[channelId]/_tabs/_dashboard/PercentageData.tsx'

type MemoryInfo = {
max: number
name: string
total: number
type: string
used: number
}
type MemoryMessage = {
jobId: number
memoryInfo: Record<string, MemoryInfo[]>
type: 'memory'
fid: number
}
const MemoryMessageDetail: React.FC<DetailComponentProps<MemoryMessage>> = ({
msg,
}) => {
const infos = useMemo(() => {
const infos: MemoryInfo[] = []
console.log(msg.memoryInfo)
for (const entry of Object.entries(msg.memoryInfo)) {
infos.push(...entry[1])
}
return infos
}, [msg.fid])
return (
<Table>
<TableHeader>
<TableColumn>名称</TableColumn>
<TableColumn>类型</TableColumn>
<TableColumn>总大小</TableColumn>
<TableColumn>使用量</TableColumn>
<TableColumn>最大</TableColumn>
</TableHeader>
<TableBody items={infos}>
{(info) => (
<TableRow key={info.name}>
<TableCell>{info.name}</TableCell>
<TableCell>{info.type}</TableCell>
<TableCell>{(info.total / 1024 / 1024).toFixed(0)}MB</TableCell>
<TableCell>
{(info.used / 1024 / 1024).toFixed(0)}MB (
<PercentageData rate={info.used / info.total} />)
</TableCell>
<TableCell>
{info.max < 0 ? (
<span className="italic">unlimited</span>
) : (
`${(info.max / 1024 / 1024).toFixed(0)}MB`
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)
}

export default MemoryMessageDetail
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type React from 'react'
import OgnlMessageView from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/_ognl_result/OgnlMessageView.tsx'

const OgnlCommonMessageDetail: React.FC<{ raw: string }> = ({ raw }) => {
return (
<div className="text-sm">
<OgnlMessageView raw={raw} />
</div>
)
}

export default OgnlCommonMessageDetail
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import React, { useCallback } from 'react'
import type { DetailComponentProps } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts'
import {
Card,
CardBody,
Code,
Link,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
Tooltip,
} from '@heroui/react'
import { updateChannelContext } from '@/store/channelSlice.ts'
import { useDispatch } from 'react-redux'
import KVGird from '@/components/KVGird'
import KVGridItem from '@/components/KVGird/KVGridItem.tsx'

type Fields = {
annotations: string[]
modifier: string
name: string
static: boolean
type: string
}

type ClassInfo = {
annotation: boolean
annotations: string[]
anonymousClass: boolean
array: boolean
classInfo: string
classLoaderHash: string
classloader: string[]
codeSource: string
enum: boolean
fields?: Fields[]
interface: boolean
interfaces: string[]
localClass: boolean
memberClass: boolean
modifier: string
name: string
primitive: boolean
simpleName: string
superClass: string[]
synthetic: boolean
}
type ScMessage = {
fid: number
withField: boolean
type: 'sc'
segment: number
jobId: number
detailed: boolean
classNames?: string[]
classInfo?: ClassInfo
}

const ListDisplay: React.FC<{
entities: string[]
name: string
color?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
}> = ({ entities, color, name }) => {
return (
<div>
<span className="text-sm font-bold">{name}:</span>
{entities.length > 0 ? (
<ul className="mt-3 ml-6 list-disc space-y-2">
{entities.map((entity) => (
<li key={entity}>
<Code color={color}>{entity}</Code>
</li>
))}
</ul>
) : (
<span className="ml-2">无</span>
)}
</div>
)
}

const ClassInfoDisplay: React.FC<{ classInfo: ClassInfo }> = ({
classInfo,
}) => {
const dispatch = useDispatch()
// context.messageBus.
const applyClassloader = useCallback(() => {
dispatch(
updateChannelContext({
classloaderHash: classInfo.classLoaderHash,
}),
)
}, [classInfo.classLoaderHash, dispatch])

let type: string
if (classInfo.interface) {
type = 'Interface'
} else if (classInfo.enum) {
type = 'Enum'
} else if (classInfo.annotation) {
type = 'Annotation'
} else if (classInfo.anonymousClass) {
type = 'Anonymous Class'
} else if (classInfo.array) {
type = 'Array'
} else if (classInfo.localClass) {
type = 'Local Class'
} else if (classInfo.memberClass) {
type = 'Member Class'
} else {
type = 'Class'
}

return (
<div className="space-y-3">
<div className="header-1">{classInfo.name}</div>
<Card>
<CardBody className="space-y-3 text-sm">
<div className="header-2">详细信息</div>
<KVGird>
<KVGridItem name="修饰符">{classInfo.modifier}</KVGridItem>
<KVGridItem name="类型">{type}</KVGridItem>
<KVGridItem name="Classloader Hash">
<Tooltip content="应用到默认 Classloader" placement="bottom">
<Link
size="sm"
onPress={applyClassloader}
className="cursor-pointer"
underline="always"
>
#{classInfo.classLoaderHash}
</Link>
</Tooltip>
</KVGridItem>
</KVGird>
<ListDisplay
name="注解"
color="warning"
entities={classInfo.annotations}
/>
<ListDisplay
name="接口"
color="primary"
entities={classInfo.interfaces}
/>
<ListDisplay name="Classloader" entities={classInfo.classloader} />
{classInfo.fields ? (
<>
<div className="header-2">字段信息</div>
<Table removeWrapper>
<TableHeader>
<TableColumn>名称</TableColumn>
<TableColumn>类型</TableColumn>
<TableColumn>静态</TableColumn>
<TableColumn>修饰符</TableColumn>
</TableHeader>
<TableBody items={classInfo.fields}>
{(field) => (
<TableRow key={field.name}>
<TableCell>{field.name}</TableCell>
<TableCell>{field.type}</TableCell>
<TableCell>{field.static.toString()}</TableCell>
<TableCell>{field.modifier}</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</>
) : null}
</CardBody>
</Card>
</div>
)
}

const ScMessageDetail: React.FC<DetailComponentProps<ScMessage>> = ({
msg,
}) => {
if (msg.classNames) {
return <ListDisplay name="搜索到以下类" entities={msg.classNames} />
} else if (msg.classInfo) {
return <ClassInfoDisplay classInfo={msg.classInfo} />
}
}

export default ScMessageDetail

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { registerMessageView } from '@/pages/channel/[channelId]/_tabs/_console/_message_view/factory.ts'
import MemoryMessageDetail from '@/pages/channel/[channelId]/_tabs/_console/_message_view/_component/MemoryMessageDetail.tsx'

registerMessageView({
detailComponent: MemoryMessageDetail,
display: (_) => ({
color: 'default',
name: '查看内存信息',
tag: 'memory',
}),
type: 'memory',
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { type DetailComponentProps, registerMessageView } from '../factory.ts'
import OgnlCommonMessageDetail from '../_component/OgnlCommonMessageDetail.tsx'
import type { ArthasResponseWithId } from '@/api/impl/arthas.ts'
import React from 'react'

function createComponent<T extends ArthasResponseWithId>(
ognlResultGetter: (r: T) => string,
): React.FC<DetailComponentProps<T>> {
const Component: React.FC<DetailComponentProps<T>> = (props) => {
return <OgnlCommonMessageDetail raw={ognlResultGetter(props.msg)} />
}
return Component
}

type WatchMessage = {
type: 'watch'
jobId: number
accessPoint: string
className: string
cost: number
methodName: string
sizeLimit: number
ts: string
value: string
fid: number
}

registerMessageView({
type: 'watch',
display: (message) => ({
name: `${message.className}#${message.methodName}`,
color: 'default',
tag: 'watch',
}),
detailComponent: createComponent<WatchMessage>((msg) => msg.value),
})

type GetStaticMessage = {
type: 'getstatic'
jobId: number
fid: number
field: string
fieldName: string
}

registerMessageView({
type: 'getstatic',
display: (message) => ({
name: `查看静态属性: ${message.fieldName}`,
color: 'default',
tag: 'getstatic',
}),
detailComponent: createComponent<GetStaticMessage>((msg) => msg.field),
})

type OgnlMessage = {
type: 'ognl'
jobId: number
fid: number
value: string
}
registerMessageView({
type: 'ognl',
display: (_) => ({
name: `执行 Ognl 表达式`,
color: 'default',
tag: 'ognl',
}),
detailComponent: createComponent<OgnlMessage>((msg) => msg.value),
})
Loading