1515package apis
1616
1717import (
18+ "fmt"
1819 "net/http"
1920 "strconv"
2021 "sync"
@@ -26,11 +27,12 @@ import (
2627 "github.com/joho/godotenv"
2728 "github.com/pingcap/log"
2829 scheserver "github.com/tikv/pd/pkg/mcs/scheduling/server"
29- "github.com/tikv/pd/pkg/mcs/utils"
30- "github.com/tikv/pd/pkg/schedule"
30+ mcsutils "github.com/tikv/pd/pkg/mcs/utils"
3131 sche "github.com/tikv/pd/pkg/schedule/core"
3232 "github.com/tikv/pd/pkg/schedule/handler"
3333 "github.com/tikv/pd/pkg/schedule/operator"
34+ "github.com/tikv/pd/pkg/statistics/utils"
35+ "github.com/tikv/pd/pkg/storage"
3436 "github.com/tikv/pd/pkg/utils/apiutil"
3537 "github.com/tikv/pd/pkg/utils/apiutil/multiservicesapi"
3638 "github.com/tikv/pd/pkg/utils/logutil"
@@ -68,15 +70,11 @@ type Service struct {
6870}
6971
7072type server struct {
71- server * scheserver.Server
73+ * scheserver.Server
7274}
7375
74- func (s * server ) GetCoordinator () * schedule.Coordinator {
75- return s .server .GetCoordinator ()
76- }
77-
78- func (s * server ) GetCluster () sche.SharedCluster {
79- return s .server .GetCluster ()
76+ func (s * server ) GetCluster () sche.SchedulerCluster {
77+ return s .Server .GetCluster ()
8078}
8179
8280func createIndentRender () * render.Render {
@@ -98,11 +96,11 @@ func NewService(srv *scheserver.Service) *Service {
9896 apiHandlerEngine .Use (gzip .Gzip (gzip .DefaultCompression ))
9997 apiHandlerEngine .Use (func (c * gin.Context ) {
10098 c .Set (multiservicesapi .ServiceContextKey , srv .Server )
101- c .Set (handlerKey , handler .NewHandler (& server {server : srv .Server }))
99+ c .Set (handlerKey , handler .NewHandler (& server {srv .Server }))
102100 c .Next ()
103101 })
104102 apiHandlerEngine .Use (multiservicesapi .ServiceRedirector ())
105- apiHandlerEngine .GET ("metrics" , utils .PromHandler ())
103+ apiHandlerEngine .GET ("metrics" , mcsutils .PromHandler ())
106104 pprof .Register (apiHandlerEngine )
107105 root := apiHandlerEngine .Group (APIPathPrefix )
108106 s := & Service {
@@ -115,6 +113,7 @@ func NewService(srv *scheserver.Service) *Service {
115113 s .RegisterOperatorsRouter ()
116114 s .RegisterSchedulersRouter ()
117115 s .RegisterCheckersRouter ()
116+ s .RegisterHotspotRouter ()
118117 return s
119118}
120119
@@ -141,6 +140,16 @@ func (s *Service) RegisterCheckersRouter() {
141140 router .POST ("/:name" , pauseOrResumeChecker )
142141}
143142
143+ // RegisterHotspotRouter registers the router of the hotspot handler.
144+ func (s * Service ) RegisterHotspotRouter () {
145+ router := s .root .Group ("hotspot" )
146+ router .GET ("/regions/write" , getHotWriteRegions )
147+ router .GET ("/regions/read" , getHotReadRegions )
148+ router .GET ("/regions/history" , getHistoryHotRegions )
149+ router .GET ("/stores" , getHotStores )
150+ router .GET ("/buckets" , getHotBuckets )
151+ }
152+
144153// RegisterOperatorsRouter registers the router of the operators handler.
145154func (s * Service ) RegisterOperatorsRouter () {
146155 router := s .root .Group ("operators" )
@@ -425,3 +434,117 @@ func pauseOrResumeScheduler(c *gin.Context) {
425434 }
426435 c .String (http .StatusOK , "Pause or resume the scheduler successfully." )
427436}
437+
438+ // @Tags hotspot
439+ // @Summary List the hot write regions.
440+ // @Produce json
441+ // @Success 200 {object} statistics.StoreHotPeersInfos
442+ // @Failure 400 {string} string "The request is invalid."
443+ // @Failure 500 {string} string "PD server failed to proceed the request."
444+ // @Router /hotspot/regions/write [get]
445+ func getHotWriteRegions (c * gin.Context ) {
446+ getHotRegions (utils .Write , c )
447+ }
448+
449+ // @Tags hotspot
450+ // @Summary List the hot read regions.
451+ // @Produce json
452+ // @Success 200 {object} statistics.StoreHotPeersInfos
453+ // @Failure 400 {string} string "The request is invalid."
454+ // @Failure 500 {string} string "PD server failed to proceed the request."
455+ // @Router /hotspot/regions/read [get]
456+ func getHotReadRegions (c * gin.Context ) {
457+ getHotRegions (utils .Read , c )
458+ }
459+
460+ func getHotRegions (typ utils.RWType , c * gin.Context ) {
461+ handler := c .MustGet (handlerKey ).(* handler.Handler )
462+
463+ storeIDs := c .QueryArray ("store_id" )
464+ if len (storeIDs ) < 1 {
465+ hotRegions , err := handler .GetHotRegions (typ )
466+ if err != nil {
467+ c .String (http .StatusInternalServerError , err .Error ())
468+ return
469+ }
470+ c .IndentedJSON (http .StatusOK , hotRegions )
471+ return
472+ }
473+
474+ var ids []uint64
475+ for _ , storeID := range storeIDs {
476+ id , err := strconv .ParseUint (storeID , 10 , 64 )
477+ if err != nil {
478+ c .String (http .StatusBadRequest , fmt .Sprintf ("invalid store id: %s" , storeID ))
479+ return
480+ }
481+ _ , err = handler .GetStore (id )
482+ if err != nil {
483+ c .String (http .StatusInternalServerError , err .Error ())
484+ return
485+ }
486+ ids = append (ids , id )
487+ }
488+
489+ hotRegions , err := handler .GetHotRegions (typ , ids ... )
490+ if err != nil {
491+ c .String (http .StatusInternalServerError , err .Error ())
492+ return
493+ }
494+ c .IndentedJSON (http .StatusOK , hotRegions )
495+ }
496+
497+ // @Tags hotspot
498+ // @Summary List the hot stores.
499+ // @Produce json
500+ // @Success 200 {object} handler.HotStoreStats
501+ // @Failure 500 {string} string "PD server failed to proceed the request."
502+ // @Router /hotspot/stores [get]
503+ func getHotStores (c * gin.Context ) {
504+ handler := c .MustGet (handlerKey ).(* handler.Handler )
505+ stores , err := handler .GetHotStores ()
506+ if err != nil {
507+ c .String (http .StatusInternalServerError , err .Error ())
508+ return
509+ }
510+ c .IndentedJSON (http .StatusOK , stores )
511+ }
512+
513+ // @Tags hotspot
514+ // @Summary List the hot buckets.
515+ // @Produce json
516+ // @Success 200 {object} handler.HotBucketsResponse
517+ // @Failure 500 {string} string "PD server failed to proceed the request."
518+ // @Router /hotspot/buckets [get]
519+ func getHotBuckets (c * gin.Context ) {
520+ handler := c .MustGet (handlerKey ).(* handler.Handler )
521+
522+ regionIDs := c .QueryArray ("region_id" )
523+ ids := make ([]uint64 , len (regionIDs ))
524+ for i , regionID := range regionIDs {
525+ if id , err := strconv .ParseUint (regionID , 10 , 64 ); err == nil {
526+ ids [i ] = id
527+ }
528+ }
529+ ret , err := handler .GetHotBuckets (ids ... )
530+ if err != nil {
531+ c .String (http .StatusInternalServerError , err .Error ())
532+ return
533+ }
534+ c .IndentedJSON (http .StatusOK , ret )
535+ }
536+
537+ // @Tags hotspot
538+ // @Summary List the history hot regions.
539+ // @Accept json
540+ // @Produce json
541+ // @Success 200 {object} storage.HistoryHotRegions
542+ // @Failure 400 {string} string "The input is invalid."
543+ // @Failure 500 {string} string "PD server failed to proceed the request."
544+ // @Router /hotspot/regions/history [get]
545+ func getHistoryHotRegions (c * gin.Context ) {
546+ // TODO: support history hotspot in scheduling server with stateless in the future.
547+ // Ref: https://github.com/tikv/pd/pull/7183
548+ var res storage.HistoryHotRegions
549+ c .IndentedJSON (http .StatusOK , res )
550+ }
0 commit comments