github.com/thanos-io/thanos@v0.32.5/pkg/block/metadata/meta.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package metadata 5 6 // metadata package implements writing and reading wrapped meta.json where Thanos puts its metadata. 7 // Those metadata contains external labels, downsampling resolution and source type. 8 // This package is minimal and separated because it used by testutils which limits test helpers we can use in 9 // this package. 10 11 import ( 12 "encoding/json" 13 "fmt" 14 "io" 15 "os" 16 "path/filepath" 17 18 "github.com/go-kit/log" 19 "github.com/oklog/ulid" 20 "github.com/pkg/errors" 21 "github.com/prometheus/prometheus/model/labels" 22 "github.com/prometheus/prometheus/model/relabel" 23 "github.com/prometheus/prometheus/promql/parser" 24 "github.com/prometheus/prometheus/tsdb" 25 "github.com/prometheus/prometheus/tsdb/fileutil" 26 "github.com/prometheus/prometheus/tsdb/tombstones" 27 "gopkg.in/yaml.v3" 28 29 "github.com/thanos-io/thanos/pkg/runutil" 30 ) 31 32 type SourceType string 33 34 const ( 35 // TODO(bwplotka): Merge with pkg/component package. 36 UnknownSource SourceType = "" 37 SidecarSource SourceType = "sidecar" 38 ReceiveSource SourceType = "receive" 39 CompactorSource SourceType = "compactor" 40 CompactorRepairSource SourceType = "compactor.repair" 41 RulerSource SourceType = "ruler" 42 BucketRepairSource SourceType = "bucket.repair" 43 BucketRewriteSource SourceType = "bucket.rewrite" 44 TestSource SourceType = "test" 45 ) 46 47 const ( 48 // MetaFilename is the known JSON filename for meta information. 49 MetaFilename = "meta.json" 50 // TSDBVersion1 is a enumeration of TSDB meta versions supported by Thanos. 51 TSDBVersion1 = 1 52 // ThanosVersion1 is a enumeration of Thanos section of TSDB meta supported by Thanos. 53 ThanosVersion1 = 1 54 ) 55 56 // Meta describes the a block's meta. It wraps the known TSDB meta structure and 57 // extends it by Thanos-specific fields. 58 type Meta struct { 59 tsdb.BlockMeta 60 61 Thanos Thanos `json:"thanos"` 62 } 63 64 func (m *Meta) String() string { 65 return fmt.Sprintf("%s (min time: %d, max time: %d)", m.ULID, m.MinTime, m.MaxTime) 66 } 67 68 // Thanos holds block meta information specific to Thanos. 69 type Thanos struct { 70 // Version of Thanos meta file. If none specified, 1 is assumed (since first version did not have explicit version specified). 71 Version int `json:"version,omitempty"` 72 73 // Labels are the external labels identifying the producer as well as tenant. 74 // See https://thanos.io/tip/thanos/storage.md#external-labels for details. 75 Labels map[string]string `json:"labels"` 76 Downsample ThanosDownsample `json:"downsample"` 77 78 // Source is a real upload source of the block. 79 Source SourceType `json:"source"` 80 81 // List of segment files (in chunks directory), in sorted order. Optional. 82 // Deprecated. Use Files instead. 83 SegmentFiles []string `json:"segment_files,omitempty"` 84 85 // File is a sorted (by rel path) list of all files in block directory of this block known to TSDB. 86 // Sorted by relative path. 87 // Useful to avoid API call to get size of each file, as well as for debugging purposes. 88 // Optional, added in v0.17.0. 89 Files []File `json:"files,omitempty"` 90 91 // Rewrites is present when any rewrite (deletion, relabel etc) were applied to this block. Optional. 92 Rewrites []Rewrite `json:"rewrites,omitempty"` 93 94 // IndexStats contains stats info related to block index. 95 IndexStats IndexStats `json:"index_stats,omitempty"` 96 97 // Extensions are used for plugin any arbitrary additional information for block. Optional. 98 Extensions any `json:"extensions,omitempty"` 99 } 100 101 type IndexStats struct { 102 SeriesMaxSize int64 `json:"series_max_size,omitempty"` 103 ChunkMaxSize int64 `json:"chunk_max_size,omitempty"` 104 } 105 106 func (m *Thanos) ParseExtensions(v any) (any, error) { 107 return ConvertExtensions(m.Extensions, v) 108 } 109 110 // ConvertExtensions converts extensions with `any` type into specific type `v` 111 // that the caller expects. 112 func ConvertExtensions(extensions any, v any) (any, error) { 113 if extensions == nil { 114 return nil, nil 115 } 116 extensionsContent, err := json.Marshal(extensions) 117 if err != nil { 118 return nil, err 119 } 120 if err = json.Unmarshal(extensionsContent, v); err != nil { 121 return nil, err 122 } 123 return v, nil 124 } 125 126 type Rewrite struct { 127 // ULIDs of all source head blocks that went into the block. 128 Sources []ulid.ULID `json:"sources,omitempty"` 129 // Deletions if applied (in order). 130 DeletionsApplied []DeletionRequest `json:"deletions_applied,omitempty"` 131 // Relabels if applied. 132 RelabelsApplied []*relabel.Config `json:"relabels_applied,omitempty"` 133 } 134 135 type Matchers []*labels.Matcher 136 137 func (m *Matchers) UnmarshalYAML(value *yaml.Node) (err error) { 138 *m, err = parser.ParseMetricSelector(value.Value) 139 if err != nil { 140 return errors.Wrapf(err, "parse metric selector %v", value.Value) 141 } 142 return nil 143 } 144 145 type DeletionRequest struct { 146 Matchers Matchers `json:"matchers" yaml:"matchers"` 147 Intervals tombstones.Intervals `json:"intervals,omitempty" yaml:"intervals,omitempty"` 148 RequestID string `json:"request_id,omitempty" yaml:"request_id,omitempty"` 149 } 150 151 type File struct { 152 RelPath string `json:"rel_path"` 153 // SizeBytes is optional (e.g meta.json does not show size). 154 SizeBytes int64 `json:"size_bytes,omitempty"` 155 156 // Hash is an optional hash of this file. Used for potentially avoiding an extra download. 157 Hash *ObjectHash `json:"hash,omitempty"` 158 } 159 160 type ThanosDownsample struct { 161 Resolution int64 `json:"resolution"` 162 } 163 164 // InjectThanos sets Thanos meta to the block meta JSON and saves it to the disk. 165 // NOTE: It should be used after writing any block by any Thanos component, otherwise we will miss crucial metadata. 166 func InjectThanos(logger log.Logger, bdir string, meta Thanos, downsampledMeta *tsdb.BlockMeta) (*Meta, error) { 167 newMeta, err := ReadFromDir(bdir) 168 if err != nil { 169 return nil, errors.Wrap(err, "read new meta") 170 } 171 newMeta.Thanos = meta 172 173 // While downsampling we need to copy original compaction. 174 if downsampledMeta != nil { 175 newMeta.Compaction = downsampledMeta.Compaction 176 } 177 178 if err := newMeta.WriteToDir(logger, bdir); err != nil { 179 return nil, errors.Wrap(err, "write new meta") 180 } 181 182 return newMeta, nil 183 } 184 185 // GroupKey returns a unique identifier for the compaction group the block belongs to. 186 // It considers the downsampling resolution and the block's labels. 187 func (m *Thanos) GroupKey() string { 188 return fmt.Sprintf("%d@%v", m.Downsample.Resolution, labels.FromMap(m.Labels).Hash()) 189 } 190 191 // ResolutionString returns a the block's resolution as a string. 192 func (m *Thanos) ResolutionString() string { 193 return fmt.Sprintf("%d", m.Downsample.Resolution) 194 } 195 196 // WriteToDir writes the encoded meta into <dir>/meta.json. 197 func (m Meta) WriteToDir(logger log.Logger, dir string) error { 198 // Make any changes to the file appear atomic. 199 path := filepath.Join(dir, MetaFilename) 200 tmp := path + ".tmp" 201 202 f, err := os.Create(tmp) 203 if err != nil { 204 return err 205 } 206 207 if err := m.Write(f); err != nil { 208 runutil.CloseWithLogOnErr(logger, f, "close meta") 209 return err 210 } 211 if err := f.Close(); err != nil { 212 return err 213 } 214 return renameFile(logger, tmp, path) 215 } 216 217 // Write writes the given encoded meta to writer. 218 func (m Meta) Write(w io.Writer) error { 219 enc := json.NewEncoder(w) 220 enc.SetIndent("", "\t") 221 return enc.Encode(&m) 222 } 223 224 func renameFile(logger log.Logger, from, to string) error { 225 if err := os.RemoveAll(to); err != nil { 226 return err 227 } 228 if err := os.Rename(from, to); err != nil { 229 return err 230 } 231 232 // Directory was renamed; sync parent dir to persist rename. 233 pdir, err := fileutil.OpenDir(filepath.Dir(to)) 234 if err != nil { 235 return err 236 } 237 238 if err = fileutil.Fdatasync(pdir); err != nil { 239 runutil.CloseWithLogOnErr(logger, pdir, "close dir") 240 return err 241 } 242 return pdir.Close() 243 } 244 245 // ReadFromDir reads the given meta from <dir>/meta.json. 246 func ReadFromDir(dir string) (*Meta, error) { 247 f, err := os.Open(filepath.Join(dir, filepath.Clean(MetaFilename))) 248 if err != nil { 249 return nil, err 250 } 251 return Read(f) 252 } 253 254 // Read the block meta from the given reader. 255 func Read(rc io.ReadCloser) (_ *Meta, err error) { 256 defer runutil.ExhaustCloseWithErrCapture(&err, rc, "close meta JSON") 257 258 var m Meta 259 if err = json.NewDecoder(rc).Decode(&m); err != nil { 260 return nil, err 261 } 262 263 if m.Version != TSDBVersion1 { 264 return nil, errors.Errorf("unexpected meta file version %d", m.Version) 265 } 266 267 version := m.Thanos.Version 268 if version == 0 { 269 // For compatibility. 270 version = ThanosVersion1 271 } 272 273 if version != ThanosVersion1 { 274 return nil, errors.Errorf("unexpected meta file Thanos section version %d", m.Version) 275 } 276 277 if m.Thanos.Labels == nil { 278 // To avoid extra nil checks, allocate map here if empty. 279 m.Thanos.Labels = make(map[string]string) 280 } 281 return &m, nil 282 }