2016-02-10 18:30:52 +00:00
|
|
|
package tsdb // import "github.com/influxdata/influxdb/cmd/influx_tsm/tsdb"
|
2015-12-26 22:13:54 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/boltdb/bolt"
|
2016-02-10 17:26:18 +00:00
|
|
|
"github.com/influxdata/influxdb/pkg/slices"
|
2015-12-26 22:13:54 +00:00
|
|
|
)
|
|
|
|
|
2016-02-24 16:39:27 +00:00
|
|
|
// Flags for differentiating between engines
|
2015-12-26 22:13:54 +00:00
|
|
|
const (
|
|
|
|
B1 = iota
|
|
|
|
BZ1
|
|
|
|
TSM1
|
|
|
|
)
|
|
|
|
|
2016-02-24 16:39:27 +00:00
|
|
|
// EngineFormat holds the flag for the engine
|
2015-12-26 22:13:54 +00:00
|
|
|
type EngineFormat int
|
|
|
|
|
|
|
|
// String returns the string format of the engine.
|
|
|
|
func (e EngineFormat) String() string {
|
|
|
|
switch e {
|
|
|
|
case TSM1:
|
|
|
|
return "tsm1"
|
|
|
|
case B1:
|
|
|
|
return "b1"
|
|
|
|
case BZ1:
|
|
|
|
return "bz1"
|
|
|
|
default:
|
|
|
|
panic("unrecognized shard engine format")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShardInfo is the description of a shard on disk.
|
|
|
|
type ShardInfo struct {
|
|
|
|
Database string
|
|
|
|
RetentionPolicy string
|
|
|
|
Path string
|
|
|
|
Format EngineFormat
|
|
|
|
Size int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// FormatAsString returns the format of the shard as a string.
|
|
|
|
func (s *ShardInfo) FormatAsString() string {
|
|
|
|
return s.Format.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// FullPath returns the full path to the shard, given the data directory root.
|
|
|
|
func (s *ShardInfo) FullPath(dataPath string) string {
|
|
|
|
return filepath.Join(dataPath, s.Database, s.RetentionPolicy, s.Path)
|
|
|
|
}
|
|
|
|
|
2016-02-24 16:39:27 +00:00
|
|
|
// ShardInfos is an array of ShardInfo
|
2015-12-26 22:13:54 +00:00
|
|
|
type ShardInfos []*ShardInfo
|
|
|
|
|
|
|
|
func (s ShardInfos) Len() int { return len(s) }
|
|
|
|
func (s ShardInfos) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
|
|
func (s ShardInfos) Less(i, j int) bool {
|
|
|
|
if s[i].Database == s[j].Database {
|
|
|
|
if s[i].RetentionPolicy == s[j].RetentionPolicy {
|
|
|
|
return s[i].Path < s[i].Path
|
|
|
|
}
|
2016-02-24 16:39:27 +00:00
|
|
|
|
|
|
|
return s[i].RetentionPolicy < s[j].RetentionPolicy
|
2015-12-26 22:13:54 +00:00
|
|
|
}
|
2016-02-24 16:39:27 +00:00
|
|
|
|
2015-12-26 22:13:54 +00:00
|
|
|
return s[i].Database < s[j].Database
|
|
|
|
}
|
|
|
|
|
|
|
|
// Databases returns the sorted unique set of databases for the shards.
|
|
|
|
func (s ShardInfos) Databases() []string {
|
|
|
|
dbm := make(map[string]bool)
|
|
|
|
for _, ss := range s {
|
|
|
|
dbm[ss.Database] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
var dbs []string
|
2016-02-24 16:39:27 +00:00
|
|
|
for k := range dbm {
|
2015-12-26 22:13:54 +00:00
|
|
|
dbs = append(dbs, k)
|
|
|
|
}
|
|
|
|
sort.Strings(dbs)
|
|
|
|
return dbs
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterFormat returns a copy of the ShardInfos, with shards of the given
|
|
|
|
// format removed.
|
|
|
|
func (s ShardInfos) FilterFormat(fmt EngineFormat) ShardInfos {
|
|
|
|
var a ShardInfos
|
|
|
|
for _, si := range s {
|
|
|
|
if si.Format != fmt {
|
|
|
|
a = append(a, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2016-01-07 21:36:25 +00:00
|
|
|
// Size returns the space on disk consumed by the shards.
|
|
|
|
func (s ShardInfos) Size() int64 {
|
|
|
|
var sz int64
|
|
|
|
for _, si := range s {
|
|
|
|
sz += si.Size
|
|
|
|
}
|
|
|
|
return sz
|
|
|
|
}
|
|
|
|
|
2015-12-26 22:13:54 +00:00
|
|
|
// ExclusiveDatabases returns a copy of the ShardInfo, with shards associated
|
|
|
|
// with the given databases present. If the given set is empty, all databases
|
|
|
|
// are returned.
|
|
|
|
func (s ShardInfos) ExclusiveDatabases(exc []string) ShardInfos {
|
|
|
|
var a ShardInfos
|
|
|
|
|
|
|
|
// Empty set? Return everything.
|
|
|
|
if len(exc) == 0 {
|
|
|
|
a = make(ShardInfos, len(s))
|
|
|
|
copy(a, s)
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, si := range s {
|
|
|
|
if slices.Exists(exc, si.Database) {
|
|
|
|
a = append(a, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
// Database represents an entire database on disk.
|
|
|
|
type Database struct {
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewDatabase creates a database instance using data at path.
|
|
|
|
func NewDatabase(path string) *Database {
|
|
|
|
return &Database{path: path}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the database.
|
|
|
|
func (d *Database) Name() string {
|
|
|
|
return path.Base(d.path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path returns the path to the database.
|
|
|
|
func (d *Database) Path() string {
|
|
|
|
return d.path
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shards returns information for every shard in the database.
|
|
|
|
func (d *Database) Shards() ([]*ShardInfo, error) {
|
|
|
|
fd, err := os.Open(d.path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get each retention policy.
|
|
|
|
rps, err := fd.Readdirnames(-1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process each retention policy.
|
|
|
|
var shardInfos []*ShardInfo
|
|
|
|
for _, rp := range rps {
|
|
|
|
rpfd, err := os.Open(filepath.Join(d.path, rp))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process each shard
|
|
|
|
shards, err := rpfd.Readdirnames(-1)
|
|
|
|
for _, sh := range shards {
|
|
|
|
fmt, sz, err := shardFormat(filepath.Join(d.path, rp, sh))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
si := &ShardInfo{
|
|
|
|
Database: d.Name(),
|
|
|
|
RetentionPolicy: path.Base(rp),
|
|
|
|
Path: sh,
|
|
|
|
Format: fmt,
|
|
|
|
Size: sz,
|
|
|
|
}
|
|
|
|
shardInfos = append(shardInfos, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(ShardInfos(shardInfos))
|
|
|
|
return shardInfos, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// shardFormat returns the format and size on disk of the shard at path.
|
|
|
|
func shardFormat(path string) (EngineFormat, int64, error) {
|
|
|
|
// If it's a directory then it's a tsm1 engine
|
2016-02-29 16:14:16 +00:00
|
|
|
fi, err := os.Stat(path)
|
2015-12-26 22:13:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
if fi.Mode().IsDir() {
|
|
|
|
return TSM1, fi.Size(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// It must be a BoltDB-based engine.
|
|
|
|
db, err := bolt.Open(path, 0666, &bolt.Options{Timeout: 1 * time.Second})
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
var format EngineFormat
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
// Retrieve the meta bucket.
|
|
|
|
b := tx.Bucket([]byte("meta"))
|
|
|
|
|
|
|
|
// If no format is specified then it must be an original b1 database.
|
|
|
|
if b == nil {
|
|
|
|
format = B1
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is an actual format indicator.
|
2016-02-29 16:14:16 +00:00
|
|
|
switch f := string(b.Get([]byte("format"))); f {
|
2015-12-26 22:13:54 +00:00
|
|
|
case "b1", "v1":
|
|
|
|
format = B1
|
|
|
|
case "bz1":
|
|
|
|
format = BZ1
|
|
|
|
default:
|
2016-02-29 16:14:16 +00:00
|
|
|
return fmt.Errorf("unrecognized engine format: %s", f)
|
2015-12-26 22:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return format, fi.Size(), err
|
|
|
|
}
|