diff --git a/cmd/tidb-dashboard/main.go b/cmd/tidb-dashboard/main.go index 8fd98d5091..a302cb90cc 100755 --- a/cmd/tidb-dashboard/main.go +++ b/cmd/tidb-dashboard/main.go @@ -69,6 +69,7 @@ func NewCLIConfig() *DashboardCLIConfig { flag.BoolVar(&cfg.CoreConfig.EnableExperimental, "experimental", cfg.CoreConfig.EnableExperimental, "allow experimental features") flag.StringVar(&cfg.CoreConfig.FeatureVersion, "feature-version", cfg.CoreConfig.FeatureVersion, "target TiDB version for standalone mode") flag.IntVar(&cfg.CoreConfig.NgmTimeout, "ngm-timeout", cfg.CoreConfig.NgmTimeout, "timeout secs for accessing the ngm API") + flag.BoolVar(&cfg.CoreConfig.EnableKeyVisualizer, "keyviz", true, "enable/disable key visualizer(default: true)") showVersion := flag.BoolP("version", "v", false, "print version information and exit") diff --git a/pkg/apiserver/clusterinfo/hostinfo/cluster_config.go b/pkg/apiserver/clusterinfo/hostinfo/cluster_config.go index 004f85cb3e..00bd24ccdf 100644 --- a/pkg/apiserver/clusterinfo/hostinfo/cluster_config.go +++ b/pkg/apiserver/clusterinfo/hostinfo/cluster_config.go @@ -3,6 +3,7 @@ package hostinfo import ( + "encoding/json" "strings" "gorm.io/gorm" @@ -24,7 +25,9 @@ func FillInstances(db *gorm.DB, m InfoMap) error { Where("(`TYPE` = 'tidb' AND `KEY` = 'log.file.filename') " + "OR (`TYPE` = 'tikv' AND `KEY` = 'storage.data-dir') " + "OR (`TYPE` = 'pd' AND `KEY` = 'data-dir') " + - "OR (`TYPE` = 'tiflash' AND `KEY` = 'engine-store.path')"). + "OR (`TYPE` = 'tiflash' AND (`KEY` = 'engine-store.path' " + + " OR `KEY` = 'engine-store.storage.main.dir' " + + " OR `KEY` = 'engine-store.storage.latest.dir'))"). Find(&rows).Error; err != nil { return err } @@ -37,9 +40,44 @@ func FillInstances(db *gorm.DB, m InfoMap) error { if _, ok := m[hostname]; !ok { m[hostname] = NewHostInfo(hostname) } - m[hostname].Instances[row.Instance] = &InstanceInfo{ - Type: row.Type, - PartitionPathL: strings.ToLower(locateInstanceMountPartition(row.Value, m[hostname].Partitions)), + switch row.Type { + case "tiflash": + if ins, ok := m[hostname].Instances[row.Instance]; ok { + if ins.Type == row.Type && ins.PartitionPathL != "" { + continue + } + } else { + m[hostname].Instances[row.Instance] = &InstanceInfo{ + Type: row.Type, + PartitionPathL: "", + } + } + var paths []string + switch row.Key { + case "engine-store.path": + items := strings.Split(row.Value, ",") + for _, path := range items { + paths = append(paths, strings.TrimSpace(path)) + } + case "engine-store.storage.main.dir", "engine-store.storage.latest.dir": + if err := json.Unmarshal([]byte(row.Value), &paths); err != nil { + return err + } + default: + paths = []string{row.Value} + } + for _, path := range paths { + mountDir := locateInstanceMountPartition(path, m[hostname].Partitions) + if mountDir != "" { + m[hostname].Instances[row.Instance].PartitionPathL = strings.ToLower(mountDir) + break + } + } + default: + m[hostname].Instances[row.Instance] = &InstanceInfo{ + Type: row.Type, + PartitionPathL: strings.ToLower(locateInstanceMountPartition(row.Value, m[hostname].Partitions)), + } } } return nil diff --git a/pkg/config/config.go b/pkg/config/config.go index ac5e262325..fc3f6274ed 100755 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -30,26 +30,28 @@ type Config struct { ClusterTLSInfo *transport.TLSInfo // TLS info for mTLS authentication between TiDB components. TiDBTLSConfig *tls.Config // TLS config for mTLS authentication between TiDB and MySQL client. - EnableTelemetry bool - EnableExperimental bool - FeatureVersion string // assign the target TiDB version when running TiDB Dashboard as standalone mode + EnableTelemetry bool + EnableExperimental bool + EnableKeyVisualizer bool + FeatureVersion string // assign the target TiDB version when running TiDB Dashboard as standalone mode NgmTimeout int // in seconds } func Default() *Config { return &Config{ - DataDir: "/tmp/dashboard-data", - TempDir: "", - PDEndPoint: "http://127.0.0.1:2379", - PublicPathPrefix: defaultPublicPathPrefix, - ClusterTLSConfig: nil, - ClusterTLSInfo: nil, - TiDBTLSConfig: nil, - EnableTelemetry: false, - EnableExperimental: false, - FeatureVersion: version.PDVersion, - NgmTimeout: 30, // s + DataDir: "/tmp/dashboard-data", + TempDir: "", + PDEndPoint: "http://127.0.0.1:2379", + PublicPathPrefix: defaultPublicPathPrefix, + ClusterTLSConfig: nil, + ClusterTLSInfo: nil, + TiDBTLSConfig: nil, + EnableTelemetry: false, + EnableExperimental: false, + EnableKeyVisualizer: true, + FeatureVersion: version.PDVersion, + NgmTimeout: 30, // s } } diff --git a/pkg/config/dynamic_config_manager.go b/pkg/config/dynamic_config_manager.go index d8438dcb3c..cd792e2ebd 100644 --- a/pkg/config/dynamic_config_manager.go +++ b/pkg/config/dynamic_config_manager.go @@ -77,6 +77,7 @@ func (m *DynamicConfigManager) Start(ctx context.Context) error { dc = &DynamicConfig{} } dc.Adjust() + dc.KeyVisual.AutoCollectionDisabled = !m.config.EnableKeyVisualizer if err := backoff.Retry(func() error { return m.Set(dc) }, bo); err != nil { log.Error("Failed to start DynamicConfigManager", zap.Error(err)) diff --git a/ui/packages/tidb-dashboard-for-op/.env.development b/ui/packages/tidb-dashboard-for-op/.env.development index 32f1fb23a3..0b2cbd7324 100644 --- a/ui/packages/tidb-dashboard-for-op/.env.development +++ b/ui/packages/tidb-dashboard-for-op/.env.development @@ -3,4 +3,4 @@ PUBLIC_URL='/dashboard' REACT_APP_VERSION=$npm_package_version REACT_APP_MIXPANEL_HOST=https://telemetry.pingcap.com/api/v1/dashboard/report REACT_APP_MIXPANEL_TOKEN= -REACT_APP_DASHBOARD_API_URL=http://127.0.0.1:2379 +REACT_APP_DASHBOARD_API_URL=http://127.0.0.1:12333 diff --git a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.tsx b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.tsx index e9a44a96e0..924a9460a3 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.tsx @@ -307,6 +307,7 @@ export function TopSQLList() { topN={TOP_N} instanceType={instance?.instance_type as InstanceType} data={topSQLData} + timeRange={timeRange} /> )} {Boolean(!topSQLData?.length && timeRange.type === 'recent') && ( diff --git a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListTable.tsx b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListTable.tsx index b6daa03527..9039f2123d 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListTable.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListTable.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react' -import { Tooltip } from 'antd' +import { Tooltip, Typography } from 'antd' import { getValueFormat } from '@baurine/grafana-value-formats' import { useTranslation } from 'react-i18next' import { @@ -15,7 +15,9 @@ import { Bar, TextWrap, HighlightSQL, - AppearAnimate + AppearAnimate, + TimeRange, + toTimeRangeValue } from '@lib/components' import { useRecordSelection } from '../../utils/useRecordSelection' @@ -24,11 +26,14 @@ import { isOthersRecord, isUnknownSQLRecord } from '../../utils/specialRecord' import { InstanceType } from './ListDetail/ListDetailTable' import { useMemoizedFn } from 'ahooks' import { telemetry } from '../../utils/telemetry' +import openLink from '@lib/utils/openLink' +import { useNavigate } from 'react-router-dom' interface ListTableProps { data: TopsqlSummaryItem[] topN: number instanceType: InstanceType + timeRange: TimeRange onRowOver: (key: string) => void onRowLeave: () => void } @@ -43,11 +48,29 @@ export function ListTable({ data, topN, instanceType, + timeRange, onRowLeave, onRowOver }: ListTableProps) { const { t } = useTranslation() const { data: tableRecords, capacity } = useTableData(data) + const navigate = useNavigate() + + function goDetail(ev: React.MouseEvent, record: SQLRecord) { + const sv = sessionStorage.getItem('statement.query_options') + if (sv) { + const queryOptions = JSON.parse(sv) + queryOptions.searchText = record.sql_digest + sessionStorage.setItem( + 'statement.query_options', + JSON.stringify(queryOptions) + ) + } + + const tv = toTimeRangeValue(timeRange) + openLink(`/statement?from=${tv[0]}&to=${tv[1]}`, ev, navigate) + } + const tableColumns = useMemo( () => [ { @@ -65,7 +88,7 @@ export function ListTable({ name: t('topsql.table.fields.sql'), key: 'query', minWidth: 250, - maxWidth: 550, + // maxWidth: 550, onRender: (rec: SQLRecord) => { const text = isUnknownSQLRecord(rec) ? `(SQL ${rec.sql_digest?.slice(0, 8)})` @@ -97,6 +120,22 @@ export function ListTable({ ) } + }, + { + name: '', + key: 'actions', + minWidth: 200, + // maxWidth: 200, + onRender: (rec) => { + if (!isOthersRecord(rec)) { + return ( + goDetail(ev, rec)}> + {t('topsql.table.actions.search_in_statements')} + + ) + } + return null + } } ], [capacity, t, topN] diff --git a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/en.yaml b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/en.yaml index 3869679f63..b83a5a54b7 100755 --- a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/en.yaml +++ b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/en.yaml @@ -34,6 +34,8 @@ topsql: fields: cpu_time: Total CPU Time sql: SQL Statement + actions: + search_in_statements: Search in SQL Statements detail: title: SQL Statement Details by Plan overall: Overall diff --git a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/zh.yaml b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/zh.yaml index 4598bebaa6..bb6a449fb2 100755 --- a/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/zh.yaml +++ b/ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/zh.yaml @@ -34,6 +34,8 @@ topsql: fields: cpu_time: 累计 CPU 耗时 sql: SQL 语句 + actions: + search_in_statements: 在 SQL 语句分析中搜索 detail: title: SQL 详情 overall: 总计 diff --git a/util/rest/error_resp.go b/util/rest/error_resp.go index e4ea6174cf..0fb0ee1a02 100644 --- a/util/rest/error_resp.go +++ b/util/rest/error_resp.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/joomcode/errorx" + "github.com/pingcap/log" + "go.uber.org/zap/zapcore" ) type ErrorResponse struct { @@ -94,11 +96,16 @@ func buildDetailMessage(err error) string { } func NewErrorResponse(err error) ErrorResponse { + logLevel := log.GetLevel() + fullText := "" + if logLevel == zapcore.DebugLevel { + fullText = buildDetailMessage(err) + } return ErrorResponse{ Error: true, Message: buildSimpleMessage(err), Code: removeErrorPrefix(buildCode(err)), - // For security reasons, we need to hide detailed stacktrace info. - // FullText: buildDetailMessage(err), + // For security reasons, we need to hide detailed stacktrace info in prod. + FullText: fullText, } }