github.com/m3db/m3@v1.5.0/src/cmd/tools/verify_index_files/main/main.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // This tool can be used to verify that the index files for multiple hosts that 22 // share the same shards are the same. If they're not identical, it will output 23 // the name of any series that are missing, or have mismatched checksums. The tool 24 // expects a directory where each subdirectory is the name of the host, and within 25 // each of those subdirectories is the "data" directory for that host, exactly as 26 // generated by M3DB itself. See the README for more details. 27 28 package main 29 30 import ( 31 "flag" 32 "io" 33 "io/ioutil" 34 "log" 35 _ "net/http/pprof" 36 "os" 37 "path" 38 "strconv" 39 "strings" 40 "time" 41 42 "github.com/m3db/m3/src/cmd/tools" 43 "github.com/m3db/m3/src/dbnode/persist/fs" 44 "github.com/m3db/m3/src/x/ident" 45 "github.com/m3db/m3/src/x/pool" 46 xtime "github.com/m3db/m3/src/x/time" 47 ) 48 49 var flagParser = flag.NewFlagSet("Verify Index files", flag.ExitOnError) 50 51 type seriesChecksums struct { 52 host string 53 shard uint32 54 block int64 55 series seriesMap 56 } 57 58 type seriesMap map[string]series 59 60 type checksum uint32 61 62 type series struct { 63 name string 64 checksum checksum 65 } 66 67 var ( 68 pathPrefixArg = flagParser.String( 69 "path-prefix", 70 "/var/lib/m3db", 71 "Path prefix - must contain a folder called 'commitlogs'", 72 ) 73 namespaceArg = flagParser.String( 74 "namespace", 75 "metrics", 76 "Namespace", 77 ) 78 shardsArg = flagParser.String( 79 "shards", 80 "", 81 "Shards - set comma separated list of shards", 82 ) 83 blocksArgs = flagParser.String( 84 "blocks", 85 "", 86 "Start unix timestamp (Seconds) - set comma separated list of unix timestamps", 87 ) 88 compareChecksumsArg = flagParser.Bool("compare-checksums", true, "Compare checksums") 89 ) 90 91 var bytesPool pool.CheckedBytesPool 92 93 func main() { 94 flagParser.Parse(os.Args[1:]) 95 96 log.SetOutput(os.Stdout) 97 log.Println("initializing bytes pool...") 98 bytesPool = tools.NewCheckedBytesPool() 99 100 var ( 101 pathPrefix = *pathPrefixArg 102 namespaceStr = *namespaceArg 103 shardsVal = *shardsArg 104 blocksVal = *blocksArgs 105 compareChecksums = *compareChecksumsArg 106 ) 107 108 blocks := parseBlockArgs(blocksVal) 109 hosts, err := ioutil.ReadDir(pathPrefix) 110 if err != nil { 111 log.Fatalf("err reading dir: %s, err: %s\n", pathPrefix, err) 112 } 113 114 shards := parseShards(shardsVal) 115 for _, block := range blocks { 116 for _, shard := range shards { 117 log.Printf("running test for shard: %d\n", shard) 118 allHostSeriesChecksumsForShard := []seriesChecksums{} 119 // Accumulate all the series checksums for each host for this shard 120 for _, host := range hosts { 121 start := (xtime.UnixNano(block)).FromNormalizedTime(time.Second) 122 hostShardReader, err := newReader(namespaceStr, pathPrefix, host.Name(), shard, start) 123 if err != nil { 124 // Ignore folders for hosts that don't have this data 125 if err == fs.ErrCheckpointFileNotFound { 126 continue 127 } 128 log.Fatalf("err creating new reader: %s\n", err.Error()) 129 } 130 hostShardSeriesChecksums := seriesChecksumsFromReader(hostShardReader, host.Name(), shard, block) 131 allHostSeriesChecksumsForShard = append(allHostSeriesChecksumsForShard, hostShardSeriesChecksums) 132 } 133 134 allHostSeriesMapsForShard := []seriesMap{} 135 for _, seriesChecksum := range allHostSeriesChecksumsForShard { 136 allHostSeriesMapsForShard = append(allHostSeriesMapsForShard, seriesChecksum.series) 137 } 138 allSeriesForShard := mergeMaps(allHostSeriesMapsForShard...) 139 for _, hostSeriesForShard := range allHostSeriesChecksumsForShard { 140 compareSeriesChecksums(allSeriesForShard, hostSeriesForShard, compareChecksums) 141 } 142 } 143 } 144 } 145 146 func seriesChecksumsFromReader( 147 reader fs.DataFileSetReader, host string, shard uint32, block int64, 148 ) seriesChecksums { 149 seriesMap := seriesMap{} 150 seriesChecksums := seriesChecksums{ 151 host: host, 152 series: seriesMap, 153 shard: shard, 154 block: block, 155 } 156 for { 157 id, _, _, checksumVal, err := reader.ReadMetadata() 158 if err == io.EOF { 159 return seriesChecksums 160 } 161 if err != nil { 162 log.Fatal("err reading from reader: ", err.Error()) 163 } 164 idString := id.String() 165 seriesMap[idString] = series{ 166 name: idString, 167 checksum: checksum(checksumVal), 168 } 169 } 170 } 171 172 func compareSeriesChecksums(against seriesMap, evaluate seriesChecksums, compareChecksums bool) { 173 againstMap := against 174 evaluateMap := evaluate.series 175 missingSeries := []series{} 176 checksumMismatchSeries := []series{} 177 178 if len(againstMap) == len(evaluateMap) { 179 log.Printf( 180 "host %s has all %d series for shard: %d and block: %d", 181 evaluate.host, len(againstMap), evaluate.shard, evaluate.block, 182 ) 183 } else { 184 log.Printf( 185 "host %s has %d series, but there are a total of %d series in shard: %d and block %d\n", 186 evaluate.host, len(evaluateMap), len(againstMap), evaluate.shard, evaluate.block, 187 ) 188 } 189 190 for seriesName, againstSeries := range againstMap { 191 evaluateSeries, ok := evaluateMap[seriesName] 192 193 if !ok { 194 missingSeries = append(missingSeries, againstSeries) 195 continue 196 } 197 198 if compareChecksums && againstSeries.checksum != evaluateSeries.checksum { 199 checksumMismatchSeries = append(checksumMismatchSeries, againstSeries) 200 } 201 } 202 203 for _, missing := range missingSeries { 204 log.Printf("host %s is missing %s\n", evaluate.host, missing.name) 205 } 206 207 for _, mismatch := range checksumMismatchSeries { 208 log.Printf("host %s has mismatching checksum for %s\n", evaluate.host, mismatch.name) 209 } 210 } 211 212 func mergeMaps(seriesMaps ...seriesMap) seriesMap { 213 merged := seriesMap{} 214 for _, sMap := range seriesMaps { 215 for key, val := range sMap { 216 merged[key] = val 217 } 218 } 219 return merged 220 } 221 222 func newReader( 223 namespace, pathPrefix, hostName string, shard uint32, start xtime.UnixNano, 224 ) (fs.DataFileSetReader, error) { 225 fsOpts := fs.NewOptions().SetFilePathPrefix(path.Join(pathPrefix, hostName)) 226 reader, err := fs.NewReader(bytesPool, fsOpts) 227 if err != nil { 228 return nil, err 229 } 230 231 openOpts := fs.DataReaderOpenOptions{ 232 Identifier: fs.FileSetFileIdentifier{ 233 Namespace: ident.StringID(namespace), 234 Shard: shard, 235 BlockStart: start, 236 }, 237 } 238 err = reader.Open(openOpts) 239 return reader, err 240 } 241 242 func parseShards(shards string) []uint32 { 243 var allShards []uint32 244 245 if strings.TrimSpace(shards) == "" { 246 return []uint32{} 247 } 248 249 // Handle comma-delimited shard list 1,3,5, etc 250 for _, shard := range strings.Split(shards, ",") { 251 shard = strings.TrimSpace(shard) 252 if shard == "" { 253 log.Fatalf("invalid shard list: '%s'", shards) 254 } 255 value, err := strconv.Atoi(shard) 256 if err != nil { 257 log.Fatalf("could not parse shard '%s': %v", shard, err) 258 } 259 allShards = append(allShards, uint32(value)) 260 } 261 262 return allShards 263 } 264 265 func parseBlockArgs(blocks string) []int64 { 266 allBlocks := []int64{} 267 268 for _, block := range strings.Split(blocks, ",") { 269 block = strings.TrimSpace(block) 270 if block == "" { 271 log.Fatalf("invalid block list: '%s'", block) 272 } 273 value, err := strconv.Atoi(block) 274 if err != nil { 275 log.Fatalf("could not parse blocks '%s': %v", block, err) 276 } 277 allBlocks = append(allBlocks, int64(value)) 278 } 279 280 return allBlocks 281 }