|
|
|
|
@@ -13,7 +13,6 @@ import logStyles from '../../styles/log-panel.module.css';
|
|
|
|
|
import chartCardStyles from '../../styles/chart-card.module.css';
|
|
|
|
|
import { useAgents, useAgentEvents } from '../../api/queries/agents';
|
|
|
|
|
import { useApplicationLogs } from '../../api/queries/logs';
|
|
|
|
|
import { useStatsTimeseries } from '../../api/queries/executions';
|
|
|
|
|
import { useAgentMetrics } from '../../api/queries/agent-metrics';
|
|
|
|
|
import { formatUptime, mapLogLevel, eventSeverity, eventIcon } from '../../utils/agent-utils';
|
|
|
|
|
import { useEnvironmentStore } from '../../api/environment-store';
|
|
|
|
|
@@ -47,7 +46,6 @@ export default function AgentInstance() {
|
|
|
|
|
const selectedEnv = useEnvironmentStore((s) => s.environment);
|
|
|
|
|
const { data: agents, isLoading } = useAgents(undefined, appId, selectedEnv);
|
|
|
|
|
const { data: events } = useAgentEvents(appId, instanceId, 50, eventRefreshTo, selectedEnv);
|
|
|
|
|
const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, undefined, appId, selectedEnv);
|
|
|
|
|
|
|
|
|
|
const agent = useMemo(
|
|
|
|
|
() => (agents || []).find((a: any) => a.instanceId === instanceId) as any,
|
|
|
|
|
@@ -59,33 +57,26 @@ export default function AgentInstance() {
|
|
|
|
|
agent?.instanceId || null,
|
|
|
|
|
['process.cpu.usage.value', 'jvm.memory.used.value', 'jvm.memory.max.value'],
|
|
|
|
|
1,
|
|
|
|
|
timeFrom, timeTo,
|
|
|
|
|
);
|
|
|
|
|
const cpuPct = latestMetrics?.metrics?.['process.cpu.usage.value']?.[0]?.value;
|
|
|
|
|
const heapUsed = latestMetrics?.metrics?.['jvm.memory.used.value']?.[0]?.value;
|
|
|
|
|
const heapMax = latestMetrics?.metrics?.['jvm.memory.max.value']?.[0]?.value;
|
|
|
|
|
const memPct = heapMax ? (heapUsed! / heapMax) * 100 : undefined;
|
|
|
|
|
|
|
|
|
|
// Chart metrics (60 buckets)
|
|
|
|
|
// Chart metrics (60 buckets, time-range-aware)
|
|
|
|
|
const { data: jvmMetrics } = useAgentMetrics(
|
|
|
|
|
agent?.instanceId || null,
|
|
|
|
|
['process.cpu.usage.value', 'jvm.memory.used.value', 'jvm.memory.max.value', 'jvm.threads.live.value', 'jvm.gc.pause.total_time'],
|
|
|
|
|
60,
|
|
|
|
|
60, timeFrom, timeTo,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const chartData = useMemo(() => {
|
|
|
|
|
const buckets: any[] = timeseries?.buckets || [];
|
|
|
|
|
// Compute bucket duration in seconds from consecutive timestamps (for msg/s conversion)
|
|
|
|
|
const bucketSecs =
|
|
|
|
|
buckets.length >= 2
|
|
|
|
|
? (new Date(buckets[1].timestamp).getTime() - new Date(buckets[0].timestamp).getTime()) / 1000
|
|
|
|
|
: 60;
|
|
|
|
|
return buckets.map((b: any) => ({
|
|
|
|
|
time: b.timestamp,
|
|
|
|
|
throughput: bucketSecs > 0 ? b.totalCount / bucketSecs : b.totalCount,
|
|
|
|
|
latency: b.avgDurationMs,
|
|
|
|
|
errorPct: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0,
|
|
|
|
|
}));
|
|
|
|
|
}, [timeseries]);
|
|
|
|
|
// Agent pipeline metrics (60 buckets, time-range-aware)
|
|
|
|
|
const { data: agentMetrics } = useAgentMetrics(
|
|
|
|
|
agent?.instanceId || null,
|
|
|
|
|
['cameleer.chunks.exported.count', 'cameleer.chunks.dropped.count'],
|
|
|
|
|
60, timeFrom, timeTo,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const feedEvents = useMemo<FeedEvent[]>(() => {
|
|
|
|
|
const mapped = (events || [])
|
|
|
|
|
@@ -127,15 +118,17 @@ export default function AgentInstance() {
|
|
|
|
|
return pts.map((p: any) => ({ time: p.time, gc: p.value }));
|
|
|
|
|
}, [jvmMetrics]);
|
|
|
|
|
|
|
|
|
|
const throughputData = useMemo(() => {
|
|
|
|
|
if (!chartData.length) return [];
|
|
|
|
|
return chartData.map((d: any) => ({ time: d.time, throughput: d.throughput }));
|
|
|
|
|
}, [chartData]);
|
|
|
|
|
const chunksExportedData = useMemo(() => {
|
|
|
|
|
const pts = agentMetrics?.metrics?.['cameleer.chunks.exported.count'];
|
|
|
|
|
if (!pts?.length) return [];
|
|
|
|
|
return pts.map((p: any) => ({ time: p.time, exported: p.value }));
|
|
|
|
|
}, [agentMetrics]);
|
|
|
|
|
|
|
|
|
|
const errorData = useMemo(() => {
|
|
|
|
|
if (!chartData.length) return [];
|
|
|
|
|
return chartData.map((d: any) => ({ time: d.time, errorPct: d.errorPct }));
|
|
|
|
|
}, [chartData]);
|
|
|
|
|
const chunksDroppedData = useMemo(() => {
|
|
|
|
|
const pts = agentMetrics?.metrics?.['cameleer.chunks.dropped.count'];
|
|
|
|
|
if (!pts?.length) return [];
|
|
|
|
|
return pts.map((p: any) => ({ time: p.time, dropped: p.value }));
|
|
|
|
|
}, [agentMetrics]);
|
|
|
|
|
|
|
|
|
|
// Application logs
|
|
|
|
|
const { data: rawLogs } = useApplicationLogs(appId, instanceId, { toOverride: logRefreshTo, source: logSource || undefined });
|
|
|
|
|
@@ -350,37 +343,17 @@ export default function AgentInstance() {
|
|
|
|
|
|
|
|
|
|
<div className={chartCardStyles.chartCard}>
|
|
|
|
|
<div className={styles.chartHeader}>
|
|
|
|
|
<span className={styles.chartTitle}>Throughput</span>
|
|
|
|
|
<span className={styles.chartMeta}>
|
|
|
|
|
{agent?.tps != null ? `${agent.tps.toFixed(1)} msg/s` : ''}
|
|
|
|
|
</span>
|
|
|
|
|
<span className={styles.chartTitle}>GC Pauses</span>
|
|
|
|
|
<span className={styles.chartMeta} />
|
|
|
|
|
</div>
|
|
|
|
|
{throughputData.length ? (
|
|
|
|
|
<ThemedChart data={throughputData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="msg/s">
|
|
|
|
|
<Line dataKey="throughput" name="msg/s" stroke={CHART_COLORS[0]}
|
|
|
|
|
strokeWidth={2} dot={false} />
|
|
|
|
|
{gcData.length ? (
|
|
|
|
|
<ThemedChart data={gcData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="ms">
|
|
|
|
|
<Area dataKey="gc" name="GC ms" stroke={CHART_COLORS[1]}
|
|
|
|
|
fill={CHART_COLORS[1]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
|
|
|
|
</ThemedChart>
|
|
|
|
|
) : (
|
|
|
|
|
<EmptyState title="No data" description="No throughput data in range" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={chartCardStyles.chartCard}>
|
|
|
|
|
<div className={styles.chartHeader}>
|
|
|
|
|
<span className={styles.chartTitle}>Error Rate</span>
|
|
|
|
|
<span className={styles.chartMeta}>
|
|
|
|
|
{agent?.errorRate != null ? `${(agent.errorRate * 100).toFixed(1)}%` : ''}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
{errorData.length ? (
|
|
|
|
|
<ThemedChart data={errorData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="%">
|
|
|
|
|
<Line dataKey="errorPct" name="Error %" stroke={CHART_COLORS[0]}
|
|
|
|
|
strokeWidth={2} dot={false} />
|
|
|
|
|
</ThemedChart>
|
|
|
|
|
) : (
|
|
|
|
|
<EmptyState title="No data" description="No error data in range" />
|
|
|
|
|
<EmptyState title="No data" description="No GC metrics available" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -406,17 +379,33 @@ export default function AgentInstance() {
|
|
|
|
|
|
|
|
|
|
<div className={chartCardStyles.chartCard}>
|
|
|
|
|
<div className={styles.chartHeader}>
|
|
|
|
|
<span className={styles.chartTitle}>GC Pauses</span>
|
|
|
|
|
<span className={styles.chartTitle}>Chunks Exported</span>
|
|
|
|
|
<span className={styles.chartMeta} />
|
|
|
|
|
</div>
|
|
|
|
|
{gcData.length ? (
|
|
|
|
|
<ThemedChart data={gcData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="ms">
|
|
|
|
|
<Area dataKey="gc" name="GC ms" stroke={CHART_COLORS[1]}
|
|
|
|
|
fill={CHART_COLORS[1]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
|
|
|
|
{chunksExportedData.length ? (
|
|
|
|
|
<ThemedChart data={chunksExportedData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="chunks">
|
|
|
|
|
<Area dataKey="exported" name="Exported" stroke={CHART_COLORS[0]}
|
|
|
|
|
fill={CHART_COLORS[0]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
|
|
|
|
</ThemedChart>
|
|
|
|
|
) : (
|
|
|
|
|
<EmptyState title="No data" description="No GC metrics available" />
|
|
|
|
|
<EmptyState title="No data" description="No chunk export metrics available" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={chartCardStyles.chartCard}>
|
|
|
|
|
<div className={styles.chartHeader}>
|
|
|
|
|
<span className={styles.chartTitle}>Chunks Dropped</span>
|
|
|
|
|
<span className={styles.chartMeta} />
|
|
|
|
|
</div>
|
|
|
|
|
{chunksDroppedData.length ? (
|
|
|
|
|
<ThemedChart data={chunksDroppedData} height={160} xDataKey="time"
|
|
|
|
|
xTickFormatter={formatTime} yLabel="chunks">
|
|
|
|
|
<Area dataKey="dropped" name="Dropped" stroke="var(--error)"
|
|
|
|
|
fill="var(--error)" fillOpacity={0.1} strokeWidth={2} dot={false} />
|
|
|
|
|
</ThemedChart>
|
|
|
|
|
) : (
|
|
|
|
|
<EmptyState title="No data" description="No chunk drop metrics available" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|