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
|
||||
}
|
||||
|
||||
const TIMEOUT_CACHE_KEY = "monitor-timeout"
|
||||
|
||||
const Monitor = (props) => {
|
||||
const {
|
||||
selectedCluster,
|
||||
formatState,
|
||||
getBreadcrumbList,
|
||||
StatisticBar,
|
||||
|
@ -61,7 +64,7 @@ const Monitor = (props) => {
|
|||
timeFormatter: formatter.dates(1),
|
||||
},
|
||||
timeInterval: formatTimeInterval(param?.timeInterval),
|
||||
timeout: formatTimeout(param?.timeout),
|
||||
timeout: formatTimeout(param?.timeout) || localStorage.getItem(TIMEOUT_CACHE_KEY) || '120s',
|
||||
param: param,
|
||||
})
|
||||
);
|
||||
|
@ -104,6 +107,11 @@ const Monitor = (props) => {
|
|||
|
||||
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 (
|
||||
<div>
|
||||
<BreadcrumbList data={breadcrumbList} />
|
||||
|
@ -111,7 +119,7 @@ const Monitor = (props) => {
|
|||
<Card bodyStyle={{ padding: 15 }}>
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<div style={{ flexGrow: 0, minWidth: 400 }}>
|
||||
<div style={{ flexGrow: 0 }}>
|
||||
<DatePicker
|
||||
locale={getLocale()}
|
||||
start={state.timeRange.min}
|
||||
|
@ -128,6 +136,7 @@ const Monitor = (props) => {
|
|||
showTimeout={true}
|
||||
timeout={state.timeout}
|
||||
onTimeSettingChange={(timeSetting) => {
|
||||
localStorage.setItem(TIMEOUT_CACHE_KEY, timeSetting.timeout)
|
||||
setState({
|
||||
...state,
|
||||
timeInterval: timeSetting.timeInterval,
|
||||
|
@ -139,16 +148,6 @@ const Monitor = (props) => {
|
|||
recentlyUsedRangesKey={'monitor'}
|
||||
/>
|
||||
</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>
|
||||
|
||||
|
@ -178,6 +177,8 @@ const Monitor = (props) => {
|
|||
pane.component
|
||||
) : (
|
||||
<pane.component
|
||||
selectedCluster={selectedCluster}
|
||||
isAgent={isAgent}
|
||||
{...state}
|
||||
handleTimeChange={handleTimeChange}
|
||||
setSpinning={setSpinning}
|
||||
|
|
|
@ -347,4 +347,7 @@ export default {
|
|||
"cluster.providers.aliyun": "Aliyun",
|
||||
"cluster.providers.tencent-cloud": "Tencent-cloud",
|
||||
"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.tencent-cloud": "腾讯云",
|
||||
"cluster.providers.ecloud": "移动云",
|
||||
|
||||
"cluster.metrics.request.copy": "复制请求",
|
||||
"cluster.metrics.request.copy.success": "复制请求成功"
|
||||
};
|
||||
|
|
|
@ -99,11 +99,7 @@ export default {
|
|||
.filter((item) => item.enabled)
|
||||
.map((item) => {
|
||||
return {
|
||||
name: item.name,
|
||||
id: item.id,
|
||||
endpoint: item.endpoint,
|
||||
host: item.host,
|
||||
version: item.version,
|
||||
...item,
|
||||
distribution: item.distribution || "elasticsearch",
|
||||
cluster_uuid: item.cluster_uuid || "",
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Tabs } from "antd";
|
||||
import { formatMessage } from "umi/locale";
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import NodeMetric from "../../components/node_metric";
|
||||
import IndexMetric from "../../components/index_metric";
|
||||
import ClusterMetric from "../../components/cluster_metric";
|
||||
|
@ -10,11 +10,22 @@ import { ESPrefix } from "@/services/common";
|
|||
const timezone = "local";
|
||||
|
||||
export default ({
|
||||
selectedCluster,
|
||||
clusterID,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
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({
|
||||
tab: "cluster",
|
||||
});
|
||||
|
@ -48,6 +59,20 @@ export default ({
|
|||
handleTimeChange={handleTimeChange}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
||||
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
|
||||
|
@ -64,6 +89,137 @@ export default ({
|
|||
param={param}
|
||||
setParam={setParam}
|
||||
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
|
||||
|
@ -80,6 +236,73 @@ export default ({
|
|||
param={param}
|
||||
setParam={setParam}
|
||||
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
|
||||
|
@ -96,6 +319,80 @@ export default ({
|
|||
param={param}
|
||||
setParam={setParam}
|
||||
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
|
||||
|
|
|
@ -27,6 +27,7 @@ const Page = (props) => {
|
|||
|
||||
return (
|
||||
<Monitor
|
||||
selectedCluster={selectedCluster}
|
||||
formatState={(state) => {
|
||||
let clusterID = props.match.params?.cluster_id;
|
||||
if (
|
||||
|
|
|
@ -8,15 +8,18 @@ export default ({
|
|||
timeRange,
|
||||
handleTimeChange,
|
||||
bucketSize,
|
||||
timeout,
|
||||
}) => {
|
||||
return (
|
||||
<ClusterMetric
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
timeout={timeout}
|
||||
handleTimeChange={handleTimeChange}
|
||||
overview={1}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/cluster_metrics`}
|
||||
bucketSize={bucketSize}
|
||||
metrics={['index_throughput', 'search_throughput', 'index_latency', 'search_latency']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ const StatisticBar = ({
|
|||
setSpinning,
|
||||
clusterAvailable,
|
||||
clusterMonitored,
|
||||
onInfoChange
|
||||
}) => {
|
||||
const { loading, error, value } = useFetch(
|
||||
`${ESPrefix}/${clusterID}/metrics`,
|
||||
|
@ -34,6 +35,12 @@ const StatisticBar = ({
|
|||
setSpinning(loading);
|
||||
}, [loading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onInfoChange) {
|
||||
onInfoChange(value)
|
||||
}
|
||||
}, [JSON.stringify(value)]);
|
||||
|
||||
let overviewStatistic = [];
|
||||
if (value?.summary) {
|
||||
let rawStats = value.summary;
|
||||
|
|
|
@ -10,7 +10,8 @@ export default ({
|
|||
timeRange,
|
||||
handleTimeChange,
|
||||
shardID,
|
||||
bucketSize
|
||||
bucketSize,
|
||||
timeout
|
||||
}) => {
|
||||
const [param, setParam] = useState({
|
||||
show_top: false,
|
||||
|
@ -26,6 +27,73 @@ export default ({
|
|||
setParam={setParam}
|
||||
shardID={shardID}
|
||||
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 StatisticBar from "./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 panes = React.useMemo(()=>{
|
||||
const panes = [
|
||||
|
@ -26,6 +28,7 @@ export default (props) => {
|
|||
}
|
||||
return (
|
||||
<Monitor
|
||||
selectedCluster={selectedCluster}
|
||||
formatState={(state) => {
|
||||
return {
|
||||
...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";
|
||||
|
||||
export default ({
|
||||
isAgent,
|
||||
clusterID,
|
||||
indexName,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
shardID,
|
||||
bucketSize,
|
||||
timeout
|
||||
}) => {
|
||||
let url = `${ESPrefix}/${clusterID}/index/${indexName}/metrics`;
|
||||
if(shardID){
|
||||
|
@ -24,6 +26,15 @@ export default ({
|
|||
overview={1}
|
||||
fetchUrl={url}
|
||||
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 NodeMetric from "../../components/node_metric";
|
||||
import QueueMetric from "../../components/queue_metric";
|
||||
|
@ -7,12 +7,23 @@ import { formatMessage } from "umi/locale";
|
|||
const timezone = "local";
|
||||
|
||||
export default ({
|
||||
selectedCluster,
|
||||
clusterID,
|
||||
nodeID,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
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({
|
||||
show_top: false,
|
||||
node_name: nodeID,
|
||||
|
@ -53,6 +64,137 @@ export default ({
|
|||
param={param}
|
||||
setParam={setParam}
|
||||
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
|
||||
|
@ -69,6 +211,80 @@ export default ({
|
|||
param={param}
|
||||
setParam={setParam}
|
||||
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>
|
||||
|
|
|
@ -5,16 +5,18 @@ import Shards from "./shards";
|
|||
import { formatMessage } from "umi/locale";
|
||||
import Monitor from "@/components/Overview/Monitor";
|
||||
import StatisticBar from "./statistic_bar";
|
||||
import { connect } from "dva";
|
||||
|
||||
const panes = [
|
||||
{ title: "Overview", component: Overview, key: "overview" },
|
||||
{ title: "Advanced", component: Advanced, key: "advanced" },
|
||||
{ title: "Shards", component: Shards, key: "shards" },
|
||||
];
|
||||
|
||||
export default (props) => {
|
||||
const Page = (props) => {
|
||||
const { clusterStatus, selectedCluster } = props;
|
||||
return (
|
||||
<Monitor
|
||||
selectedCluster={selectedCluster}
|
||||
formatState={(state) => {
|
||||
return {
|
||||
...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 StatisticBar from "./statistic_bar";
|
||||
import ClusterMetric from "../../components/cluster_metric";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const timezone = "local";
|
||||
|
||||
export default ({
|
||||
isAgent,
|
||||
clusterID,
|
||||
nodeID,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
bucketSize,
|
||||
timeout,
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<ClusterMetric
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
timeout={timeout}
|
||||
handleTimeChange={handleTimeChange}
|
||||
overview={1}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/node/${nodeID}/metrics`}
|
||||
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;
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.vizChartItem {
|
||||
background: white !important;
|
||||
|
||||
|
|
|
@ -1,298 +1,73 @@
|
|||
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 { Spin, Radio, Select, Skeleton } from "antd";
|
||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
||||
import "./node_metric.scss";
|
||||
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 _ from "lodash";
|
||||
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||
|
||||
export default ({
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
overview,
|
||||
fetchUrl,
|
||||
renderExtra,
|
||||
bucketSize
|
||||
}) => {
|
||||
if (!fetchUrl) {
|
||||
export default (props) => {
|
||||
|
||||
const { fetchUrl, overview, metrics = [], renderExtra, timeRange, timeout, timezone, bucketSize, handleTimeChange } = props
|
||||
|
||||
if (!fetchUrl || metrics.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const queryParams = React.useMemo(() => {
|
||||
const bounds = calculateBounds({
|
||||
from: timeRange.min,
|
||||
to: timeRange.max,
|
||||
});
|
||||
let params = {
|
||||
min: bounds.min.valueOf(),
|
||||
max: bounds.max.valueOf(),
|
||||
};
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
const newParams = formatTimeRange(timeRange);
|
||||
if (overview) {
|
||||
params.overview = overview;
|
||||
newParams.overview = overview;
|
||||
}
|
||||
if (bucketSize) {
|
||||
params.bucket_size = bucketSize
|
||||
newParams.bucket_size = bucketSize
|
||||
}
|
||||
return params;
|
||||
return newParams;
|
||||
}, [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;
|
||||
|
||||
return (
|
||||
<div id="cluster-metric">
|
||||
<div className={styles.metricList}>
|
||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
||||
{Object.keys(metrics).map((e, i) => {
|
||||
let axis = metrics[e].axis;
|
||||
let lines = metrics[e].lines;
|
||||
if (lines.length == 0 || (lines && lines[0]?.data?.length == 0)) {
|
||||
return null;
|
||||
}
|
||||
let disableHeaderFormat = false;
|
||||
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;
|
||||
{metrics.map((metricKey, i) => (
|
||||
<MetricChart
|
||||
key={metricKey}
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
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);
|
||||
}
|
||||
return line;
|
||||
});
|
||||
}
|
||||
}
|
||||
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 "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 && (
|
||||
<div key={"metric_extra"} className={styles.vizChartContainer}>
|
||||
{extra}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Skeleton>
|
||||
return metric
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{
|
||||
extra && (
|
||||
<div key={"metric_extra"} className={styles.vizChartContainer}>
|
||||
{extra}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,51 +1,35 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
Axis,
|
||||
Chart,
|
||||
CurveType,
|
||||
LineSeries,
|
||||
BarSeries,
|
||||
niceTimeFormatByDay,
|
||||
Position,
|
||||
ScaleType,
|
||||
Settings,
|
||||
timeFormatter,
|
||||
} from "@elastic/charts";
|
||||
import IndexSelect from "@/components/IndexSelect";
|
||||
import useFetch from "@/lib/hooks/use_fetch";
|
||||
import { ESPrefix } from "@/services/common";
|
||||
import styles from "./Metrics.scss";
|
||||
import { Spin, Radio, Select, Skeleton } from "antd";
|
||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
||||
import { Radio } from "antd";
|
||||
import "./node_metric.scss";
|
||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
||||
import moment from "moment";
|
||||
import { formatMessage } from "umi/locale";
|
||||
import MetricContainer from "./metric_container";
|
||||
import _ from "lodash";
|
||||
import IndexSelect from "@/components/IndexSelect";
|
||||
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||
import NodeSelect from "@/components/NodeSelect";
|
||||
import Anchor from "@/components/Anchor";
|
||||
import MetricChart from "./MetricChart";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const gorupOrder = [
|
||||
"operations",
|
||||
"latency",
|
||||
"storage",
|
||||
"document",
|
||||
"memory",
|
||||
"cache",
|
||||
];
|
||||
export default ({
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
shardID,
|
||||
bucketSize
|
||||
}) => {
|
||||
if (!clusterID) {
|
||||
export default (props) => {
|
||||
|
||||
const {
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
shardID,
|
||||
bucketSize,
|
||||
metrics = [],
|
||||
timeout
|
||||
} = props
|
||||
|
||||
if (!clusterID || metrics.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showTop = param.show_top ?? true;
|
||||
|
||||
const topChange = (e) => {
|
||||
|
@ -68,16 +52,8 @@ export default ({
|
|||
};
|
||||
});
|
||||
};
|
||||
const queryParams = React.useMemo(() => {
|
||||
const bounds = calculateBounds({
|
||||
from: timeRange.min,
|
||||
to: timeRange.max,
|
||||
});
|
||||
let newParams = {
|
||||
min: bounds.min.valueOf(),
|
||||
max: bounds.max.valueOf(),
|
||||
};
|
||||
console.log(shardID)
|
||||
const queryParams = useMemo(() => {
|
||||
const newParams = formatTimeRange(timeRange);
|
||||
if(shardID){
|
||||
newParams.shard_id = shardID;
|
||||
}
|
||||
|
@ -92,73 +68,16 @@ export default ({
|
|||
}
|
||||
return newParams;
|
||||
}, [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(
|
||||
`${ESPrefix}/${clusterID}/_cat/indices`,
|
||||
{},
|
||||
[clusterID]
|
||||
);
|
||||
const formatedIndices = React.useMemo(() => {
|
||||
const formatedIndices = useMemo(() => {
|
||||
return Object.values(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 (
|
||||
<div id="node-metric">
|
||||
{showTop ? (
|
||||
|
@ -189,179 +108,40 @@ export default ({
|
|||
|
||||
<div className="px-box">
|
||||
<div className="px">
|
||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
||||
{gorupOrder.map((e, i) => {
|
||||
if (!metrics[e]) {
|
||||
return null;
|
||||
}
|
||||
{metrics.map((item, i) => {
|
||||
return (
|
||||
<div key={e} style={{ margin: "8px 0" }}>
|
||||
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||
<MetricContainer
|
||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
||||
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||
collapsed={false}
|
||||
id={e}
|
||||
id={item[0]}
|
||||
>
|
||||
<div className="metric-inner-cnt">
|
||||
{metrics[e].map((metric) => {
|
||||
let axis = metric.axis;
|
||||
let lines = metric.lines;
|
||||
if (
|
||||
lines.length == 0 ||
|
||||
(lines && lines[0]?.data?.length == 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
{
|
||||
item[1].map((metricKey) => (
|
||||
<MetricChart
|
||||
key={metricKey}
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
handleTimeChange={handleTimeChange}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/index_metrics`}
|
||||
metricKey={metricKey}
|
||||
title={formatMessage({id: "cluster.metrics.index.axis." + metricKey + ".title"})}
|
||||
queryParams={queryParams}
|
||||
className={"metric-item"}
|
||||
timeout={timeout}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</MetricContainer>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Skeleton>
|
||||
</div>
|
||||
<Anchor links={gorupOrder}></Anchor>
|
||||
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,63 +1,36 @@
|
|||
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 { ESPrefix } from "@/services/common";
|
||||
import styles from "./Metrics.scss";
|
||||
import { Spin, Radio, Select, Skeleton, Row, Col, InputNumber } from "antd";
|
||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
||||
import { Radio } from "antd";
|
||||
import "./node_metric.scss";
|
||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
||||
import moment from "moment";
|
||||
import { formatMessage } from "umi/locale";
|
||||
import MetricContainer from "./metric_container";
|
||||
import _ from "lodash";
|
||||
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||
import NodeSelect from "@/components/NodeSelect";
|
||||
import Anchor from "@/components/Anchor";
|
||||
import MetricChart from "./MetricChart";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
const gorupOrder = [
|
||||
"operations",
|
||||
"latency",
|
||||
"system",
|
||||
"circuit_breaker",
|
||||
"io",
|
||||
"transport",
|
||||
export default (props) => {
|
||||
|
||||
"storage",
|
||||
"document",
|
||||
"http",
|
||||
"JVM",
|
||||
"memory",
|
||||
"cache",
|
||||
];
|
||||
const {
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
bucketSize,
|
||||
timeout,
|
||||
metrics = []
|
||||
} = props
|
||||
|
||||
export default ({
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
bucketSize,
|
||||
}) => {
|
||||
// const [filter, setFilter] = React.useState({
|
||||
// top: "5",
|
||||
// node_name: param?.transport,
|
||||
// });
|
||||
if (!clusterID || metrics.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showTop = param.show_top ?? true;
|
||||
|
||||
const topChange = React.useCallback(
|
||||
const topChange = useCallback(
|
||||
(e) => {
|
||||
// setFilter({
|
||||
// node_name: undefined,
|
||||
|
@ -74,7 +47,7 @@ export default ({
|
|||
[param]
|
||||
);
|
||||
|
||||
const nodeValueChange = React.useCallback(
|
||||
const nodeValueChange = useCallback(
|
||||
(value) => {
|
||||
const nodeNames = value.map(item=>item.host);
|
||||
setParam((param) => {
|
||||
|
@ -87,8 +60,15 @@ export default ({
|
|||
},
|
||||
[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) {
|
||||
newParams.top = param.top;
|
||||
}
|
||||
|
@ -100,40 +80,7 @@ export default ({
|
|||
}
|
||||
return newParams;
|
||||
}, [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(() => {
|
||||
if (!nodes) {
|
||||
return [];
|
||||
|
@ -146,36 +93,6 @@ export default ({
|
|||
});
|
||||
}, [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 (
|
||||
<div id="node-metric">
|
||||
{showTop ? (
|
||||
|
@ -214,147 +131,39 @@ export default ({
|
|||
|
||||
<div className="px-box">
|
||||
<div className="px">
|
||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
||||
{//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;
|
||||
}
|
||||
{metrics.map((item, i) => {
|
||||
return (
|
||||
<div key={e} style={{ margin: "8px 0" }}>
|
||||
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||
<MetricContainer
|
||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
||||
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||
collapsed={false}
|
||||
id={e}
|
||||
id={item[0]}
|
||||
>
|
||||
<div className="metric-inner-cnt">
|
||||
{metrics[e].map((metric) => {
|
||||
let axis = metric.axis;
|
||||
let lines = metric.lines;
|
||||
if (
|
||||
lines.length == 0 ||
|
||||
(lines && lines[0]?.data?.length == 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
{
|
||||
item[1].map((metricKey) => (
|
||||
<MetricChart
|
||||
key={metricKey}
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
handleTimeChange={handleTimeChange}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/node_metrics`}
|
||||
metricKey={metricKey}
|
||||
title={formatMessage({id:"cluster.metrics.node.axis." + metricKey + ".title"})}
|
||||
queryParams={queryParams}
|
||||
className={"metric-item"}
|
||||
timeout={timeout}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</MetricContainer>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Skeleton>
|
||||
</div>
|
||||
<Anchor links={gorupOrder}></Anchor>
|
||||
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -1,52 +1,41 @@
|
|||
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 { ESPrefix } from "@/services/common";
|
||||
import styles from "./Metrics.scss";
|
||||
import { Spin, Radio, Select, Skeleton, Row, Col } from "antd";
|
||||
import { formatter, getFormatter, getNumFormatter } from "@/utils/format";
|
||||
import { Radio } from "antd";
|
||||
import "./node_metric.scss";
|
||||
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
||||
import moment from "moment";
|
||||
import { formatMessage } from "umi/locale";
|
||||
import MetricContainer from "./metric_container";
|
||||
import _ from "lodash";
|
||||
import { formatTimeRange } from "@/lib/elasticsearch/util";
|
||||
import NodeSelect from "@/components/NodeSelect";
|
||||
import Anchor from "@/components/Anchor";
|
||||
import MetricChart from "./MetricChart";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
const gorupOrder = [
|
||||
"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 (props) => {
|
||||
|
||||
const {
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
bucketSize,
|
||||
metrics = [],
|
||||
timeout
|
||||
} = props
|
||||
|
||||
if (!clusterID || metrics.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default ({
|
||||
clusterID,
|
||||
timezone,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
param,
|
||||
setParam,
|
||||
bucketSize,
|
||||
}) => {
|
||||
const showTop = param.show_top ?? true;
|
||||
const topChange = React.useCallback(
|
||||
|
||||
const topChange = useCallback(
|
||||
(e) => {
|
||||
// setFilter({
|
||||
// node_name: undefined,
|
||||
// top: e.target.value,
|
||||
// });
|
||||
setParam((param) => {
|
||||
delete param["node_name"];
|
||||
return {
|
||||
|
@ -58,7 +47,7 @@ export default ({
|
|||
[param]
|
||||
);
|
||||
|
||||
const nodeValueChange = React.useCallback(
|
||||
const nodeValueChange = useCallback(
|
||||
(value) => {
|
||||
const nodeNames = value.map(item=>item.host);
|
||||
setParam((param) => {
|
||||
|
@ -71,15 +60,15 @@ export default ({
|
|||
},
|
||||
[param]
|
||||
);
|
||||
const queryParams = React.useMemo(() => {
|
||||
const bounds = calculateBounds({
|
||||
from: timeRange.min,
|
||||
to: timeRange.max,
|
||||
});
|
||||
let newParams = {
|
||||
min: bounds.min.valueOf(),
|
||||
max: bounds.max.valueOf(),
|
||||
};
|
||||
|
||||
const { value: nodes } = useFetch(
|
||||
`${ESPrefix}/${clusterID}/nodes/realtime`,
|
||||
{},
|
||||
[clusterID]
|
||||
);
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
const newParams = formatTimeRange(timeRange);
|
||||
if (param.top) {
|
||||
newParams.top = param.top;
|
||||
}
|
||||
|
@ -91,40 +80,7 @@ export default ({
|
|||
}
|
||||
return newParams;
|
||||
}, [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(() => {
|
||||
if (!nodes) {
|
||||
return [];
|
||||
|
@ -137,208 +93,77 @@ export default ({
|
|||
});
|
||||
}, [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 (
|
||||
<div id="node-metric">
|
||||
{showTop ? <div className="px">
|
||||
<div className="metric-control">
|
||||
<div className="selector">
|
||||
<div className="top_radio">
|
||||
<Radio.Group onChange={topChange} value={param.top}>
|
||||
<Radio.Button key="5" value="5">
|
||||
Top5
|
||||
</Radio.Button>
|
||||
<Radio.Button key="10" value="10">
|
||||
Top10
|
||||
</Radio.Button>
|
||||
<Radio.Button key="15" value="15">
|
||||
Top15
|
||||
</Radio.Button>
|
||||
<Radio.Button key="20" value="20">
|
||||
Top20
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div className="value-selector">
|
||||
<NodeSelect nodes={formatedNodes}
|
||||
mode="multiple"
|
||||
placeholder="Select node"
|
||||
allowClear
|
||||
onChange={nodeValueChange}/>
|
||||
{showTop ? (
|
||||
<div className="px">
|
||||
<div className="metric-control">
|
||||
<div className="selector">
|
||||
<div className="top_radio">
|
||||
<Radio.Group onChange={topChange} value={param.top}>
|
||||
<Radio.Button key="5" value="5">
|
||||
Top5
|
||||
</Radio.Button>
|
||||
<Radio.Button key="10" value="10">
|
||||
Top10
|
||||
</Radio.Button>
|
||||
<Radio.Button key="15" value="15">
|
||||
Top15
|
||||
</Radio.Button>
|
||||
<Radio.Button key="20" value="20">
|
||||
Top20
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div className="value-selector">
|
||||
<NodeSelect nodes={formatedNodes}
|
||||
mode="multiple"
|
||||
placeholder="Select node"
|
||||
allowClear
|
||||
onChange={nodeValueChange}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>:null}
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
<div className="px-box">
|
||||
<div className="px">
|
||||
<Skeleton active loading={!value} paragraph={{ rows: 20 }}>
|
||||
{//Object.keys(metrics)
|
||||
gorupOrder.map((e, i) => {
|
||||
if (!metrics[e]) {
|
||||
return null;
|
||||
}
|
||||
{metrics.map((item, i) => {
|
||||
return (
|
||||
<div key={e} style={{ margin: "8px 0" }}>
|
||||
<div key={item[0]} style={{ margin: "8px 0" }}>
|
||||
<MetricContainer
|
||||
title={formatMessage({ id: `cluster.metrics.group.${e}` })}
|
||||
title={formatMessage({ id: `cluster.metrics.group.${item[0]}` })}
|
||||
collapsed={false}
|
||||
id={e}
|
||||
id={item[0]}
|
||||
>
|
||||
<div className="metric-inner-cnt">
|
||||
{metrics[e].map((metric) => {
|
||||
let axis = metric.axis;
|
||||
let lines = metric.lines;
|
||||
if (
|
||||
lines.length == 0 ||
|
||||
(lines && lines[0]?.data?.length == 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
{
|
||||
item[1].map((metricKey) => (
|
||||
<MetricChart
|
||||
key={metricKey}
|
||||
timezone={timezone}
|
||||
timeRange={timeRange}
|
||||
handleTimeChange={handleTimeChange}
|
||||
fetchUrl={`${ESPrefix}/${clusterID}/queue_metrics`}
|
||||
metricKey={metricKey}
|
||||
title={formatMessage({id:"cluster.metrics.threadpool.axis." + metricKey + ".title"})}
|
||||
queryParams={queryParams}
|
||||
className={"metric-item"}
|
||||
timeout={timeout}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</MetricContainer>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Skeleton>
|
||||
</div>
|
||||
<Anchor links={gorupOrder}></Anchor>
|
||||
<Anchor links={metrics.map((item) => item[0])}></Anchor>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue