diff --git a/pkg/core/region.go b/pkg/core/region.go old mode 100644 new mode 100755 index 53268589c8a..6408566663c --- a/pkg/core/region.go +++ b/pkg/core/region.go @@ -1789,7 +1789,42 @@ func (r *RegionsInfo) GetRegionCount(startKey, endKey []byte) int { defer r.t.RUnlock() start := ®ionItem{&RegionInfo{meta: &metapb.Region{StartKey: startKey}}} end := ®ionItem{&RegionInfo{meta: &metapb.Region{StartKey: endKey}}} - // it returns 0 if startKey is nil. + + // check if both keys are in the uncovered ranges. + startItem, sit := r.tree.tree.GetWithIndex(start) + endItem, eit := r.tree.tree.GetWithIndex(end) + if startItem == nil { + startItem = r.tree.tree.GetAt(sit - 1) + } else { + sit += 1 + } + if endItem == nil { + endItem = r.tree.tree.GetAt(eit - 1) + } else { + eit += 1 + } + + startInAnInterval := false + if startItem != nil { + startInAnInterval = (bytes.Compare(startItem.GetStartKey(), startKey) <= 0) && (bytes.Compare(startKey, startItem.GetEndKey()) <= 0) + } + endInAnInterval := false + if endItem != nil { + endInAnInterval = (bytes.Compare(endItem.GetStartKey(), endKey) <= 0) && (bytes.Compare(endKey, endItem.GetEndKey()) <= 0) + } + + if len(endKey) == 0 { + endItem = r.tree.tree.GetAt(r.tree.tree.Len() - 1) + eit = r.tree.tree.Len() + endInAnInterval = false + if endItem != nil { + endInAnInterval = (bytes.Compare(endItem.GetEndKey(), endKey) <= 0) && (bytes.Compare(endKey, endItem.GetEndKey()) <= 0) + } + } + if sit == eit && (!startInAnInterval) && (!endInAnInterval) { + return 0 + } + _, startIndex := r.tree.tree.GetWithIndex(start) var endIndex int var item *regionItem diff --git a/server/api/stats_test.go b/server/api/stats_test.go index 1485f9eb5af..3142eef2904 100644 --- a/server/api/stats_test.go +++ b/server/api/stats_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/pingcap/kvproto/pkg/metapb" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/tikv/pd/pkg/core" "github.com/tikv/pd/pkg/statistics" @@ -204,3 +205,99 @@ func (suite *statsTestSuite) TestRegionStats() { } } } + +func TestRegionStatsHoles(t *testing.T) { + re := require.New(t) + svr, cleanup := mustNewServer(re) + defer cleanup() + server.MustWaitLeader(re, []*server.Server{svr}) + + addr := svr.GetAddr() + urlPrefix := fmt.Sprintf("%s%s/api/v1", addr, apiPrefix) + + mustBootstrapCluster(re, svr) + + statsURL := urlPrefix + "/stats/region" + epoch := &metapb.RegionEpoch{ + ConfVer: 1, + Version: 1, + } + + // range holes + regionsWithholes := []*core.RegionInfo{ + core.NewRegionInfo( + &metapb.Region{ + Id: 1, + StartKey: []byte("-"), + EndKey: []byte("c"), + Peers: []*metapb.Peer{ + {Id: 101, StoreId: 1}, + {Id: 102, StoreId: 2}, + {Id: 103, StoreId: 3}, + }, + RegionEpoch: epoch, + }, + &metapb.Peer{Id: 101, StoreId: 1}, + core.SetApproximateSize(100), + core.SetApproximateKvSize(80), + core.SetApproximateKeys(50), + ), + core.NewRegionInfo( + &metapb.Region{ + Id: 2, + StartKey: []byte("g"), + EndKey: []byte("l"), + Peers: []*metapb.Peer{ + {Id: 104, StoreId: 1}, + {Id: 105, StoreId: 4}, + {Id: 106, StoreId: 5}, + }, + RegionEpoch: epoch, + }, + &metapb.Peer{Id: 105, StoreId: 4}, + core.SetApproximateSize(200), + core.SetApproximateKvSize(180), + core.SetApproximateKeys(150), + ), + core.NewRegionInfo( + &metapb.Region{ + Id: 3, + StartKey: []byte("o"), + EndKey: []byte("u"), + Peers: []*metapb.Peer{ + {Id: 106, StoreId: 1}, + {Id: 107, StoreId: 5}, + }, + RegionEpoch: epoch, + }, + &metapb.Peer{Id: 107, StoreId: 5}, + core.SetApproximateSize(1), + core.SetApproximateKvSize(1), + core.SetApproximateKeys(1), + ), + } + + for _, r := range regionsWithholes { + mustRegionHeartbeat(re, svr, r) + } + + // holes in between : + // | - c| ... |g - l| ... |o - u| ... + + startKeys := [6]string{"d", "b", "b", "i", "", "v"} + endKeys := [6]string{"e", "e", "i", "j", "", ""} + ans := [6]int{0, 1, 2, 1, 3, 0} + + for i := range startKeys { + startKey := url.QueryEscape(startKeys[i]) + endKey := url.QueryEscape(endKeys[i]) + argsWithHoles := fmt.Sprintf("?start_key=%s&end_key=%s&count", startKey, endKey) + res, err := testDialClient.Get(statsURL + argsWithHoles) + re.NoError(err) + stats := &statistics.RegionStats{} + err = apiutil.ReadJSON(res.Body, stats) + res.Body.Close() + re.NoError(err) + re.Equal(ans[i], stats.Count) + } +}