diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index c2a401d575..9186d06f0f 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -32,17 +32,21 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sso" "github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sso/ssoauth" "github.com/pingcap/tidb-dashboard/pkg/apiserver/visualplan" + "github.com/pingcap/tidb-dashboard/pkg/scheduling" "github.com/pingcap/tidb-dashboard/pkg/ticdc" "github.com/pingcap/tidb-dashboard/pkg/tiflash" "github.com/pingcap/tidb-dashboard/pkg/tiproxy" + "github.com/pingcap/tidb-dashboard/pkg/tso" "github.com/pingcap/tidb-dashboard/pkg/utils/version" "github.com/pingcap/tidb-dashboard/util/client/httpclient" "github.com/pingcap/tidb-dashboard/util/client/pdclient" + "github.com/pingcap/tidb-dashboard/util/client/schedulingclient" "github.com/pingcap/tidb-dashboard/util/client/ticdcclient" "github.com/pingcap/tidb-dashboard/util/client/tidbclient" "github.com/pingcap/tidb-dashboard/util/client/tiflashclient" "github.com/pingcap/tidb-dashboard/util/client/tikvclient" "github.com/pingcap/tidb-dashboard/util/client/tiproxyclient" + "github.com/pingcap/tidb-dashboard/util/client/tsoclient" "github.com/pingcap/tidb-dashboard/util/featureflag" "github.com/pingcap/tidb-dashboard/util/rest" @@ -113,6 +117,8 @@ var Modules = fx.Options( httpc.NewHTTPClient, pd.NewEtcdClient, pd.NewPDClient, + tso.NewTSOClient, + scheduling.NewSchedulingClient, config.NewDynamicConfigManager, tidb.NewTiDBClient, tikv.NewTiKVClient, @@ -199,6 +205,8 @@ func newClients(lc fx.Lifecycle, config *config.Config) ( pdClient *pdclient.APIClient, ticdcClient *ticdcclient.StatusClient, tiproxyClient *tiproxyclient.StatusClient, + tsoClient *tsoclient.StatusClient, + schedulingClient *schedulingclient.StatusClient, ) { httpConfig := httpclient.Config{ TLSConfig: config.ClusterTLSConfig, @@ -209,6 +217,8 @@ func newClients(lc fx.Lifecycle, config *config.Config) ( pdClient = pdclient.NewAPIClient(httpConfig) ticdcClient = ticdcclient.NewStatusClient(httpConfig) tiproxyClient = tiproxyclient.NewStatusClient(httpConfig) + tsoClient = tsoclient.NewStatusClient(httpConfig) + schedulingClient = schedulingclient.NewStatusClient(httpConfig) lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { dbClient.SetDefaultCtx(ctx) diff --git a/pkg/apiserver/clusterinfo/host.go b/pkg/apiserver/clusterinfo/host.go index 3cf4b6a2ed..ecb1e78d9e 100644 --- a/pkg/apiserver/clusterinfo/host.go +++ b/pkg/apiserver/clusterinfo/host.go @@ -60,6 +60,22 @@ func (s *Service) fetchAllInstanceHosts() ([]string, error) { allHostsMap[i.IP] = struct{}{} } + tsoInfo, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + return nil, err + } + for _, i := range tsoInfo { + allHostsMap[i.IP] = struct{}{} + } + + schedulingInfo, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + return nil, err + } + for _, i := range schedulingInfo { + allHostsMap[i.IP] = struct{}{} + } + allHosts := lo.Keys(allHostsMap) sort.Strings(allHosts) diff --git a/pkg/apiserver/clusterinfo/hostinfo/hostinfo.go b/pkg/apiserver/clusterinfo/hostinfo/hostinfo.go index ff0ccd368d..9e9d3549c5 100644 --- a/pkg/apiserver/clusterinfo/hostinfo/hostinfo.go +++ b/pkg/apiserver/clusterinfo/hostinfo/hostinfo.go @@ -53,7 +53,7 @@ type InfoMap = map[string]*Info var clusterTableQueryTemplate = template.Must(template.New("").Parse(` SELECT *, - FIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb', 'tiproxy') AS _ORDER + FIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb', 'tiproxy', 'tso', 'scheduling') AS _ORDER FROM ( SELECT TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME, JSON_OBJECTAGG(NAME, VALUE) AS JSON_VALUE diff --git a/pkg/apiserver/clusterinfo/service.go b/pkg/apiserver/clusterinfo/service.go index ef10767781..d1a4695ba8 100644 --- a/pkg/apiserver/clusterinfo/service.go +++ b/pkg/apiserver/clusterinfo/service.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "net/http" + "strings" "sync" "time" @@ -59,6 +60,8 @@ func RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) { endpoint.DELETE("/tidb/:address", s.deleteTiDBTopology) endpoint.GET("/store", s.getStoreTopology) endpoint.GET("/pd", s.getPDTopology) + endpoint.GET("/tso", s.getTSOTopology) + endpoint.GET("/scheduling", s.getSchedulingTopology) endpoint.GET("/alertmanager", s.getAlertManagerTopology) endpoint.GET("/alertmanager/:address/count", s.getAlertManagerCounts) endpoint.GET("/grafana", s.getGrafanaTopology) @@ -156,6 +159,46 @@ func (s *Service) getTiProxyTopology(c *gin.Context) { c.JSON(http.StatusOK, instances) } +// @ID getTSOTopology +// @Summary Get all TSO instances +// @Success 200 {array} topology.TSOInfo +// @Router /topology/tso [get] +// @Security JwtAuth +// @Failure 401 {object} rest.ErrorResponse +func (s *Service) getTSOTopology(c *gin.Context) { + instances, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + // TODO: refine later + if strings.Contains(err.Error(), "status code 404") { + rest.Error(c, rest.ErrNotFound.Wrap(err, "api not found")) + } else { + rest.Error(c, err) + } + return + } + c.JSON(http.StatusOK, instances) +} + +// @ID getSchedulingTopology +// @Summary Get all Scheduling instances +// @Success 200 {array} topology.SchedulingInfo +// @Router /topology/scheduling [get] +// @Security JwtAuth +// @Failure 401 {object} rest.ErrorResponse +func (s *Service) getSchedulingTopology(c *gin.Context) { + instances, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + // TODO: refine later + if strings.Contains(err.Error(), "status code 404") { + rest.Error(c, rest.ErrNotFound.Wrap(err, "api not found")) + } else { + rest.Error(c, err) + } + return + } + c.JSON(http.StatusOK, instances) +} + type StoreTopologyResponse struct { TiKV []topology.StoreInfo `json:"tikv"` TiFlash []topology.StoreInfo `json:"tiflash"` diff --git a/pkg/apiserver/clusterinfo/statistics.go b/pkg/apiserver/clusterinfo/statistics.go index d819098d47..0ef8699032 100644 --- a/pkg/apiserver/clusterinfo/statistics.go +++ b/pkg/apiserver/clusterinfo/statistics.go @@ -77,6 +77,8 @@ func (s *Service) calculateStatistics(db *gorm.DB) (*ClusterStatistics, error) { infoByIk["tiflash"] = newInstanceKindImmediateInfo() infoByIk["ticdc"] = newInstanceKindImmediateInfo() infoByIk["tiproxy"] = newInstanceKindImmediateInfo() + infoByIk["tso"] = newInstanceKindImmediateInfo() + infoByIk["scheduling"] = newInstanceKindImmediateInfo() // Fill from topology info pdInfo, err := topology.FetchPDTopology(s.params.PDClient) @@ -135,6 +137,26 @@ func (s *Service) calculateStatistics(db *gorm.DB) (*ClusterStatistics, error) { globalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} infoByIk["tiproxy"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} } + tsoInfo, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + return nil, err + } + for _, i := range tsoInfo { + globalHostsSet[i.IP] = struct{}{} + globalVersionsSet[i.Version] = struct{}{} + globalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} + infoByIk["tso"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} + } + schedulingInfo, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient) + if err != nil { + return nil, err + } + for _, i := range schedulingInfo { + globalHostsSet[i.IP] = struct{}{} + globalVersionsSet[i.Version] = struct{}{} + globalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} + infoByIk["scheduling"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{} + } // Fill from hardware info allHostsInfoMap := make(map[string]*hostinfo.Info) @@ -198,6 +220,20 @@ func (s *Service) calculateStatistics(db *gorm.DB) (*ClusterStatistics, error) { globalFailureHostsSet[i.IP] = struct{}{} } } + for _, i := range tsoInfo { + if v, ok := globalInfo.hosts[i.IP]; ok { + infoByIk["tso"].hosts[i.IP] = v + } else { + globalFailureHostsSet[i.IP] = struct{}{} + } + } + for _, i := range schedulingInfo { + if v, ok := globalInfo.hosts[i.IP]; ok { + infoByIk["scheduling"].hosts[i.IP] = v + } else { + globalFailureHostsSet[i.IP] = struct{}{} + } + } // Generate result.. versions := lo.Keys(globalVersionsSet) diff --git a/pkg/apiserver/debugapi/endpoint/payload.go b/pkg/apiserver/debugapi/endpoint/payload.go index c16144940c..27e18ffc07 100644 --- a/pkg/apiserver/debugapi/endpoint/payload.go +++ b/pkg/apiserver/debugapi/endpoint/payload.go @@ -18,11 +18,13 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/utils/topology" "github.com/pingcap/tidb-dashboard/util/client/httpclient" "github.com/pingcap/tidb-dashboard/util/client/pdclient" + "github.com/pingcap/tidb-dashboard/util/client/schedulingclient" "github.com/pingcap/tidb-dashboard/util/client/ticdcclient" "github.com/pingcap/tidb-dashboard/util/client/tidbclient" "github.com/pingcap/tidb-dashboard/util/client/tiflashclient" "github.com/pingcap/tidb-dashboard/util/client/tikvclient" "github.com/pingcap/tidb-dashboard/util/client/tiproxyclient" + "github.com/pingcap/tidb-dashboard/util/client/tsoclient" "github.com/pingcap/tidb-dashboard/util/rest" "github.com/pingcap/tidb-dashboard/util/topo" ) @@ -38,12 +40,14 @@ type RequestPayload struct { } type HTTPClients struct { - PDAPIClient *pdclient.APIClient - TiDBStatusClient *tidbclient.StatusClient - TiKVStatusClient *tikvclient.StatusClient - TiFlashStatusClient *tiflashclient.StatusClient - TiCDCStatusClient *ticdcclient.StatusClient - TiProxyStatusClient *tiproxyclient.StatusClient + PDAPIClient *pdclient.APIClient + TiDBStatusClient *tidbclient.StatusClient + TiKVStatusClient *tikvclient.StatusClient + TiFlashStatusClient *tiflashclient.StatusClient + TiCDCStatusClient *ticdcclient.StatusClient + TiProxyStatusClient *tiproxyclient.StatusClient + TSOStatusClient *tsoclient.StatusClient + SchedulingStatusClient *schedulingclient.StatusClient } func (c HTTPClients) GetHTTPClientByNodeKind(kind topo.Kind) *httpclient.Client { @@ -306,6 +310,36 @@ func (p *ResolvedRequestPayload) verifyEndpoint(ctx context.Context, etcdClient if !matched { return ErrInvalidEndpoint.New("invalid endpoint '%s:%d'", p.host, p.port) } + case topo.KindTSO: + infos, err := topology.FetchTSOTopology(ctx, pdClient) + if err != nil { + return ErrInvalidEndpoint.Wrap(err, "failed to fetch tso topology") + } + matched := false + for _, info := range infos { + if info.IP == p.host && info.Port == uint(p.port) { + matched = true + break + } + } + if !matched { + return ErrInvalidEndpoint.New("invalid endpoint '%s:%d'", p.host, p.port) + } + case topo.KindScheduling: + infos, err := topology.FetchSchedulingTopology(ctx, pdClient) + if err != nil { + return ErrInvalidEndpoint.Wrap(err, "failed to fetch scheduling topology") + } + matched := false + for _, info := range infos { + if info.IP == p.host && info.Port == uint(p.port) { + matched = true + break + } + } + if !matched { + return ErrInvalidEndpoint.New("invalid endpoint '%s:%d'", p.host, p.port) + } default: return ErrUnknownComponent.New("Unknown component '%s'", p.api.Component) } diff --git a/pkg/apiserver/debugapi/service.go b/pkg/apiserver/debugapi/service.go index e5f05113f8..c55b60f9a2 100644 --- a/pkg/apiserver/debugapi/service.go +++ b/pkg/apiserver/debugapi/service.go @@ -16,11 +16,13 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/apiserver/user" "github.com/pingcap/tidb-dashboard/pkg/pd" "github.com/pingcap/tidb-dashboard/util/client/pdclient" + "github.com/pingcap/tidb-dashboard/util/client/schedulingclient" "github.com/pingcap/tidb-dashboard/util/client/ticdcclient" "github.com/pingcap/tidb-dashboard/util/client/tidbclient" "github.com/pingcap/tidb-dashboard/util/client/tiflashclient" "github.com/pingcap/tidb-dashboard/util/client/tikvclient" "github.com/pingcap/tidb-dashboard/util/client/tiproxyclient" + "github.com/pingcap/tidb-dashboard/util/client/tsoclient" "github.com/pingcap/tidb-dashboard/util/rest" "github.com/pingcap/tidb-dashboard/util/rest/fileswap" ) @@ -37,14 +39,16 @@ func registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) { type ServiceParams struct { fx.In - PDAPIClient *pdclient.APIClient - TiDBStatusClient *tidbclient.StatusClient - TiKVStatusClient *tikvclient.StatusClient - TiFlashStatusClient *tiflashclient.StatusClient - TiCDCStatusClient *ticdcclient.StatusClient - TiProxyStatusClient *tiproxyclient.StatusClient - EtcdClient *clientv3.Client - PDClient *pd.Client + PDAPIClient *pdclient.APIClient + TiDBStatusClient *tidbclient.StatusClient + TiKVStatusClient *tikvclient.StatusClient + TiFlashStatusClient *tiflashclient.StatusClient + TiCDCStatusClient *ticdcclient.StatusClient + TiProxyStatusClient *tiproxyclient.StatusClient + EtcdClient *clientv3.Client + PDClient *pd.Client + TSOStatusClient *tsoclient.StatusClient + SchedulingStatusClient *schedulingclient.StatusClient } type Service struct { @@ -57,12 +61,14 @@ type Service struct { func newService(p ServiceParams) *Service { httpClients := endpoint.HTTPClients{ - PDAPIClient: p.PDAPIClient, - TiDBStatusClient: p.TiDBStatusClient, - TiKVStatusClient: p.TiKVStatusClient, - TiFlashStatusClient: p.TiFlashStatusClient, - TiCDCStatusClient: p.TiCDCStatusClient, - TiProxyStatusClient: p.TiProxyStatusClient, + PDAPIClient: p.PDAPIClient, + TiDBStatusClient: p.TiDBStatusClient, + TiKVStatusClient: p.TiKVStatusClient, + TiFlashStatusClient: p.TiFlashStatusClient, + TiCDCStatusClient: p.TiCDCStatusClient, + TiProxyStatusClient: p.TiProxyStatusClient, + TSOStatusClient: p.TSOStatusClient, + SchedulingStatusClient: p.SchedulingStatusClient, } return &Service{ httpClients: httpClients, diff --git a/pkg/apiserver/model/common_models.go b/pkg/apiserver/model/common_models.go index a322e7f053..dae63a6dc9 100644 --- a/pkg/apiserver/model/common_models.go +++ b/pkg/apiserver/model/common_models.go @@ -10,12 +10,14 @@ import ( type NodeKind string const ( - NodeKindTiDB NodeKind = "tidb" - NodeKindTiKV NodeKind = "tikv" - NodeKindPD NodeKind = "pd" - NodeKindTiFlash NodeKind = "tiflash" - NodeKindTiCDC NodeKind = "ticdc" - NodeKindTiProxy NodeKind = "tiproxy" + NodeKindTiDB NodeKind = "tidb" + NodeKindTiKV NodeKind = "tikv" + NodeKindPD NodeKind = "pd" + NodeKindTiFlash NodeKind = "tiflash" + NodeKindTiCDC NodeKind = "ticdc" + NodeKindTiProxy NodeKind = "tiproxy" + NodeKindTSO NodeKind = "tso" + NodeKindScheduling NodeKind = "scheduling" ) type RequestTargetNode struct { @@ -35,12 +37,14 @@ func (n *RequestTargetNode) FileName() string { } type RequestTargetStatistics struct { - NumTiKVNodes int `json:"num_tikv_nodes"` - NumTiDBNodes int `json:"num_tidb_nodes"` - NumPDNodes int `json:"num_pd_nodes"` - NumTiFlashNodes int `json:"num_tiflash_nodes"` - NumTiCDCNodes int `json:"num_ticdc_nodes"` - NumTiProxyNodes int `json:"num_tiproxy_nodes"` + NumTiKVNodes int `json:"num_tikv_nodes"` + NumTiDBNodes int `json:"num_tidb_nodes"` + NumPDNodes int `json:"num_pd_nodes"` + NumTiFlashNodes int `json:"num_tiflash_nodes"` + NumTiCDCNodes int `json:"num_ticdc_nodes"` + NumTiProxyNodes int `json:"num_tiproxy_nodes"` + NumTSONodes int `json:"num_tso_nodes"` + NumSchedulingNodes int `json:"num_scheduling_nodes"` } func NewRequestTargetStatisticsFromArray(arr *[]RequestTargetNode) RequestTargetStatistics { @@ -59,6 +63,10 @@ func NewRequestTargetStatisticsFromArray(arr *[]RequestTargetNode) RequestTarget stats.NumTiCDCNodes++ case NodeKindTiProxy: stats.NumTiProxyNodes++ + case NodeKindTSO: + stats.NumTSONodes++ + case NodeKindScheduling: + stats.NumSchedulingNodes++ } } return stats diff --git a/pkg/apiserver/profiling/fetcher.go b/pkg/apiserver/profiling/fetcher.go index 7698305b62..62d32adc92 100644 --- a/pkg/apiserver/profiling/fetcher.go +++ b/pkg/apiserver/profiling/fetcher.go @@ -17,11 +17,13 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/config" "github.com/pingcap/tidb-dashboard/pkg/pd" + "github.com/pingcap/tidb-dashboard/pkg/scheduling" "github.com/pingcap/tidb-dashboard/pkg/ticdc" "github.com/pingcap/tidb-dashboard/pkg/tidb" "github.com/pingcap/tidb-dashboard/pkg/tiflash" "github.com/pingcap/tidb-dashboard/pkg/tikv" "github.com/pingcap/tidb-dashboard/pkg/tiproxy" + "github.com/pingcap/tidb-dashboard/pkg/tso" ) const ( @@ -39,12 +41,14 @@ type profileFetcher interface { } type fetchers struct { - tikv profileFetcher - tiflash profileFetcher - tidb profileFetcher - pd profileFetcher - ticdc profileFetcher - tiproxy profileFetcher + tikv profileFetcher + tiflash profileFetcher + tidb profileFetcher + pd profileFetcher + ticdc profileFetcher + tiproxy profileFetcher + tso profileFetcher + scheduling profileFetcher } var newFetchers = fx.Provide(func( @@ -54,6 +58,8 @@ var newFetchers = fx.Provide(func( tiflashClient *tiflash.Client, ticdcClient *ticdc.Client, tiproxyClient *tiproxy.Client, + tsoClient *tso.Client, + schedulingClient *scheduling.Client, config *config.Config, ) *fetchers { return &fetchers{ @@ -76,6 +82,12 @@ var newFetchers = fx.Provide(func( tiproxy: &tiproxyFecther{ client: tiproxyClient, }, + tso: &tsoFetcher{ + client: tsoClient, + }, + scheduling: &schedulingFetcher{ + client: schedulingClient, + }, } }) @@ -174,3 +186,19 @@ type tiproxyFecther struct { func (f *tiproxyFecther) fetch(op *fetchOptions) ([]byte, error) { return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path) } + +type tsoFetcher struct { + client *tso.Client +} + +func (f *tsoFetcher) fetch(op *fetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path) +} + +type schedulingFetcher struct { + client *scheduling.Client +} + +func (f *schedulingFetcher) fetch(op *fetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path) +} diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 6d7b46857f..17fe586202 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -30,6 +30,10 @@ func profileAndWritePprof(ctx context.Context, fts *fetchers, target *model.Requ return fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.ticdc, profilingType: profilingType}) case model.NodeKindTiProxy: return fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tiproxy, profilingType: profilingType}) + case model.NodeKindTSO: + return fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tso, profilingType: profilingType}) + case model.NodeKindScheduling: + return fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.scheduling, profilingType: profilingType}) default: return "", "", ErrUnsupportedProfilingTarget.New(target.String()) } diff --git a/pkg/scheduling/client.go b/pkg/scheduling/client.go new file mode 100644 index 0000000000..ca2327f0af --- /dev/null +++ b/pkg/scheduling/client.go @@ -0,0 +1,78 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package scheduling + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "strconv" + "time" + + "go.uber.org/fx" + + "github.com/pingcap/tidb-dashboard/pkg/config" + "github.com/pingcap/tidb-dashboard/pkg/httpc" + "github.com/pingcap/tidb-dashboard/util/distro" +) + +var ErrSchedulingClientRequestFailed = ErrNS.NewType("client_request_failed") + +const ( + defaultSchedulingStatusAPITimeout = time.Second * 10 +) + +type Client struct { + httpClient *httpc.Client + httpScheme string + lifecycleCtx context.Context + timeout time.Duration +} + +func NewSchedulingClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client { + client := &Client{ + httpClient: httpClient, + httpScheme: config.GetClusterHTTPScheme(), + lifecycleCtx: nil, + timeout: defaultSchedulingStatusAPITimeout, + } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + client.lifecycleCtx = ctx + return nil + }, + }) + + return client +} + +func (c Client) WithTimeout(timeout time.Duration) *Client { + c.timeout = timeout + return &c +} + +func (c Client) AddRequestHeader(key, value string) *Client { + c.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value) + return &c +} + +func (c *Client) Get(host string, port int, relativeURI string) (*httpc.Response, error) { + uri := fmt.Sprintf("%s://%s%s", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI) + return c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrSchedulingClientRequestFailed, distro.R().Scheduling) +} + +func (c *Client) SendGetRequest(host string, port int, relativeURI string) ([]byte, error) { + res, err := c.Get(host, port, relativeURI) + if err != nil { + return nil, err + } + return res.Body() +} + +func (c *Client) SendPostRequest(host string, port int, relativeURI string, body io.Reader) ([]byte, error) { + uri := fmt.Sprintf("%s://%s%s", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI) + return c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrSchedulingClientRequestFailed, distro.R().Scheduling) +} diff --git a/pkg/scheduling/scheduling.go b/pkg/scheduling/scheduling.go new file mode 100644 index 0000000000..455c169f9a --- /dev/null +++ b/pkg/scheduling/scheduling.go @@ -0,0 +1,9 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package scheduling + +import ( + "github.com/joomcode/errorx" +) + +var ErrNS = errorx.NewNamespace("error.scheduling") diff --git a/pkg/tso/client.go b/pkg/tso/client.go new file mode 100644 index 0000000000..c724fddf73 --- /dev/null +++ b/pkg/tso/client.go @@ -0,0 +1,78 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package tso + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "strconv" + "time" + + "go.uber.org/fx" + + "github.com/pingcap/tidb-dashboard/pkg/config" + "github.com/pingcap/tidb-dashboard/pkg/httpc" + "github.com/pingcap/tidb-dashboard/util/distro" +) + +var ErrTSOClientRequestFailed = ErrNS.NewType("client_request_failed") + +const ( + defaultTSOStatusAPITimeout = time.Second * 10 +) + +type Client struct { + httpClient *httpc.Client + httpScheme string + lifecycleCtx context.Context + timeout time.Duration +} + +func NewTSOClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client { + client := &Client{ + httpClient: httpClient, + httpScheme: config.GetClusterHTTPScheme(), + lifecycleCtx: nil, + timeout: defaultTSOStatusAPITimeout, + } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + client.lifecycleCtx = ctx + return nil + }, + }) + + return client +} + +func (c Client) WithTimeout(timeout time.Duration) *Client { + c.timeout = timeout + return &c +} + +func (c Client) AddRequestHeader(key, value string) *Client { + c.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value) + return &c +} + +func (c *Client) Get(host string, port int, relativeURI string) (*httpc.Response, error) { + uri := fmt.Sprintf("%s://%s%s", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI) + return c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTSOClientRequestFailed, distro.R().TSO) +} + +func (c *Client) SendGetRequest(host string, port int, relativeURI string) ([]byte, error) { + res, err := c.Get(host, port, relativeURI) + if err != nil { + return nil, err + } + return res.Body() +} + +func (c *Client) SendPostRequest(host string, port int, relativeURI string, body io.Reader) ([]byte, error) { + uri := fmt.Sprintf("%s://%s%s", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI) + return c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrTSOClientRequestFailed, distro.R().TSO) +} diff --git a/pkg/tso/tso.go b/pkg/tso/tso.go new file mode 100644 index 0000000000..8a32b66277 --- /dev/null +++ b/pkg/tso/tso.go @@ -0,0 +1,9 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package tso + +import ( + "github.com/joomcode/errorx" +) + +var ErrNS = errorx.NewNamespace("error.tso") diff --git a/pkg/utils/topology/models.go b/pkg/utils/topology/models.go index 650fb46111..96deb5b87e 100644 --- a/pkg/utils/topology/models.go +++ b/pkg/utils/topology/models.go @@ -56,6 +56,26 @@ type TiProxyInfo struct { StartTimestamp int64 `json:"start_timestamp"` } +type TSOInfo struct { + GitHash string `json:"git_hash"` + Version string `json:"version"` + IP string `json:"ip"` + Port uint `json:"port"` + DeployPath string `json:"deploy_path"` + Status ComponentStatus `json:"status"` + StartTimestamp int64 `json:"start_timestamp"` +} + +type SchedulingInfo struct { + GitHash string `json:"git_hash"` + Version string `json:"version"` + IP string `json:"ip"` + Port uint `json:"port"` + DeployPath string `json:"deploy_path"` + Status ComponentStatus `json:"status"` + StartTimestamp int64 `json:"start_timestamp"` +} + // Store may be a TiKV store or TiFlash store. type StoreInfo struct { GitHash string `json:"git_hash"` diff --git a/pkg/utils/topology/scheduling.go b/pkg/utils/topology/scheduling.go new file mode 100644 index 0000000000..76cd786b43 --- /dev/null +++ b/pkg/utils/topology/scheduling.go @@ -0,0 +1,64 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package topology + +import ( + "context" + "encoding/json" + "sort" + + "github.com/pingcap/tidb-dashboard/pkg/pd" + "github.com/pingcap/tidb-dashboard/util/distro" + "github.com/pingcap/tidb-dashboard/util/netutil" +) + +func FetchSchedulingTopology(ctx context.Context, pdClient *pd.Client) ([]SchedulingInfo, error) { + nodes := make([]SchedulingInfo, 0) + data, err := pdClient.WithoutPrefix().SendGetRequest("/pd/api/v2/ms/members/scheduling") + if err != nil { + return nil, err + } + + ds := []struct { + ServiceAddr string `json:"service-addr"` + Version string `json:"version"` + GitHash string `json:"git-hash"` + DeployPath string `json:"deploy-path"` + StartTimestamp int64 `json:"start-timestamp"` + }{} + + err = json.Unmarshal(data, &ds) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "%s members API unmarshal failed", distro.R().Scheduling) + } + + for _, ds := range ds { + u := ds.ServiceAddr + hostname, port, err := netutil.ParseHostAndPortFromAddressURL(u) + if err != nil { + continue + } + + nodes = append(nodes, SchedulingInfo{ + GitHash: ds.GitHash, + Version: ds.Version, + IP: hostname, + Port: port, + DeployPath: ds.DeployPath, + Status: ComponentStatusUp, + StartTimestamp: ds.StartTimestamp, + }) + } + + sort.Slice(nodes, func(i, j int) bool { + if nodes[i].IP < nodes[j].IP { + return true + } + if nodes[i].IP > nodes[j].IP { + return false + } + return nodes[i].Port < nodes[j].Port + }) + + return nodes, nil +} diff --git a/pkg/utils/topology/tso.go b/pkg/utils/topology/tso.go new file mode 100644 index 0000000000..f5bafb7560 --- /dev/null +++ b/pkg/utils/topology/tso.go @@ -0,0 +1,64 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +package topology + +import ( + "context" + "encoding/json" + "sort" + + "github.com/pingcap/tidb-dashboard/pkg/pd" + "github.com/pingcap/tidb-dashboard/util/distro" + "github.com/pingcap/tidb-dashboard/util/netutil" +) + +func FetchTSOTopology(ctx context.Context, pdClient *pd.Client) ([]TSOInfo, error) { + nodes := make([]TSOInfo, 0) + data, err := pdClient.WithoutPrefix().SendGetRequest("/pd/api/v2/ms/members/tso") + if err != nil { + return nil, err + } + + ds := []struct { + ServiceAddr string `json:"service-addr"` + Version string `json:"version"` + GitHash string `json:"git-hash"` + DeployPath string `json:"deploy-path"` + StartTimestamp int64 `json:"start-timestamp"` + }{} + + err = json.Unmarshal(data, &ds) + if err != nil { + return nil, ErrInvalidTopologyData.Wrap(err, "%s members API unmarshal failed", distro.R().TSO) + } + + for _, ds := range ds { + u := ds.ServiceAddr + hostname, port, err := netutil.ParseHostAndPortFromAddressURL(u) + if err != nil { + continue + } + + nodes = append(nodes, TSOInfo{ + GitHash: ds.GitHash, + Version: ds.Version, + IP: hostname, + Port: port, + DeployPath: ds.DeployPath, + Status: ComponentStatusUp, + StartTimestamp: ds.StartTimestamp, + }) + } + + sort.Slice(nodes, func(i, j int) bool { + if nodes[i].IP < nodes[j].IP { + return true + } + if nodes[i].IP > nodes[j].IP { + return false + } + return nodes[i].Port < nodes[j].Port + }) + + return nodes, nil +} diff --git a/ui/packages/clinic-client/src/client/api/api.ts b/ui/packages/clinic-client/src/client/api/api.ts index 8c37de37c7..7942ac4d73 100644 --- a/ui/packages/clinic-client/src/client/api/api.ts +++ b/ui/packages/clinic-client/src/client/api/api.ts @@ -115,7 +115,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati } - + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -171,7 +171,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati } - + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; diff --git a/ui/packages/tidb-dashboard-client/src/client/api/api/default-api.ts b/ui/packages/tidb-dashboard-client/src/client/api/api/default-api.ts index e1ea55320f..2304912f1a 100644 --- a/ui/packages/tidb-dashboard-client/src/client/api/api/default-api.ts +++ b/ui/packages/tidb-dashboard-client/src/client/api/api/default-api.ts @@ -135,8 +135,12 @@ import { TopologyGrafanaInfo } from '../models'; // @ts-ignore import { TopologyPDInfo } from '../models'; // @ts-ignore +import { TopologySchedulingInfo } from '../models'; +// @ts-ignore import { TopologyStoreLocation } from '../models'; // @ts-ignore +import { TopologyTSOInfo } from '../models'; +// @ts-ignore import { TopologyTiCDCInfo } from '../models'; // @ts-ignore import { TopologyTiDBInfo } from '../models'; @@ -1482,6 +1486,39 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Get all Scheduling instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getSchedulingTopology: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/topology/scheduling`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication JwtAuth required + await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration) + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -1548,6 +1585,39 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Get all TSO instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTSOTopology: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/topology/tso`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication JwtAuth required + await setApiKeyToObject(localVarHeaderParameter, "Authorization", configuration) + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -4469,6 +4539,16 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getProfilingGroups(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary Get all Scheduling instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getSchedulingTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getSchedulingTopology(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Get location labels of all TiKV / TiFlash instances @@ -4489,6 +4569,16 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getStoreTopology(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary Get all TSO instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTSOTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTSOTopology(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Get all TiCDC instances @@ -5566,6 +5656,15 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa getProfilingGroups(options?: any): AxiosPromise> { return localVarFp.getProfilingGroups(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Get all Scheduling instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getSchedulingTopology(options?: any): AxiosPromise> { + return localVarFp.getSchedulingTopology(options).then((request) => request(axios, basePath)); + }, /** * * @summary Get location labels of all TiKV / TiFlash instances @@ -5584,6 +5683,15 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa getStoreTopology(options?: any): AxiosPromise { return localVarFp.getStoreTopology(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Get all TSO instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTSOTopology(options?: any): AxiosPromise> { + return localVarFp.getTSOTopology(options).then((request) => request(axios, basePath)); + }, /** * * @summary Get all TiCDC instances @@ -7911,6 +8019,17 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).getProfilingGroups(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Get all Scheduling instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getSchedulingTopology(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getSchedulingTopology(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Get location labels of all TiKV / TiFlash instances @@ -7933,6 +8052,17 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).getStoreTopology(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Get all TSO instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getTSOTopology(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getTSOTopology(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Get all TiCDC instances diff --git a/ui/packages/tidb-dashboard-client/src/client/api/api/statement-api.ts b/ui/packages/tidb-dashboard-client/src/client/api/api/statement-api.ts index a8ea359238..7cd6e528d9 100644 --- a/ui/packages/tidb-dashboard-client/src/client/api/api/statement-api.ts +++ b/ui/packages/tidb-dashboard-client/src/client/api/api/statement-api.ts @@ -31,7 +31,7 @@ import { StatementBinding } from '../models'; export const StatementApiAxiosParamCreator = function (configuration?: Configuration) { return { /** - * + * * @summary Drop all manually created bindings for a statement * @param {string} sqlDigest query template ID (a.k.a. sql digest) * @param {*} [options] Override http request option. @@ -60,7 +60,7 @@ export const StatementApiAxiosParamCreator = function (configuration?: Configura } - + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -71,7 +71,7 @@ export const StatementApiAxiosParamCreator = function (configuration?: Configura }; }, /** - * + * * @summary Get the bound plan digest (if exists) of a statement * @param {string} sqlDigest query template id * @param {number} beginTime begin time @@ -114,7 +114,7 @@ export const StatementApiAxiosParamCreator = function (configuration?: Configura } - + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -125,7 +125,7 @@ export const StatementApiAxiosParamCreator = function (configuration?: Configura }; }, /** - * + * * @summary Create a binding for a statement and a plan * @param {string} planDigest plan digest id * @param {*} [options] Override http request option. @@ -154,7 +154,7 @@ export const StatementApiAxiosParamCreator = function (configuration?: Configura } - + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -175,7 +175,7 @@ export const StatementApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = StatementApiAxiosParamCreator(configuration) return { /** - * + * * @summary Drop all manually created bindings for a statement * @param {string} sqlDigest query template ID (a.k.a. sql digest) * @param {*} [options] Override http request option. @@ -186,7 +186,7 @@ export const StatementApiFp = function(configuration?: Configuration) { return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** - * + * * @summary Get the bound plan digest (if exists) of a statement * @param {string} sqlDigest query template id * @param {number} beginTime begin time @@ -199,7 +199,7 @@ export const StatementApiFp = function(configuration?: Configuration) { return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** - * + * * @summary Create a binding for a statement and a plan * @param {string} planDigest plan digest id * @param {*} [options] Override http request option. @@ -220,7 +220,7 @@ export const StatementApiFactory = function (configuration?: Configuration, base const localVarFp = StatementApiFp(configuration) return { /** - * + * * @summary Drop all manually created bindings for a statement * @param {string} sqlDigest query template ID (a.k.a. sql digest) * @param {*} [options] Override http request option. @@ -230,7 +230,7 @@ export const StatementApiFactory = function (configuration?: Configuration, base return localVarFp.statementsPlanBindingDelete(sqlDigest, options).then((request) => request(axios, basePath)); }, /** - * + * * @summary Get the bound plan digest (if exists) of a statement * @param {string} sqlDigest query template id * @param {number} beginTime begin time @@ -242,7 +242,7 @@ export const StatementApiFactory = function (configuration?: Configuration, base return localVarFp.statementsPlanBindingGet(sqlDigest, beginTime, endTime, options).then((request) => request(axios, basePath)); }, /** - * + * * @summary Create a binding for a statement and a plan * @param {string} planDigest plan digest id * @param {*} [options] Override http request option. diff --git a/ui/packages/tidb-dashboard-client/src/client/api/models/index.ts b/ui/packages/tidb-dashboard-client/src/client/api/models/index.ts index daf063b7ba..38e5b0bf01 100644 --- a/ui/packages/tidb-dashboard-client/src/client/api/models/index.ts +++ b/ui/packages/tidb-dashboard-client/src/client/api/models/index.ts @@ -75,9 +75,11 @@ export * from './statement-model'; export * from './topology-alert-manager-info'; export * from './topology-grafana-info'; export * from './topology-pdinfo'; +export * from './topology-scheduling-info'; export * from './topology-store-info'; export * from './topology-store-labels'; export * from './topology-store-location'; +export * from './topology-tsoinfo'; export * from './topology-ti-cdcinfo'; export * from './topology-ti-dbinfo'; export * from './topology-ti-proxy-info'; diff --git a/ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-statistics.ts b/ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-statistics.ts index e21340d4b6..591b5452cc 100644 --- a/ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-statistics.ts +++ b/ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-statistics.ts @@ -26,6 +26,12 @@ export interface ModelRequestTargetStatistics { * @memberof ModelRequestTargetStatistics */ 'num_pd_nodes'?: number; + /** + * + * @type {number} + * @memberof ModelRequestTargetStatistics + */ + 'num_scheduling_nodes'?: number; /** * * @type {number} @@ -56,5 +62,11 @@ export interface ModelRequestTargetStatistics { * @memberof ModelRequestTargetStatistics */ 'num_tiproxy_nodes'?: number; + /** + * + * @type {number} + * @memberof ModelRequestTargetStatistics + */ + 'num_tso_nodes'?: number; } diff --git a/ui/packages/tidb-dashboard-client/src/client/api/models/topology-scheduling-info.ts b/ui/packages/tidb-dashboard-client/src/client/api/models/topology-scheduling-info.ts new file mode 100644 index 0000000000..f5611b54ab --- /dev/null +++ b/ui/packages/tidb-dashboard-client/src/client/api/models/topology-scheduling-info.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Dashboard API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface TopologySchedulingInfo + */ +export interface TopologySchedulingInfo { + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'deploy_path'?: string; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'git_hash'?: string; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'ip'?: string; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'port'?: number; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'start_timestamp'?: number; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'status'?: number; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'version'?: string; +} + diff --git a/ui/packages/tidb-dashboard-client/src/client/api/models/topology-tsoinfo.ts b/ui/packages/tidb-dashboard-client/src/client/api/models/topology-tsoinfo.ts new file mode 100644 index 0000000000..51d75ff45d --- /dev/null +++ b/ui/packages/tidb-dashboard-client/src/client/api/models/topology-tsoinfo.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Dashboard API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface TopologyTSOInfo + */ +export interface TopologyTSOInfo { + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'deploy_path'?: string; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'git_hash'?: string; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'ip'?: string; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'port'?: number; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'start_timestamp'?: number; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'status'?: number; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'version'?: string; +} + diff --git a/ui/packages/tidb-dashboard-client/swagger/spec.json b/ui/packages/tidb-dashboard-client/swagger/spec.json index d30a3d71fc..a3722345d3 100644 --- a/ui/packages/tidb-dashboard-client/swagger/spec.json +++ b/ui/packages/tidb-dashboard-client/swagger/spec.json @@ -3163,6 +3163,34 @@ } } }, + "/topology/scheduling": { + "get": { + "security": [ + { + "JwtAuth": [] + } + ], + "summary": "Get all Scheduling instances", + "operationId": "getSchedulingTopology", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/topology.SchedulingInfo" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, "/topology/store": { "get": { "security": [ @@ -3327,6 +3355,34 @@ } } }, + "/topology/tso": { + "get": { + "security": [ + { + "JwtAuth": [] + } + ], + "summary": "Get all TSO instances", + "operationId": "getTSOTopology", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/topology.TSOInfo" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/rest.ErrorResponse" + } + } + } + } + }, "/topsql/config": { "get": { "security": [ @@ -4764,6 +4820,9 @@ "num_pd_nodes": { "type": "integer" }, + "num_scheduling_nodes": { + "type": "integer" + }, "num_ticdc_nodes": { "type": "integer" }, @@ -4778,6 +4837,9 @@ }, "num_tiproxy_nodes": { "type": "integer" + }, + "num_tso_nodes": { + "type": "integer" } } }, @@ -5703,6 +5765,32 @@ } } }, + "topology.SchedulingInfo": { + "type": "object", + "properties": { + "deploy_path": { + "type": "string" + }, + "git_hash": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "start_timestamp": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "version": { + "type": "string" + } + } + }, "topology.StoreInfo": { "type": "object", "properties": { @@ -5769,6 +5857,32 @@ } } }, + "topology.TSOInfo": { + "type": "object", + "properties": { + "deploy_path": { + "type": "string" + }, + "git_hash": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "start_timestamp": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "version": { + "type": "string" + } + } + }, "topology.TiCDCInfo": { "type": "object", "properties": { diff --git a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/context.ts b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/context.ts index d1a864b288..1255de4d3a 100644 --- a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/context.ts +++ b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/context.ts @@ -35,6 +35,14 @@ class DataSource implements IClusterInfoDataSource { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } + topologyTidbAddressDelete(address: string, options?: ReqConfig) { return client.getInstance().topologyTidbAddressDelete({ address }, options) } diff --git a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/context.ts b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/context.ts index 408d6881fa..8f3dbbbf8b 100644 --- a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/context.ts +++ b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/context.ts @@ -40,6 +40,12 @@ class DataSource implements IInstanceProfilingDataSource { getTiProxyTopology(options?: ReqConfig) { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } } const ds = new DataSource() diff --git a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/context.ts b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/context.ts index 0f54dd710e..fcd4b5653a 100644 --- a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/context.ts +++ b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/context.ts @@ -29,6 +29,14 @@ class DataSource implements IOverviewDataSource { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } + metricsQueryGet(params: { endTimeSec?: number query?: string diff --git a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/context.ts b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/context.ts index 3d41bbf054..8d1809821d 100644 --- a/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/context.ts +++ b/ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/context.ts @@ -61,6 +61,12 @@ class DataSource implements ISearchLogsDataSource { getTiProxyTopology(options?: ReqConfig) { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } } const ds = new DataSource() diff --git a/ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/context.ts b/ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/context.ts index d1a864b288..1255de4d3a 100644 --- a/ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/context.ts +++ b/ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/context.ts @@ -35,6 +35,14 @@ class DataSource implements IClusterInfoDataSource { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } + topologyTidbAddressDelete(address: string, options?: ReqConfig) { return client.getInstance().topologyTidbAddressDelete({ address }, options) } diff --git a/ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/context.ts b/ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/context.ts index 408d6881fa..8f3dbbbf8b 100644 --- a/ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/context.ts +++ b/ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/context.ts @@ -40,6 +40,12 @@ class DataSource implements IInstanceProfilingDataSource { getTiProxyTopology(options?: ReqConfig) { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } } const ds = new DataSource() diff --git a/ui/packages/tidb-dashboard-for-op/src/apps/Overview/context.ts b/ui/packages/tidb-dashboard-for-op/src/apps/Overview/context.ts index e615f04f08..cf24c0b3ff 100644 --- a/ui/packages/tidb-dashboard-for-op/src/apps/Overview/context.ts +++ b/ui/packages/tidb-dashboard-for-op/src/apps/Overview/context.ts @@ -28,6 +28,14 @@ class DataSource implements IOverviewDataSource { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } + metricsQueryGet(params: { endTimeSec?: number query?: string diff --git a/ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/context.ts b/ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/context.ts index 3d41bbf054..8d1809821d 100644 --- a/ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/context.ts +++ b/ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/context.ts @@ -61,6 +61,12 @@ class DataSource implements ISearchLogsDataSource { getTiProxyTopology(options?: ReqConfig) { return client.getInstance().getTiProxyTopology(options) } + getTSOTopology(options?: ReqConfig) { + return client.getInstance().getTSOTopology(options) + } + getSchedulingTopology(options?: ReqConfig) { + return client.getInstance().getSchedulingTopology(options) + } } const ds = new DataSource() diff --git a/ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json b/ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json index 3a1a0ea2d9..5a1dcdbff4 100644 --- a/ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json +++ b/ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json @@ -1 +1 @@ -{"tidb":"TiDB","tikv":"TiKV","pd":"PD","tiflash":"TiFlash","ticdc":"TiCDC","tiproxy":"TiProxy"} \ No newline at end of file +{"tidb":"TiDB","tikv":"TiKV","pd":"PD","tiflash":"TiFlash","ticdc":"TiCDC","tiproxy":"TiProxy","tso":"TSO","scheduling":"Scheduling"} \ No newline at end of file diff --git a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/DiskTable.tsx b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/DiskTable.tsx index ab4677f152..d6637cd57e 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/DiskTable.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/DiskTable.tsx @@ -43,7 +43,9 @@ function expandDisksItems(rows: HostinfoInfo[]): IExpandedDiskItem[] { tikv: 0, tiflash: 0, ticdc: 0, - tiproxy: 0 + tiproxy: 0, + tso: 0, + scheduling: 0 } } instancesPerPartition[i.partition_path_lower!][i.type!]++ @@ -76,7 +78,9 @@ function expandDisksItems(rows: HostinfoInfo[]): IExpandedDiskItem[] { tikv: 0, tiflash: 0, ticdc: 0, - tiproxy: 0 + tiproxy: 0, + tso: 0, + scheduling: 0 } }) } diff --git a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/HostTable.tsx b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/HostTable.tsx index b05ff60010..b7fb4a5f27 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/HostTable.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/HostTable.tsx @@ -29,7 +29,9 @@ function expandHostItems(rows: HostinfoInfo[]): IExpandedHostItem[] { tikv: 0, tiflash: 0, ticdc: 0, - tiproxy: 0 + tiproxy: 0, + tso: 0, + scheduling: 0 } Object.values(row.instances ?? {}).forEach((i) => { diff --git a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/InstanceTable.tsx b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/InstanceTable.tsx index b62821244d..e0389464c1 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/InstanceTable.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/InstanceTable.tsx @@ -91,11 +91,29 @@ export default function ListPage() { error: errTiProxy } = useClientRequest(ctx!.ds.getTiProxyTopology) + const { + data: dataTSO, + isLoading: loadingTSO, + error: errTSO + } = useClientRequest(ctx!.ds.getTSOTopology) + + const { + data: dataScheduling, + isLoading: loadingScheduling, + error: errScheduling + } = useClientRequest(ctx!.ds.getSchedulingTopology) + // query TiCDC and TiProxy components returns 404 under TiDB 7.6.0 // filter out the 404 error - const errors = [errTiDB, errStores, errPD, errTiCDC, errTiProxy].filter( - (e) => e?.response?.status !== 404 - ) + const errors = [ + errTiDB, + errStores, + errPD, + errTiCDC, + errTiProxy, + errTSO, + errScheduling + ].filter((e) => e?.response?.status !== 404) const [tableData, groupData] = useMemo( () => @@ -106,9 +124,19 @@ export default function ListPage() { dataTiFlash: dataStores?.tiflash, dataTiCDC, dataTiProxy, + dataTSO, + dataScheduling, includeTiFlash: true }), - [dataTiDB, dataStores, dataPD, dataTiCDC, dataTiProxy] + [ + dataTiDB, + dataStores, + dataPD, + dataTiCDC, + dataTiProxy, + dataTSO, + dataScheduling + ] ) const handleHideTiDB = useCallback( @@ -204,7 +232,9 @@ export default function ListPage() { loadingStores || loadingPD || loadingTiCDC || - loadingTiProxy + loadingTiProxy || + loadingTSO || + loadingScheduling } columns={columns} items={tableData} diff --git a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/context/index.ts b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/context/index.ts index f4c6a15e55..71ba06ce41 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/context/index.ts +++ b/ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/context/index.ts @@ -10,7 +10,9 @@ import { TopologyPDInfo, ClusterinfoClusterStatistics, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' import { IContextConfig, ReqConfig } from '@lib/types' @@ -38,6 +40,12 @@ export interface IClusterInfoDataSource { options?: ReqConfig ): AxiosPromise> + getTSOTopology(options?: ReqConfig): AxiosPromise> + + getSchedulingTopology( + options?: ReqConfig + ): AxiosPromise> + topologyTidbAddressDelete( address: string, options?: ReqConfig diff --git a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/context/index.ts b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/context/index.ts index 44a00f77b3..f73c52536f 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/context/index.ts +++ b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/context/index.ts @@ -11,7 +11,9 @@ import { ClusterinfoStoreTopologyResponse, TopologyPDInfo, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' import { IContextConfig, ReqConfig } from '@lib/types' @@ -54,6 +56,12 @@ export interface IInstanceProfilingDataSource { getTiProxyTopology( options?: ReqConfig ): AxiosPromise> + + getTSOTopology(options?: ReqConfig): AxiosPromise> + + getSchedulingTopology( + options?: ReqConfig + ): AxiosPromise> } export interface IInstanceProfilingContext { diff --git a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.tsx b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.tsx index f7838bb2df..863091b29f 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.tsx @@ -76,6 +76,8 @@ export default function Page() { let port switch (instance.instanceKind) { case 'pd': + case 'tso': + case 'scheduling': port = instance.port break case 'tidb': @@ -127,7 +129,7 @@ export default function Page() { name: t('instance_profiling.list.table.columns.targets'), key: 'targets', minWidth: 300, - maxWidth: 400, + maxWidth: 480, onRender: (rec) => { return combineTargetStats(rec.target_stats) } @@ -238,6 +240,8 @@ export default function Page() { getPDTopology={ctx!.ds.getPDTopology} getTiCDCTopology={ctx!.ds.getTiCDCTopology} getTiProxyTopology={ctx!.ds.getTiProxyTopology} + getTSOTopology={ctx!.ds.getTSOTopology} + getSchedulingTopology={ctx!.ds.getSchedulingTopology} /> diff --git a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/combineTargetStats.ts b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/combineTargetStats.ts index d88de2bcf5..194688afa6 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/combineTargetStats.ts +++ b/ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/combineTargetStats.ts @@ -7,7 +7,9 @@ const targetNameMap = { num_pd_nodes: () => instanceKindName('pd'), num_tiflash_nodes: () => instanceKindName('tiflash'), num_ticdc_nodes: () => instanceKindName('ticdc'), - num_tiproxy_nodes: () => instanceKindName('tiproxy') + num_tiproxy_nodes: () => instanceKindName('tiproxy'), + num_tso_nodes: () => instanceKindName('tso'), + num_scheduling_nodes: () => instanceKindName('scheduling') } export const combineTargetStats = (stats: ModelRequestTargetStatistics) => diff --git a/ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Instances.tsx b/ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Instances.tsx index 0b42209efe..b4b778a10f 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Instances.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Instances.tsx @@ -82,6 +82,8 @@ export default function Nodes() { const pdResp = useClientRequest(ctx!.ds.getPDTopology) const tiCDCResp = useClientRequest(ctx!.ds.getTiCDCTopology) const tiProxyResp = useClientRequest(ctx!.ds.getTiProxyTopology) + const tsoResp = useClientRequest(ctx!.ds.getTSOTopology) + const schedulingResp = useClientRequest(ctx!.ds.getSchedulingTopology) return ( + + + + + + + + ) diff --git a/ui/packages/tidb-dashboard-lib/src/apps/Overview/context/index.ts b/ui/packages/tidb-dashboard-lib/src/apps/Overview/context/index.ts index 5be2ac0b7c..c0637abd35 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/Overview/context/index.ts +++ b/ui/packages/tidb-dashboard-lib/src/apps/Overview/context/index.ts @@ -10,7 +10,9 @@ import { ClusterinfoStoreTopologyResponse, MetricsQueryResponse, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' import { IContextConfig, ReqConfig } from '@lib/types' @@ -47,6 +49,12 @@ export interface IOverviewDataSource { options?: ReqConfig ): AxiosPromise> + getTSOTopology(options?: ReqConfig): AxiosPromise> + + getSchedulingTopology( + options?: ReqConfig + ): AxiosPromise> + getGrafanaTopology(options?: ReqConfig): AxiosPromise getAlertManagerTopology( diff --git a/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchHeader.tsx b/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchHeader.tsx index be27eeab89..165412f01e 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchHeader.tsx +++ b/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchHeader.tsx @@ -92,6 +92,8 @@ export default function SearchHeader({ taskGroupID }: Props) { case 'tikv': case 'tiflash': case 'ticdc': + case 'tso': + case 'scheduling': port = instance.port break case 'tidb': @@ -173,6 +175,8 @@ export default function SearchHeader({ taskGroupID }: Props) { getPDTopology={ctx!.ds.getPDTopology} getTiCDCTopology={ctx!.ds.getTiCDCTopology} getTiProxyTopology={ctx!.ds.getTiProxyTopology} + getTSOTopology={ctx!.ds.getTSOTopology} + getSchedulingTopology={ctx!.ds.getSchedulingTopology} /> diff --git a/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/context/index.ts b/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/context/index.ts index 03d2b86c18..888d5aedc3 100644 --- a/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/context/index.ts +++ b/ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/context/index.ts @@ -11,7 +11,9 @@ import { ClusterinfoStoreTopologyResponse, TopologyPDInfo, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' import { IContextConfig, ReqConfig } from '@lib/types' @@ -68,6 +70,12 @@ export interface ISearchLogsDataSource { getTiProxyTopology( options?: ReqConfig ): AxiosPromise> + + getTSOTopology(options?: ReqConfig): AxiosPromise> + + getSchedulingTopology( + options?: ReqConfig + ): AxiosPromise> } export interface ISearchLogsContext { diff --git a/ui/packages/tidb-dashboard-lib/src/client/models.ts b/ui/packages/tidb-dashboard-lib/src/client/models.ts index 9c5acf4d5a..64c41d53dd 100644 --- a/ui/packages/tidb-dashboard-lib/src/client/models.ts +++ b/ui/packages/tidb-dashboard-lib/src/client/models.ts @@ -1744,6 +1744,12 @@ export interface ModelRequestTargetStatistics { * @memberof ModelRequestTargetStatistics */ 'num_pd_nodes'?: number; + /** + * + * @type {number} + * @memberof ModelRequestTargetStatistics + */ + 'num_scheduling_nodes'?: number; /** * * @type {number} @@ -1774,6 +1780,12 @@ export interface ModelRequestTargetStatistics { * @memberof ModelRequestTargetStatistics */ 'num_tiproxy_nodes'?: number; + /** + * + * @type {number} + * @memberof ModelRequestTargetStatistics + */ + 'num_tso_nodes'?: number; } @@ -3496,6 +3508,59 @@ export interface TopologyPDInfo { +/** + * + * @export + * @interface TopologySchedulingInfo + */ +export interface TopologySchedulingInfo { + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'deploy_path'?: string; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'git_hash'?: string; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'ip'?: string; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'port'?: number; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'start_timestamp'?: number; + /** + * + * @type {number} + * @memberof TopologySchedulingInfo + */ + 'status'?: number; + /** + * + * @type {string} + * @memberof TopologySchedulingInfo + */ + 'version'?: string; +} + + + + /** * * @export @@ -3790,6 +3855,59 @@ export interface TopologyTiProxyInfo { +/** + * + * @export + * @interface TopologyTSOInfo + */ +export interface TopologyTSOInfo { + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'deploy_path'?: string; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'git_hash'?: string; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'ip'?: string; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'port'?: number; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'start_timestamp'?: number; + /** + * + * @type {number} + * @memberof TopologyTSOInfo + */ + 'status'?: number; + /** + * + * @type {string} + * @memberof TopologyTSOInfo + */ + 'version'?: string; +} + + + + /** * * @export diff --git a/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/ValueDisplay.tsx b/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/ValueDisplay.tsx index a00114d196..4f854d487a 100644 --- a/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/ValueDisplay.tsx +++ b/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/ValueDisplay.tsx @@ -38,7 +38,9 @@ export default function ValueDisplay({ tikv: newInstanceStat(), tiflash: newInstanceStat(), ticdc: newInstanceStat(), - tiproxy: newInstanceStat() + tiproxy: newInstanceStat(), + tso: newInstanceStat(), + scheduling: newInstanceStat() } items.forEach((item) => { instanceStats[item.instanceKind].all++ diff --git a/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/index.tsx b/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/index.tsx index a15871ca5b..0330f9aaaf 100644 --- a/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/index.tsx +++ b/ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/index.tsx @@ -28,7 +28,9 @@ import { ClusterinfoStoreTopologyResponse, TopologyPDInfo, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' import { ReqConfig } from '@lib/types' @@ -53,6 +55,10 @@ export interface IInstanceSelectProps getTiProxyTopology?: ( options?: ReqConfig ) => AxiosPromise> + getTSOTopology?: (options?: ReqConfig) => AxiosPromise> + getSchedulingTopology?: ( + options?: ReqConfig + ) => AxiosPromise> } export interface IInstanceSelectRefProps { @@ -118,6 +124,8 @@ function InstanceSelect( getStoreTopology, getTiCDCTopology, getTiProxyTopology, + getTSOTopology, + getSchedulingTopology, ...restProps } = props @@ -132,6 +140,10 @@ function InstanceSelect( useClientRequest(getTiCDCTopology) const { data: dataTiProxy, isLoading: loadingTiProxy } = useClientRequest(getTiProxyTopology) + const { data: dataTSO, isLoading: loadingTSO } = + useClientRequest(getTSOTopology) + const { data: dataScheduling, isLoading: loadingScheduling } = + useClientRequest(getSchedulingTopology) const columns: IColumn[] = useMemo( () => [ @@ -173,7 +185,9 @@ function InstanceSelect( loadingStores || loadingPD || loadingTiCDC || - loadingTiProxy + loadingTiProxy || + loadingTSO || + loadingScheduling ) { return [[], []] } @@ -184,6 +198,8 @@ function InstanceSelect( dataTiFlash: dataStores?.tiflash, dataTiCDC, dataTiProxy, + dataTSO, + dataScheduling, includeTiFlash: enableTiFlash }) }, [ @@ -193,11 +209,15 @@ function InstanceSelect( dataPD, dataTiCDC, dataTiProxy, + dataTSO, + dataScheduling, loadingTiDB, loadingStores, loadingPD, loadingTiCDC, - loadingTiProxy + loadingTiProxy, + loadingTSO, + loadingScheduling ]) const selection = useRef( diff --git a/ui/packages/tidb-dashboard-lib/src/utils/instanceTable.ts b/ui/packages/tidb-dashboard-lib/src/utils/instanceTable.ts index 1108d42990..e58223f9fc 100644 --- a/ui/packages/tidb-dashboard-lib/src/utils/instanceTable.ts +++ b/ui/packages/tidb-dashboard-lib/src/utils/instanceTable.ts @@ -7,7 +7,9 @@ import { TopologyTiDBInfo, TopologyStoreInfo, TopologyTiCDCInfo, - TopologyTiProxyInfo + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo } from '@lib/client' export const InstanceKinds = [ @@ -16,7 +18,9 @@ export const InstanceKinds = [ 'tikv', 'tiflash', 'ticdc', - 'tiproxy' + 'tiproxy', + 'tso', + 'scheduling' ] as const export type InstanceKind = typeof InstanceKinds[number] @@ -37,7 +41,9 @@ export interface IInstanceTableItem TopologyTiDBInfo, TopologyStoreInfo, TopologyTiCDCInfo, - TopologyTiProxyInfo { + TopologyTiProxyInfo, + TopologyTSOInfo, + TopologySchedulingInfo { key: string instanceKind: InstanceKind } @@ -49,6 +55,8 @@ export interface IBuildInstanceTableProps { dataTiFlash?: TopologyStoreInfo[] dataTiCDC?: TopologyTiCDCInfo[] dataTiProxy?: TopologyTiProxyInfo[] + dataTSO?: TopologyTSOInfo[] + dataScheduling?: TopologySchedulingInfo[] includeTiFlash?: boolean } @@ -59,6 +67,8 @@ export function buildInstanceTable({ dataTiFlash, dataTiCDC, dataTiProxy, + dataTSO, + dataScheduling, includeTiFlash }: IBuildInstanceTableProps): [IInstanceTableItem[], IGroup[]] { const tableData: IInstanceTableItem[] = [] @@ -72,6 +82,8 @@ export function buildInstanceTable({ | TopologyStoreInfo[] | TopologyTiCDCInfo[] | TopologyTiProxyInfo[] + | TopologyTSOInfo[] + | TopologySchedulingInfo[] | undefined } = {} kinds.pd = dataPD @@ -79,6 +91,8 @@ export function buildInstanceTable({ kinds.tikv = dataTiKV kinds.ticdc = dataTiCDC kinds.tiproxy = dataTiProxy + kinds.tso = dataTSO + kinds.scheduling = dataScheduling if (includeTiFlash) { kinds.tiflash = dataTiFlash } diff --git a/util/client/schedulingclient/status_client.go b/util/client/schedulingclient/status_client.go new file mode 100644 index 0000000000..721f116dde --- /dev/null +++ b/util/client/schedulingclient/status_client.go @@ -0,0 +1,22 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +// Package schedulingclient provides a flexible Scheduling API access to any Scheduling instance. +package schedulingclient + +import ( + "github.com/pingcap/tidb-dashboard/util/client/httpclient" + "github.com/pingcap/tidb-dashboard/util/distro" +) + +type StatusClient struct { + *httpclient.Client +} + +func NewStatusClient(config httpclient.Config) *StatusClient { + config.KindTag = distro.R().Scheduling + return &StatusClient{httpclient.New(config)} +} + +func (c *StatusClient) Clone() *StatusClient { + return &StatusClient{c.Client.Clone()} +} diff --git a/util/client/tsoclient/status_client.go b/util/client/tsoclient/status_client.go new file mode 100644 index 0000000000..a93f152417 --- /dev/null +++ b/util/client/tsoclient/status_client.go @@ -0,0 +1,22 @@ +// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0. + +// Package tsoclient provides a flexible TSO API access to any TSO instance. +package tsoclient + +import ( + "github.com/pingcap/tidb-dashboard/util/client/httpclient" + "github.com/pingcap/tidb-dashboard/util/distro" +) + +type StatusClient struct { + *httpclient.Client +} + +func NewStatusClient(config httpclient.Config) *StatusClient { + config.KindTag = distro.R().TSO + return &StatusClient{httpclient.New(config)} +} + +func (c *StatusClient) Clone() *StatusClient { + return &StatusClient{c.Client.Clone()} +} diff --git a/util/distro/distro.go b/util/distro/distro.go index a95143ef49..d2563da17c 100644 --- a/util/distro/distro.go +++ b/util/distro/distro.go @@ -17,23 +17,27 @@ import ( ) type DistributionResource struct { - IsDistro bool `json:"is_distro,omitempty"` - TiDB string `json:"tidb,omitempty"` - TiKV string `json:"tikv,omitempty"` - PD string `json:"pd,omitempty"` - TiFlash string `json:"tiflash,omitempty"` - TiCDC string `json:"ticdc,omitempty"` - TiProxy string `json:"tiproxy,omitempty"` + IsDistro bool `json:"is_distro,omitempty"` + TiDB string `json:"tidb,omitempty"` + TiKV string `json:"tikv,omitempty"` + PD string `json:"pd,omitempty"` + TiFlash string `json:"tiflash,omitempty"` + TiCDC string `json:"ticdc,omitempty"` + TiProxy string `json:"tiproxy,omitempty"` + TSO string `json:"tso,omitempty"` + Scheduling string `json:"scheduling,omitempty"` } var defaultDistroRes = DistributionResource{ - IsDistro: false, - TiDB: "TiDB", - TiKV: "TiKV", - PD: "PD", - TiFlash: "TiFlash", - TiCDC: "TiCDC", - TiProxy: "TiProxy", + IsDistro: false, + TiDB: "TiDB", + TiKV: "TiKV", + PD: "PD", + TiFlash: "TiFlash", + TiCDC: "TiCDC", + TiProxy: "TiProxy", + TSO: "TSO", + Scheduling: "Scheduling", } var ( diff --git a/util/topo/model.go b/util/topo/model.go index 307d3a690f..1dc995497d 100644 --- a/util/topo/model.go +++ b/util/topo/model.go @@ -22,6 +22,8 @@ const ( KindTiFlash Kind = "tiflash" KindTiCDC Kind = "ticdc" KindTiProxy Kind = "tiproxy" + KindTSO Kind = "tso" + KindScheduling Kind = "scheduling" KindAlertManager Kind = "alert_manager" KindGrafana Kind = "grafana" KindPrometheus Kind = "prometheus" @@ -177,6 +179,54 @@ func (i *TiProxyInfo) Info() CompInfo { } } +type TSOInfo struct { + GitHash string + Version string + IP string + Port uint + DeployPath string + Status CompStatus + StartTimestamp int64 +} + +var _ Info = &TSOInfo{} + +func (i *TSOInfo) Info() CompInfo { + return CompInfo{ + CompDescriptor: CompDescriptor{ + IP: i.IP, + Port: i.Port, + Kind: KindTSO, + }, + Version: i.Version, + Status: i.Status, + } +} + +type SchedulingInfo struct { + GitHash string + Version string + IP string + Port uint + DeployPath string + Status CompStatus + StartTimestamp int64 +} + +var _ Info = &SchedulingInfo{} + +func (i *SchedulingInfo) Info() CompInfo { + return CompInfo{ + CompDescriptor: CompDescriptor{ + IP: i.IP, + Port: i.Port, + Kind: KindScheduling, + }, + Version: i.Version, + Status: i.Status, + } +} + type StandardDeployInfo struct { IP string Port uint