// Package dumptsi inspects low-level details about tsi1 files. package dumptsi import ( "flag" "fmt" "io" "os" "path/filepath" "regexp" "text/tabwriter" "github.com/influxdata/influxdb/models" "github.com/influxdata/influxdb/tsdb/index/tsi1" ) // Command represents the program execution for "influxd dumptsi". type Command struct { // Standard input/output, overridden for testing. Stderr io.Writer Stdout io.Writer paths []string showSeries bool showMeasurements bool showTagKeys bool showTagValues bool showTagValueSeries bool measurementFilter *regexp.Regexp tagKeyFilter *regexp.Regexp tagValueFilter *regexp.Regexp } // NewCommand returns a new instance of Command. func NewCommand() *Command { return &Command{ Stderr: os.Stderr, Stdout: os.Stdout, } } // Run executes the command. func (cmd *Command) Run(args ...string) error { var measurementFilter, tagKeyFilter, tagValueFilter string fs := flag.NewFlagSet("dumptsi", flag.ExitOnError) fs.BoolVar(&cmd.showSeries, "series", false, "Show raw series data") fs.BoolVar(&cmd.showMeasurements, "measurements", false, "Show raw measurement data") fs.BoolVar(&cmd.showTagKeys, "tag-keys", false, "Show raw tag key data") fs.BoolVar(&cmd.showTagValues, "tag-values", false, "Show raw tag value data") fs.BoolVar(&cmd.showTagValueSeries, "tag-value-series", false, "Show raw series data for each value") fs.StringVar(&measurementFilter, "measurement-filter", "", "Regex measurement filter") fs.StringVar(&tagKeyFilter, "tag-key-filter", "", "Regex tag key filter") fs.StringVar(&tagValueFilter, "tag-value-filter", "", "Regex tag value filter") fs.SetOutput(cmd.Stdout) fs.Usage = cmd.printUsage if err := fs.Parse(args); err != nil { return err } // Parse filters. if measurementFilter != "" { re, err := regexp.Compile(measurementFilter) if err != nil { return err } cmd.measurementFilter = re } if tagKeyFilter != "" { re, err := regexp.Compile(tagKeyFilter) if err != nil { return err } cmd.tagKeyFilter = re } if tagValueFilter != "" { re, err := regexp.Compile(tagValueFilter) if err != nil { return err } cmd.tagValueFilter = re } cmd.paths = fs.Args() if len(cmd.paths) == 0 { fmt.Printf("at least one path required\n\n") fs.Usage() return nil } // Some flags imply other flags. if cmd.showTagValueSeries { cmd.showTagValues = true } if cmd.showTagValues { cmd.showTagKeys = true } if cmd.showTagKeys { cmd.showMeasurements = true } return cmd.run() } func (cmd *Command) run() error { // Build a file set from the paths on the command line. idx, fs, err := cmd.readFileSet() if err != nil { return err } if idx != nil { defer idx.Close() } else { defer fs.Close() } defer fs.Release() // Show either raw data or summary stats. if cmd.showSeries || cmd.showMeasurements { if err := cmd.printMerged(fs); err != nil { return err } } else { if err := cmd.printFileSummaries(fs); err != nil { return err } } return nil } func (cmd *Command) readFileSet() (*tsi1.Index, *tsi1.FileSet, error) { // If only one path exists and it's a directory then open as an index. if len(cmd.paths) == 1 { fi, err := os.Stat(cmd.paths[0]) if err != nil { return nil, nil, err } else if fi.IsDir() { idx := tsi1.NewIndex() idx.Path = cmd.paths[0] idx.CompactionEnabled = false if err := idx.Open(); err != nil { return nil, nil, err } return idx, idx.RetainFileSet(), nil } } // Open each file and group into a fileset. var files []tsi1.File for _, path := range cmd.paths { switch ext := filepath.Ext(path); ext { case tsi1.LogFileExt: f := tsi1.NewLogFile(path) if err := f.Open(); err != nil { return nil, nil, err } files = append(files, f) case tsi1.IndexFileExt: f := tsi1.NewIndexFile() f.SetPath(path) if err := f.Open(); err != nil { return nil, nil, err } files = append(files, f) default: return nil, nil, fmt.Errorf("unexpected file extension: %s", ext) } } fs, err := tsi1.NewFileSet(nil, files) if err != nil { return nil, nil, err } fs.Retain() return nil, fs, nil } func (cmd *Command) printMerged(fs *tsi1.FileSet) error { if err := cmd.printSeries(fs); err != nil { return err } else if err := cmd.printMeasurements(fs); err != nil { return err } return nil } func (cmd *Command) printSeries(fs *tsi1.FileSet) error { if !cmd.showSeries { return nil } // Print header. tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) fmt.Fprintln(tw, "Series\t") // Iterate over each series. itr := fs.SeriesIterator() for e := itr.Next(); e != nil; e = itr.Next() { name, tags := e.Name(), e.Tags() if !cmd.matchSeries(e.Name(), e.Tags()) { continue } fmt.Fprintf(tw, "%s%s\t%v\n", name, tags.HashKey(), deletedString(e.Deleted())) } // Flush & write footer spacing. if err := tw.Flush(); err != nil { return err } fmt.Fprint(cmd.Stdout, "\n\n") return nil } func (cmd *Command) printMeasurements(fs *tsi1.FileSet) error { if !cmd.showMeasurements { return nil } tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) fmt.Fprintln(tw, "Measurement\t") // Iterate over each series. itr := fs.MeasurementIterator() for e := itr.Next(); e != nil; e = itr.Next() { if cmd.measurementFilter != nil && !cmd.measurementFilter.Match(e.Name()) { continue } fmt.Fprintf(tw, "%s\t%v\n", e.Name(), deletedString(e.Deleted())) if err := tw.Flush(); err != nil { return err } if err := cmd.printTagKeys(fs, e.Name()); err != nil { return err } } fmt.Fprint(cmd.Stdout, "\n\n") return nil } func (cmd *Command) printTagKeys(fs *tsi1.FileSet, name []byte) error { if !cmd.showTagKeys { return nil } // Iterate over each key. tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) itr := fs.TagKeyIterator(name) for e := itr.Next(); e != nil; e = itr.Next() { if cmd.tagKeyFilter != nil && !cmd.tagKeyFilter.Match(e.Key()) { continue } fmt.Fprintf(tw, " %s\t%v\n", e.Key(), deletedString(e.Deleted())) if err := tw.Flush(); err != nil { return err } if err := cmd.printTagValues(fs, name, e.Key()); err != nil { return err } } fmt.Fprint(cmd.Stdout, "\n") return nil } func (cmd *Command) printTagValues(fs *tsi1.FileSet, name, key []byte) error { if !cmd.showTagValues { return nil } // Iterate over each value. tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) itr := fs.TagValueIterator(name, key) for e := itr.Next(); e != nil; e = itr.Next() { if cmd.tagValueFilter != nil && !cmd.tagValueFilter.Match(e.Value()) { continue } fmt.Fprintf(tw, " %s\t%v\n", e.Value(), deletedString(e.Deleted())) if err := tw.Flush(); err != nil { return err } if err := cmd.printTagValueSeries(fs, name, key, e.Value()); err != nil { return err } } fmt.Fprint(cmd.Stdout, "\n") return nil } func (cmd *Command) printTagValueSeries(fs *tsi1.FileSet, name, key, value []byte) error { if !cmd.showTagValueSeries { return nil } // Iterate over each series. tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) itr := fs.TagValueSeriesIterator(name, key, value) for e := itr.Next(); e != nil; e = itr.Next() { if !cmd.matchSeries(e.Name(), e.Tags()) { continue } fmt.Fprintf(tw, " %s%s\n", e.Name(), e.Tags().HashKey()) if err := tw.Flush(); err != nil { return err } } fmt.Fprint(cmd.Stdout, "\n") return nil } func (cmd *Command) printFileSummaries(fs *tsi1.FileSet) error { for _, f := range fs.Files() { switch f := f.(type) { case *tsi1.LogFile: if err := cmd.printLogFileSummary(f); err != nil { return err } case *tsi1.IndexFile: if err := cmd.printIndexFileSummary(f); err != nil { return err } default: panic("unreachable") } fmt.Fprintln(cmd.Stdout, "") } return nil } func (cmd *Command) printLogFileSummary(f *tsi1.LogFile) error { fmt.Fprintf(cmd.Stdout, "[LOG FILE] %s\n", filepath.Base(f.Path())) tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) fmt.Fprintf(tw, "Series:\t%d\n", f.SeriesN()) fmt.Fprintf(tw, "Measurements:\t%d\n", f.MeasurementN()) fmt.Fprintf(tw, "Tag Keys:\t%d\n", f.TagKeyN()) fmt.Fprintf(tw, "Tag Values:\t%d\n", f.TagValueN()) return tw.Flush() } func (cmd *Command) printIndexFileSummary(f *tsi1.IndexFile) error { fmt.Fprintf(cmd.Stdout, "[INDEX FILE] %s\n", filepath.Base(f.Path())) // Calculate summary stats. seriesN := f.SeriesN() var measurementN, measurementSeriesN, measurementSeriesSize uint64 var keyN uint64 var valueN, valueSeriesN, valueSeriesSize uint64 mitr := f.MeasurementIterator() for me, _ := mitr.Next().(*tsi1.MeasurementBlockElem); me != nil; me, _ = mitr.Next().(*tsi1.MeasurementBlockElem) { kitr := f.TagKeyIterator(me.Name()) for ke, _ := kitr.Next().(*tsi1.TagBlockKeyElem); ke != nil; ke, _ = kitr.Next().(*tsi1.TagBlockKeyElem) { vitr := f.TagValueIterator(me.Name(), ke.Key()) for ve, _ := vitr.Next().(*tsi1.TagBlockValueElem); ve != nil; ve, _ = vitr.Next().(*tsi1.TagBlockValueElem) { valueN++ valueSeriesN += uint64(ve.SeriesN()) valueSeriesSize += uint64(len(ve.SeriesData())) } keyN++ } measurementN++ measurementSeriesN += uint64(me.SeriesN()) measurementSeriesSize += uint64(len(me.SeriesData())) } // Write stats. tw := tabwriter.NewWriter(cmd.Stdout, 8, 8, 1, '\t', 0) fmt.Fprintf(tw, "Series:\t%d\n", seriesN) fmt.Fprintf(tw, "Measurements:\t%d\n", measurementN) fmt.Fprintf(tw, " Series data size:\t%d (%s)\n", measurementSeriesSize, formatSize(measurementSeriesSize)) fmt.Fprintf(tw, " Bytes per series:\t%.01fb\n", float64(measurementSeriesSize)/float64(measurementSeriesN)) fmt.Fprintf(tw, "Tag Keys:\t%d\n", keyN) fmt.Fprintf(tw, "Tag Values:\t%d\n", valueN) fmt.Fprintf(tw, " Series:\t%d\n", valueSeriesN) fmt.Fprintf(tw, " Series data size:\t%d (%s)\n", valueSeriesSize, formatSize(valueSeriesSize)) fmt.Fprintf(tw, " Bytes per series:\t%.01fb\n", float64(valueSeriesSize)/float64(valueSeriesN)) fmt.Fprintf(tw, "Avg tags per series:\t%.01f\n", float64(valueSeriesN)/float64(seriesN)) if err := tw.Flush(); err != nil { return err } return nil } // matchSeries returns true if the command filters matches the series. func (cmd *Command) matchSeries(name []byte, tags models.Tags) bool { // Filter by measurement. if cmd.measurementFilter != nil && !cmd.measurementFilter.Match(name) { return false } // Filter by tag key/value. if cmd.tagKeyFilter != nil || cmd.tagValueFilter != nil { var matched bool for _, tag := range tags { if (cmd.tagKeyFilter == nil || cmd.tagKeyFilter.Match(tag.Key)) && (cmd.tagValueFilter == nil || cmd.tagValueFilter.Match(tag.Value)) { matched = true break } } if !matched { return false } } return true } // printUsage prints the usage message to STDERR. func (cmd *Command) printUsage() { usage := `Dumps low-level details about tsi1 files. Usage: influx_inspect dumptsi [flags] path... -series Dump raw series data -measurements Dump raw measurement data -tag-keys Dump raw tag keys -tag-values Dump raw tag values -tag-value-series Dump raw series for each tag value -measurement-filter REGEXP Filters data by measurement regular expression -tag-key-filter REGEXP Filters data by tag key regular expression -tag-value-filter REGEXP Filters data by tag value regular expression If no flags are specified then summary stats are provided for each file. ` fmt.Fprintf(cmd.Stdout, usage) } // deletedString returns "(deleted)" if v is true. func deletedString(v bool) string { if v { return "(deleted)" } return "" } func formatSize(v uint64) string { denom := uint64(1) var uom string for _, uom = range []string{"b", "kb", "mb", "gb", "tb"} { if denom*1024 > v { break } denom *= 1024 } return fmt.Sprintf("%0.01f%s", float64(v)/float64(denom), uom) }