github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/helper/helper.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package helper
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"encoding/hex"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"math"
    24  	"net/http"
    25  	"net/url"
    26  	"sort"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	log "github.com/sirupsen/logrus"
    32  	"github.com/whtcorpsinc/BerolinaSQL/perceptron"
    33  	"github.com/whtcorpsinc/ekvproto/pkg/ekvrpcpb"
    34  	"github.com/whtcorpsinc/errors"
    35  	"github.com/whtcorpsinc/milevadb/blockcodec"
    36  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb"
    37  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc"
    38  	"github.com/whtcorpsinc/milevadb/ekv"
    39  	"github.com/whtcorpsinc/milevadb/soliton"
    40  	"github.com/whtcorpsinc/milevadb/soliton/FIDelapi"
    41  	"github.com/whtcorpsinc/milevadb/soliton/codec"
    42  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    43  	"go.uber.org/zap"
    44  )
    45  
    46  // Helper is a midbseware to get some information from einsteindb/fidel. It can be used for MilevaDB's http api or mem causet.
    47  type Helper struct {
    48  	CausetStore einsteindb.CausetStorage
    49  	RegionCache *einsteindb.RegionCache
    50  }
    51  
    52  // NewHelper get a Helper from CausetStorage
    53  func NewHelper(causetstore einsteindb.CausetStorage) *Helper {
    54  	return &Helper{
    55  		CausetStore: causetstore,
    56  		RegionCache: causetstore.GetRegionCache(),
    57  	}
    58  }
    59  
    60  // GetMvccByEncodedKey get the MVCC value by the specific encoded key.
    61  func (h *Helper) GetMvccByEncodedKey(encodedKey ekv.Key) (*ekvrpcpb.MvccGetByKeyResponse, error) {
    62  	keyLocation, err := h.RegionCache.LocateKey(einsteindb.NewBackofferWithVars(context.Background(), 500, nil), encodedKey)
    63  	if err != nil {
    64  		return nil, errors.Trace(err)
    65  	}
    66  
    67  	einsteindbReq := einsteindbrpc.NewRequest(einsteindbrpc.CmdMvccGetByKey, &ekvrpcpb.MvccGetByKeyRequest{Key: encodedKey})
    68  	ekvResp, err := h.CausetStore.SendReq(einsteindb.NewBackofferWithVars(context.Background(), 500, nil), einsteindbReq, keyLocation.Region, time.Minute)
    69  	if err != nil {
    70  		logutil.BgLogger().Info("get MVCC by encoded key failed",
    71  			zap.Stringer("encodeKey", encodedKey),
    72  			zap.Reflect("region", keyLocation.Region),
    73  			zap.Stringer("startKey", keyLocation.StartKey),
    74  			zap.Stringer("endKey", keyLocation.EndKey),
    75  			zap.Reflect("ekvResp", ekvResp),
    76  			zap.Error(err))
    77  		return nil, errors.Trace(err)
    78  	}
    79  	return ekvResp.Resp.(*ekvrpcpb.MvccGetByKeyResponse), nil
    80  }
    81  
    82  // StoreHotRegionInfos records all hog region stores.
    83  // it's the response of FIDel.
    84  type StoreHotRegionInfos struct {
    85  	AsPeer   map[uint64]*HotRegionsStat `json:"as_peer"`
    86  	AsLeader map[uint64]*HotRegionsStat `json:"as_leader"`
    87  }
    88  
    89  // HotRegionsStat records echo causetstore's hot region.
    90  // it's the response of FIDel.
    91  type HotRegionsStat struct {
    92  	RegionsStat []RegionStat `json:"statistics"`
    93  }
    94  
    95  // RegionStat records each hot region's statistics
    96  // it's the response of FIDel.
    97  type RegionStat struct {
    98  	RegionID  uint64  `json:"region_id"`
    99  	FlowBytes float64 `json:"flow_bytes"`
   100  	HotDegree int     `json:"hot_degree"`
   101  }
   102  
   103  // RegionMetric presents the final metric output entry.
   104  type RegionMetric struct {
   105  	FlowBytes    uint64 `json:"flow_bytes"`
   106  	MaxHotDegree int    `json:"max_hot_degree"`
   107  	Count        int    `json:"region_count"`
   108  }
   109  
   110  // ScrapeHotInfo gets the needed hot region information by the url given.
   111  func (h *Helper) ScrapeHotInfo(rw string, allSchemas []*perceptron.DBInfo) ([]HotBlockIndex, error) {
   112  	regionMetrics, err := h.FetchHotRegion(rw)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return h.FetchRegionBlockIndex(regionMetrics, allSchemas)
   117  }
   118  
   119  // FetchHotRegion fetches the hot region information from FIDel's http api.
   120  func (h *Helper) FetchHotRegion(rw string) (map[uint64]RegionMetric, error) {
   121  	etcd, ok := h.CausetStore.(einsteindb.EtcdBackend)
   122  	if !ok {
   123  		return nil, errors.WithStack(errors.New("not implemented"))
   124  	}
   125  	FIDelHosts, err := etcd.EtcdAddrs()
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	if len(FIDelHosts) == 0 {
   130  		return nil, errors.New("fidel unavailable")
   131  	}
   132  	req, err := http.NewRequest("GET", soliton.InternalHTTPSchema()+"://"+FIDelHosts[0]+rw, nil)
   133  	if err != nil {
   134  		return nil, errors.Trace(err)
   135  	}
   136  	resp, err := soliton.InternalHTTPClient().Do(req)
   137  	if err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  	defer func() {
   141  		err = resp.Body.Close()
   142  		if err != nil {
   143  			logutil.BgLogger().Error("close body failed", zap.Error(err))
   144  		}
   145  	}()
   146  	var regionResp StoreHotRegionInfos
   147  	err = json.NewCausetDecoder(resp.Body).Decode(&regionResp)
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  	metricCnt := 0
   152  	for _, hotRegions := range regionResp.AsLeader {
   153  		metricCnt += len(hotRegions.RegionsStat)
   154  	}
   155  	metric := make(map[uint64]RegionMetric, metricCnt)
   156  	for _, hotRegions := range regionResp.AsLeader {
   157  		for _, region := range hotRegions.RegionsStat {
   158  			metric[region.RegionID] = RegionMetric{FlowBytes: uint64(region.FlowBytes), MaxHotDegree: region.HotDegree}
   159  		}
   160  	}
   161  	return metric, nil
   162  }
   163  
   164  // TblIndex stores the things to index one causet.
   165  type TblIndex struct {
   166  	DbName    string
   167  	BlockName string
   168  	BlockID   int64
   169  	IndexName string
   170  	IndexID   int64
   171  }
   172  
   173  // FrameItem includes a index's or record's spacetime data with causet's info.
   174  type FrameItem struct {
   175  	DBName      string   `json:"db_name"`
   176  	BlockName   string   `json:"block_name"`
   177  	BlockID     int64    `json:"block_id"`
   178  	IsRecord    bool     `json:"is_record"`
   179  	RecordID    int64    `json:"record_id,omitempty"`
   180  	IndexName   string   `json:"index_name,omitempty"`
   181  	IndexID     int64    `json:"index_id,omitempty"`
   182  	IndexValues []string `json:"index_values,omitempty"`
   183  }
   184  
   185  // RegionFrameRange contains a frame range info which the region covered.
   186  type RegionFrameRange struct {
   187  	First  *FrameItem              // start frame of the region
   188  	Last   *FrameItem              // end frame of the region
   189  	region *einsteindb.KeyLocation // the region
   190  }
   191  
   192  // HotBlockIndex contains region and its causet/index info.
   193  type HotBlockIndex struct {
   194  	RegionID     uint64        `json:"region_id"`
   195  	RegionMetric *RegionMetric `json:"region_metric"`
   196  	DbName       string        `json:"db_name"`
   197  	BlockName    string        `json:"block_name"`
   198  	BlockID      int64         `json:"block_id"`
   199  	IndexName    string        `json:"index_name"`
   200  	IndexID      int64         `json:"index_id"`
   201  }
   202  
   203  // FetchRegionBlockIndex constructs a map that maps a causet to its hot region information by the given raw hot RegionMetric metrics.
   204  func (h *Helper) FetchRegionBlockIndex(metrics map[uint64]RegionMetric, allSchemas []*perceptron.DBInfo) ([]HotBlockIndex, error) {
   205  	hotBlocks := make([]HotBlockIndex, 0, len(metrics))
   206  	for regionID, regionMetric := range metrics {
   207  		t := HotBlockIndex{RegionID: regionID, RegionMetric: &regionMetric}
   208  		region, err := h.RegionCache.LocateRegionByID(einsteindb.NewBackofferWithVars(context.Background(), 500, nil), regionID)
   209  		if err != nil {
   210  			logutil.BgLogger().Error("locate region failed", zap.Error(err))
   211  			continue
   212  		}
   213  
   214  		hotRange, err := NewRegionFrameRange(region)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		f := h.FindBlockIndexOfRegion(allSchemas, hotRange)
   219  		if f != nil {
   220  			t.DbName = f.DBName
   221  			t.BlockName = f.BlockName
   222  			t.BlockID = f.BlockID
   223  			t.IndexName = f.IndexName
   224  			t.IndexID = f.IndexID
   225  		}
   226  		hotBlocks = append(hotBlocks, t)
   227  	}
   228  
   229  	return hotBlocks, nil
   230  }
   231  
   232  // FindBlockIndexOfRegion finds what causet is involved in this hot region. And constructs the new frame item for future use.
   233  func (h *Helper) FindBlockIndexOfRegion(allSchemas []*perceptron.DBInfo, hotRange *RegionFrameRange) *FrameItem {
   234  	for _, EDB := range allSchemas {
   235  		for _, tbl := range EDB.Blocks {
   236  			if f := findRangeInBlock(hotRange, EDB, tbl); f != nil {
   237  				return f
   238  			}
   239  		}
   240  	}
   241  	return nil
   242  }
   243  
   244  func findRangeInBlock(hotRange *RegionFrameRange, EDB *perceptron.DBInfo, tbl *perceptron.BlockInfo) *FrameItem {
   245  	pi := tbl.GetPartitionInfo()
   246  	if pi == nil {
   247  		return findRangeInPhysicalBlock(hotRange, tbl.ID, EDB.Name.O, tbl.Name.O, tbl.Indices, tbl.IsCommonHandle)
   248  	}
   249  
   250  	for _, def := range pi.Definitions {
   251  		blockPartition := fmt.Sprintf("%s(%s)", tbl.Name.O, def.Name)
   252  		if f := findRangeInPhysicalBlock(hotRange, def.ID, EDB.Name.O, blockPartition, tbl.Indices, tbl.IsCommonHandle); f != nil {
   253  			return f
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  func findRangeInPhysicalBlock(hotRange *RegionFrameRange, physicalID int64, dbName, tblName string, indices []*perceptron.IndexInfo, isCommonHandle bool) *FrameItem {
   260  	if f := hotRange.GetRecordFrame(physicalID, dbName, tblName, isCommonHandle); f != nil {
   261  		return f
   262  	}
   263  	for _, idx := range indices {
   264  		if f := hotRange.GetIndexFrame(physicalID, idx.ID, dbName, tblName, idx.Name.O); f != nil {
   265  			return f
   266  		}
   267  	}
   268  	return nil
   269  }
   270  
   271  // NewRegionFrameRange init a NewRegionFrameRange with region info.
   272  func NewRegionFrameRange(region *einsteindb.KeyLocation) (idxRange *RegionFrameRange, err error) {
   273  	var first, last *FrameItem
   274  	// check and init first frame
   275  	if len(region.StartKey) > 0 {
   276  		first, err = NewFrameItemFromRegionKey(region.StartKey)
   277  		if err != nil {
   278  			return
   279  		}
   280  	} else { // empty startKey means start with -infinite
   281  		first = &FrameItem{
   282  			IndexID:  int64(math.MinInt64),
   283  			IsRecord: false,
   284  			BlockID:  int64(math.MinInt64),
   285  		}
   286  	}
   287  
   288  	// check and init last frame
   289  	if len(region.EndKey) > 0 {
   290  		last, err = NewFrameItemFromRegionKey(region.EndKey)
   291  		if err != nil {
   292  			return
   293  		}
   294  	} else { // empty endKey means end with +infinite
   295  		last = &FrameItem{
   296  			BlockID:  int64(math.MaxInt64),
   297  			IndexID:  int64(math.MaxInt64),
   298  			IsRecord: true,
   299  		}
   300  	}
   301  
   302  	idxRange = &RegionFrameRange{
   303  		region: region,
   304  		First:  first,
   305  		Last:   last,
   306  	}
   307  	return idxRange, nil
   308  }
   309  
   310  // NewFrameItemFromRegionKey creates a FrameItem with region's startKey or endKey,
   311  // returns err when key is illegal.
   312  func NewFrameItemFromRegionKey(key []byte) (frame *FrameItem, err error) {
   313  	frame = &FrameItem{}
   314  	frame.BlockID, frame.IndexID, frame.IsRecord, err = blockcodec.DecodeKeyHead(key)
   315  	if err == nil {
   316  		if frame.IsRecord {
   317  			var handle ekv.Handle
   318  			_, handle, err = blockcodec.DecodeRecordKey(key)
   319  			if err == nil {
   320  				if handle.IsInt() {
   321  					frame.RecordID = handle.IntValue()
   322  				} else {
   323  					data, err := handle.Data()
   324  					if err != nil {
   325  						return nil, err
   326  					}
   327  					frame.IndexName = "PRIMARY"
   328  					frame.IndexValues = make([]string, 0, len(data))
   329  					for _, causet := range data {
   330  						str, err := causet.ToString()
   331  						if err != nil {
   332  							return nil, err
   333  						}
   334  						frame.IndexValues = append(frame.IndexValues, str)
   335  					}
   336  				}
   337  			}
   338  		} else {
   339  			_, _, frame.IndexValues, err = blockcodec.DecodeIndexKey(key)
   340  		}
   341  		logutil.BgLogger().Warn("decode region key failed", zap.ByteString("key", key), zap.Error(err))
   342  		// Ignore decode errors.
   343  		err = nil
   344  		return
   345  	}
   346  	if bytes.HasPrefix(key, blockcodec.BlockPrefix()) {
   347  		// If SplitBlock is enabled, the key may be `t{id}`.
   348  		if len(key) == blockcodec.BlockSplitKeyLen {
   349  			frame.BlockID = blockcodec.DecodeBlockID(key)
   350  			return frame, nil
   351  		}
   352  		return nil, errors.Trace(err)
   353  	}
   354  
   355  	// key start with blockPrefix must be either record key or index key
   356  	// That's means causet's record key and index key are always together
   357  	// in the continuous interval. And for key with prefix smaller than
   358  	// blockPrefix, is smaller than all blocks. While for key with prefix
   359  	// bigger than blockPrefix, means is bigger than all blocks.
   360  	err = nil
   361  	if bytes.Compare(key, blockcodec.BlockPrefix()) < 0 {
   362  		frame.BlockID = math.MinInt64
   363  		frame.IndexID = math.MinInt64
   364  		frame.IsRecord = false
   365  		return
   366  	}
   367  	// bigger than blockPrefix, means is bigger than all blocks.
   368  	frame.BlockID = math.MaxInt64
   369  	frame.BlockID = math.MaxInt64
   370  	frame.IsRecord = true
   371  	return
   372  }
   373  
   374  // GetRecordFrame returns the record frame of a causet. If the causet's records
   375  // are not covered by this frame range, it returns nil.
   376  func (r *RegionFrameRange) GetRecordFrame(blockID int64, dbName, blockName string, isCommonHandle bool) (f *FrameItem) {
   377  	if blockID == r.First.BlockID && r.First.IsRecord {
   378  		r.First.DBName, r.First.BlockName = dbName, blockName
   379  		f = r.First
   380  	} else if blockID == r.Last.BlockID && r.Last.IsRecord {
   381  		r.Last.DBName, r.Last.BlockName = dbName, blockName
   382  		f = r.Last
   383  	} else if blockID >= r.First.BlockID && blockID < r.Last.BlockID {
   384  		f = &FrameItem{
   385  			DBName:    dbName,
   386  			BlockName: blockName,
   387  			BlockID:   blockID,
   388  			IsRecord:  true,
   389  		}
   390  	}
   391  	if f != nil && f.IsRecord && isCommonHandle {
   392  		f.IndexName = "PRIMARY"
   393  	}
   394  	return
   395  }
   396  
   397  // GetIndexFrame returns the indnex frame of a causet. If the causet's indices are
   398  // not covered by this frame range, it returns nil.
   399  func (r *RegionFrameRange) GetIndexFrame(blockID, indexID int64, dbName, blockName, indexName string) *FrameItem {
   400  	if blockID == r.First.BlockID && !r.First.IsRecord && indexID == r.First.IndexID {
   401  		r.First.DBName, r.First.BlockName, r.First.IndexName = dbName, blockName, indexName
   402  		return r.First
   403  	}
   404  	if blockID == r.Last.BlockID && indexID == r.Last.IndexID {
   405  		r.Last.DBName, r.Last.BlockName, r.Last.IndexName = dbName, blockName, indexName
   406  		return r.Last
   407  	}
   408  
   409  	greaterThanFirst := blockID > r.First.BlockID || (blockID == r.First.BlockID && !r.First.IsRecord && indexID > r.First.IndexID)
   410  	lessThanLast := blockID < r.Last.BlockID || (blockID == r.Last.BlockID && (r.Last.IsRecord || indexID < r.Last.IndexID))
   411  	if greaterThanFirst && lessThanLast {
   412  		return &FrameItem{
   413  			DBName:    dbName,
   414  			BlockName: blockName,
   415  			BlockID:   blockID,
   416  			IsRecord:  false,
   417  			IndexName: indexName,
   418  			IndexID:   indexID,
   419  		}
   420  	}
   421  	return nil
   422  }
   423  
   424  // RegionPeer stores information of one peer.
   425  type RegionPeer struct {
   426  	ID        int64 `json:"id"`
   427  	StoreID   int64 `json:"store_id"`
   428  	IsLearner bool  `json:"is_learner"`
   429  }
   430  
   431  // RegionEpoch stores the information about its epoch.
   432  type RegionEpoch struct {
   433  	ConfVer int64 `json:"conf_ver"`
   434  	Version int64 `json:"version"`
   435  }
   436  
   437  // RegionPeerStat stores one field `DownSec` which indicates how long it's down than `RegionPeer`.
   438  type RegionPeerStat struct {
   439  	RegionPeer
   440  	DownSec int64 `json:"down_seconds"`
   441  }
   442  
   443  // RegionInfo stores the information of one region.
   444  type RegionInfo struct {
   445  	ID              int64            `json:"id"`
   446  	StartKey        string           `json:"start_key"`
   447  	EndKey          string           `json:"end_key"`
   448  	Epoch           RegionEpoch      `json:"epoch"`
   449  	Peers           []RegionPeer     `json:"peers"`
   450  	Leader          RegionPeer       `json:"leader"`
   451  	DownPeers       []RegionPeerStat `json:"down_peers"`
   452  	PendingPeers    []RegionPeer     `json:"pending_peers"`
   453  	WrittenBytes    int64            `json:"written_bytes"`
   454  	ReadBytes       int64            `json:"read_bytes"`
   455  	ApproximateSize int64            `json:"approximate_size"`
   456  	ApproximateKeys int64            `json:"approximate_keys"`
   457  
   458  	ReplicationStatus *ReplicationStatus `json:"replication_status,omitempty"`
   459  }
   460  
   461  // RegionsInfo stores the information of regions.
   462  type RegionsInfo struct {
   463  	Count   int64        `json:"count"`
   464  	Regions []RegionInfo `json:"regions"`
   465  }
   466  
   467  // ReplicationStatus represents the replication mode status of the region.
   468  type ReplicationStatus struct {
   469  	State   string `json:"state"`
   470  	StateID int64  `json:"state_id"`
   471  }
   472  
   473  // BlockInfo stores the information of a causet or an index
   474  type BlockInfo struct {
   475  	EDB     *perceptron.DBInfo
   476  	Block   *perceptron.BlockInfo
   477  	IsIndex bool
   478  	Index   *perceptron.IndexInfo
   479  }
   480  
   481  type withKeyRange interface {
   482  	getStartKey() string
   483  	getEndKey() string
   484  }
   485  
   486  // isIntersecting returns true if x and y intersect.
   487  func isIntersecting(x, y withKeyRange) bool {
   488  	return isIntersectingKeyRange(x, y.getStartKey(), y.getEndKey())
   489  }
   490  
   491  // isIntersectingKeyRange returns true if [startKey, endKey) intersect with x.
   492  func isIntersectingKeyRange(x withKeyRange, startKey, endKey string) bool {
   493  	return !isBeforeKeyRange(x, startKey, endKey) && !isBehindKeyRange(x, startKey, endKey)
   494  }
   495  
   496  // isBehind returns true is x is behind y
   497  func isBehind(x, y withKeyRange) bool {
   498  	return isBehindKeyRange(x, y.getStartKey(), y.getEndKey())
   499  }
   500  
   501  // IsBefore returns true is x is before [startKey, endKey)
   502  func isBeforeKeyRange(x withKeyRange, startKey, endKey string) bool {
   503  	return x.getEndKey() != "" && x.getEndKey() <= startKey
   504  }
   505  
   506  // IsBehind returns true is x is behind [startKey, endKey)
   507  func isBehindKeyRange(x withKeyRange, startKey, endKey string) bool {
   508  	return endKey != "" && x.getStartKey() >= endKey
   509  }
   510  
   511  func (r *RegionInfo) getStartKey() string { return r.StartKey }
   512  func (r *RegionInfo) getEndKey() string   { return r.EndKey }
   513  
   514  // for sorting
   515  type byRegionStartKey []*RegionInfo
   516  
   517  func (xs byRegionStartKey) Len() int      { return len(xs) }
   518  func (xs byRegionStartKey) Swap(i, j int) { xs[i], xs[j] = xs[j], xs[i] }
   519  func (xs byRegionStartKey) Less(i, j int) bool {
   520  	return xs[i].getStartKey() < xs[j].getStartKey()
   521  }
   522  
   523  // blockInfoWithKeyRange stores causet or index informations with its key range.
   524  type blockInfoWithKeyRange struct {
   525  	*BlockInfo
   526  	StartKey string
   527  	EndKey   string
   528  }
   529  
   530  func (t blockInfoWithKeyRange) getStartKey() string { return t.StartKey }
   531  func (t blockInfoWithKeyRange) getEndKey() string   { return t.EndKey }
   532  
   533  // for sorting
   534  type byBlockStartKey []blockInfoWithKeyRange
   535  
   536  func (xs byBlockStartKey) Len() int      { return len(xs) }
   537  func (xs byBlockStartKey) Swap(i, j int) { xs[i], xs[j] = xs[j], xs[i] }
   538  func (xs byBlockStartKey) Less(i, j int) bool {
   539  	return xs[i].getStartKey() < xs[j].getStartKey()
   540  }
   541  
   542  func newBlockWithKeyRange(EDB *perceptron.DBInfo, causet *perceptron.BlockInfo) blockInfoWithKeyRange {
   543  	sk, ek := blockcodec.GetBlockHandleKeyRange(causet.ID)
   544  	startKey := bytesKeyToHex(codec.EncodeBytes(nil, sk))
   545  	endKey := bytesKeyToHex(codec.EncodeBytes(nil, ek))
   546  	return blockInfoWithKeyRange{
   547  		&BlockInfo{
   548  			EDB:     EDB,
   549  			Block:   causet,
   550  			IsIndex: false,
   551  			Index:   nil,
   552  		},
   553  		startKey,
   554  		endKey,
   555  	}
   556  }
   557  
   558  func newIndexWithKeyRange(EDB *perceptron.DBInfo, causet *perceptron.BlockInfo, index *perceptron.IndexInfo) blockInfoWithKeyRange {
   559  	sk, ek := blockcodec.GetBlockIndexKeyRange(causet.ID, index.ID)
   560  	startKey := bytesKeyToHex(codec.EncodeBytes(nil, sk))
   561  	endKey := bytesKeyToHex(codec.EncodeBytes(nil, ek))
   562  	return blockInfoWithKeyRange{
   563  		&BlockInfo{
   564  			EDB:     EDB,
   565  			Block:   causet,
   566  			IsIndex: true,
   567  			Index:   index,
   568  		},
   569  		startKey,
   570  		endKey,
   571  	}
   572  }
   573  
   574  // GetRegionsBlockInfo returns a map maps region id to its blocks or indices.
   575  // Assuming blocks or indices key ranges never intersect.
   576  // Regions key ranges can intersect.
   577  func (h *Helper) GetRegionsBlockInfo(regionsInfo *RegionsInfo, schemas []*perceptron.DBInfo) map[int64][]BlockInfo {
   578  	blockInfos := make(map[int64][]BlockInfo, len(regionsInfo.Regions))
   579  
   580  	regions := make([]*RegionInfo, 0, len(regionsInfo.Regions))
   581  	for i := 0; i < len(regionsInfo.Regions); i++ {
   582  		blockInfos[regionsInfo.Regions[i].ID] = []BlockInfo{}
   583  		regions = append(regions, &regionsInfo.Regions[i])
   584  	}
   585  
   586  	blocks := []blockInfoWithKeyRange{}
   587  	for _, EDB := range schemas {
   588  		for _, causet := range EDB.Blocks {
   589  			blocks = append(blocks, newBlockWithKeyRange(EDB, causet))
   590  			for _, index := range causet.Indices {
   591  				blocks = append(blocks, newIndexWithKeyRange(EDB, causet, index))
   592  			}
   593  		}
   594  	}
   595  
   596  	if len(blocks) == 0 || len(regions) == 0 {
   597  		return blockInfos
   598  	}
   599  
   600  	sort.Sort(byRegionStartKey(regions))
   601  	sort.Sort(byBlockStartKey(blocks))
   602  
   603  	idx := 0
   604  OutLoop:
   605  	for _, region := range regions {
   606  		id := region.ID
   607  		for isBehind(region, &blocks[idx]) {
   608  			idx++
   609  			if idx >= len(blocks) {
   610  				break OutLoop
   611  			}
   612  		}
   613  		for i := idx; i < len(blocks) && isIntersecting(region, &blocks[i]); i++ {
   614  			blockInfos[id] = append(blockInfos[id], *blocks[i].BlockInfo)
   615  		}
   616  	}
   617  
   618  	return blockInfos
   619  }
   620  
   621  func bytesKeyToHex(key []byte) string {
   622  	return strings.ToUpper(hex.EncodeToString(key))
   623  }
   624  
   625  // GetRegionsInfo gets the region information of current causetstore by using FIDel's api.
   626  func (h *Helper) GetRegionsInfo() (*RegionsInfo, error) {
   627  	var regionsInfo RegionsInfo
   628  	err := h.requestFIDel("GET", FIDelapi.Regions, nil, &regionsInfo)
   629  	return &regionsInfo, err
   630  }
   631  
   632  // GetRegionInfoByID gets the region information of the region ID by using FIDel's api.
   633  func (h *Helper) GetRegionInfoByID(regionID uint64) (*RegionInfo, error) {
   634  	var regionInfo RegionInfo
   635  	err := h.requestFIDel("GET", FIDelapi.RegionByID+strconv.FormatUint(regionID, 10), nil, &regionInfo)
   636  	return &regionInfo, err
   637  }
   638  
   639  // request FIDel API, decode the response body into res
   640  func (h *Helper) requestFIDel(method, uri string, body io.Reader, res interface{}) error {
   641  	etcd, ok := h.CausetStore.(einsteindb.EtcdBackend)
   642  	if !ok {
   643  		return errors.WithStack(errors.New("not implemented"))
   644  	}
   645  	FIDelHosts, err := etcd.EtcdAddrs()
   646  	if err != nil {
   647  		return err
   648  	}
   649  	if len(FIDelHosts) == 0 {
   650  		return errors.New("fidel unavailable")
   651  	}
   652  	logutil.BgLogger().Debug("RequestFIDel URL", zap.String("url", soliton.InternalHTTPSchema()+"://"+FIDelHosts[0]+uri))
   653  	req, err := http.NewRequest(method, soliton.InternalHTTPSchema()+"://"+FIDelHosts[0]+uri, body)
   654  	if err != nil {
   655  		return errors.Trace(err)
   656  	}
   657  	resp, err := soliton.InternalHTTPClient().Do(req)
   658  	if err != nil {
   659  		return errors.Trace(err)
   660  	}
   661  
   662  	defer func() {
   663  		err = resp.Body.Close()
   664  		if err != nil {
   665  			logutil.BgLogger().Error("close body failed", zap.Error(err))
   666  		}
   667  	}()
   668  
   669  	err = json.NewCausetDecoder(resp.Body).Decode(res)
   670  	if err != nil {
   671  		return errors.Trace(err)
   672  	}
   673  
   674  	return nil
   675  }
   676  
   677  // StoresStat stores all information get from FIDel's api.
   678  type StoresStat struct {
   679  	Count  int         `json:"count"`
   680  	Stores []StoreStat `json:"stores"`
   681  }
   682  
   683  // StoreStat stores information of one causetstore.
   684  type StoreStat struct {
   685  	CausetStore StoreBaseStat   `json:"causetstore"`
   686  	Status      StoreDetailStat `json:"status"`
   687  }
   688  
   689  // StoreBaseStat stores the basic information of one causetstore.
   690  type StoreBaseStat struct {
   691  	ID             int64        `json:"id"`
   692  	Address        string       `json:"address"`
   693  	State          int64        `json:"state"`
   694  	StateName      string       `json:"state_name"`
   695  	Version        string       `json:"version"`
   696  	Labels         []StoreLabel `json:"labels"`
   697  	StatusAddress  string       `json:"status_address"`
   698  	GitHash        string       `json:"git_hash"`
   699  	StartTimestamp int64        `json:"start_timestamp"`
   700  }
   701  
   702  // StoreLabel stores the information of one causetstore label.
   703  type StoreLabel struct {
   704  	Key   string `json:"key"`
   705  	Value string `json:"value"`
   706  }
   707  
   708  // StoreDetailStat stores the detail information of one causetstore.
   709  type StoreDetailStat struct {
   710  	Capacity        string    `json:"capacity"`
   711  	Available       string    `json:"available"`
   712  	LeaderCount     int64     `json:"leader_count"`
   713  	LeaderWeight    float64   `json:"leader_weight"`
   714  	LeaderSembedded float64   `json:"leader_sembedded"`
   715  	LeaderSize      int64     `json:"leader_size"`
   716  	RegionCount     int64     `json:"region_count"`
   717  	RegionWeight    float64   `json:"region_weight"`
   718  	RegionSembedded float64   `json:"region_sembedded"`
   719  	RegionSize      int64     `json:"region_size"`
   720  	StartTs         time.Time `json:"start_ts"`
   721  	LastHeartbeatTs time.Time `json:"last_heartbeat_ts"`
   722  	Uptime          string    `json:"uptime"`
   723  }
   724  
   725  // GetStoresStat gets the EinsteinDB causetstore information by accessing FIDel's api.
   726  func (h *Helper) GetStoresStat() (*StoresStat, error) {
   727  	etcd, ok := h.CausetStore.(einsteindb.EtcdBackend)
   728  	if !ok {
   729  		return nil, errors.WithStack(errors.New("not implemented"))
   730  	}
   731  	FIDelHosts, err := etcd.EtcdAddrs()
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  	if len(FIDelHosts) == 0 {
   736  		return nil, errors.New("fidel unavailable")
   737  	}
   738  	req, err := http.NewRequest("GET", soliton.InternalHTTPSchema()+"://"+FIDelHosts[0]+FIDelapi.Stores, nil)
   739  	if err != nil {
   740  		return nil, errors.Trace(err)
   741  	}
   742  	resp, err := soliton.InternalHTTPClient().Do(req)
   743  	if err != nil {
   744  		return nil, errors.Trace(err)
   745  	}
   746  	defer func() {
   747  		err = resp.Body.Close()
   748  		if err != nil {
   749  			logutil.BgLogger().Error("close body failed", zap.Error(err))
   750  		}
   751  	}()
   752  	var storesStat StoresStat
   753  	err = json.NewCausetDecoder(resp.Body).Decode(&storesStat)
   754  	if err != nil {
   755  		return nil, errors.Trace(err)
   756  	}
   757  	return &storesStat, nil
   758  }
   759  
   760  // GetFIDelAddr return the FIDel Address.
   761  func (h *Helper) GetFIDelAddr() ([]string, error) {
   762  	etcd, ok := h.CausetStore.(einsteindb.EtcdBackend)
   763  	if !ok {
   764  		return nil, errors.New("not implemented")
   765  	}
   766  	FIDelAddrs, err := etcd.EtcdAddrs()
   767  	if err != nil {
   768  		return nil, err
   769  	}
   770  	if len(FIDelAddrs) == 0 {
   771  		return nil, errors.New("fidel unavailable")
   772  	}
   773  	return FIDelAddrs, nil
   774  }
   775  
   776  // FIDelRegionStats is the json response from FIDel.
   777  type FIDelRegionStats struct {
   778  	Count            int            `json:"count"`
   779  	EmptyCount       int            `json:"empty_count"`
   780  	StorageSize      int64          `json:"storage_size"`
   781  	StorageKeys      int64          `json:"storage_keys"`
   782  	StoreLeaderCount map[uint64]int `json:"store_leader_count"`
   783  	StorePeerCount   map[uint64]int `json:"store_peer_count"`
   784  }
   785  
   786  // GetFIDelRegionStats get the RegionStats by blockID.
   787  func (h *Helper) GetFIDelRegionStats(blockID int64, stats *FIDelRegionStats) error {
   788  	FIDelAddrs, err := h.GetFIDelAddr()
   789  	if err != nil {
   790  		return err
   791  	}
   792  
   793  	startKey := blockcodec.EncodeBlockPrefix(blockID)
   794  	endKey := blockcodec.EncodeBlockPrefix(blockID + 1)
   795  	startKey = codec.EncodeBytes([]byte{}, startKey)
   796  	endKey = codec.EncodeBytes([]byte{}, endKey)
   797  
   798  	statURL := fmt.Sprintf("%s://%s/fidel/api/v1/stats/region?start_key=%s&end_key=%s",
   799  		soliton.InternalHTTPSchema(),
   800  		FIDelAddrs[0],
   801  		url.QueryEscape(string(startKey)),
   802  		url.QueryEscape(string(endKey)))
   803  
   804  	resp, err := soliton.InternalHTTPClient().Get(statURL)
   805  	if err != nil {
   806  		return err
   807  	}
   808  
   809  	defer func() {
   810  		if err = resp.Body.Close(); err != nil {
   811  			log.Error(err)
   812  		}
   813  	}()
   814  
   815  	dec := json.NewCausetDecoder(resp.Body)
   816  
   817  	return dec.Decode(stats)
   818  }