github.com/m3db/m3@v1.5.0/src/cmd/tools/annotation_checker/main/main.go (about)

     1  // Copyright (c) 2021 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  package main
    22  
    23  import (
    24  	"encoding/base64"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"log"
    29  	"os"
    30  	"sort"
    31  	"strconv"
    32  
    33  	"github.com/pborman/getopt"
    34  	"go.uber.org/zap"
    35  
    36  	"github.com/m3db/m3/src/dbnode/encoding"
    37  	"github.com/m3db/m3/src/dbnode/encoding/m3tsz"
    38  	"github.com/m3db/m3/src/dbnode/persist"
    39  	"github.com/m3db/m3/src/dbnode/persist/fs"
    40  	"github.com/m3db/m3/src/dbnode/x/xio"
    41  	xerrors "github.com/m3db/m3/src/x/errors"
    42  	"github.com/m3db/m3/src/x/ident"
    43  	"github.com/m3db/m3/src/x/pool"
    44  	xtime "github.com/m3db/m3/src/x/time"
    45  )
    46  
    47  const (
    48  	snapshotType = "snapshot"
    49  	flushType    = "flush"
    50  
    51  	allShards = -1
    52  )
    53  
    54  func main() {
    55  	var (
    56  		optPathPrefix  = getopt.StringLong("path-prefix", 'p', "", "Path prefix [e.g. /var/lib/m3db]")
    57  		fileSetTypeArg = getopt.StringLong("fileset-type", 't', flushType, fmt.Sprintf("%s|%s", flushType, snapshotType))
    58  		optNamespace   = getopt.StringLong("namespace", 'n', "default", "Namespace [e.g. metrics]")
    59  		optShard       = getopt.IntLong("shard", 's', allShards,
    60  			fmt.Sprintf("Shard number, or %v for all shards in the directory", allShards))
    61  		optBlockstart = getopt.Int64Long("block-start", 'b', 0, "Block Start Time [in nsec]")
    62  		volume        = getopt.Int64Long("volume", 'v', 0, "Volume number")
    63  
    64  		annotationRewrittenFilter = getopt.BoolLong("annotation-rewritten", 'R', "Filters metrics with annotation rewrites")
    65  	)
    66  	getopt.Parse()
    67  
    68  	rawLogger, err := zap.NewDevelopment()
    69  	if err != nil {
    70  		log.Fatalf("unable to create logger: %+v", err)
    71  	}
    72  	log := rawLogger.Sugar()
    73  
    74  	if *optPathPrefix == "" ||
    75  		*optNamespace == "" ||
    76  		*optShard < allShards ||
    77  		*optBlockstart <= 0 ||
    78  		*volume < 0 ||
    79  		(*fileSetTypeArg != snapshotType && *fileSetTypeArg != flushType) {
    80  		getopt.Usage()
    81  		os.Exit(1)
    82  	}
    83  
    84  	var fileSetType persist.FileSetType
    85  	switch *fileSetTypeArg {
    86  	case flushType:
    87  		fileSetType = persist.FileSetFlushType
    88  	case snapshotType:
    89  		fileSetType = persist.FileSetSnapshotType
    90  	default:
    91  		log.Fatalf("unknown fileset type: %s", *fileSetTypeArg)
    92  	}
    93  
    94  	shards := []uint32{uint32(*optShard)}
    95  	if *optShard == allShards {
    96  		shards, err = getShards(*optPathPrefix, fileSetType, *optNamespace)
    97  		if err != nil {
    98  			log.Fatalf("failed extracting the list of shards: %v", err)
    99  		}
   100  	}
   101  
   102  	// Not using bytes pool with streaming reads/writes to avoid the fixed memory overhead.
   103  	var bytesPool pool.CheckedBytesPool
   104  	encodingOpts := encoding.NewOptions().SetBytesPool(bytesPool)
   105  
   106  	fsOpts := fs.NewOptions().SetFilePathPrefix(*optPathPrefix)
   107  	reader, err := fs.NewReader(bytesPool, fsOpts)
   108  	if err != nil {
   109  		log.Fatalf("could not create new reader: %v", err)
   110  	}
   111  
   112  	metricsMap := make(map[string]struct{})
   113  
   114  	for _, shard := range shards {
   115  		openOpts := fs.DataReaderOpenOptions{
   116  			Identifier: fs.FileSetFileIdentifier{
   117  				Namespace:   ident.StringID(*optNamespace),
   118  				Shard:       shard,
   119  				BlockStart:  xtime.UnixNano(*optBlockstart),
   120  				VolumeIndex: int(*volume),
   121  			},
   122  			FileSetType:      fileSetType,
   123  			StreamingEnabled: true,
   124  		}
   125  
   126  		err = reader.Open(openOpts)
   127  		if err != nil {
   128  			log.Fatalf("unable to open reader for shard %v: %v", shard, err)
   129  		}
   130  
   131  		for {
   132  			entry, err := reader.StreamingRead()
   133  			if xerrors.Is(err, io.EOF) {
   134  				break
   135  			}
   136  			if err != nil {
   137  				log.Fatalf("err reading metadata: %v", err)
   138  			}
   139  
   140  			noInitialAnnotation, annotationRewritten, err := checkAnnotations(entry.Data, encodingOpts)
   141  			if err != nil {
   142  				log.Fatalf("failed checking annotations: %v", err)
   143  			}
   144  
   145  			if (!*annotationRewrittenFilter && noInitialAnnotation) || (*annotationRewrittenFilter && annotationRewritten) {
   146  				metricsMap[entry.ID.String()] = struct{}{}
   147  			}
   148  		}
   149  
   150  		if err := reader.Close(); err != nil {
   151  			log.Fatalf("unable to close reader: %v", err)
   152  		}
   153  	}
   154  
   155  	metrics := make([]string, 0)
   156  	for m := range metricsMap {
   157  		metrics = append(metrics, m)
   158  	}
   159  	sort.Strings(metrics)
   160  	for _, metric := range metrics {
   161  		fmt.Println(metric) // nolint: forbidigo
   162  	}
   163  }
   164  
   165  func checkAnnotations(data []byte, encodingOpts encoding.Options) (bool, bool, error) {
   166  	iter := m3tsz.NewReaderIterator(xio.NewBytesReader64(data), true, encodingOpts)
   167  	defer iter.Close()
   168  
   169  	var (
   170  		previousAnnotationBase64 *string
   171  		noInitialAnnotation      = true
   172  		annotationRewritten      = false
   173  
   174  		firstDatapoint = true
   175  	)
   176  
   177  	for iter.Next() {
   178  		_, _, annotation := iter.Current()
   179  		if len(annotation) > 0 {
   180  			if firstDatapoint {
   181  				noInitialAnnotation = false
   182  			}
   183  			annotationBase64 := base64.StdEncoding.EncodeToString(annotation)
   184  			if previousAnnotationBase64 != nil && *previousAnnotationBase64 != annotationBase64 {
   185  				annotationRewritten = true
   186  			}
   187  			previousAnnotationBase64 = &annotationBase64
   188  		}
   189  		firstDatapoint = false
   190  	}
   191  	if err := iter.Err(); err != nil {
   192  		return false, false, fmt.Errorf("unable to iterate original data: %w", err)
   193  	}
   194  
   195  	return noInitialAnnotation, annotationRewritten, nil
   196  }
   197  
   198  func getShards(pathPrefix string, fileSetType persist.FileSetType, namespace string) ([]uint32, error) {
   199  	nsID := ident.StringID(namespace)
   200  	path := fs.NamespaceDataDirPath(pathPrefix, nsID)
   201  	if fileSetType == persist.FileSetSnapshotType {
   202  		path = fs.NamespaceSnapshotsDirPath(pathPrefix, nsID)
   203  	}
   204  
   205  	files, err := ioutil.ReadDir(path)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("failed reading namespace directory: %w", err)
   208  	}
   209  
   210  	shards := make([]uint32, 0)
   211  	for _, f := range files {
   212  		if !f.IsDir() {
   213  			continue
   214  		}
   215  		i, err := strconv.Atoi(f.Name())
   216  		if err != nil {
   217  			return nil, fmt.Errorf("failed extracting shard number: %w", err)
   218  		}
   219  		if i < 0 {
   220  			return nil, fmt.Errorf("negative shard number %v", i)
   221  		}
   222  		shards = append(shards, uint32(i))
   223  	}
   224  
   225  	return shards, nil
   226  }