From 2411352d26fb15725490391ebbcfc69ee26ad9b7 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Thu, 12 Nov 2020 12:07:54 +0800 Subject: [PATCH] server: Add table range api to http status server (#20456) (#20620) Signed-off-by: Xiaoguang Sun --- server/http_handler.go | 82 +++++++++++++++++++++++++++++++++---- server/http_handler_test.go | 40 ++++++++++++++++++ server/http_status.go | 1 + 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/server/http_handler.go b/server/http_handler.go index c99a8afd4ce94..fb7875a481665 100644 --- a/server/http_handler.go +++ b/server/http_handler.go @@ -417,6 +417,7 @@ type valueHandler struct { const ( opTableRegions = "regions" + opTableRanges = "ranges" opTableDiskUsage = "disk-usage" opTableScatter = "scatter-table" opStopTableScatter = "stop-scatter-table" @@ -521,6 +522,34 @@ type TableRegions struct { Indices []IndexRegions `json:"indices"` } +// RangeDetail contains detail information about a particular range +type RangeDetail struct { + StartKey []byte `json:"start_key"` + EndKey []byte `json:"end_key"` + StartKeyHex string `json:"start_key_hex"` + EndKeyHex string `json:"end_key_hex"` +} + +func createRangeDetail(start, end []byte) RangeDetail { + return RangeDetail{ + StartKey: start, + EndKey: end, + StartKeyHex: hex.EncodeToString(start), + EndKeyHex: hex.EncodeToString(end), + } +} + +// TableRanges is the response data for list table's ranges. +// It contains ranges list for record and indices as well as the whole table. +type TableRanges struct { + TableName string `json:"name"` + TableID int64 `json:"id"` + Range RangeDetail `json:"table"` + Record RangeDetail `json:"record"` + Index RangeDetail `json:"index"` + Indices map[string]RangeDetail `json:"indices,omitempty"` +} + // RegionMeta contains a region's peer detail type RegionMeta struct { ID uint64 `json:"region_id"` @@ -539,10 +568,9 @@ type IndexRegions struct { // RegionDetail is the response data for get region by ID // it includes indices and records detail in current region. type RegionDetail struct { - RegionID uint64 `json:"region_id"` - StartKey []byte `json:"start_key"` - EndKey []byte `json:"end_key"` - Frames []*helper.FrameItem `json:"frames"` + RangeDetail `json:",inline"` + RegionID uint64 `json:"region_id"` + Frames []*helper.FrameItem `json:"frames"` } // addTableInRange insert a table into RegionDetail @@ -964,6 +992,8 @@ func (h tableHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch h.op { case opTableRegions: h.handleRegionRequest(schema, tableVal, w, req) + case opTableRanges: + h.handleRangeRequest(schema, tableVal, w, req) case opTableDiskUsage: h.handleDiskUsageRequest(tableVal, w) case opTableScatter: @@ -1219,6 +1249,45 @@ func (h tableHandler) handleRegionRequest(schema infoschema.InfoSchema, tbl tabl writeData(w, tableRegions) } +func createTableRanges(tblID int64, tblName string, indices []*model.IndexInfo) *TableRanges { + indexPrefix := tablecodec.GenTableIndexPrefix(tblID) + recordPrefix := tablecodec.GenTableRecordPrefix(tblID) + tableEnd := tablecodec.EncodeTablePrefix(tblID + 1) + ranges := &TableRanges{ + TableName: tblName, + TableID: tblID, + Range: createRangeDetail(tablecodec.EncodeTablePrefix(tblID), tableEnd), + Record: createRangeDetail(recordPrefix, tableEnd), + Index: createRangeDetail(indexPrefix, recordPrefix), + } + if len(indices) != 0 { + indexRanges := make(map[string]RangeDetail) + for _, index := range indices { + start := tablecodec.EncodeTableIndexPrefix(tblID, index.ID) + end := tablecodec.EncodeTableIndexPrefix(tblID, index.ID+1) + indexRanges[index.Name.String()] = createRangeDetail(start, end) + } + ranges.Indices = indexRanges + } + return ranges +} + +func (h tableHandler) handleRangeRequest(schema infoschema.InfoSchema, tbl table.Table, w http.ResponseWriter, req *http.Request) { + meta := tbl.Meta() + pi := meta.GetPartitionInfo() + if pi != nil { + // Partitioned table. + var data []*TableRanges + for _, def := range pi.Definitions { + data = append(data, createTableRanges(def.ID, def.Name.String(), meta.Indices)) + } + writeData(w, data) + return + } + + writeData(w, createTableRanges(meta.ID, meta.Name.String(), meta.Indices)) +} + func (h tableHandler) getRegionsByID(tbl table.Table, id int64, name string) (*TableRegions, error) { // for record startKey, endKey := tablecodec.GetTableHandleKeyRange(id) @@ -1374,9 +1443,8 @@ func (h regionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // create RegionDetail from RegionFrameRange regionDetail := &RegionDetail{ - RegionID: regionID, - StartKey: region.StartKey, - EndKey: region.EndKey, + RegionID: regionID, + RangeDetail: createRangeDetail(region.StartKey, region.EndKey), } schema, err := h.schema() if err != nil { diff --git a/server/http_handler_test.go b/server/http_handler_test.go index c356da2de5dd5..3a02cfe4074a0 100644 --- a/server/http_handler_test.go +++ b/server/http_handler_test.go @@ -209,6 +209,22 @@ func (ts *HTTPHandlerTestSuite) TestRegionsAPI(c *C) { } } +func (ts *HTTPHandlerTestSuite) TestRangesAPI(c *C) { + ts.startServer(c) + defer ts.stopServer(c) + ts.prepareData(c) + resp, err := ts.fetchStatus("/tables/information_schema/SCHEMATA/ranges") + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + + var data TableRanges + err = decoder.Decode(&data) + c.Assert(err, IsNil) + c.Assert(data.TableName, Equals, "SCHEMATA") +} + func (ts *HTTPHandlerTestSuite) regionContainsTable(c *C, regionID uint64, tableID int64) bool { resp, err := ts.fetchStatus(fmt.Sprintf("/regions/%d", regionID)) c.Assert(err, IsNil) @@ -250,6 +266,30 @@ func (ts *HTTPHandlerTestSuite) TestListTableRegions(c *C) { c.Assert(err, IsNil) } +func (ts *HTTPHandlerTestSuite) TestListTableRanges(c *C) { + ts.startServer(c) + defer ts.stopServer(c) + ts.prepareData(c) + // Test list table regions with error + resp, err := ts.fetchStatus("/tables/fdsfds/aaa/ranges") + c.Assert(err, IsNil) + defer resp.Body.Close() + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + + resp, err = ts.fetchStatus("/tables/tidb/pt/ranges") + c.Assert(err, IsNil) + defer resp.Body.Close() + + var data []*TableRanges + dec := json.NewDecoder(resp.Body) + err = dec.Decode(&data) + c.Assert(err, IsNil) + c.Assert(len(data), Equals, 3) + for i, partition := range data { + c.Assert(partition.TableName, Equals, fmt.Sprintf("p%d", i)) + } +} + func (ts *HTTPHandlerTestSuite) TestGetRegionByIDWithError(c *C) { ts.startServer(c) defer ts.stopServer(c) diff --git a/server/http_status.go b/server/http_status.go index 8e626f04d6178..3216dee53ae5c 100644 --- a/server/http_status.go +++ b/server/http_status.go @@ -139,6 +139,7 @@ func (s *Server) startHTTPServer() { if s.cfg.Store == "tikv" { // HTTP path for tikv. router.Handle("/tables/{db}/{table}/regions", tableHandler{tikvHandlerTool, opTableRegions}) + router.Handle("/tables/{db}/{table}/ranges", tableHandler{tikvHandlerTool, opTableRanges}) router.Handle("/tables/{db}/{table}/scatter", tableHandler{tikvHandlerTool, opTableScatter}) router.Handle("/tables/{db}/{table}/stop-scatter", tableHandler{tikvHandlerTool, opStopTableScatter}) router.Handle("/tables/{db}/{table}/disk-usage", tableHandler{tikvHandlerTool, opTableDiskUsage})