Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2020.09.21.1 #760

Merged
merged 1 commit into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 116 additions & 5 deletions pkg/apiserver/diagnose/diagnose.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ package diagnose

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"sync"
"time"

"github.com/gin-gonic/gin"
"github.com/goccy/go-graphviz"
"github.com/pingcap/log"
"go.uber.org/zap"

Expand All @@ -35,6 +41,8 @@ const (
timeLayout = "2006-01-02 15:04:05"
)

var graphvizMutex sync.Mutex

type Service struct {
// FIXME: Use fx.In
config *config.Config
Expand Down Expand Up @@ -71,13 +79,109 @@ func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) {
endpoint.GET("/reports/:id/status",
auth.MWAuthRequired(),
s.reportStatusHandler)
endpoint.POST("/metrics_relation/generate", auth.MWAuthRequired(), s.metricsRelationHandler)
endpoint.GET("/metrics_relation/view", s.metricsRelationViewHandler)
}

type GenerateReportRequest struct {
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
CompareStartTime int64 `json:"compare_start_time"`
CompareEndTime int64 `json:"compare_end_time"`
func (s *Service) generateMetricsRelation(startTime, endTime time.Time, graphType string) (string, error) {
params := url.Values{}
params.Add("start", startTime.Format(time.RFC3339))
params.Add("end", endTime.Format(time.RFC3339))
params.Add("type", graphType)
encodedParams := params.Encode()

data, err := s.tidbClient.SendGetRequest("/metrics/profile?" + encodedParams)
if err != nil {
return "", err
}

file, err := ioutil.TempFile("", "metrics*.svg")
if err != nil {
return "", fmt.Errorf("failed to create temp file: %v", err)
}
_ = file.Close()

g := graphviz.New()
// FIXME: should share a global mutex for profiling.
graphvizMutex.Lock()
defer graphvizMutex.Unlock()
graph, err := graphviz.ParseBytes(data)
if err != nil {
_ = os.Remove(file.Name())
return "", fmt.Errorf("failed to parse DOT file: %v", err)
}

if err := g.RenderFilename(graph, graphviz.SVG, file.Name()); err != nil {
_ = os.Remove(file.Name())
return "", fmt.Errorf("failed to render SVG: %v", err)
}

return file.Name(), nil
}

type GenerateMetricsRelationRequest struct {
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Type string `json:"type"`
}

// @Id diagnoseGenerateMetricsRelationship
// @Summary Generate metrics relationship graph.
// @Param request body GenerateMetricsRelationRequest true "Request body"
// @Router /diagnose/metrics_relation/generate [post]
// @Success 200 {string} string
// @Security JwtAuth
// @Failure 401 {object} utils.APIError "Unauthorized failure"
func (s *Service) metricsRelationHandler(c *gin.Context) {
var req GenerateMetricsRelationRequest
if err := c.ShouldBindJSON(&req); err != nil {
utils.MakeInvalidRequestErrorFromError(c, err)
return
}

startTime := time.Unix(req.StartTime, 0)
endTime := time.Unix(req.EndTime, 0)

path, err := s.generateMetricsRelation(startTime, endTime, req.Type)
if err != nil {
_ = c.Error(err)
return
}

token, err := utils.NewJWTString("diagnose/metrics", path)
if err != nil {
_ = c.Error(err)
return
}

c.JSON(http.StatusOK, token)
}

// @Summary View metrics relationship graph.
// @Produce image/svg
// @Param token query string true "token"
// @Failure 400 {object} utils.APIError
// @Failure 401 {object} utils.APIError "Unauthorized failure"
// @Failure 500 {object} utils.APIError
// @Router /diagnose/metrics_relation/view [get]
func (s *Service) metricsRelationViewHandler(c *gin.Context) {
token := c.Query("token")
path, err := utils.ParseJWTString("diagnose/metrics", token)
if err != nil {
utils.MakeInvalidRequestErrorFromError(c, err)
return
}

data, err := ioutil.ReadFile(path)
if err != nil {
_ = c.Error(err)
return
}

// Do not remove it? Otherwise the link will just expire..
// _ = os.Remove(path)

c.Data(http.StatusOK, "image/svg+xml", data)
}

// @Summary SQL diagnosis reports history
Expand All @@ -95,6 +199,13 @@ func (s *Service) reportsHandler(c *gin.Context) {
c.JSON(http.StatusOK, reports)
}

type GenerateReportRequest struct {
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
CompareStartTime int64 `json:"compare_start_time"`
CompareEndTime int64 `json:"compare_end_time"`
}

// @Summary SQL diagnosis report
// @Description Generate sql diagnosis report
// @Param request body GenerateReportRequest true "Request body"
Expand Down
2 changes: 1 addition & 1 deletion release-version
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# This file specifies the TiDB Dashboard internal version, which will be printed in `--version`
# and UI. In release branch, changing this file will result in publishing a new version and tag.
2020.09.08.1
2020.09.21.1
75 changes: 71 additions & 4 deletions ui/lib/apps/Diagnose/pages/DiagnoseGenerator.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Button, Form, Input, InputNumber, message, Select, Switch } from 'antd'
import {
Button,
Form,
Input,
InputNumber,
message,
Modal,
Select,
Switch,
} from 'antd'
import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'
import React from 'react'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { getValueFormat } from '@baurine/grafana-value-formats'

import client from '@lib/client'
import { Card } from '@lib/components'
import { Card, Pre } from '@lib/components'
import { DatePicker } from '@lib/components'

import DiagnoseHistory from '../components/DiagnoseHistory'
Expand Down Expand Up @@ -47,11 +55,62 @@ export default function DiagnoseGenerator() {
const { t } = useTranslation()
const navigate = useNavigate()
const handleFinish = useFinishHandler(navigate)
const [form] = Form.useForm()
const [isGenerateRelationPosting, setGenerateRelationPosting] = useState(
false
)

const handleMetricsRelation = useCallback(async () => {
try {
await form.validateFields()
} catch (e) {
return
}

const fieldsValue = form.getFieldsValue()

const start_time = fieldsValue['rangeBegin'].unix()
let range_duration = fieldsValue['rangeDuration']
if (fieldsValue['rangeDuration'] === 0) {
range_duration = fieldsValue['rangeDurationCustom']
}
const end_time = start_time + range_duration * 60

try {
setGenerateRelationPosting(true)

const resp = await client
.getInstance()
.diagnoseGenerateMetricsRelationship({
start_time,
end_time,
type: 'sum',
})
Modal.success({
title: t('diagnose.metrics_relation.success.title'),
okText: t('diagnose.metrics_relation.success.button'),
okButtonProps: {
target: '_blank',
href:
`${client.getBasePath()}/diagnose/metrics_relation/view?token=` +
encodeURIComponent(resp.data),
},
})
} catch (e) {
Modal.error({
title: 'Error',
content: <Pre>{e?.response?.data?.message ?? e.message}</Pre>,
})
}

setGenerateRelationPosting(false)
}, [t, form])

return (
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
<Card title={t('diagnose.generate.title')}>
<Form
form={form}
layout="inline"
onFinish={handleFinish}
initialValues={{ rangeDuration: 10, rangeDurationCustom: 10 }}
Expand Down Expand Up @@ -139,6 +198,14 @@ export default function DiagnoseGenerator() {
{t('diagnose.generate.submit')}
</Button>
</Form.Item>
<Form.Item>
<Button
onClick={handleMetricsRelation}
loading={isGenerateRelationPosting}
>
{t('diagnose.generate.metrics_relation')}
</Button>
</Form.Item>
</Form>
</Card>

Expand Down
5 changes: 5 additions & 0 deletions ui/lib/apps/Diagnose/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ diagnose:
is_compare: Compare by Baseline
compare_range_begin: Baseline Range Start Time
submit: Start
metrics_relation: Generate Metrics Relation
list_table:
id: Report ID
diagnose_create_time: Diagnose At
Expand All @@ -26,3 +27,7 @@ diagnose:
progress: Progress
time_duration:
custom: Custom
metrics_relation:
success:
title: Generate metrics relation graph successfully
button: View Graph
5 changes: 5 additions & 0 deletions ui/lib/apps/Diagnose/translations/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ diagnose:
is_compare: 与基线区间对比
compare_range_begin: 基线区间起始时间
submit: 开始
metrics_relation: 生成监控关系图
list_table:
id: 报告 ID
diagnose_create_time: 诊断时间
Expand All @@ -26,3 +27,7 @@ diagnose:
progress: 生成进度
time_duration:
custom: 自定义
metrics_relation:
success:
title: 监控耗时关系图生成成功
button: 查看关系图