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  }