fix: optimize monitor data fetching for large time ranges (#5)
* fix: adjust cluster monitor (fetch data independently, lazy load, copy request) Co-authored-by: yaojiping <yaojiping@infini.ltd>
This commit is contained in:
parent
4823339a01
commit
d6a8e2ff6b
|
@ -39,8 +39,11 @@ const formatTimeout = (timeout) => {
|
||||||
return timeout
|
return timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TIMEOUT_CACHE_KEY = "monitor-timeout"
|
||||||
|
|
||||||
const Monitor = (props) => {
|
const Monitor = (props) => {
|
||||||
const {
|
const {
|
||||||
|
selectedCluster,
|
||||||
formatState,
|
formatState,
|
||||||
getBreadcrumbList,
|
getBreadcrumbList,
|
||||||
StatisticBar,
|
StatisticBar,
|
||||||
|
@ -61,7 +64,7 @@ const Monitor = (props) => {
|
||||||
timeFormatter: formatter.dates(1),
|
timeFormatter: formatter.dates(1),
|
||||||
},
|
},
|
||||||
timeInterval: formatTimeInterval(param?.timeInterval),
|
timeInterval: formatTimeInterval(param?.timeInterval),
|
||||||
timeout: formatTimeout(param?.timeout),
|
timeout: formatTimeout(param?.timeout) || localStorage.getItem(TIMEOUT_CACHE_KEY) || '120s',
|
||||||
param: param,
|
param: param,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -104,6 +107,11 @@ const Monitor = (props) => {
|
||||||
|
|
||||||
const breadcrumbList = getBreadcrumbList(state);
|
const breadcrumbList = getBreadcrumbList(state);
|
||||||
|
|
||||||
|
const isAgent = useMemo(() => {
|
||||||
|
const { monitor_configs = {} } = selectedCluster || {}
|
||||||
|
return monitor_configs?.node_stats?.enabled === false && monitor_configs?.index_stats?.enabled === false
|
||||||
|
}, [JSON.stringify(selectedCluster?.monitor_configs)])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<BreadcrumbList data={breadcrumbList} />
|
<BreadcrumbList data={breadcrumbList} />
|
||||||
|
@ -111,7 +119,7 @@ const Monitor = (props) => {
|
||||||
<Card bodyStyle={{ padding: 15 }}>
|
<Card bodyStyle={{ padding: 15 }}>
|
||||||
<div style={{ marginBottom: 5 }}>
|
<div style={{ marginBottom: 5 }}>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<div style={{ flexGrow: 0, minWidth: 400 }}>
|
<div style={{ flexGrow: 0 }}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
locale={getLocale()}
|
locale={getLocale()}
|
||||||
start={state.timeRange.min}
|
start={state.timeRange.min}
|
||||||
|
@ -128,6 +136,7 @@ const Monitor = (props) => {
|
||||||
showTimeout={true}
|
showTimeout={true}
|
||||||
timeout={state.timeout}
|
timeout={state.timeout}
|
||||||
onTimeSettingChange={(timeSetting) => {
|
onTimeSettingChange={(timeSetting) => {
|
||||||
|
localStorage.setItem(TIMEOUT_CACHE_KEY, timeSetting.timeout)
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
timeInterval: timeSetting.timeInterval,
|
timeInterval: timeSetting.timeInterval,
|
||||||
|
@ -139,16 +148,6 @@ const Monitor = (props) => {
|
||||||
recentlyUsedRangesKey={'monitor'}
|
recentlyUsedRangesKey={'monitor'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
loading={spinning}
|
|
||||||
icon={"reload"}
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
handleTimeChange({ start: state.timeRange.min, end: state.timeRange.max, timeInterval: state.timeInterval, timeout: state.timeout})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatMessage({ id: "form.button.refresh"})}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -178,6 +177,8 @@ const Monitor = (props) => {
|
||||||
pane.component
|
pane.component
|
||||||
) : (
|
) : (
|
||||||
<pane.component
|
<pane.component
|
||||||
|
selectedCluster={selectedCluster}
|
||||||
|
isAgent={isAgent}
|
||||||
{...state}
|
{...state}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
setSpinning={setSpinning}
|
setSpinning={setSpinning}
|
||||||
|
|
|
@ -347,4 +347,7 @@ export default {
|
||||||
"cluster.providers.aliyun": "Aliyun",
|
"cluster.providers.aliyun": "Aliyun",
|
||||||
"cluster.providers.tencent-cloud": "Tencent-cloud",
|
"cluster.providers.tencent-cloud": "Tencent-cloud",
|
||||||
"cluster.providers.ecloud": "Ecloud",
|
"cluster.providers.ecloud": "Ecloud",
|
||||||
|
|
||||||
|
"cluster.metrics.request.copy": "Copy request",
|
||||||
|
"cluster.metrics.request.copy.success": "Copy request successfully"
|
||||||
};
|
};
|
||||||
|
|
|
@ -332,4 +332,7 @@ export default {
|
||||||
"cluster.providers.aliyun": "阿里云",
|
"cluster.providers.aliyun": "阿里云",
|
||||||
"cluster.providers.tencent-cloud": "腾讯云",
|
"cluster.providers.tencent-cloud": "腾讯云",
|
||||||
"cluster.providers.ecloud": "移动云",
|
"cluster.providers.ecloud": "移动云",
|
||||||
|
|
||||||
|
"cluster.metrics.request.copy": "复制请求",
|
||||||
|
"cluster.metrics.request.copy.success": "复制请求成功"
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,11 +99,7 @@ export default {
|
||||||
.filter((item) => item.enabled)
|
.filter((item) => item.enabled)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return {
|
return {
|
||||||
name: item.name,
|
...item,
|
||||||
id: item.id,
|
|
||||||
endpoint: item.endpoint,
|
|
||||||
host: item.host,
|
|
||||||
version: item.version,
|
|
||||||
distribution: item.distribution || "elasticsearch",
|
distribution: item.distribution || "elasticsearch",
|
||||||
cluster_uuid: item.cluster_uuid || "",
|
cluster_uuid: item.cluster_uuid || "",
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tabs } from "antd";
|
import { Tabs } from "antd";
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import NodeMetric from "../../components/node_metric";
|
import NodeMetric from "../../components/node_metric";
|
||||||
import IndexMetric from "../../components/index_metric";
|
import IndexMetric from "../../components/index_metric";
|
||||||
import ClusterMetric from "../../components/cluster_metric";
|
import ClusterMetric from "../../components/cluster_metric";
|
||||||
|
@ -10,11 +10,22 @@ import { ESPrefix } from "@/services/common";
|
||||||
const timezone = "local";
|
const timezone = "local";
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
|
selectedCluster,
|
||||||
clusterID,
|
clusterID,
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
|
timeout,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const isVersionGTE6 = useMemo(() => {
|
||||||
|
const main = selectedCluster?.version?.split('.')[0]
|
||||||
|
if (main && parseInt(main) >= 6) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}, [selectedCluster?.version])
|
||||||
|
|
||||||
const [param, setParam] = useState({
|
const [param, setParam] = useState({
|
||||||
tab: "cluster",
|
tab: "cluster",
|
||||||
});
|
});
|
||||||
|
@ -48,6 +59,20 @@ export default ({
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
'cluster_health',
|
||||||
|
'index_throughput',
|
||||||
|
'search_throughput',
|
||||||
|
'index_latency',
|
||||||
|
'search_latency',
|
||||||
|
'cluster_documents',
|
||||||
|
'node_count',
|
||||||
|
'cluster_indices',
|
||||||
|
'circuit_breaker',
|
||||||
|
'shard_count',
|
||||||
|
'cluster_storage'
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
@ -64,6 +89,137 @@ export default ({
|
||||||
param={param}
|
param={param}
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
[
|
||||||
|
"operations",
|
||||||
|
[
|
||||||
|
"indexing_rate",
|
||||||
|
"indexing_bytes",
|
||||||
|
"query_rate",
|
||||||
|
"fetch_rate",
|
||||||
|
"scroll_rate",
|
||||||
|
"refresh_rate",
|
||||||
|
"flush_rate",
|
||||||
|
"merges_rate",
|
||||||
|
"scroll_open_contexts"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"latency",
|
||||||
|
[
|
||||||
|
"indexing_latency",
|
||||||
|
"query_latency",
|
||||||
|
"fetch_latency",
|
||||||
|
"scroll_latency",
|
||||||
|
"refresh_latency",
|
||||||
|
"flush_latency",
|
||||||
|
"merge_latency"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"system",
|
||||||
|
[
|
||||||
|
"cpu",
|
||||||
|
"disk",
|
||||||
|
"open_file",
|
||||||
|
"open_file_percent",
|
||||||
|
"os_cpu",
|
||||||
|
"os_load_average_1m",
|
||||||
|
"os_used_mem",
|
||||||
|
"os_used_swap"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"circuit_breaker",
|
||||||
|
[
|
||||||
|
"parent_breaker",
|
||||||
|
"accounting_breaker",
|
||||||
|
"fielddata_breaker",
|
||||||
|
"request_breaker",
|
||||||
|
"in_flight_requests_breaker",
|
||||||
|
"model_inference_breaker"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"io",
|
||||||
|
[
|
||||||
|
"total_io_operations",
|
||||||
|
"total_read_io_operations",
|
||||||
|
"total_write_io_operations"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"transport",
|
||||||
|
[
|
||||||
|
"transport_rx_bytes",
|
||||||
|
"transport_rx_rate",
|
||||||
|
"transport_tx_bytes",
|
||||||
|
"transport_tx_rate",
|
||||||
|
"transport_outbound_connections"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"storage",
|
||||||
|
[
|
||||||
|
"segment_count",
|
||||||
|
"index_storage"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"document",
|
||||||
|
[
|
||||||
|
"docs_count",
|
||||||
|
"docs_deleted"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http",
|
||||||
|
[
|
||||||
|
"http_connect_num",
|
||||||
|
"http_rate"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"JVM",
|
||||||
|
[
|
||||||
|
"jvm_heap_used_percent",
|
||||||
|
"jvm_used_heap",
|
||||||
|
"jvm_mem_young_peak_used",
|
||||||
|
"jvm_mem_young_used",
|
||||||
|
"jvm_young_gc_latency",
|
||||||
|
"jvm_young_gc_rate",
|
||||||
|
"jvm_mem_old_peak_used",
|
||||||
|
"jvm_mem_old_used",
|
||||||
|
"jvm_old_gc_latency",
|
||||||
|
"jvm_old_gc_rate"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"memory",
|
||||||
|
[
|
||||||
|
"segment_doc_values_memory",
|
||||||
|
"segment_index_writer_memory",
|
||||||
|
"segment_memory",
|
||||||
|
"segment_stored_fields_memory",
|
||||||
|
"segment_term_vectors_memory",
|
||||||
|
"segment_terms_memory"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cache",
|
||||||
|
[
|
||||||
|
"query_cache",
|
||||||
|
"request_cache",
|
||||||
|
"fielddata_cache",
|
||||||
|
"query_cache_count",
|
||||||
|
"query_cache_hit",
|
||||||
|
"request_cache_hit",
|
||||||
|
"query_cache_miss",
|
||||||
|
"request_cache_miss"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
@ -80,6 +236,73 @@ export default ({
|
||||||
param={param}
|
param={param}
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
[
|
||||||
|
"operations",
|
||||||
|
[
|
||||||
|
"indexing_rate",
|
||||||
|
"indexing_bytes",
|
||||||
|
"query_times",
|
||||||
|
"fetch_times",
|
||||||
|
"scroll_times",
|
||||||
|
"refresh_times",
|
||||||
|
"flush_times",
|
||||||
|
"merge_times"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"latency",
|
||||||
|
[
|
||||||
|
"indexing_latency",
|
||||||
|
"query_latency",
|
||||||
|
"fetch_latency",
|
||||||
|
"scroll_latency",
|
||||||
|
"refresh_latency",
|
||||||
|
"flush_latency",
|
||||||
|
"merge_latency"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"storage",
|
||||||
|
[
|
||||||
|
"index_storage",
|
||||||
|
"segment_count"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"document",
|
||||||
|
[
|
||||||
|
"doc_count",
|
||||||
|
"docs_deleted",
|
||||||
|
"doc_percent"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"memory",
|
||||||
|
[
|
||||||
|
"segment_doc_values_memory",
|
||||||
|
"segment_fields_memory",
|
||||||
|
"segment_memory",
|
||||||
|
"segment_terms_memory",
|
||||||
|
"segment_index_writer_memory",
|
||||||
|
"segment_term_vectors_memory"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cache",
|
||||||
|
[
|
||||||
|
"query_cache",
|
||||||
|
"request_cache",
|
||||||
|
"fielddata_cache",
|
||||||
|
"query_cache_count",
|
||||||
|
"query_cache_hit",
|
||||||
|
"request_cache_hit",
|
||||||
|
"query_cache_miss",
|
||||||
|
"request_cache_miss"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
@ -96,6 +319,80 @@ export default ({
|
||||||
param={param}
|
param={param}
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
isVersionGTE6 ? [
|
||||||
|
"thread_pool_write",
|
||||||
|
[
|
||||||
|
"write_active",
|
||||||
|
"write_queue",
|
||||||
|
"write_rejected",
|
||||||
|
"write_threads"
|
||||||
|
]
|
||||||
|
] : [
|
||||||
|
"thread_pool_index",
|
||||||
|
[
|
||||||
|
"index_active",
|
||||||
|
"index_queue",
|
||||||
|
"index_rejected",
|
||||||
|
"index_threads",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_search",
|
||||||
|
[
|
||||||
|
"search_active",
|
||||||
|
"search_queue",
|
||||||
|
"search_rejected",
|
||||||
|
"search_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
!isVersionGTE6 ? [
|
||||||
|
"thread_pool_bulk",
|
||||||
|
[
|
||||||
|
"bulk_active",
|
||||||
|
"bulk_queue",
|
||||||
|
"bulk_rejected",
|
||||||
|
"bulk_threads",
|
||||||
|
]
|
||||||
|
] : undefined,
|
||||||
|
[
|
||||||
|
"thread_pool_get",
|
||||||
|
[
|
||||||
|
"get_active",
|
||||||
|
"get_queue",
|
||||||
|
"get_rejected",
|
||||||
|
"get_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_flush",
|
||||||
|
[
|
||||||
|
"flush_active",
|
||||||
|
"flush_queue",
|
||||||
|
"flush_rejected",
|
||||||
|
"flush_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_refresh",
|
||||||
|
[
|
||||||
|
"refresh_active",
|
||||||
|
"refresh_queue",
|
||||||
|
"refresh_rejected",
|
||||||
|
"refresh_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_force_merge",
|
||||||
|
[
|
||||||
|
"force_merge_active",
|
||||||
|
"force_merge_queue",
|
||||||
|
"force_merge_rejected",
|
||||||
|
"force_merge_threads"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
].filter((item) => !!item)}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
{/* <Tabs.TabPane
|
{/* <Tabs.TabPane
|
||||||
|
|
|
@ -27,6 +27,7 @@ const Page = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Monitor
|
<Monitor
|
||||||
|
selectedCluster={selectedCluster}
|
||||||
formatState={(state) => {
|
formatState={(state) => {
|
||||||
let clusterID = props.match.params?.cluster_id;
|
let clusterID = props.match.params?.cluster_id;
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -8,15 +8,18 @@ export default ({
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
|
timeout,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ClusterMetric
|
<ClusterMetric
|
||||||
timezone={timezone}
|
timezone={timezone}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
|
timeout={timeout}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
overview={1}
|
overview={1}
|
||||||
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
metrics={['index_throughput', 'search_throughput', 'index_latency', 'search_latency']}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ const StatisticBar = ({
|
||||||
setSpinning,
|
setSpinning,
|
||||||
clusterAvailable,
|
clusterAvailable,
|
||||||
clusterMonitored,
|
clusterMonitored,
|
||||||
|
onInfoChange
|
||||||
}) => {
|
}) => {
|
||||||
const { loading, error, value } = useFetch(
|
const { loading, error, value } = useFetch(
|
||||||
`${ESPrefix}/${clusterID}/metrics`,
|
`${ESPrefix}/${clusterID}/metrics`,
|
||||||
|
@ -34,6 +35,12 @@ const StatisticBar = ({
|
||||||
setSpinning(loading);
|
setSpinning(loading);
|
||||||
}, [loading]);
|
}, [loading]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onInfoChange) {
|
||||||
|
onInfoChange(value)
|
||||||
|
}
|
||||||
|
}, [JSON.stringify(value)]);
|
||||||
|
|
||||||
let overviewStatistic = [];
|
let overviewStatistic = [];
|
||||||
if (value?.summary) {
|
if (value?.summary) {
|
||||||
let rawStats = value.summary;
|
let rawStats = value.summary;
|
||||||
|
|
|
@ -10,7 +10,8 @@ export default ({
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
shardID,
|
shardID,
|
||||||
bucketSize
|
bucketSize,
|
||||||
|
timeout
|
||||||
}) => {
|
}) => {
|
||||||
const [param, setParam] = useState({
|
const [param, setParam] = useState({
|
||||||
show_top: false,
|
show_top: false,
|
||||||
|
@ -26,6 +27,73 @@ export default ({
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
shardID={shardID}
|
shardID={shardID}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
[
|
||||||
|
"operations",
|
||||||
|
[
|
||||||
|
"indexing_rate",
|
||||||
|
"indexing_bytes",
|
||||||
|
"query_times",
|
||||||
|
"fetch_times",
|
||||||
|
"scroll_times",
|
||||||
|
"refresh_times",
|
||||||
|
"flush_times",
|
||||||
|
"merge_times"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"latency",
|
||||||
|
[
|
||||||
|
"indexing_latency",
|
||||||
|
"query_latency",
|
||||||
|
"fetch_latency",
|
||||||
|
"scroll_latency",
|
||||||
|
"refresh_latency",
|
||||||
|
"flush_latency",
|
||||||
|
"merge_latency"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"storage",
|
||||||
|
[
|
||||||
|
"index_storage",
|
||||||
|
"segment_count"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"document",
|
||||||
|
[
|
||||||
|
"doc_count",
|
||||||
|
"docs_deleted",
|
||||||
|
"doc_percent"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"memory",
|
||||||
|
[
|
||||||
|
"segment_doc_values_memory",
|
||||||
|
"segment_fields_memory",
|
||||||
|
"segment_memory",
|
||||||
|
"segment_terms_memory",
|
||||||
|
"segment_index_writer_memory",
|
||||||
|
"segment_term_vectors_memory"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cache",
|
||||||
|
[
|
||||||
|
"query_cache",
|
||||||
|
"request_cache",
|
||||||
|
"fielddata_cache",
|
||||||
|
"query_cache_count",
|
||||||
|
"query_cache_hit",
|
||||||
|
"request_cache_hit",
|
||||||
|
"query_cache_miss",
|
||||||
|
"request_cache_miss"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import { formatMessage } from "umi/locale";
|
||||||
import Monitor from "@/components/Overview/Monitor";
|
import Monitor from "@/components/Overview/Monitor";
|
||||||
import StatisticBar from "./statistic_bar";
|
import StatisticBar from "./statistic_bar";
|
||||||
import ShardStatisticBar from "./shard_statistic_bar";
|
import ShardStatisticBar from "./shard_statistic_bar";
|
||||||
|
import { connect } from "dva";
|
||||||
|
|
||||||
export default (props) => {
|
const Page = (props) => {
|
||||||
|
const { clusterStatus, selectedCluster } = props;
|
||||||
const {shard_id} = props.location.query;
|
const {shard_id} = props.location.query;
|
||||||
const panes = React.useMemo(()=>{
|
const panes = React.useMemo(()=>{
|
||||||
const panes = [
|
const panes = [
|
||||||
|
@ -26,6 +28,7 @@ export default (props) => {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Monitor
|
<Monitor
|
||||||
|
selectedCluster={selectedCluster}
|
||||||
formatState={(state) => {
|
formatState={(state) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -80,3 +83,8 @@ export default (props) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default connect(({ global }) => ({
|
||||||
|
selectedCluster: global.selectedCluster,
|
||||||
|
clusterStatus: global.clusterStatus,
|
||||||
|
}))(Page);
|
||||||
|
|
|
@ -5,12 +5,14 @@ import ClusterMetric from "../../components/cluster_metric";
|
||||||
const timezone = "local";
|
const timezone = "local";
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
|
isAgent,
|
||||||
clusterID,
|
clusterID,
|
||||||
indexName,
|
indexName,
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
shardID,
|
shardID,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
|
timeout
|
||||||
}) => {
|
}) => {
|
||||||
let url = `${ESPrefix}/${clusterID}/index/${indexName}/metrics`;
|
let url = `${ESPrefix}/${clusterID}/index/${indexName}/metrics`;
|
||||||
if(shardID){
|
if(shardID){
|
||||||
|
@ -24,6 +26,15 @@ export default ({
|
||||||
overview={1}
|
overview={1}
|
||||||
fetchUrl={url}
|
fetchUrl={url}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
"index_health",
|
||||||
|
"index_throughput",
|
||||||
|
"search_throughput",
|
||||||
|
"index_latency",
|
||||||
|
"search_latency",
|
||||||
|
isAgent ? "shard_state" : undefined,
|
||||||
|
].filter((item) => !!item)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState,useEffect } from "react";
|
import { useState,useEffect, useMemo } from "react";
|
||||||
import { Tabs } from "antd";
|
import { Tabs } from "antd";
|
||||||
import NodeMetric from "../../components/node_metric";
|
import NodeMetric from "../../components/node_metric";
|
||||||
import QueueMetric from "../../components/queue_metric";
|
import QueueMetric from "../../components/queue_metric";
|
||||||
|
@ -7,12 +7,23 @@ import { formatMessage } from "umi/locale";
|
||||||
const timezone = "local";
|
const timezone = "local";
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
|
selectedCluster,
|
||||||
clusterID,
|
clusterID,
|
||||||
nodeID,
|
nodeID,
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
|
timeout,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const isVersionGTE6 = useMemo(() => {
|
||||||
|
const main = selectedCluster?.version?.split('.')[0]
|
||||||
|
if (main && parseInt(main) >= 6) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}, [selectedCluster?.version])
|
||||||
|
|
||||||
const [param, setParam] = useState({
|
const [param, setParam] = useState({
|
||||||
show_top: false,
|
show_top: false,
|
||||||
node_name: nodeID,
|
node_name: nodeID,
|
||||||
|
@ -53,6 +64,137 @@ export default ({
|
||||||
param={param}
|
param={param}
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
[
|
||||||
|
"operations",
|
||||||
|
[
|
||||||
|
"indexing_rate",
|
||||||
|
"indexing_bytes",
|
||||||
|
"query_rate",
|
||||||
|
"fetch_rate",
|
||||||
|
"scroll_rate",
|
||||||
|
"refresh_rate",
|
||||||
|
"flush_rate",
|
||||||
|
"merges_rate",
|
||||||
|
"scroll_open_contexts"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"latency",
|
||||||
|
[
|
||||||
|
"indexing_latency",
|
||||||
|
"query_latency",
|
||||||
|
"fetch_latency",
|
||||||
|
"scroll_latency",
|
||||||
|
"refresh_latency",
|
||||||
|
"flush_latency",
|
||||||
|
"merge_latency"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"system",
|
||||||
|
[
|
||||||
|
"cpu",
|
||||||
|
"disk",
|
||||||
|
"open_file",
|
||||||
|
"open_file_percent",
|
||||||
|
"os_cpu",
|
||||||
|
"os_load_average_1m",
|
||||||
|
"os_used_mem",
|
||||||
|
"os_used_swap"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"circuit_breaker",
|
||||||
|
[
|
||||||
|
"parent_breaker",
|
||||||
|
"accounting_breaker",
|
||||||
|
"fielddata_breaker",
|
||||||
|
"request_breaker",
|
||||||
|
"in_flight_requests_breaker",
|
||||||
|
"model_inference_breaker"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"io",
|
||||||
|
[
|
||||||
|
"total_io_operations",
|
||||||
|
"total_read_io_operations",
|
||||||
|
"total_write_io_operations"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"transport",
|
||||||
|
[
|
||||||
|
"transport_rx_bytes",
|
||||||
|
"transport_rx_rate",
|
||||||
|
"transport_tx_bytes",
|
||||||
|
"transport_tx_rate",
|
||||||
|
"transport_outbound_connections"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"storage",
|
||||||
|
[
|
||||||
|
"segment_count",
|
||||||
|
"index_storage"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"document",
|
||||||
|
[
|
||||||
|
"docs_count",
|
||||||
|
"docs_deleted"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http",
|
||||||
|
[
|
||||||
|
"http_connect_num",
|
||||||
|
"http_rate"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"JVM",
|
||||||
|
[
|
||||||
|
"jvm_heap_used_percent",
|
||||||
|
"jvm_used_heap",
|
||||||
|
"jvm_mem_young_peak_used",
|
||||||
|
"jvm_mem_young_used",
|
||||||
|
"jvm_young_gc_latency",
|
||||||
|
"jvm_young_gc_rate",
|
||||||
|
"jvm_mem_old_peak_used",
|
||||||
|
"jvm_mem_old_used",
|
||||||
|
"jvm_old_gc_latency",
|
||||||
|
"jvm_old_gc_rate"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"memory",
|
||||||
|
[
|
||||||
|
"segment_doc_values_memory",
|
||||||
|
"segment_index_writer_memory",
|
||||||
|
"segment_memory",
|
||||||
|
"segment_stored_fields_memory",
|
||||||
|
"segment_term_vectors_memory",
|
||||||
|
"segment_terms_memory"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cache",
|
||||||
|
[
|
||||||
|
"query_cache",
|
||||||
|
"request_cache",
|
||||||
|
"fielddata_cache",
|
||||||
|
"query_cache_count",
|
||||||
|
"query_cache_hit",
|
||||||
|
"request_cache_hit",
|
||||||
|
"query_cache_miss",
|
||||||
|
"request_cache_miss"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
@ -69,6 +211,80 @@ export default ({
|
||||||
param={param}
|
param={param}
|
||||||
setParam={setParam}
|
setParam={setParam}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
timeout={timeout}
|
||||||
|
metrics={[
|
||||||
|
isVersionGTE6 ? [
|
||||||
|
"thread_pool_write",
|
||||||
|
[
|
||||||
|
"write_active",
|
||||||
|
"write_queue",
|
||||||
|
"write_rejected",
|
||||||
|
"write_threads"
|
||||||
|
]
|
||||||
|
] : [
|
||||||
|
"thread_pool_index",
|
||||||
|
[
|
||||||
|
"index_active",
|
||||||
|
"index_queue",
|
||||||
|
"index_rejected",
|
||||||
|
"index_threads",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_search",
|
||||||
|
[
|
||||||
|
"search_active",
|
||||||
|
"search_queue",
|
||||||
|
"search_rejected",
|
||||||
|
"search_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
!isVersionGTE6 ? [
|
||||||
|
"thread_pool_bulk",
|
||||||
|
[
|
||||||
|
"bulk_active",
|
||||||
|
"bulk_queue",
|
||||||
|
"bulk_rejected",
|
||||||
|
"bulk_threads",
|
||||||
|
]
|
||||||
|
] : undefined,
|
||||||
|
[
|
||||||
|
"thread_pool_get",
|
||||||
|
[
|
||||||
|
"get_active",
|
||||||
|
"get_queue",
|
||||||
|
"get_rejected",
|
||||||
|
"get_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_flush",
|
||||||
|
[
|
||||||
|
"flush_active",
|
||||||
|
"flush_queue",
|
||||||
|
"flush_rejected",
|
||||||
|
"flush_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_refresh",
|
||||||
|
[
|
||||||
|
"refresh_active",
|
||||||
|
"refresh_queue",
|
||||||
|
"refresh_rejected",
|
||||||
|
"refresh_threads"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"thread_pool_force_merge",
|
||||||
|
[
|
||||||
|
"force_merge_active",
|
||||||
|
"force_merge_queue",
|
||||||
|
"force_merge_rejected",
|
||||||
|
"force_merge_threads"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
].filter((item) => !!item)}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -5,16 +5,18 @@ import Shards from "./shards";
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import Monitor from "@/components/Overview/Monitor";
|
import Monitor from "@/components/Overview/Monitor";
|
||||||
import StatisticBar from "./statistic_bar";
|
import StatisticBar from "./statistic_bar";
|
||||||
|
import { connect } from "dva";
|
||||||
|
|
||||||
const panes = [
|
const panes = [
|
||||||
{ title: "Overview", component: Overview, key: "overview" },
|
{ title: "Overview", component: Overview, key: "overview" },
|
||||||
{ title: "Advanced", component: Advanced, key: "advanced" },
|
{ title: "Advanced", component: Advanced, key: "advanced" },
|
||||||
{ title: "Shards", component: Shards, key: "shards" },
|
{ title: "Shards", component: Shards, key: "shards" },
|
||||||
];
|
];
|
||||||
|
const Page = (props) => {
|
||||||
export default (props) => {
|
const { clusterStatus, selectedCluster } = props;
|
||||||
return (
|
return (
|
||||||
<Monitor
|
<Monitor
|
||||||
|
selectedCluster={selectedCluster}
|
||||||
formatState={(state) => {
|
formatState={(state) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -54,3 +56,8 @@ export default (props) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default connect(({ global }) => ({
|
||||||
|
selectedCluster: global.selectedCluster,
|
||||||
|
clusterStatus: global.clusterStatus,
|
||||||
|
}))(Page);
|
|
@ -1,24 +1,40 @@
|
||||||
import { ESPrefix } from "@/services/common";
|
import { ESPrefix } from "@/services/common";
|
||||||
import StatisticBar from "./statistic_bar";
|
import StatisticBar from "./statistic_bar";
|
||||||
import ClusterMetric from "../../components/cluster_metric";
|
import ClusterMetric from "../../components/cluster_metric";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
const timezone = "local";
|
const timezone = "local";
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
|
isAgent,
|
||||||
clusterID,
|
clusterID,
|
||||||
nodeID,
|
nodeID,
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
|
timeout,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClusterMetric
|
<ClusterMetric
|
||||||
timezone={timezone}
|
timezone={timezone}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
|
timeout={timeout}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
overview={1}
|
overview={1}
|
||||||
fetchUrl={`${ESPrefix}/${clusterID}/node/${nodeID}/metrics`}
|
fetchUrl={`${ESPrefix}/${clusterID}/node/${nodeID}/metrics`}
|
||||||
bucketSize={bucketSize}
|
bucketSize={bucketSize}
|
||||||
|
metrics={[
|
||||||
|
"node_health",
|
||||||
|
"cpu",
|
||||||
|
"jvm",
|
||||||
|
"index_throughput",
|
||||||
|
"search_throughput",
|
||||||
|
"index_latency",
|
||||||
|
"search_latency",
|
||||||
|
"parent_breaker",
|
||||||
|
isAgent ? "shard_state" : undefined,
|
||||||
|
].filter((item) => !!item)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { formatMessage } from "umi/locale";
|
||||||
|
import styles from "./Metrics.scss";
|
||||||
|
import { Alert, Empty, Icon, message, Spin, Tooltip } from "antd";
|
||||||
|
import {
|
||||||
|
Axis,
|
||||||
|
Chart,
|
||||||
|
CurveType,
|
||||||
|
LineSeries,
|
||||||
|
BarSeries,
|
||||||
|
niceTimeFormatByDay,
|
||||||
|
Position,
|
||||||
|
ScaleType,
|
||||||
|
Settings,
|
||||||
|
timeFormatter,
|
||||||
|
} from "@elastic/charts";
|
||||||
|
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
||||||
|
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||||
|
import moment from "moment";
|
||||||
|
import { ESPrefix } from "@/services/common";
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
timezone,
|
||||||
|
timeRange,
|
||||||
|
timeout,
|
||||||
|
handleTimeChange,
|
||||||
|
fetchUrl,
|
||||||
|
metricKey,
|
||||||
|
title,
|
||||||
|
queryParams,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
formatMetric
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const [metric, setMetric] = useState()
|
||||||
|
|
||||||
|
const [isInView, setIsInView] = useState(false);
|
||||||
|
|
||||||
|
const [error, setError] = useState();
|
||||||
|
|
||||||
|
const observerRef = useRef({ isInView: false })
|
||||||
|
|
||||||
|
const containerRef = useRef(null)
|
||||||
|
|
||||||
|
const firstFetchRef = useRef(true)
|
||||||
|
|
||||||
|
const fetchData = async (queryParams, fetchUrl, metricKey, showLoading) => {
|
||||||
|
if (!observerRef.current.isInView || !fetchUrl) return;
|
||||||
|
setError()
|
||||||
|
if (firstFetchRef.current || showLoading) {
|
||||||
|
setLoading(true)
|
||||||
|
}
|
||||||
|
const res = await request(fetchUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
queryParams: {
|
||||||
|
...queryParams,
|
||||||
|
key: metricKey,
|
||||||
|
timeout
|
||||||
|
},
|
||||||
|
ignoreTimeout: true
|
||||||
|
}, false, false)
|
||||||
|
if (res?.error?.reason) {
|
||||||
|
setError(res.error.reason)
|
||||||
|
} else if (res && !res.error) {
|
||||||
|
const { metrics = {} } = res || {};
|
||||||
|
const metric = metrics[metricKey]
|
||||||
|
setMetric(formatMetric ? formatMetric(metric) : metric);
|
||||||
|
}
|
||||||
|
if (firstFetchRef.current || showLoading) {
|
||||||
|
setLoading(false)
|
||||||
|
firstFetchRef.current = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
observerRef.current.deps = cloneDeep([queryParams, fetchUrl, metricKey])
|
||||||
|
fetchData(queryParams, fetchUrl, metricKey)
|
||||||
|
}, [JSON.stringify(queryParams), fetchUrl, metricKey])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
entries => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
observerRef.current.isInView = true
|
||||||
|
if (JSON.stringify(observerRef.current.deps) !== JSON.stringify(observerRef.current.lastDeps)) {
|
||||||
|
observerRef.current.lastDeps = cloneDeep(observerRef.current.deps)
|
||||||
|
fetchData(...observerRef.current.deps)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
observerRef.current.isInView = false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: null,
|
||||||
|
threshold: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (containerRef.current) {
|
||||||
|
observer.observe(containerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
observer.unobserve(containerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isInView]);
|
||||||
|
|
||||||
|
const chartRef = useRef();
|
||||||
|
|
||||||
|
const pointerUpdate = (event) => {
|
||||||
|
if (chartRef.current) {
|
||||||
|
chartRef.current.dispatchExternalPointerEvent(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChartBrush = ({ x }) => {
|
||||||
|
if (!x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let [from, to] = x;
|
||||||
|
if (typeof handleTimeChange == "function") {
|
||||||
|
if (to - from < 20 * 1000) {
|
||||||
|
from -= 10 * 1000;
|
||||||
|
to += 10 * 1000;
|
||||||
|
}
|
||||||
|
handleTimeChange({
|
||||||
|
start: moment(from).toISOString(),
|
||||||
|
end: moment(to).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderChart = () => {
|
||||||
|
if (loading) return <div style={{ height: 200 }}></div>
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div style={{ height: 200, padding: 10 }}>
|
||||||
|
<Alert style={{ maxHeight: '100%', overflow: 'auto', wordBreak: 'break-all'}} message={error} type="error"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const axis = metric?.axis || [];
|
||||||
|
const lines = metric?.lines || [];
|
||||||
|
if (lines.every((item) => !item.data || item.data.length === 0)) {
|
||||||
|
return <Empty style={{ height: 200, margin: 0, paddingTop: 64 }} image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
size={[, 200]}
|
||||||
|
className={styles.vizChartItem}
|
||||||
|
ref={chartRef}
|
||||||
|
>
|
||||||
|
<Settings
|
||||||
|
pointerUpdateDebounce={0}
|
||||||
|
pointerUpdateTrigger="x"
|
||||||
|
onPointerUpdate={pointerUpdate}
|
||||||
|
showLegend
|
||||||
|
legendPosition={Position.Bottom}
|
||||||
|
onBrushEnd={handleChartBrush}
|
||||||
|
tooltip={{
|
||||||
|
headerFormatter: ({ value }) =>
|
||||||
|
`${formatter.full_dates(value)}`,
|
||||||
|
}}
|
||||||
|
debug={false}
|
||||||
|
/>
|
||||||
|
<Axis
|
||||||
|
id={`${metricKey}-bottom`}
|
||||||
|
position={Position.Bottom}
|
||||||
|
showOverlappingTicks
|
||||||
|
labelFormat={timeRange.timeFormatter}
|
||||||
|
tickFormat={timeRange.timeFormatter}
|
||||||
|
/>
|
||||||
|
{metricKey == "cluster_health" ? (
|
||||||
|
<Axis
|
||||||
|
id="cluster_health"
|
||||||
|
position={Position.Left}
|
||||||
|
tickFormat={(d) => Number(d).toFixed(0) + "%"}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{axis.map((item) => {
|
||||||
|
return (
|
||||||
|
<Axis
|
||||||
|
key={metricKey + "-" + item.id}
|
||||||
|
id={metricKey + "-" + item.id}
|
||||||
|
showGridLines={item.showGridLines}
|
||||||
|
groupId={item.group}
|
||||||
|
position={item.position}
|
||||||
|
ticks={item.ticks}
|
||||||
|
labelFormat={getFormatter(
|
||||||
|
item.formatType,
|
||||||
|
item.labelFormat
|
||||||
|
)}
|
||||||
|
tickFormat={getFormatter(
|
||||||
|
item.formatType,
|
||||||
|
item.tickFormat
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{lines.map((item) => {
|
||||||
|
if (item.type == "Bar") {
|
||||||
|
return (
|
||||||
|
<BarSeries
|
||||||
|
key={item.metric.label}
|
||||||
|
xScaleType={ScaleType.Time}
|
||||||
|
yScaleType={ScaleType.Linear}
|
||||||
|
xAccessor="x"
|
||||||
|
yAccessors={["y"]}
|
||||||
|
stackAccessors={["x"]}
|
||||||
|
splitSeriesAccessors={["g"]}
|
||||||
|
data={item.data}
|
||||||
|
color={({ specId, yAccessor, splitAccessors }) => {
|
||||||
|
const g = splitAccessors.get("g");
|
||||||
|
if (
|
||||||
|
yAccessor === "y"
|
||||||
|
|
||||||
|
) {
|
||||||
|
if( ["red", "yellow", "green"].includes(g)){
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
if(g == "online" || g == "available"){
|
||||||
|
return "green";
|
||||||
|
}
|
||||||
|
if(g == "offline" || g == "unavailable" || g == "N/A"){
|
||||||
|
return "gray";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<LineSeries
|
||||||
|
key={item.metric.label}
|
||||||
|
id={item.metric.label}
|
||||||
|
groupId={item.metric.group}
|
||||||
|
timeZone={timezone}
|
||||||
|
color={item.color}
|
||||||
|
xScaleType={ScaleType.Time}
|
||||||
|
yScaleType={ScaleType.Linear}
|
||||||
|
xAccessor={0}
|
||||||
|
tickFormat={getFormatter(
|
||||||
|
item.metric.formatType,
|
||||||
|
item.metric.tickFormat,
|
||||||
|
item.metric.units
|
||||||
|
)}
|
||||||
|
yAccessors={[1]}
|
||||||
|
data={item.data}
|
||||||
|
curve={CurveType.CURVE_MONOTONE_X}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Chart>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartTitle = { title };
|
||||||
|
const lines = metric?.lines || [];
|
||||||
|
if (metricKey == "cluster_health") {
|
||||||
|
chartTitle.units = "%";
|
||||||
|
} else {
|
||||||
|
if (lines[0]?.metric) {
|
||||||
|
if (lines[0].metric.formatType.toLowerCase == "bytes") {
|
||||||
|
chartTitle.units = lines[0].metric.formatType;
|
||||||
|
} else {
|
||||||
|
chartTitle.units = lines[0].metric.units;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={metricKey} ref={containerRef} className={className} style={style}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<div className={styles.vizChartItemTitle}>
|
||||||
|
<span>
|
||||||
|
{chartTitle.title}
|
||||||
|
{chartTitle.units ? `(${chartTitle.units})` : ""}
|
||||||
|
</span>
|
||||||
|
{
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
metric?.request && (
|
||||||
|
<CopyToClipboard text={`GET .infini_metrics/_search\n${metric.request}`}>
|
||||||
|
<Tooltip title={formatMessage({id: "cluster.metrics.request.copy"})}>
|
||||||
|
<Icon
|
||||||
|
className={styles.copy}
|
||||||
|
type="copy"
|
||||||
|
onClick={() => message.success(formatMessage({id: "cluster.metrics.request.copy.success"}))}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</CopyToClipboard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Tooltip title={formatMessage({id: "form.button.refresh"})}>
|
||||||
|
<Icon className={styles.copy} style={{ marginLeft: 12 }} type="reload" onClick={() => fetchData(...observerRef.current.deps, true)}/>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{renderChart()}
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,6 +18,13 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vizChartItem {
|
.vizChartItem {
|
||||||
background: white !important;
|
background: white !important;
|
||||||
|
|
||||||
|
|
|
@ -1,289 +1,65 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
Axis,
|
|
||||||
Chart,
|
|
||||||
CurveType,
|
|
||||||
LineSeries,
|
|
||||||
BarSeries,
|
|
||||||
niceTimeFormatByDay,
|
|
||||||
Position,
|
|
||||||
ScaleType,
|
|
||||||
Settings,
|
|
||||||
timeFormatter,
|
|
||||||
} from "@elastic/charts";
|
|
||||||
import useFetch from "@/lib/hooks/use_fetch";
|
|
||||||
import { ESPrefix } from "@/services/common";
|
|
||||||
import styles from "./Metrics.scss";
|
import styles from "./Metrics.scss";
|
||||||
import { Spin, Radio, Select, Skeleton } from "antd";
|
|
||||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
|
||||||
import "./node_metric.scss";
|
import "./node_metric.scss";
|
||||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
||||||
import moment from "moment";
|
import MetricChart from "./MetricChart";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import _ from "lodash";
|
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||||
|
|
||||||
export default ({
|
export default (props) => {
|
||||||
timezone,
|
|
||||||
timeRange,
|
const { fetchUrl, overview, metrics = [], renderExtra, timeRange, timeout, timezone, bucketSize, handleTimeChange } = props
|
||||||
handleTimeChange,
|
|
||||||
overview,
|
if (!fetchUrl || metrics.length === 0) {
|
||||||
fetchUrl,
|
|
||||||
renderExtra,
|
|
||||||
bucketSize
|
|
||||||
}) => {
|
|
||||||
if (!fetchUrl) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const queryParams = React.useMemo(() => {
|
|
||||||
const bounds = calculateBounds({
|
const queryParams = useMemo(() => {
|
||||||
from: timeRange.min,
|
const newParams = formatTimeRange(timeRange);
|
||||||
to: timeRange.max,
|
|
||||||
});
|
|
||||||
let params = {
|
|
||||||
min: bounds.min.valueOf(),
|
|
||||||
max: bounds.max.valueOf(),
|
|
||||||
};
|
|
||||||
if (overview) {
|
if (overview) {
|
||||||
params.overview = overview;
|
newParams.overview = overview;
|
||||||
}
|
}
|
||||||
if (bucketSize) {
|
if (bucketSize) {
|
||||||
params.bucket_size = bucketSize
|
newParams.bucket_size = bucketSize
|
||||||
}
|
}
|
||||||
return params;
|
return newParams;
|
||||||
}, [timeRange, bucketSize]);
|
}, [timeRange, bucketSize]);
|
||||||
const { loading, error, value } = useFetch(
|
|
||||||
fetchUrl,
|
|
||||||
{
|
|
||||||
queryParams: queryParams,
|
|
||||||
},
|
|
||||||
[queryParams, fetchUrl]
|
|
||||||
);
|
|
||||||
|
|
||||||
const metrics = React.useMemo(() => {
|
|
||||||
const { metrics = {} } = value || {};
|
|
||||||
return Object.values(metrics)
|
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
.map((m) => {
|
|
||||||
let lines = m.lines || [];
|
|
||||||
m.lines = lines.map((line) => {
|
|
||||||
const data = line.data || [];
|
|
||||||
if (data.length > 1) {
|
|
||||||
line.data = line.data.slice(0, data.length - 1);
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const chartRefs = React.useRef();
|
|
||||||
React.useEffect(() => {
|
|
||||||
let refs = [];
|
|
||||||
Object.keys(metrics).map((m) => {
|
|
||||||
refs.push(React.createRef());
|
|
||||||
});
|
|
||||||
chartRefs.current = refs;
|
|
||||||
}, [metrics]);
|
|
||||||
|
|
||||||
const pointerUpdate = (event) => {
|
|
||||||
chartRefs.current.forEach((ref) => {
|
|
||||||
if (ref.current) {
|
|
||||||
ref.current.dispatchExternalPointerEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChartBrush = ({ x }) => {
|
|
||||||
if (!x) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let [from, to] = x;
|
|
||||||
if (typeof handleTimeChange == "function") {
|
|
||||||
if (to - from < 20 * 1000) {
|
|
||||||
from -= 10 * 1000;
|
|
||||||
to += 10 * 1000;
|
|
||||||
}
|
|
||||||
handleTimeChange({
|
|
||||||
start: moment(from).toISOString(),
|
|
||||||
end: moment(to).toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let refIdx = 0;
|
|
||||||
if (Object.keys(metrics).length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extra = renderExtra ? renderExtra() : null;
|
const extra = renderExtra ? renderExtra() : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="cluster-metric">
|
<div id="cluster-metric">
|
||||||
<div className={styles.metricList}>
|
<div className={styles.metricList}>
|
||||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
{metrics.map((metricKey, i) => (
|
||||||
{Object.keys(metrics).map((e, i) => {
|
<MetricChart
|
||||||
let axis = metrics[e].axis;
|
key={metricKey}
|
||||||
let lines = metrics[e].lines;
|
timezone={timezone}
|
||||||
if (lines.length == 0 || (lines && lines[0]?.data?.length == 0)) {
|
timeRange={timeRange}
|
||||||
return null;
|
timeout={timeout}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
fetchUrl={fetchUrl}
|
||||||
|
metricKey={metricKey}
|
||||||
|
title={formatMessage({
|
||||||
|
id: "cluster.metrics.axis." + metricKey + ".title",
|
||||||
|
})}
|
||||||
|
queryParams={queryParams}
|
||||||
|
className={styles.vizChartContainer}
|
||||||
|
style={{ flex: metricKey == "cluster_health" ? '0 0 calc(100%)' : '0 0 calc(50% - 5px)'}}
|
||||||
|
formatMetric={(metric) => {
|
||||||
|
if (metric) {
|
||||||
|
const lines = metric.lines || []
|
||||||
|
metric.lines = lines.map((line) => {
|
||||||
|
const data = line.data || [];
|
||||||
|
if (data.length > 1) {
|
||||||
|
line.data = line.data.slice(0, data.length - 1);
|
||||||
}
|
}
|
||||||
let disableHeaderFormat = false;
|
return line;
|
||||||
let headerUnit = "";
|
|
||||||
let chartTitle = {};
|
|
||||||
if (metrics[e].key == "cluster_health") {
|
|
||||||
chartTitle.units = "%";
|
|
||||||
} else {
|
|
||||||
if (lines[0].metric.formatType.toLowerCase == "bytes") {
|
|
||||||
chartTitle.units = lines[0].metric.formatType;
|
|
||||||
} else {
|
|
||||||
chartTitle.units = lines[0].metric.units;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chartTitle.title = formatMessage({
|
|
||||||
id: "cluster.metrics.axis." + metrics[e].key + ".title",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={e} className={styles.vizChartContainer} style={{ flex: metrics[e].key == "cluster_health" ? '0 0 calc(100%)' : '0 0 calc(50% - 5px)'}}>
|
|
||||||
<div className={styles.vizChartItemTitle}>
|
|
||||||
<span>
|
|
||||||
{chartTitle.title}
|
|
||||||
{chartTitle.units ? `(${chartTitle.units})` : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Chart
|
|
||||||
size={[, 200]}
|
|
||||||
className={styles.vizChartItem}
|
|
||||||
ref={chartRefs[i]}
|
|
||||||
>
|
|
||||||
<Settings
|
|
||||||
pointerUpdateDebounce={0}
|
|
||||||
pointerUpdateTrigger="x"
|
|
||||||
// externalPointerEvents={{
|
|
||||||
// tooltip: { visible: true },
|
|
||||||
// }}
|
|
||||||
onPointerUpdate={pointerUpdate}
|
|
||||||
// theme={theme}
|
|
||||||
showLegend
|
|
||||||
legendPosition={Position.Bottom}
|
|
||||||
onBrushEnd={handleChartBrush}
|
|
||||||
tooltip={{
|
|
||||||
headerFormatter: disableHeaderFormat
|
|
||||||
? undefined
|
|
||||||
: ({ value }) =>
|
|
||||||
`${formatter.full_dates(value)}${
|
|
||||||
headerUnit ? ` ${headerUnit}` : ""
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
debug={false}
|
|
||||||
/>
|
|
||||||
<Axis
|
|
||||||
id="{e}-bottom"
|
|
||||||
position={Position.Bottom}
|
|
||||||
showOverlappingTicks
|
|
||||||
labelFormat={timeRange.timeFormatter}
|
|
||||||
tickFormat={timeRange.timeFormatter}
|
|
||||||
/>
|
|
||||||
{metrics[e].key == "cluster_health" ? (
|
|
||||||
<Axis
|
|
||||||
id="cluster_health"
|
|
||||||
// title={formatMessage({
|
|
||||||
// id:
|
|
||||||
// "dashboard.charts.title." +
|
|
||||||
// metrics[e].key +
|
|
||||||
// ".axis.percent",
|
|
||||||
// })}
|
|
||||||
position={Position.Left}
|
|
||||||
tickFormat={(d) => Number(d).toFixed(0) + "%"}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{axis.map((item) => {
|
|
||||||
return (
|
|
||||||
<Axis
|
|
||||||
key={e + "-" + item.id}
|
|
||||||
id={e + "-" + item.id}
|
|
||||||
showGridLines={item.showGridLines}
|
|
||||||
groupId={item.group}
|
|
||||||
// title={formatMessage({
|
|
||||||
// id:
|
|
||||||
// "dashboard.charts.title." +
|
|
||||||
// metrics[e].key +
|
|
||||||
// ".axis." +
|
|
||||||
// item.title,
|
|
||||||
// })}
|
|
||||||
position={item.position}
|
|
||||||
ticks={item.ticks}
|
|
||||||
labelFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.labelFormat
|
|
||||||
)}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.tickFormat
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{lines.map((item) => {
|
|
||||||
if (item.type == "Bar") {
|
|
||||||
return (
|
|
||||||
<BarSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor="x"
|
|
||||||
yAccessors={["y"]}
|
|
||||||
stackAccessors={["x"]}
|
|
||||||
splitSeriesAccessors={["g"]}
|
|
||||||
data={item.data}
|
|
||||||
color={({ specId, yAccessor, splitAccessors }) => {
|
|
||||||
const g = splitAccessors.get("g");
|
|
||||||
if (
|
|
||||||
yAccessor === "y"
|
|
||||||
|
|
||||||
) {
|
|
||||||
if( ["red", "yellow", "green"].includes(g)){
|
|
||||||
return g;
|
|
||||||
}
|
}
|
||||||
if(g == "online" || g == "available"){
|
return metric
|
||||||
return "green";
|
|
||||||
}
|
|
||||||
if(g == "offline" || g == "unavailable" || g == "N/A"){
|
|
||||||
return "gray";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
))}
|
||||||
}
|
|
||||||
return (
|
|
||||||
<LineSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
id={item.metric.label}
|
|
||||||
groupId={item.metric.group}
|
|
||||||
timeZone={timezone}
|
|
||||||
color={item.color}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor={0}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.metric.formatType,
|
|
||||||
item.metric.tickFormat,
|
|
||||||
item.metric.units
|
|
||||||
)}
|
|
||||||
yAccessors={[1]}
|
|
||||||
data={item.data}
|
|
||||||
curve={CurveType.CURVE_MONOTONE_X}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Chart>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{
|
{
|
||||||
extra && (
|
extra && (
|
||||||
<div key={"metric_extra"} className={styles.vizChartContainer}>
|
<div key={"metric_extra"} className={styles.vizChartContainer}>
|
||||||
|
@ -291,7 +67,6 @@ export default ({
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Skeleton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,39 +1,19 @@
|
||||||
import * as React from "react";
|
import IndexSelect from "@/components/IndexSelect";
|
||||||
import {
|
|
||||||
Axis,
|
|
||||||
Chart,
|
|
||||||
CurveType,
|
|
||||||
LineSeries,
|
|
||||||
BarSeries,
|
|
||||||
niceTimeFormatByDay,
|
|
||||||
Position,
|
|
||||||
ScaleType,
|
|
||||||
Settings,
|
|
||||||
timeFormatter,
|
|
||||||
} from "@elastic/charts";
|
|
||||||
import useFetch from "@/lib/hooks/use_fetch";
|
import useFetch from "@/lib/hooks/use_fetch";
|
||||||
import { ESPrefix } from "@/services/common";
|
import { ESPrefix } from "@/services/common";
|
||||||
import styles from "./Metrics.scss";
|
import { Radio } from "antd";
|
||||||
import { Spin, Radio, Select, Skeleton } from "antd";
|
|
||||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
|
||||||
import "./node_metric.scss";
|
import "./node_metric.scss";
|
||||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
|
||||||
import moment from "moment";
|
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import MetricContainer from "./metric_container";
|
import MetricContainer from "./metric_container";
|
||||||
import _ from "lodash";
|
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||||
import IndexSelect from "@/components/IndexSelect";
|
import NodeSelect from "@/components/NodeSelect";
|
||||||
import Anchor from "@/components/Anchor";
|
import Anchor from "@/components/Anchor";
|
||||||
|
import MetricChart from "./MetricChart";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
const gorupOrder = [
|
export default (props) => {
|
||||||
"operations",
|
|
||||||
"latency",
|
const {
|
||||||
"storage",
|
|
||||||
"document",
|
|
||||||
"memory",
|
|
||||||
"cache",
|
|
||||||
];
|
|
||||||
export default ({
|
|
||||||
clusterID,
|
clusterID,
|
||||||
timezone,
|
timezone,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
@ -41,11 +21,15 @@ export default ({
|
||||||
param,
|
param,
|
||||||
setParam,
|
setParam,
|
||||||
shardID,
|
shardID,
|
||||||
bucketSize
|
bucketSize,
|
||||||
}) => {
|
metrics = [],
|
||||||
if (!clusterID) {
|
timeout
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (!clusterID || metrics.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showTop = param.show_top ?? true;
|
const showTop = param.show_top ?? true;
|
||||||
|
|
||||||
const topChange = (e) => {
|
const topChange = (e) => {
|
||||||
|
@ -68,16 +52,8 @@ export default ({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const queryParams = React.useMemo(() => {
|
const queryParams = useMemo(() => {
|
||||||
const bounds = calculateBounds({
|
const newParams = formatTimeRange(timeRange);
|
||||||
from: timeRange.min,
|
|
||||||
to: timeRange.max,
|
|
||||||
});
|
|
||||||
let newParams = {
|
|
||||||
min: bounds.min.valueOf(),
|
|
||||||
max: bounds.max.valueOf(),
|
|
||||||
};
|
|
||||||
console.log(shardID)
|
|
||||||
if(shardID){
|
if(shardID){
|
||||||
newParams.shard_id = shardID;
|
newParams.shard_id = shardID;
|
||||||
}
|
}
|
||||||
|
@ -92,73 +68,16 @@ export default ({
|
||||||
}
|
}
|
||||||
return newParams;
|
return newParams;
|
||||||
}, [param, timeRange, bucketSize]);
|
}, [param, timeRange, bucketSize]);
|
||||||
const { loading, error, value } = useFetch(
|
|
||||||
`${ESPrefix}/${clusterID}/index_metrics`,
|
|
||||||
{
|
|
||||||
queryParams: queryParams,
|
|
||||||
},
|
|
||||||
[clusterID, queryParams]
|
|
||||||
);
|
|
||||||
|
|
||||||
const metrics = React.useMemo(() => {
|
|
||||||
const grpMetrics = _.groupBy(value?.metrics, "group");
|
|
||||||
let metrics = {};
|
|
||||||
Object.keys(grpMetrics).forEach((k) => {
|
|
||||||
metrics[k] = (grpMetrics[k] || [])
|
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
});
|
|
||||||
return metrics;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const chartRefs = React.useRef();
|
|
||||||
React.useEffect(() => {
|
|
||||||
let refs = [];
|
|
||||||
Object.values(metrics).map((m) => {
|
|
||||||
m.forEach(() => {
|
|
||||||
refs.push(React.createRef());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
chartRefs.current = refs;
|
|
||||||
}, [metrics]);
|
|
||||||
|
|
||||||
const { value: indices } = useFetch(
|
const { value: indices } = useFetch(
|
||||||
`${ESPrefix}/${clusterID}/_cat/indices`,
|
`${ESPrefix}/${clusterID}/_cat/indices`,
|
||||||
{},
|
{},
|
||||||
[clusterID]
|
[clusterID]
|
||||||
);
|
);
|
||||||
const formatedIndices = React.useMemo(() => {
|
const formatedIndices = useMemo(() => {
|
||||||
return Object.values(indices || []);
|
return Object.values(indices || []);
|
||||||
}, [indices]);
|
}, [indices]);
|
||||||
|
|
||||||
const pointerUpdate = (event) => {
|
|
||||||
chartRefs.current.forEach((ref) => {
|
|
||||||
if (ref.current) {
|
|
||||||
ref.current.dispatchExternalPointerEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChartBrush = ({ x }) => {
|
|
||||||
if (!x) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let [from, to] = x;
|
|
||||||
if (typeof handleTimeChange == "function") {
|
|
||||||
if (to - from < 20 * 1000) {
|
|
||||||
from -= 10 * 1000;
|
|
||||||
to += 10 * 1000;
|
|
||||||
}
|
|
||||||
handleTimeChange({
|
|
||||||
start: moment(from).toISOString(),
|
|
||||||
end: moment(to).toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let refIdx = 0;
|
|
||||||
if (Object.keys(metrics).length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="node-metric">
|
<div id="node-metric">
|
||||||
{showTop ? (
|
{showTop ? (
|
||||||
|
@ -189,177 +108,38 @@ export default ({
|
||||||
|
|
||||||
<div className="px-box">
|
<div className="px-box">
|
||||||
<div className="px">
|
<div className="px">
|
||||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
{metrics.map((item, i) => {
|
||||||
{gorupOrder.map((e, i) => {
|
|
||||||
if (!metrics[e]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div key={e} style={{ margin: "8px 0" }}>
|
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||||
<MetricContainer
|
<MetricContainer
|
||||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
id={e}
|
id={item[0]}
|
||||||
>
|
>
|
||||||
<div className="metric-inner-cnt">
|
<div className="metric-inner-cnt">
|
||||||
{metrics[e].map((metric) => {
|
{
|
||||||
let axis = metric.axis;
|
item[1].map((metricKey) => (
|
||||||
let lines = metric.lines;
|
<MetricChart
|
||||||
if (
|
key={metricKey}
|
||||||
lines.length == 0 ||
|
timezone={timezone}
|
||||||
(lines && lines[0]?.data?.length == 0)
|
timeRange={timeRange}
|
||||||
) {
|
handleTimeChange={handleTimeChange}
|
||||||
return null;
|
fetchUrl={`${ESPrefix}/${clusterID}/index_metrics`}
|
||||||
|
metricKey={metricKey}
|
||||||
|
title={formatMessage({id: "cluster.metrics.index.axis." + metricKey + ".title"})}
|
||||||
|
queryParams={queryParams}
|
||||||
|
className={"metric-item"}
|
||||||
|
timeout={timeout}
|
||||||
|
/>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
let disableHeaderFormat = false;
|
|
||||||
let headerUnit = "";
|
|
||||||
let chartTitle = {};
|
|
||||||
if (lines[0].metric.formatType.toLowerCase == "bytes") {
|
|
||||||
chartTitle.units = lines[0].metric.formatType;
|
|
||||||
} else {
|
|
||||||
chartTitle.units = lines[0].metric.units;
|
|
||||||
}
|
|
||||||
chartTitle.title = formatMessage({
|
|
||||||
id:
|
|
||||||
"cluster.metrics.index.axis." + metric.key + ".title",
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div key={metric.key} className="metric-item">
|
|
||||||
<div className={styles.vizChartItemTitle}>
|
|
||||||
<span>
|
|
||||||
{chartTitle.title}
|
|
||||||
{chartTitle.units ? `(${chartTitle.units})` : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Chart
|
|
||||||
size={[, 200]}
|
|
||||||
className={styles.vizChartItem}
|
|
||||||
ref={chartRefs.current[refIdx++]}
|
|
||||||
>
|
|
||||||
<Settings
|
|
||||||
// theme={theme}
|
|
||||||
pointerUpdateDebounce={0}
|
|
||||||
pointerUpdateTrigger="x"
|
|
||||||
// externalPointerEvents={{
|
|
||||||
// tooltip: { visible: true },
|
|
||||||
// }}
|
|
||||||
onPointerUpdate={pointerUpdate}
|
|
||||||
showLegend
|
|
||||||
legendPosition={Position.Bottom}
|
|
||||||
onBrushEnd={handleChartBrush}
|
|
||||||
tooltip={{
|
|
||||||
headerFormatter: disableHeaderFormat
|
|
||||||
? undefined
|
|
||||||
: ({ value }) =>
|
|
||||||
`${formatter.full_dates(value)}${
|
|
||||||
headerUnit ? ` ${headerUnit}` : ""
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
debug={false}
|
|
||||||
/>
|
|
||||||
<Axis
|
|
||||||
id="{e}-bottom"
|
|
||||||
position={Position.Bottom}
|
|
||||||
showOverlappingTicks
|
|
||||||
labelFormat={timeRange.timeFormatter}
|
|
||||||
tickFormat={timeRange.timeFormatter}
|
|
||||||
ticks={8}
|
|
||||||
/>
|
|
||||||
{lines[0].type == "Bar" ? (
|
|
||||||
<Axis
|
|
||||||
id={"axis_left"}
|
|
||||||
position={Position.Left}
|
|
||||||
tickFormat={(d) => Number(d).toFixed(0) + "%"}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{axis.map((item) => {
|
|
||||||
return (
|
|
||||||
<Axis
|
|
||||||
key={e + "-" + item.id}
|
|
||||||
id={e + "-" + item.id}
|
|
||||||
showGridLines={item.showGridLines}
|
|
||||||
groupId={item.group}
|
|
||||||
// title={formatMessage({
|
|
||||||
// id:
|
|
||||||
// "cluster.metrics.index.axis." +
|
|
||||||
// metric.key +
|
|
||||||
// ".title",
|
|
||||||
// })}
|
|
||||||
position={item.position}
|
|
||||||
ticks={item.ticks}
|
|
||||||
labelFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.labelFormat
|
|
||||||
)}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.tickFormat
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{lines.map((item) => {
|
|
||||||
if (item.type == "Bar") {
|
|
||||||
return (
|
|
||||||
<BarSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor="x"
|
|
||||||
yAccessors={["y"]}
|
|
||||||
stackAccessors={["x"]}
|
|
||||||
splitSeriesAccessors={["g"]}
|
|
||||||
data={item.data}
|
|
||||||
color={({
|
|
||||||
specId,
|
|
||||||
yAccessor,
|
|
||||||
splitAccessors,
|
|
||||||
}) => {
|
|
||||||
const g = splitAccessors.get("g");
|
|
||||||
if (
|
|
||||||
yAccessor === "y" &&
|
|
||||||
["red", "yellow", "green"].includes(g)
|
|
||||||
) {
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<LineSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
id={item.metric.label}
|
|
||||||
groupId={item.metric.group}
|
|
||||||
timeZone={timezone}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor={0}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.metric.formatType,
|
|
||||||
item.metric.tickFormat,
|
|
||||||
item.metric.units
|
|
||||||
)}
|
|
||||||
yAccessors={[1]}
|
|
||||||
data={item.data}
|
|
||||||
curve={CurveType.CURVE_MONOTONE_X}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Chart>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</MetricContainer>
|
</MetricContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Skeleton>
|
|
||||||
</div>
|
</div>
|
||||||
<Anchor links={gorupOrder}></Anchor>
|
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,47 +1,18 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
Axis,
|
|
||||||
Chart,
|
|
||||||
CurveType,
|
|
||||||
LineSeries,
|
|
||||||
niceTimeFormatByDay,
|
|
||||||
Position,
|
|
||||||
ScaleType,
|
|
||||||
Settings,
|
|
||||||
timeFormatter,
|
|
||||||
} from "@elastic/charts";
|
|
||||||
import useFetch from "@/lib/hooks/use_fetch";
|
import useFetch from "@/lib/hooks/use_fetch";
|
||||||
import { ESPrefix } from "@/services/common";
|
import { ESPrefix } from "@/services/common";
|
||||||
import styles from "./Metrics.scss";
|
import { Radio } from "antd";
|
||||||
import { Spin, Radio, Select, Skeleton, Row, Col, InputNumber } from "antd";
|
|
||||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
|
||||||
import "./node_metric.scss";
|
import "./node_metric.scss";
|
||||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
|
||||||
import moment from "moment";
|
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import MetricContainer from "./metric_container";
|
import MetricContainer from "./metric_container";
|
||||||
import _ from "lodash";
|
|
||||||
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||||
import NodeSelect from "@/components/NodeSelect";
|
import NodeSelect from "@/components/NodeSelect";
|
||||||
import Anchor from "@/components/Anchor";
|
import Anchor from "@/components/Anchor";
|
||||||
|
import MetricChart from "./MetricChart";
|
||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
const gorupOrder = [
|
export default (props) => {
|
||||||
"operations",
|
|
||||||
"latency",
|
|
||||||
"system",
|
|
||||||
"circuit_breaker",
|
|
||||||
"io",
|
|
||||||
"transport",
|
|
||||||
|
|
||||||
"storage",
|
const {
|
||||||
"document",
|
|
||||||
"http",
|
|
||||||
"JVM",
|
|
||||||
"memory",
|
|
||||||
"cache",
|
|
||||||
];
|
|
||||||
|
|
||||||
export default ({
|
|
||||||
clusterID,
|
clusterID,
|
||||||
timezone,
|
timezone,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
@ -49,15 +20,17 @@ export default ({
|
||||||
param,
|
param,
|
||||||
setParam,
|
setParam,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
}) => {
|
timeout,
|
||||||
// const [filter, setFilter] = React.useState({
|
metrics = []
|
||||||
// top: "5",
|
} = props
|
||||||
// node_name: param?.transport,
|
|
||||||
// });
|
if (!clusterID || metrics.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const showTop = param.show_top ?? true;
|
const showTop = param.show_top ?? true;
|
||||||
|
|
||||||
const topChange = React.useCallback(
|
const topChange = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
// setFilter({
|
// setFilter({
|
||||||
// node_name: undefined,
|
// node_name: undefined,
|
||||||
|
@ -74,7 +47,7 @@ export default ({
|
||||||
[param]
|
[param]
|
||||||
);
|
);
|
||||||
|
|
||||||
const nodeValueChange = React.useCallback(
|
const nodeValueChange = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
const nodeNames = value.map(item=>item.host);
|
const nodeNames = value.map(item=>item.host);
|
||||||
setParam((param) => {
|
setParam((param) => {
|
||||||
|
@ -87,8 +60,15 @@ export default ({
|
||||||
},
|
},
|
||||||
[param]
|
[param]
|
||||||
);
|
);
|
||||||
const queryParams = React.useMemo(() => {
|
|
||||||
let newParams = formatTimeRange(timeRange);
|
const { value: nodes } = useFetch(
|
||||||
|
`${ESPrefix}/${clusterID}/nodes/realtime`,
|
||||||
|
{},
|
||||||
|
[clusterID]
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryParams = useMemo(() => {
|
||||||
|
const newParams = formatTimeRange(timeRange);
|
||||||
if (param.top) {
|
if (param.top) {
|
||||||
newParams.top = param.top;
|
newParams.top = param.top;
|
||||||
}
|
}
|
||||||
|
@ -100,40 +80,7 @@ export default ({
|
||||||
}
|
}
|
||||||
return newParams;
|
return newParams;
|
||||||
}, [param, timeRange, bucketSize]);
|
}, [param, timeRange, bucketSize]);
|
||||||
const { loading, error, value } = useFetch(
|
|
||||||
`${ESPrefix}/${clusterID}/node_metrics`,
|
|
||||||
{
|
|
||||||
queryParams: queryParams,
|
|
||||||
},
|
|
||||||
[clusterID, queryParams]
|
|
||||||
);
|
|
||||||
|
|
||||||
const metrics = React.useMemo(() => {
|
|
||||||
const grpMetrics = _.groupBy(value?.metrics, "group");
|
|
||||||
let metrics = {};
|
|
||||||
Object.keys(grpMetrics).forEach((k) => {
|
|
||||||
metrics[k] = (grpMetrics[k] || [])
|
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
});
|
|
||||||
return metrics;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const chartRefs = React.useRef();
|
|
||||||
React.useEffect(() => {
|
|
||||||
let refs = [];
|
|
||||||
Object.values(metrics).map((m) => {
|
|
||||||
m.forEach(() => {
|
|
||||||
refs.push(React.createRef());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
chartRefs.current = refs;
|
|
||||||
}, [metrics]);
|
|
||||||
|
|
||||||
const { value: nodes } = useFetch(
|
|
||||||
`${ESPrefix}/${clusterID}/nodes/realtime`,
|
|
||||||
{},
|
|
||||||
[clusterID]
|
|
||||||
);
|
|
||||||
const formatedNodes = React.useMemo(() => {
|
const formatedNodes = React.useMemo(() => {
|
||||||
if (!nodes) {
|
if (!nodes) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -146,36 +93,6 @@ export default ({
|
||||||
});
|
});
|
||||||
}, [nodes]);
|
}, [nodes]);
|
||||||
|
|
||||||
const pointerUpdate = (event) => {
|
|
||||||
chartRefs.current.forEach((ref) => {
|
|
||||||
if (ref.current) {
|
|
||||||
ref.current.dispatchExternalPointerEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChartBrush = ({ x }) => {
|
|
||||||
if (!x) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let [from, to] = x;
|
|
||||||
if (typeof handleTimeChange == "function") {
|
|
||||||
if (to - from < 20 * 1000) {
|
|
||||||
from -= 10 * 1000;
|
|
||||||
to += 10 * 1000;
|
|
||||||
}
|
|
||||||
handleTimeChange({
|
|
||||||
start: moment(from).toISOString(),
|
|
||||||
end: moment(to).toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let refIdx = 0;
|
|
||||||
if (Object.keys(metrics).length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="node-metric">
|
<div id="node-metric">
|
||||||
{showTop ? (
|
{showTop ? (
|
||||||
|
@ -214,146 +131,38 @@ export default ({
|
||||||
|
|
||||||
<div className="px-box">
|
<div className="px-box">
|
||||||
<div className="px">
|
<div className="px">
|
||||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
{metrics.map((item, i) => {
|
||||||
{//Object.keys(metrics)
|
|
||||||
gorupOrder.map((e, i) => {
|
|
||||||
let hasData = (metrics[e] || []).some(
|
|
||||||
(m) => m.lines && m.lines[0]?.data.length > 0
|
|
||||||
);
|
|
||||||
if (!hasData) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div key={e} style={{ margin: "8px 0" }}>
|
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||||
<MetricContainer
|
<MetricContainer
|
||||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
id={e}
|
id={item[0]}
|
||||||
>
|
>
|
||||||
<div className="metric-inner-cnt">
|
<div className="metric-inner-cnt">
|
||||||
{metrics[e].map((metric) => {
|
{
|
||||||
let axis = metric.axis;
|
item[1].map((metricKey) => (
|
||||||
let lines = metric.lines;
|
<MetricChart
|
||||||
if (
|
key={metricKey}
|
||||||
lines.length == 0 ||
|
timezone={timezone}
|
||||||
(lines && lines[0]?.data?.length == 0)
|
timeRange={timeRange}
|
||||||
) {
|
handleTimeChange={handleTimeChange}
|
||||||
return null;
|
fetchUrl={`${ESPrefix}/${clusterID}/node_metrics`}
|
||||||
|
metricKey={metricKey}
|
||||||
|
title={formatMessage({id:"cluster.metrics.node.axis." + metricKey + ".title"})}
|
||||||
|
queryParams={queryParams}
|
||||||
|
className={"metric-item"}
|
||||||
|
timeout={timeout}
|
||||||
|
/>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
let disableHeaderFormat = false;
|
|
||||||
let headerUnit = "";
|
|
||||||
let chartTitle = {};
|
|
||||||
if (lines[0].metric.formatType.toLowerCase == "bytes") {
|
|
||||||
chartTitle.units = lines[0].metric.formatType;
|
|
||||||
} else {
|
|
||||||
chartTitle.units = lines[0].metric.units;
|
|
||||||
}
|
|
||||||
chartTitle.title = formatMessage({
|
|
||||||
id:
|
|
||||||
"cluster.metrics.node.axis." + metric.key + ".title",
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div key={metric.key} className="metric-item">
|
|
||||||
<div className={styles.vizChartItemTitle}>
|
|
||||||
<span>
|
|
||||||
{chartTitle.title}
|
|
||||||
{chartTitle.units ? `(${chartTitle.units})` : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Chart
|
|
||||||
size={[, 200]}
|
|
||||||
className={styles.vizChartItem}
|
|
||||||
ref={chartRefs.current[refIdx++]}
|
|
||||||
>
|
|
||||||
<Settings
|
|
||||||
// theme={theme}
|
|
||||||
pointerUpdateDebounce={0}
|
|
||||||
pointerUpdateTrigger="x"
|
|
||||||
// externalPointerEvents={{
|
|
||||||
// tooltip: { visible: true },
|
|
||||||
// }}
|
|
||||||
onPointerUpdate={pointerUpdate}
|
|
||||||
showLegend
|
|
||||||
legendPosition={Position.Bottom}
|
|
||||||
onBrushEnd={handleChartBrush}
|
|
||||||
tooltip={{
|
|
||||||
headerFormatter: disableHeaderFormat
|
|
||||||
? undefined
|
|
||||||
: ({ value }) =>
|
|
||||||
`${formatter.full_dates(value)}${
|
|
||||||
headerUnit ? ` ${headerUnit}` : ""
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
debug={false}
|
|
||||||
/>
|
|
||||||
<Axis
|
|
||||||
id="{e}-bottom"
|
|
||||||
position={Position.Bottom}
|
|
||||||
showOverlappingTicks
|
|
||||||
labelFormat={timeRange.timeFormatter}
|
|
||||||
tickFormat={timeRange.timeFormatter}
|
|
||||||
ticks={8}
|
|
||||||
/>
|
|
||||||
{axis.map((item) => {
|
|
||||||
return (
|
|
||||||
<Axis
|
|
||||||
key={e + "-" + item.id}
|
|
||||||
id={e + "-" + item.id}
|
|
||||||
showGridLines={item.showGridLines}
|
|
||||||
groupId={item.group}
|
|
||||||
// title={formatMessage({
|
|
||||||
// id:
|
|
||||||
// "cluster.metrics.node.axis." +
|
|
||||||
// metric.key +
|
|
||||||
// ".title",
|
|
||||||
// })}
|
|
||||||
position={item.position}
|
|
||||||
ticks={item.ticks}
|
|
||||||
labelFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.labelFormat
|
|
||||||
)}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.tickFormat
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{lines.map((item) => {
|
|
||||||
return (
|
|
||||||
<LineSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
id={item.metric.label}
|
|
||||||
groupId={item.metric.group}
|
|
||||||
timeZone={timezone}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor={0}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.metric.formatType,
|
|
||||||
item.metric.tickFormat,
|
|
||||||
item.metric.units
|
|
||||||
)}
|
|
||||||
yAccessors={[1]}
|
|
||||||
data={item.data}
|
|
||||||
curve={CurveType.CURVE_MONOTONE_X}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Chart>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</MetricContainer>
|
</MetricContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Skeleton>
|
|
||||||
</div>
|
</div>
|
||||||
<Anchor links={gorupOrder}></Anchor>
|
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,41 +1,18 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
Axis,
|
|
||||||
Chart,
|
|
||||||
CurveType,
|
|
||||||
LineSeries,
|
|
||||||
niceTimeFormatByDay,
|
|
||||||
Position,
|
|
||||||
ScaleType,
|
|
||||||
Settings,
|
|
||||||
timeFormatter,
|
|
||||||
} from "@elastic/charts";
|
|
||||||
import useFetch from "@/lib/hooks/use_fetch";
|
import useFetch from "@/lib/hooks/use_fetch";
|
||||||
import { ESPrefix } from "@/services/common";
|
import { ESPrefix } from "@/services/common";
|
||||||
import styles from "./Metrics.scss";
|
import { Radio } from "antd";
|
||||||
import { Spin, Radio, Select, Skeleton, Row, Col } from "antd";
|
|
||||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
|
||||||
import "./node_metric.scss";
|
import "./node_metric.scss";
|
||||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
|
||||||
import moment from "moment";
|
|
||||||
import { formatMessage } from "umi/locale";
|
import { formatMessage } from "umi/locale";
|
||||||
import MetricContainer from "./metric_container";
|
import MetricContainer from "./metric_container";
|
||||||
import _ from "lodash";
|
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||||
import NodeSelect from "@/components/NodeSelect";
|
import NodeSelect from "@/components/NodeSelect";
|
||||||
import Anchor from "@/components/Anchor";
|
import Anchor from "@/components/Anchor";
|
||||||
|
import MetricChart from "./MetricChart";
|
||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
const gorupOrder = [
|
export default (props) => {
|
||||||
"thread_pool_write",
|
|
||||||
"thread_pool_index",
|
|
||||||
"thread_pool_search",
|
|
||||||
"thread_pool_bulk",
|
|
||||||
"thread_pool_get",
|
|
||||||
"thread_pool_flush",
|
|
||||||
"thread_pool_refresh",
|
|
||||||
"thread_pool_force_merge",
|
|
||||||
];
|
|
||||||
|
|
||||||
export default ({
|
const {
|
||||||
clusterID,
|
clusterID,
|
||||||
timezone,
|
timezone,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
@ -43,10 +20,22 @@ export default ({
|
||||||
param,
|
param,
|
||||||
setParam,
|
setParam,
|
||||||
bucketSize,
|
bucketSize,
|
||||||
}) => {
|
metrics = [],
|
||||||
|
timeout
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (!clusterID || metrics.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const showTop = param.show_top ?? true;
|
const showTop = param.show_top ?? true;
|
||||||
const topChange = React.useCallback(
|
|
||||||
|
const topChange = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
// setFilter({
|
||||||
|
// node_name: undefined,
|
||||||
|
// top: e.target.value,
|
||||||
|
// });
|
||||||
setParam((param) => {
|
setParam((param) => {
|
||||||
delete param["node_name"];
|
delete param["node_name"];
|
||||||
return {
|
return {
|
||||||
|
@ -58,7 +47,7 @@ export default ({
|
||||||
[param]
|
[param]
|
||||||
);
|
);
|
||||||
|
|
||||||
const nodeValueChange = React.useCallback(
|
const nodeValueChange = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
const nodeNames = value.map(item=>item.host);
|
const nodeNames = value.map(item=>item.host);
|
||||||
setParam((param) => {
|
setParam((param) => {
|
||||||
|
@ -71,15 +60,15 @@ export default ({
|
||||||
},
|
},
|
||||||
[param]
|
[param]
|
||||||
);
|
);
|
||||||
const queryParams = React.useMemo(() => {
|
|
||||||
const bounds = calculateBounds({
|
const { value: nodes } = useFetch(
|
||||||
from: timeRange.min,
|
`${ESPrefix}/${clusterID}/nodes/realtime`,
|
||||||
to: timeRange.max,
|
{},
|
||||||
});
|
[clusterID]
|
||||||
let newParams = {
|
);
|
||||||
min: bounds.min.valueOf(),
|
|
||||||
max: bounds.max.valueOf(),
|
const queryParams = useMemo(() => {
|
||||||
};
|
const newParams = formatTimeRange(timeRange);
|
||||||
if (param.top) {
|
if (param.top) {
|
||||||
newParams.top = param.top;
|
newParams.top = param.top;
|
||||||
}
|
}
|
||||||
|
@ -91,40 +80,7 @@ export default ({
|
||||||
}
|
}
|
||||||
return newParams;
|
return newParams;
|
||||||
}, [param, timeRange, bucketSize]);
|
}, [param, timeRange, bucketSize]);
|
||||||
const { loading, error, value } = useFetch(
|
|
||||||
`${ESPrefix}/${clusterID}/queue_metrics`,
|
|
||||||
{
|
|
||||||
queryParams: queryParams,
|
|
||||||
},
|
|
||||||
[clusterID, queryParams]
|
|
||||||
);
|
|
||||||
|
|
||||||
const metrics = React.useMemo(() => {
|
|
||||||
const grpMetrics = _.groupBy(value?.metrics, "group");
|
|
||||||
let metrics = {};
|
|
||||||
Object.keys(grpMetrics).forEach((k) => {
|
|
||||||
metrics[k] = (grpMetrics[k] || [])
|
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
});
|
|
||||||
return metrics;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const chartRefs = React.useRef();
|
|
||||||
React.useEffect(() => {
|
|
||||||
let refs = [];
|
|
||||||
Object.values(metrics).map((m) => {
|
|
||||||
m.forEach(() => {
|
|
||||||
refs.push(React.createRef());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
chartRefs.current = refs;
|
|
||||||
}, [metrics]);
|
|
||||||
|
|
||||||
const { value: nodes } = useFetch(
|
|
||||||
`${ESPrefix}/${clusterID}/nodes/realtime`,
|
|
||||||
{},
|
|
||||||
[clusterID]
|
|
||||||
);
|
|
||||||
const formatedNodes = React.useMemo(() => {
|
const formatedNodes = React.useMemo(() => {
|
||||||
if (!nodes) {
|
if (!nodes) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -137,38 +93,10 @@ export default ({
|
||||||
});
|
});
|
||||||
}, [nodes]);
|
}, [nodes]);
|
||||||
|
|
||||||
const pointerUpdate = (event) => {
|
|
||||||
chartRefs.current.forEach((ref) => {
|
|
||||||
if (ref.current) {
|
|
||||||
ref.current.dispatchExternalPointerEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChartBrush = ({ x }) => {
|
|
||||||
if (!x) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let [from, to] = x;
|
|
||||||
if (typeof handleTimeChange == "function") {
|
|
||||||
if (to - from < 20 * 1000) {
|
|
||||||
from -= 10 * 1000;
|
|
||||||
to += 10 * 1000;
|
|
||||||
}
|
|
||||||
handleTimeChange({
|
|
||||||
start: moment(from).toISOString(),
|
|
||||||
end: moment(to).toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let refIdx = 0;
|
|
||||||
if (Object.keys(metrics).length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div id="node-metric">
|
<div id="node-metric">
|
||||||
{showTop ? <div className="px">
|
{showTop ? (
|
||||||
|
<div className="px">
|
||||||
<div className="metric-control">
|
<div className="metric-control">
|
||||||
<div className="selector">
|
<div className="selector">
|
||||||
<div className="top_radio">
|
<div className="top_radio">
|
||||||
|
@ -196,148 +124,45 @@ export default ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>:null}
|
</div>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="px-box">
|
<div className="px-box">
|
||||||
<div className="px">
|
<div className="px">
|
||||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
{metrics.map((item, i) => {
|
||||||
{//Object.keys(metrics)
|
|
||||||
gorupOrder.map((e, i) => {
|
|
||||||
if (!metrics[e]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div key={e} style={{ margin: "8px 0" }}>
|
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||||
<MetricContainer
|
<MetricContainer
|
||||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
id={e}
|
id={item[0]}
|
||||||
>
|
>
|
||||||
<div className="metric-inner-cnt">
|
<div className="metric-inner-cnt">
|
||||||
{metrics[e].map((metric) => {
|
{
|
||||||
let axis = metric.axis;
|
item[1].map((metricKey) => (
|
||||||
let lines = metric.lines;
|
<MetricChart
|
||||||
if (
|
key={metricKey}
|
||||||
lines.length == 0 ||
|
timezone={timezone}
|
||||||
(lines && lines[0]?.data?.length == 0)
|
timeRange={timeRange}
|
||||||
) {
|
handleTimeChange={handleTimeChange}
|
||||||
return null;
|
fetchUrl={`${ESPrefix}/${clusterID}/queue_metrics`}
|
||||||
|
metricKey={metricKey}
|
||||||
|
title={formatMessage({id:"cluster.metrics.threadpool.axis." + metricKey + ".title"})}
|
||||||
|
queryParams={queryParams}
|
||||||
|
className={"metric-item"}
|
||||||
|
timeout={timeout}
|
||||||
|
/>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
let disableHeaderFormat = false;
|
|
||||||
let headerUnit = "";
|
|
||||||
let chartTitle = {};
|
|
||||||
if (lines[0].metric.formatType.toLowerCase == "bytes") {
|
|
||||||
chartTitle.units = lines[0].metric.formatType;
|
|
||||||
} else {
|
|
||||||
chartTitle.units = lines[0].metric.units;
|
|
||||||
}
|
|
||||||
chartTitle.title = formatMessage({
|
|
||||||
id:
|
|
||||||
"cluster.metrics.threadpool.axis." +
|
|
||||||
metric.key +
|
|
||||||
".title",
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div key={metric.key} className="metric-item">
|
|
||||||
<div className={styles.vizChartItemTitle}>
|
|
||||||
<span>
|
|
||||||
{chartTitle.title}
|
|
||||||
{chartTitle.units ? `(${chartTitle.units})` : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Chart
|
|
||||||
size={[, 200]}
|
|
||||||
className={styles.vizChartItem}
|
|
||||||
ref={chartRefs.current[refIdx++]}
|
|
||||||
>
|
|
||||||
<Settings
|
|
||||||
// theme={theme}
|
|
||||||
pointerUpdateDebounce={0}
|
|
||||||
pointerUpdateTrigger="x"
|
|
||||||
// externalPointerEvents={{
|
|
||||||
// tooltip: { visible: true },
|
|
||||||
// }}
|
|
||||||
onPointerUpdate={pointerUpdate}
|
|
||||||
showLegend
|
|
||||||
legendPosition={Position.Bottom}
|
|
||||||
onBrushEnd={handleChartBrush}
|
|
||||||
tooltip={{
|
|
||||||
headerFormatter: disableHeaderFormat
|
|
||||||
? undefined
|
|
||||||
: ({ value }) =>
|
|
||||||
`${formatter.full_dates(value)}${
|
|
||||||
headerUnit ? ` ${headerUnit}` : ""
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
debug={false}
|
|
||||||
/>
|
|
||||||
<Axis
|
|
||||||
id="{e}-bottom"
|
|
||||||
position={Position.Bottom}
|
|
||||||
showOverlappingTicks
|
|
||||||
labelFormat={timeRange.timeFormatter}
|
|
||||||
tickFormat={timeRange.timeFormatter}
|
|
||||||
ticks={8}
|
|
||||||
/>
|
|
||||||
{axis.map((item) => {
|
|
||||||
return (
|
|
||||||
<Axis
|
|
||||||
key={e + "-" + item.id}
|
|
||||||
id={e + "-" + item.id}
|
|
||||||
showGridLines={item.showGridLines}
|
|
||||||
groupId={item.group}
|
|
||||||
// title={formatMessage({
|
|
||||||
// id:
|
|
||||||
// "cluster.metrics.threadpool.axis." +
|
|
||||||
// metric.key +
|
|
||||||
// ".title",
|
|
||||||
// })}
|
|
||||||
position={item.position}
|
|
||||||
ticks={item.ticks}
|
|
||||||
labelFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.labelFormat
|
|
||||||
)}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.formatType,
|
|
||||||
item.tickFormat
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{lines.map((item) => {
|
|
||||||
return (
|
|
||||||
<LineSeries
|
|
||||||
key={item.metric.label}
|
|
||||||
id={item.metric.label}
|
|
||||||
groupId={item.metric.group}
|
|
||||||
timeZone={timezone}
|
|
||||||
xScaleType={ScaleType.Time}
|
|
||||||
yScaleType={ScaleType.Linear}
|
|
||||||
xAccessor={0}
|
|
||||||
tickFormat={getFormatter(
|
|
||||||
item.metric.formatType,
|
|
||||||
item.metric.tickFormat,
|
|
||||||
item.metric.units
|
|
||||||
)}
|
|
||||||
yAccessors={[1]}
|
|
||||||
data={item.data}
|
|
||||||
curve={CurveType.CURVE_MONOTONE_X}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Chart>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</MetricContainer>
|
</MetricContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Skeleton>
|
|
||||||
</div>
|
</div>
|
||||||
<Anchor links={gorupOrder}></Anchor>
|
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue