├── .gitignore ├── screenshot.png ├── go.mod ├── README.md ├── cli ├── command │ ├── domain.go │ ├── global.go │ ├── region_command.go │ └── hot_command.go └── ctl.go ├── main.go ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | region-dist-cli 2 | *.xlsx 3 | *.xls 4 | *.idea 5 | *.csv -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oh-my-tidb/region-dist-cli/HEAD/screenshot.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/disksing/region-dist-cli 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.0 7 | github.com/chzyer/logex v1.1.10 // indirect 8 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect 10 | github.com/mattn/go-shellwords v1.0.3 11 | github.com/spf13/cobra v1.0.0 12 | github.com/spf13/pflag v1.0.5 13 | ) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # region-dist-cli 2 | 3 | Visualize region distribution of a [TiDB](https://github.com/pingcap/tidb) Cluster. 4 | 5 | ![Screenshot](screenshot.png) 6 | 7 | ## Usage: 8 | 1. compile: 9 | ```bash 10 | go get github.com/disksing/region-dist-cli 11 | ``` 12 | 13 | 2. show region distribute 14 | ```bash 15 | region-dist-cli region print 16 | 17 | ``` 18 | 19 | 3. export hot region info 20 | ```bash 21 | ./region-dist-cli hot write/read 22 | ``` 23 | -------------------------------------------------------------------------------- /cli/command/domain.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | type Peer struct { 4 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 5 | StoreId uint64 `protobuf:"varint,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty"` 6 | IsLearner bool `protobuf:"varint,3,opt,name=is_learner,json=isLearner,proto3" json:"is_learner,omitempty"` 7 | } 8 | 9 | type RegionInfo struct { 10 | ID uint64 `json:"id"` 11 | StartKey string `json:"start_key"` 12 | EndKey string `json:"end_key"` 13 | Peers []*Peer `json:"peers,omitempty"` 14 | Leader *Peer `json:"leader,omitempty"` 15 | } 16 | 17 | type RegionsInfo struct { 18 | Count int `json:"count"` 19 | Regions []*RegionInfo `json:"regions"` 20 | } 21 | 22 | type StoreInfo struct { 23 | Store struct { 24 | ID uint64 `json:"id"` 25 | } `json:"store"` 26 | } 27 | 28 | type StoresInfo struct { 29 | Stores []*StoreInfo `json:"stores"` 30 | } 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/signal" 8 | "strings" 9 | "syscall" 10 | 11 | "github.com/disksing/region-dist-cli/cli" 12 | ) 13 | 14 | func main() { 15 | pdAddr := os.Getenv("PD_ADDR") 16 | if pdAddr != "" { 17 | os.Args = append(os.Args, "-u", pdAddr) 18 | } 19 | 20 | sc := make(chan os.Signal, 1) 21 | signal.Notify(sc, 22 | syscall.SIGHUP, 23 | syscall.SIGINT, 24 | syscall.SIGTERM, 25 | syscall.SIGQUIT) 26 | 27 | go func() { 28 | sig := <-sc 29 | fmt.Printf("\nGot signal [%v] to exit.\n", sig) 30 | switch sig { 31 | case syscall.SIGTERM: 32 | os.Exit(0) 33 | default: 34 | os.Exit(1) 35 | } 36 | }() 37 | 38 | var input []string 39 | stat, _ := os.Stdin.Stat() 40 | if (stat.Mode() & os.ModeCharDevice) == 0 { 41 | b, err := io.ReadAll(os.Stdin) 42 | if err != nil { 43 | fmt.Println(err) 44 | return 45 | } 46 | input = strings.Split(strings.TrimSpace(string(b[:])), " ") 47 | } 48 | 49 | cli.MainStart(append(os.Args[1:], input...)) 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 disksing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cli/command/global.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func getEndpoints(cmd *cobra.Command) []string { 14 | addrs, err := cmd.Flags().GetString("pd") 15 | if err != nil { 16 | cmd.Println("get pd address failed, should set flag with '-u'") 17 | os.Exit(1) 18 | } 19 | eps := strings.Split(addrs, ",") 20 | for i, ep := range eps { 21 | if j := strings.Index(ep, "//"); j == -1 { 22 | eps[i] = "//" + ep 23 | } 24 | } 25 | return eps 26 | } 27 | 28 | // GetStoresInfo 29 | func GetStoresInfo(cmd *cobra.Command) (*StoresInfo, error) { 30 | addrs := getEndpoints(cmd) 31 | res, err := http.Get(addrs[0] + "/pd/api/v1/stores") 32 | if err != nil { 33 | return nil, err 34 | } 35 | defer res.Body.Close() 36 | var stores StoresInfo 37 | err = json.NewDecoder(res.Body).Decode(&stores) 38 | if err != nil { 39 | fmt.Println(err) 40 | return nil, err 41 | } 42 | return &stores, nil 43 | } 44 | 45 | func GetRegionsInfo(cmd *cobra.Command) (*RegionsInfo, error) { 46 | addrs := getEndpoints(cmd) 47 | res, err := http.Get(addrs[0] + "/pd/api/v1/regions") 48 | if err != nil { 49 | return nil, err 50 | } 51 | defer res.Body.Close() 52 | var regions RegionsInfo 53 | err = json.NewDecoder(res.Body).Decode(®ions) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return ®ions, nil 58 | } 59 | -------------------------------------------------------------------------------- /cli/ctl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 TiKV Project Authors. 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 cli 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | "os" 20 | "strings" 21 | 22 | "github.com/disksing/region-dist-cli/cli/command" 23 | 24 | "github.com/chzyer/readline" 25 | "github.com/mattn/go-shellwords" 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/pflag" 28 | ) 29 | 30 | func init() { 31 | cobra.EnablePrefixMatching = true 32 | } 33 | 34 | // GetRootCmd is exposed for integration tests. But it can be embedded into another suite, too. 35 | func GetRootCmd() *cobra.Command { 36 | rootCmd := &cobra.Command{ 37 | Use: "pd-analyze", 38 | Short: "Placement Driver Analyze", 39 | } 40 | 41 | rootCmd.PersistentFlags().StringP("pd", "u", "http://172.16.4.3:35434/", "address of pd") 42 | rootCmd.AddCommand( 43 | command.NewHotRegionCommand(), 44 | command.NewRegionCommand(), 45 | ) 46 | rootCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true 47 | rootCmd.SilenceErrors = true 48 | return rootCmd 49 | } 50 | 51 | // MainStart start main command 52 | func MainStart(args []string) { 53 | rootCmd := GetRootCmd() 54 | rootCmd.Flags().BoolP("version", "V", false, "Print version information and exit.") 55 | 56 | rootCmd.Run = func(cmd *cobra.Command, args []string) { 57 | if v, err := cmd.Flags().GetBool("interact"); err == nil && v { 58 | readlineCompleter := readline.NewPrefixCompleter(genCompleter(cmd)...) 59 | loop(cmd.PersistentFlags(), readlineCompleter) 60 | } 61 | } 62 | 63 | rootCmd.SetArgs(args) 64 | rootCmd.ParseFlags(args) 65 | rootCmd.SetOutput(os.Stdout) 66 | 67 | if err := rootCmd.Execute(); err != nil { 68 | rootCmd.Println(err) 69 | os.Exit(1) 70 | } 71 | } 72 | 73 | func loop(persistentFlags *pflag.FlagSet, readlineCompleter readline.AutoCompleter) { 74 | l, err := readline.NewEx(&readline.Config{ 75 | Prompt: "\033[31m»\033[0m ", 76 | HistoryFile: "/tmp/readline.tmp", 77 | AutoComplete: readlineCompleter, 78 | InterruptPrompt: "^C", 79 | EOFPrompt: "^D", 80 | HistorySearchFold: true, 81 | }) 82 | if err != nil { 83 | panic(err) 84 | } 85 | defer l.Close() 86 | 87 | getREPLCmd := func() *cobra.Command { 88 | rootCmd := GetRootCmd() 89 | persistentFlags.VisitAll(func(flag *pflag.Flag) { 90 | if flag.Changed { 91 | rootCmd.PersistentFlags().Set(flag.Name, flag.Value.String()) 92 | } 93 | }) 94 | rootCmd.LocalFlags().MarkHidden("pd") 95 | rootCmd.LocalFlags().MarkHidden("cacert") 96 | rootCmd.LocalFlags().MarkHidden("cert") 97 | rootCmd.LocalFlags().MarkHidden("key") 98 | rootCmd.SetOutput(os.Stdout) 99 | return rootCmd 100 | } 101 | 102 | for { 103 | line, err := l.Readline() 104 | if err != nil { 105 | if err == readline.ErrInterrupt { 106 | break 107 | } else if err == io.EOF { 108 | break 109 | } 110 | continue 111 | } 112 | if line == "exit" { 113 | os.Exit(0) 114 | } 115 | args, err := shellwords.Parse(line) 116 | if err != nil { 117 | fmt.Printf("parse command err: %v\n", err) 118 | continue 119 | } 120 | 121 | rootCmd := getREPLCmd() 122 | rootCmd.SetArgs(args) 123 | rootCmd.ParseFlags(args) 124 | if err := rootCmd.Execute(); err != nil { 125 | rootCmd.Println(err) 126 | } 127 | } 128 | } 129 | 130 | func genCompleter(cmd *cobra.Command) []readline.PrefixCompleterInterface { 131 | pc := []readline.PrefixCompleterInterface{} 132 | 133 | for _, v := range cmd.Commands() { 134 | if v.HasFlags() { 135 | flagsPc := []readline.PrefixCompleterInterface{} 136 | flagUsages := strings.Split(strings.Trim(v.Flags().FlagUsages(), " "), "\n") 137 | for i := 0; i < len(flagUsages)-1; i++ { 138 | flagsPc = append(flagsPc, readline.PcItem(strings.Split(strings.Trim(flagUsages[i], " "), " ")[0])) 139 | } 140 | flagsPc = append(flagsPc, genCompleter(v)...) 141 | pc = append(pc, readline.PcItem(strings.Split(v.Use, " ")[0], flagsPc...)) 142 | } else { 143 | pc = append(pc, readline.PcItem(strings.Split(v.Use, " ")[0], genCompleter(v)...)) 144 | } 145 | } 146 | return pc 147 | } 148 | -------------------------------------------------------------------------------- /cli/command/region_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | "unicode/utf8" 11 | 12 | "github.com/360EntSecGroup-Skylar/excelize/v2" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var stores string 18 | 19 | // NewHotRegionCommand 20 | func NewRegionCommand() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "region", 23 | Short: "show region info of the cluster", 24 | } 25 | cmd.AddCommand( 26 | newRegionPrintCommand(), 27 | newRegionExportCommand()) 28 | cmd.PersistentFlags().StringVarP(&stores, "stores", "s", "", "store") 29 | return cmd 30 | } 31 | 32 | // newRegionPrintCommand 33 | func newRegionPrintCommand() *cobra.Command { 34 | cmd := &cobra.Command{ 35 | Use: "print", 36 | Short: "print regions info ", 37 | Run: PrintRegionsInfo, 38 | } 39 | return cmd 40 | } 41 | 42 | // newRegionPrintCommand 43 | func newRegionExportCommand() *cobra.Command { 44 | cmd := &cobra.Command{ 45 | Use: "export", 46 | Short: "export regions info ", 47 | Run: ExportRegionsInfo, 48 | } 49 | return cmd 50 | } 51 | 52 | func PrintRegionsInfo(cmd *cobra.Command, _ []string) { 53 | stores, err := GetStoresInfo(cmd) 54 | if err != nil { 55 | cmd.Printf("get stores info error :%s \n", err) 56 | } 57 | regions, err := GetRegionsInfo(cmd) 58 | if err != nil { 59 | cmd.Printf("get regions info error :%s \n", err) 60 | } 61 | print(stores.Stores, regions.Regions) 62 | } 63 | 64 | func ExportRegionsInfo(cmd *cobra.Command, _ []string) { 65 | stores, err := GetStoresInfo(cmd) 66 | if err != nil { 67 | cmd.Printf("get stores info error :%s \n", err) 68 | } 69 | regions, err := GetRegionsInfo(cmd) 70 | if err != nil { 71 | cmd.Printf("get regions info error :%s \n", err) 72 | } 73 | if err = export(stores, regions); err != nil { 74 | cmd.Printf("export region error :%s \n", err) 75 | } 76 | } 77 | 78 | func export(stores *StoresInfo, regions *RegionsInfo) error { 79 | f := excelize.NewFile() 80 | storeMap := mapStore(stores) 81 | sheetName := "region" 82 | sheet := f.NewSheet(sheetName) 83 | f.SetCellValue("hot region", "A2", "test") 84 | count := len(stores.Stores) 85 | 86 | a := string('B'+int8(count)) + strconv.Itoa(1) 87 | // record data 88 | for storeId, idx := range storeMap { 89 | a = string('B'+idx) + strconv.Itoa(1) 90 | f.SetCellInt(sheetName, a, int(storeId)) 91 | } 92 | 93 | for k, v := range []string{"leader", "start_key", "end_key", "table", "is_index"} { 94 | a = string('B'+int8(count+k)) + strconv.Itoa(1) 95 | f.SetCellStr(sheetName, a, v) 96 | } 97 | 98 | for i, region := range regions.Regions { 99 | // set regionID 100 | a = string('A') + strconv.Itoa(i+2) 101 | f.SetCellInt(sheetName, a, int(region.ID)) 102 | 103 | // set region leader 104 | a = string('B'+count) + strconv.Itoa(i+2) 105 | f.SetCellInt(sheetName, a, storeMap[region.Leader.StoreId]) 106 | // set read metrics 107 | a = string('B'+count+1) + strconv.Itoa(i+2) 108 | f.SetCellStr(sheetName, a, region.StartKey) 109 | a = string('B'+count+2) + strconv.Itoa(i+2) 110 | f.SetCellStr(sheetName, a, region.EndKey) 111 | 112 | for _, peer := range region.Peers { 113 | index := storeMap[peer.StoreId] 114 | a = string('B'+int8(index)) + strconv.Itoa(i+2) 115 | f.SetCellInt(sheetName, a, 1) 116 | } 117 | } 118 | f.SetActiveSheet(sheet) 119 | if err := f.SaveAs("region.csv"); err != nil { 120 | return err 121 | } 122 | return nil 123 | } 124 | 125 | func print(stores []*StoreInfo, regions []*RegionInfo) { 126 | sort.Slice(regions, func(i, j int) bool { return regions[i].StartKey < regions[j].StartKey }) 127 | maxKeyLen := 6 128 | for _, r := range regions { 129 | r.StartKey, r.EndKey = convertKey(r.StartKey), convertKey(r.EndKey) 130 | if l := fieldLen(r.StartKey); l > maxKeyLen { 131 | maxKeyLen = l 132 | } 133 | if l := fieldLen(r.EndKey); l > maxKeyLen { 134 | maxKeyLen = l 135 | } 136 | } 137 | var maxRegionIDLen int 138 | for _, r := range regions { 139 | if l := fieldLen(r.ID); l > maxRegionIDLen { 140 | maxRegionIDLen = l 141 | } 142 | } 143 | sort.Slice(stores, func(i, j int) bool { return stores[i].Store.ID < stores[j].Store.ID }) 144 | var storeLen []int 145 | for _, s := range stores { 146 | storeLen = append(storeLen, fieldLen(s.Store.ID)) 147 | } 148 | 149 | field(maxRegionIDLen, "", "") 150 | for i := range stores { 151 | field(storeLen[i], "S"+strconv.FormatUint(stores[i].Store.ID, 10), "") 152 | } 153 | field(maxKeyLen, "start", "") 154 | field(maxKeyLen, "end", "") 155 | fmt.Println() 156 | 157 | for _, region := range regions { 158 | field(maxRegionIDLen, "R"+strconv.FormatUint(region.ID, 10), "") 159 | STORE: 160 | for i, s := range stores { 161 | if region.Leader != nil && s.Store.ID == region.Leader.StoreId { 162 | field(storeLen[i], "▀", "\u001b[31m") 163 | continue 164 | } 165 | for _, p := range region.Peers { 166 | if p.StoreId == s.Store.ID { 167 | if p.IsLearner { 168 | field(storeLen[i], "▀", "\u001b[33m") 169 | } else { 170 | field(storeLen[i], "▀", "\u001b[34m") 171 | } 172 | continue STORE 173 | } 174 | } 175 | field(storeLen[i], "", "") 176 | } 177 | field(maxKeyLen, region.StartKey, "") 178 | field(maxKeyLen, region.EndKey, "") 179 | fmt.Println() 180 | } 181 | } 182 | 183 | func convertKey(k string) string { 184 | b, err := hex.DecodeString(k) 185 | if err != nil { 186 | return k 187 | } 188 | d, ok := decodeBytes(b) 189 | if !ok { 190 | return k 191 | } 192 | return strings.ToUpper(hex.EncodeToString(d)) 193 | } 194 | 195 | func fieldLen(f interface{}) int { 196 | return len(fmt.Sprintf("%v", f)) + 2 197 | } 198 | 199 | func field(l int, s string, color string) { 200 | slen := utf8.RuneCountInString(s) 201 | if slen > l { 202 | fmt.Print(s[:l]) 203 | return 204 | } 205 | if slen < l { 206 | fmt.Print(strings.Repeat(" ", l-slen)) 207 | } 208 | if color != "" { 209 | fmt.Print(color) 210 | } 211 | fmt.Print(s) 212 | if color != "" { 213 | fmt.Print("\u001b[0m") 214 | } 215 | } 216 | 217 | func decodeBytes(b []byte) ([]byte, bool) { 218 | var buf bytes.Buffer 219 | for len(b) >= 9 { 220 | if p := 0xff - b[8]; p >= 0 && p <= 8 { 221 | buf.Write(b[:8-p]) 222 | b = b[9:] 223 | } else { 224 | return nil, false 225 | } 226 | } 227 | return buf.Bytes(), len(b) == 0 228 | } 229 | -------------------------------------------------------------------------------- /cli/command/hot_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/360EntSecGroup-Skylar/excelize/v2" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type hotType string 15 | 16 | const ( 17 | // Initiated by admin. 18 | Read hotType = "/hotspot/regions/read" 19 | // Initiated by merge checker or merge scheduler. Note that it may not include region merge. 20 | // the order describe the operator's producer and is very helpful to decouple scheduler or checker limit 21 | Write hotType = "/hotspot/regions/write" 22 | ) 23 | 24 | var defaultHotType = Read 25 | 26 | // NewHotRegionCommand 27 | func NewHotRegionCommand() *cobra.Command { 28 | cmd := &cobra.Command{ 29 | Use: "hot", 30 | Short: "export hot region info of the cluster", 31 | } 32 | cmd.AddCommand( 33 | newReadHotExportCommand(), 34 | newWriteHotExportCommand()) 35 | return cmd 36 | } 37 | 38 | func newReadHotExportCommand() *cobra.Command { 39 | cmd := &cobra.Command{ 40 | Use: "read", 41 | Short: "export hot read regions info ", 42 | Run: ShowRegionDistributionFnc, 43 | } 44 | return cmd 45 | } 46 | 47 | func newWriteHotExportCommand() *cobra.Command { 48 | pre := func(cmd *cobra.Command, args []string) { 49 | defaultHotType = Write 50 | } 51 | cmd := &cobra.Command{ 52 | Use: "write", 53 | Short: "export hot write regions info ", 54 | Run: ShowRegionDistributionFnc, 55 | PreRun: pre, 56 | } 57 | return cmd 58 | } 59 | 60 | func ShowRegionDistributionFnc(cmd *cobra.Command, _ []string) { 61 | stores, err := GetStoresInfo(cmd) 62 | if err != nil { 63 | cmd.Printf("get stores info error :%s \n", err) 64 | } 65 | regions, err := GetRegionsInfo(cmd) 66 | if err != nil { 67 | cmd.Printf("get regions info error :%s \n", err) 68 | } 69 | pd := getEndpoints(cmd) 70 | export := NewHotRegionExport(pd[0], stores, regions) 71 | err = export.prepare() 72 | if err != nil { 73 | cmd.Printf("get regions info error :%s \n", err) 74 | } 75 | err = export.export() 76 | if err != nil { 77 | cmd.Printf("export regions info error :%s \n", err) 78 | } 79 | } 80 | 81 | type StoreInfos struct { 82 | StoreHotPeersStat *StoreHotPeersInfos 83 | topReadPath string 84 | pd string 85 | storeDic map[uint64]int 86 | regionDic map[uint64]*RegionInfo 87 | } 88 | type StoreHotPeersInfos struct { 89 | AsPeer map[string]*HotPeersStat `json:"as_peer"` 90 | AsLeader map[string]*HotPeersStat `json:"as_leader"` 91 | } 92 | 93 | // HotPeersStat records all hot regions statistics 94 | type HotPeersStat struct { 95 | TotalLoads []float64 `json:"-"` 96 | TotalBytesRate float64 `json:"total_flow_bytes"` 97 | TotalKeysRate float64 `json:"total_flow_keys"` 98 | TotalQueryRate float64 `json:"total_flow_query"` 99 | Count int `json:"regions_count"` 100 | Stats []HotPeerStatShow `json:"statistics"` 101 | } 102 | 103 | // HotPeerStatShow records the hot region statistics for output 104 | type HotPeerStatShow struct { 105 | StoreID uint64 `json:"store_id"` 106 | RegionID uint64 `json:"region_id"` 107 | HotDegree int `json:"hot_degree"` 108 | ByteRate float64 `json:"flow_bytes"` 109 | KeyRate float64 `json:"flow_keys"` 110 | QueryRate float64 `json:"flow_query"` 111 | AntiCount int `json:"anti_count"` 112 | LastUpdateTime time.Time `json:"last_update_time"` 113 | } 114 | 115 | func NewHotRegionExport(pd string, stores *StoresInfo, regions *RegionsInfo) *StoreInfos { 116 | storeDic := mapStore(stores) 117 | regionDic := mapRegion(regions) 118 | return &StoreInfos{ 119 | pd: pd, 120 | topReadPath: "/pd/api/v1/" + string(defaultHotType), 121 | storeDic: storeDic, 122 | regionDic: regionDic, 123 | } 124 | } 125 | 126 | func (h *StoreInfos) prepare() error { 127 | fmt.Printf("url:%s \n", h.pd+h.topReadPath) 128 | res, err := http.Get(h.pd + h.topReadPath) 129 | if err != nil { 130 | return err 131 | } 132 | defer res.Body.Close() 133 | var s StoreHotPeersInfos 134 | err = json.NewDecoder(res.Body).Decode(&s) 135 | if err != nil { 136 | return err 137 | } 138 | h.StoreHotPeersStat = &s 139 | return nil 140 | } 141 | 142 | func mapStore(stores *StoresInfo) map[uint64]int { 143 | dic := make(map[uint64]int, len(stores.Stores)) 144 | for i, v := range stores.Stores { 145 | dic[v.Store.ID] = i 146 | } 147 | return dic 148 | } 149 | 150 | // mapRegion returns the relationship between region leader and stores 151 | func mapRegion(regions *RegionsInfo) map[uint64]*RegionInfo { 152 | rst := make(map[uint64]*RegionInfo, len(regions.Regions)) 153 | for _, v := range regions.Regions { 154 | rst[v.ID] = v 155 | } 156 | return rst 157 | } 158 | 159 | func (h *StoreInfos) export() error { 160 | f := excelize.NewFile() 161 | sheetName := "hot-region" 162 | sheet := f.NewSheet(sheetName) 163 | count := len(h.storeDic) 164 | a := "" 165 | _ = f.SetCellStr(sheetName, "A1", "region") 166 | for k, v := range []string{"lead", "read_bytes", "read_keys", "read_qps", "write_bytes", "write_bytes", "write_qps", 167 | "start_key", "end_key", "table", "is_index"} { 168 | a = string(rune('B'+count+k)) + strconv.Itoa(1) 169 | _ = f.SetCellStr(sheetName, a, v) 170 | } 171 | 172 | regionCount := 1 173 | // record data 174 | for id, store := range h.StoreHotPeersStat.AsLeader { 175 | storeID, _ := strconv.Atoi(id) 176 | a = string(rune('B'+h.storeDic[uint64(storeID)])) + strconv.Itoa(1) 177 | _ = f.SetCellInt(sheetName, a, storeID) 178 | for _, region := range store.Stats { 179 | regionCount++ 180 | // set regionID 181 | a = string('A') + strconv.Itoa(regionCount) 182 | _ = f.SetCellInt(sheetName, a, int(region.RegionID)) 183 | 184 | // set region leader 185 | a = string(rune('B'+count)) + strconv.Itoa(regionCount) 186 | _ = f.SetCellInt(sheetName, a, h.storeDic[uint64(storeID)]) 187 | 188 | // set read metrics 189 | if defaultHotType == Read { 190 | for k, v := range []float64{region.ByteRate, region.KeyRate, region.QueryRate} { 191 | a = string(rune('B'+count+k+1)) + strconv.Itoa(regionCount) 192 | _ = f.SetCellFloat(sheetName, a, v, 2, 32) 193 | } 194 | } else { 195 | for k, v := range []float64{region.ByteRate, region.KeyRate, region.QueryRate} { 196 | a = string(rune('B'+count+k+1+3)) + strconv.Itoa(regionCount) 197 | _ = f.SetCellFloat(sheetName, a, v, 2, 32) 198 | } 199 | } 200 | 201 | regionInfo := h.regionDic[region.RegionID] 202 | // set read metrics 203 | a = string(rune('B'+count+7)) + strconv.Itoa(regionCount) 204 | _ = f.SetCellStr(sheetName, a, regionInfo.StartKey) 205 | a = string(rune('B'+count+8)) + strconv.Itoa(regionCount) 206 | _ = f.SetCellStr(sheetName, a, regionInfo.EndKey) 207 | 208 | for _, peer := range regionInfo.Peers { 209 | index := h.storeDic[peer.StoreId] 210 | a = string(rune('B'+int8(index))) + strconv.Itoa(regionCount) 211 | 212 | _ = f.SetCellInt(sheetName, a, 1) 213 | } 214 | } 215 | } 216 | 217 | // set total metrics 218 | for _, v := range []string{"total_bytes", "total_keys", "total_qps"} { 219 | regionCount++ 220 | a = string('A') + strconv.Itoa(regionCount) 221 | _ = f.SetCellStr(sheetName, a, v) 222 | for id, store := range h.StoreHotPeersStat.AsLeader { 223 | storeID, _ := strconv.Atoi(id) 224 | a = string('B'+int8(h.storeDic[uint64(storeID)])) + strconv.Itoa(regionCount) 225 | switch v { 226 | case "total_bytes": 227 | _ = f.SetCellFloat(sheetName, a, store.TotalBytesRate, 2, 32) 228 | case "total_keys": 229 | _ = f.SetCellFloat(sheetName, a, store.TotalKeysRate, 2, 32) 230 | case "total_qps": 231 | _ = f.SetCellFloat(sheetName, a, store.TotalQueryRate, 2, 32) 232 | } 233 | } 234 | } 235 | 236 | f.SetActiveSheet(sheet) 237 | if err := f.SaveAs(sheetName + ".csv"); err != nil { 238 | return err 239 | } 240 | return nil 241 | } 242 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.0 h1:2lpOhz+vjzm+RsDid1AK2S5Gief3aX2fLPJ6SzY8F9U= 3 | github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.0/go.mod h1:fkN+AZg31J/y9B4AtylxjzzNYetIxi6cElYu/WBUb7g= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 6 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 7 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 8 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 10 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 11 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 12 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 13 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 14 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 15 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 16 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 17 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 20 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 21 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 22 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 23 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 24 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 29 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 30 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 31 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 32 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 33 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 34 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 35 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 36 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 37 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 38 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 39 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 44 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 45 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 46 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 47 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 48 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 49 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 50 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 51 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 52 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 53 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 54 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 55 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 56 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 57 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 58 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 59 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 60 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 61 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 62 | github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk= 63 | github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= 64 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 65 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 66 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 67 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 68 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 69 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 70 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 71 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 72 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 73 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 76 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 77 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 78 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 79 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 80 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 81 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 82 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 83 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 84 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 85 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 86 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 87 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 88 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 89 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 90 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 91 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 92 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 93 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 94 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 95 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 96 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 97 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 98 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 99 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 100 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 102 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 103 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 104 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 105 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 106 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 107 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 108 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 109 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 110 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 111 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 112 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 113 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 114 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 115 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 116 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 117 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 118 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 119 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 120 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 121 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 122 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 125 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 128 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 130 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 131 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 132 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 133 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 134 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 135 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 136 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 137 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 138 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 139 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 140 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 141 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 142 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 144 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 145 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 146 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 147 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 148 | --------------------------------------------------------------------------------