| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| import React, { PureComponent } from 'react'; |
| import { connect } from 'dva'; |
| import { Row, Col, Form, Button, Icon, Select } from 'antd'; |
| import { |
| ChartCard, MiniArea, MiniBar, Line, EndpointDeps, |
| } from 'components/Charts'; |
| import { axisY, axisMY } from '../../utils/time'; |
| import { avgTS } from '../../utils/utils'; |
| import { Panel, Search } from '../../components/Page'; |
| import TraceList from '../../components/Trace/TraceList'; |
| import TraceTimeline from '../Trace/TraceTimeline'; |
| |
| const { Item: FormItem } = Form; |
| const { Option } = Select; |
| |
| @connect(state => ({ |
| endpoint: state.endpoint, |
| duration: state.global.duration, |
| globalVariables: state.global.globalVariables, |
| loading: state.loading.models.endpoint, |
| })) |
| @Form.create({ |
| mapPropsToFields(props) { |
| const { variables: { values, labels } } = props.endpoint; |
| return { |
| serviceId: Form.createFormField({ |
| value: { key: values.serviceId ? values.serviceId : '', label: labels.serviceId ? labels.serviceId : '' }, |
| }), |
| endpointId: Form.createFormField({ |
| value: { key: values.endpointId ? values.endpointId : '', label: labels.endpointId ? labels.endpointId : '' }, |
| }), |
| }; |
| }, |
| }) |
| export default class Endpoint extends PureComponent { |
| componentDidMount() { |
| const {...propsData} = this.props; |
| propsData.dispatch({ |
| type: 'endpoint/initOptions', |
| payload: { variables: propsData.globalVariables, reducer: 'saveServiceInfo' }, |
| }); |
| } |
| |
| componentWillUpdate(nextProps) { |
| const {...propsData} = this.props; |
| if (nextProps.globalVariables.duration === propsData.globalVariables.duration) { |
| return; |
| } |
| propsData.dispatch({ |
| type: 'endpoint/initOptions', |
| payload: { variables: nextProps.globalVariables, reducer: 'saveServiceInfo' }, |
| }); |
| } |
| |
| handleServiceSelect = (selected) => { |
| const {...propsData} = this.props; |
| propsData.dispatch({ |
| type: 'endpoint/save', |
| payload: { |
| variables: { |
| values: { serviceId: selected.key, endpointId: null }, |
| labels: { serviceId: selected.label, endpointId: null }, |
| }, |
| data: { |
| serviceInfo: { serviceId: selected.key }, |
| }, |
| }, |
| }); |
| } |
| |
| handleSelect = (selected) => { |
| const {...propsData} = this.props; |
| propsData.dispatch({ |
| type: 'endpoint/save', |
| payload: { |
| variables: { |
| values: { endpointId: selected.key }, |
| labels: { endpointId: selected.label }, |
| }, |
| data: { |
| endpointInfo: selected, |
| }, |
| }, |
| }); |
| } |
| |
| handleChange = (variables) => { |
| const {...propsData} = this.props; |
| const { variables: { values } } = propsData.endpoint; |
| if (!values.serviceId) { |
| return; |
| } |
| const { key: endpointId, label: endpointName, duration } = variables; |
| if (!endpointId) { |
| return; |
| } |
| propsData.dispatch({ |
| type: 'endpoint/fetchData', |
| payload: { variables: { |
| endpointId, |
| duration, |
| traceCondition: { |
| endpointId: values.endpointId, |
| endpointName, |
| queryDuration: duration, |
| traceState: 'ALL', |
| queryOrder: 'BY_DURATION', |
| paging: { |
| pageNum: 1, |
| pageSize: 20, |
| needTotal: false, |
| }, |
| }, |
| } }, |
| }); |
| } |
| |
| handleShowTrace = (traceId) => { |
| const { dispatch } = this.props; |
| dispatch({ |
| type: 'endpoint/fetchSpans', |
| payload: { variables: { traceId } }, |
| }); |
| } |
| |
| handleGoBack = () => { |
| const { dispatch } = this.props; |
| dispatch({ |
| type: 'endpoint/hideTimeline', |
| }); |
| } |
| |
| handleLoadMetrics = ({ calls }) => { |
| const { dispatch, globalVariables: { duration } } = this.props; |
| dispatch({ |
| type: 'endpoint/fetchMetrics', |
| payload: { variables: { |
| idsS: calls.filter(_ => _.detectPoint === 'SERVER').map(_ => _.id), |
| idsC: calls.filter(_ => _.detectPoint === 'CLIENT').map(_ => _.id), |
| duration, |
| }}, |
| }); |
| } |
| |
| edgeWith = edge => edge.cpm; |
| |
| renderPanel = () => { |
| const {...propsData} = this.props; |
| const { endpoint, duration } = this.props; |
| const { variables: { values }, data } = endpoint; |
| const { getEndpointResponseTimeTrend, getEndpointThroughputTrend, |
| getEndpointSLATrend, queryBasicTraces } = data; |
| if (!values.endpointId) { |
| return null; |
| } |
| return ( |
| <Panel |
| variables={data.endpointInfo} |
| globalVariables={propsData.globalVariables} |
| onChange={this.handleChange} |
| > |
| <Row gutter={8}> |
| <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Avg Throughput" |
| total={`${avgTS(getEndpointThroughputTrend.values)} cpm`} |
| contentHeight={46} |
| > |
| <MiniArea |
| color="#975FE4" |
| data={axisY(duration, getEndpointThroughputTrend.values)} |
| /> |
| </ChartCard> |
| </Col> |
| <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Avg Response Time" |
| total={`${avgTS(getEndpointResponseTimeTrend.values)} ms`} |
| contentHeight={46} |
| > |
| <MiniArea |
| data={axisY(duration, getEndpointResponseTimeTrend.values)} |
| /> |
| </ChartCard> |
| </Col> |
| <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Avg SLA" |
| total={`${(avgTS(getEndpointSLATrend.values) / 100).toFixed(2)} %`} |
| > |
| <MiniBar |
| animate={false} |
| height={46} |
| data={axisY(duration, getEndpointSLATrend.values, |
| ({ x, y }) => ({ x, y: y / 100 }))} |
| /> |
| </ChartCard> |
| </Col> |
| </Row> |
| <Row> |
| <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Response Time" |
| > |
| <Line |
| height={150} |
| data={axisMY(propsData.duration, [{ title: 'p99', value: data.getP99}, { title: 'p95', value: data.getP95} |
| , { title: 'p90', value: data.getP90}, { title: 'p75', value: data.getP75}, { title: 'p50', value: data.getP50}])} |
| /> |
| </ChartCard> |
| </Col> |
| </Row> |
| <Row gutter={8}> |
| <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Dependency Map" |
| contentHeight={200} |
| > |
| <EndpointDeps |
| deps={data.getEndpointTopology} |
| metrics={data.metrics} |
| onLoadMetrics={this.handleLoadMetrics} |
| /> |
| </ChartCard> |
| </Col> |
| </Row> |
| <Row gutter={8}> |
| <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 8 }}> |
| <ChartCard |
| title="Top 20 Slow Traces" |
| > |
| <TraceList |
| data={queryBasicTraces.traces} |
| onClickTraceTag={this.handleShowTrace} |
| loading={propsData.loading} |
| /> |
| </ChartCard> |
| </Col> |
| </Row> |
| </Panel> |
| ); |
| } |
| |
| render() { |
| const { form, endpoint } = this.props; |
| const { getFieldDecorator } = form; |
| const { variables: { options }, data } = endpoint; |
| const { showTimeline, queryTrace, currentTraceId } = data; |
| if (!this.serviceInfo) { |
| this.serviceInfo = data.serviceInfo; |
| } |
| if (data.serviceInfo && this.serviceInfo.serviceId !== data.serviceInfo.serviceId) { |
| this.serviceInfo = data.serviceInfo; |
| } |
| return ( |
| <div> |
| {showTimeline ? ( |
| <Row type="flex" justify="start"> |
| <Col style={{ marginBottom: 24 }}> |
| <Button ghost type="primary" size="small" onClick={() => { this.handleGoBack(); }}> |
| <Icon type="left" />Go back |
| </Button> |
| </Col> |
| </Row> |
| ) : null} |
| <Row type="flex" justify="start"> |
| <Col span={showTimeline ? 0 : 24}> |
| <Form layout="inline"> |
| <FormItem> |
| {getFieldDecorator('serviceId')( |
| <Select |
| showSearch |
| optionFilterProp="children" |
| style={{ width: 200 }} |
| placeholder="Select a service" |
| labelInValue |
| onSelect={this.handleServiceSelect.bind(this)} |
| > |
| {options.serviceId && options.serviceId.map(service => |
| <Option key={service.key} value={service.key}>{service.label}</Option>)} |
| </Select> |
| )} |
| </FormItem> |
| {this.serviceInfo && this.serviceInfo.serviceId ? ( |
| <FormItem> |
| {getFieldDecorator('endpointId')( |
| <Search |
| placeholder="Search a endpoint" |
| onSelect={this.handleSelect.bind(this)} |
| url="/graphql" |
| variables={this.serviceInfo} |
| query={` |
| query SearchEndpoint($serviceId: ID!, $keyword: String!) { |
| searchEndpoint(serviceId: $serviceId, keyword: $keyword, limit: 10) { |
| key: id |
| label: name |
| } |
| } |
| `} |
| /> |
| )} |
| </FormItem> |
| ) : null} |
| </Form> |
| {this.renderPanel()} |
| </Col> |
| <Col span={showTimeline ? 24 : 0}> |
| {showTimeline ? ( |
| <TraceTimeline |
| trace={{ data: { queryTrace, currentTraceId } }} |
| /> |
| ) : null} |
| </Col> |
| </Row> |
| </div> |
| ); |
| } |
| } |