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 }