@@ -74,131 +74,172 @@ func main() {
7474 printBlocks (db .Blocks (), listCmdHumanReadable )
7575
7676 case scanCmd .FullCommand ():
77- var didSomething bool
78- var tmpFiles []string
79- filepath .Walk (* scanPath , func (path string , f os.FileInfo , _ error ) error {
80- if filepath .Ext (path ) == ".tmp" {
81- tmpFiles = append (tmpFiles , path )
82- }
83-
84- return nil
85- })
86- if len (tmpFiles ) > 0 {
87- didSomething = true
88- fmt .Println (`
89- These are usually caused by a crash or incomplete compaction and
90- are safe to delete as long as you are sure that no other application is currently using this database.` )
91- promptDelete (tmpFiles , scanCmdHumanReadable )
92- }
77+ scanTmps (* scanPath , scanCmdHumanReadable )
9378
9479 scan , err := tsdb .NewDBScanner (* scanPath , logger )
95-
96- unreadableBlocks , err := scan .Unreadable ()
9780 if err != nil {
9881 exitWithError (err )
9982 }
100- if len (unreadableBlocks ) > 0 {
101- didSomething = true
102- fmt .Println ("Unreadable blocks!" )
103- fmt .Println ("Deleting these will remove all data in the listed time range." )
104- promptDelete (unreadableBlocks , scanCmdHumanReadable )
105- }
83+ scanTmbst (scan , scanCmdHumanReadable )
84+ scanIndexes (scan , scanCmdHumanReadable )
85+ scanOverlapping (scan , scanCmdHumanReadable )
10686
107- repaired , err := scan .RepairIndex ()
108- if err != nil {
109- exitWithError (err )
87+ fmt .Println ("Scan complete!" )
88+ fmt .Println ("Hooray! The db is clean(or the scan tool is broken):\U0001f638 " )
89+ }
90+ flag .CommandLine .Set ("log.level" , "debug" )
91+ }
92+
93+ func scanOverlapping (scan tsdb.Scanner , hformat * bool ) {
94+ overlaps , err := scan .Overlapping ()
95+ if err != nil {
96+ exitWithError (err )
97+ }
98+ if len (overlaps ) > 0 {
99+ fmt .Println ("Overlaping blocks." )
100+ fmt .Println ("Deleting these will remove all data in the listed time range." )
101+ var blocksDel []* tsdb.Block
102+ for t , overBcks := range overlaps {
103+ fmt .Printf ("overlapping blocks : %v-%v \n " , time .Unix (t .Min / 1000 , 0 ).Format ("06/01/02 15:04" ), time .Unix (t .Max / 1000 , 0 ).Format ("15:04 06/01/02" ))
104+
105+ var largest int
106+ for i , b := range overBcks {
107+ if b .Meta ().Stats .NumSamples > overBcks [largest ].Meta ().Stats .NumSamples {
108+ largest = i
109+ }
110+ }
111+ // Don't delete the largest block in the overlaps.
112+ blocksDel = append (overBcks [:largest ], overBcks [largest + 1 :]... )
113+ fmt .Printf ("\n Block %v contains highest samples count and is ommited from the deletion list! \n \n " , overBcks [largest ])
110114 }
111- if len (repaired ) > 0 {
112- didSomething = true
113- fmt .Println ("Corrupted indexes that were repaired." )
114- for path , stats := range repaired {
115- fmt .Printf ("path:%v stats:%+v \n " , path , stats )
115+
116+ var paths []string
117+ for _ , b := range blocksDel {
118+ _ , folder := path .Split (b .Dir ())
119+ if _ , err := ulid .Parse (folder ); err != nil {
120+ fmt .Printf ("\n skipping invalid block dir: %v :%v \n \n " , b .Dir (), err )
121+ continue
116122 }
123+ paths = append (paths , b .Dir ())
117124 }
118- overlappingBlocks , err := scan .Overlapping ()
119- if err != nil {
120- exitWithError (err )
125+ printBlocks (blocksDel , hformat )
126+ if prompt () {
127+ if err = dellAll (paths ); err != nil {
128+ exitWithError (errors .Wrap (err , "deleting overlapping blocks" ))
129+ }
121130 }
122- if len (overlappingBlocks ) > 0 {
123- didSomething = true
124- fmt .Println ("Overlaping blocks." )
125- fmt .Println ("Deleting these will remove all data in the listed time range." )
126- for t , blocks := range overlappingBlocks {
127- fmt .Printf ("overlapping blocks : %v-%v \n " , time .Unix (t .Min / 1000 , 0 ).Format ("06/01/02 15:04" ), time .Unix (t .Max / 1000 , 0 ).Format ("15:04 06/01/02" ))
128-
129- var biggestIndex int
130- biggest := & tsdb.Block {}
131- for i , b := range blocks {
132- if b .Meta ().Stats .NumSamples > biggest .Meta ().Stats .NumSamples {
133- biggest = b
134- biggestIndex = i
135- }
136- }
137- // Don't delete the bigest block in the overlaps.
138- blocks = append (blocks [:biggestIndex ], blocks [biggestIndex + 1 :]... )
139- fmt .Printf ("\n Block %v contains most samples and is ommited from the deletion list! \n \n " , biggest )
131+ }
132+ }
140133
141- promptDelete (blocks , scanCmdHumanReadable )
142- }
134+ func scanIndexes (scan tsdb.Scanner , hformat * bool ) {
135+ unrepairable , repaired , err := scan .Indexes ()
136+ if err != nil {
137+ exitWithError (err )
138+ }
143139
140+ if len (repaired ) > 0 {
141+ fmt .Println ("Corrupted indexes that were repaired." )
142+ for _ , stats := range repaired {
143+ fmt .Printf ("path:%v stats:%+v \n " , stats .BlockDir , stats )
144144 }
145+ }
145146
146- fmt .Println ("Scan complete!" )
147- if ! didSomething {
148- fmt .Println ("Hooray! The db is clean(or the scan tool is broken):\U0001f638 " )
149- return
147+ if len (unrepairable ) > 0 {
148+ for cause , bdirs := range unrepairable {
149+ fmt .Println ("Blocks with unrepairable indexes:" , cause )
150+ printFiles (bdirs , hformat )
151+ if prompt () {
152+ if err = dellAll (bdirs ); err != nil {
153+ exitWithError (errors .Wrap (err , "deleting blocks with invalid indexes" ))
154+ }
155+ }
150156 }
151157 }
152- flag .CommandLine .Set ("log.level" , "debug" )
153158}
154159
155- func promptDelete (i interface {}, humanReadable * bool ) {
156- var paths []string
157- switch i .(type ) {
158- case []* tsdb.Block :
159- blocks := i .([]* tsdb.Block )
160- for _ , b := range blocks {
161- _ , folder := path .Split (b .Dir ())
162- if _ , err := ulid .Parse (folder ); err != nil {
163- exitWithError (fmt .Errorf ("dir doesn't contain a valid ULID:%v" , err ))
160+ func scanTmbst (scan tsdb.Scanner , hformat * bool ) {
161+ invalid , err := scan .Tombstones ()
162+ if err != nil {
163+ exitWithError (errors .Wrap (err , "scannings Tombstones" ))
164+ }
165+
166+ if len (invalid ) > 0 {
167+ fmt .Println ("Tombstones include data to be deleted so removing these will cancel deleting these timeseries." )
168+ for cause , files := range invalid {
169+ for _ , p := range files {
170+ _ , file := filepath .Split (p )
171+ if file != "tombstone" {
172+ exitWithError (fmt .Errorf ("path doesn't contain a valid tombstone filename: %v" , p ))
173+ }
174+ }
175+ fmt .Println ("invalid tombstones:" , cause )
176+ printFiles (files , hformat )
177+ if prompt () {
178+ if err = dellAll (files ); err != nil {
179+ exitWithError (errors .Wrap (err , "deleting Tombstones" ))
180+ }
164181 }
165- paths = append (paths , b .Dir ())
166182 }
167- printBlocks (blocks , humanReadable )
168- case []string :
169- paths = i .([]string )
183+ }
184+ }
170185
171- for _ , p := range paths {
186+ func scanTmps (scanPath string , hformat * bool ) {
187+ var files []string
188+ filepath .Walk (scanPath , func (path string , f os.FileInfo , _ error ) error {
189+ if filepath .Ext (path ) == ".tmp" {
190+ files = append (files , path )
191+ }
192+ return nil
193+ })
194+ if len (files ) > 0 {
195+ fmt .Println (`
196+ These are usually caused by a crash or incomplete compaction and
197+ are safe to delete as long as you are sure that no other application is currently using this database.` )
198+ for _ , p := range files {
172199 if filepath .Ext (p ) != ".tmp" {
173200 exitWithError (fmt .Errorf ("path doesn't contain a valid tmp extension: %v" , p ))
174201 }
175202 }
176- printTmps (paths , humanReadable )
203+ printFiles (files , hformat )
204+ if prompt () {
205+ if err := dellAll (files ); err != nil {
206+ exitWithError (errors .Wrap (err , "deleting Tombstones" ))
207+ }
208+ }
177209 }
210+ }
178211
179- fmt . Printf ( "DELETE (y/N)? " )
180- var s string
181- _ , err := fmt . Scanln ( & s )
182- if err != nil {
183- exitWithError ( err )
212+ func dellAll ( paths [] string ) error {
213+ for _ , p := range paths {
214+ if err := os . RemoveAll ( p ); err != nil {
215+ return fmt . Errorf ( "error deleting: %v, %v" , p , err )
216+ }
184217 }
218+ return nil
219+ }
220+
221+ func prompt () bool {
222+ for x := 0 ; x < 3 ; x ++ {
223+ fmt .Println ("DELETE (y/N)?" )
224+ var s string
225+ _ , err := fmt .Scanln (& s )
226+ if err != nil {
227+ exitWithError (err )
228+ }
185229
186- s = strings .TrimSpace (s )
187- s = strings .ToLower (s )
230+ s = strings .TrimSpace (s )
231+ s = strings .ToLower (s )
188232
189- if s == "y" || s == "yes" {
190- for _ , p := range paths {
191- if err := os .RemoveAll (p ); err != nil {
192- fmt .Printf ("error deleting: %v, %v" , p , err )
193- }
233+ if s == "y" || s == "yes" {
234+ return true
194235 }
195- return
196- }
197- if s == "n" || s == "no" {
198- return
236+ if s == "n" || s == "no" {
237+ return false
238+ }
239+ fmt . Println ( s , "is not a valid answer" )
199240 }
200- fmt .Printf ("%v is not a valid answer \n " , s )
201- promptDelete ( i , humanReadable )
241+ fmt .Printf ("Bailing out, too many invalid answers! \n \n " )
242+ return false
202243}
203244
204245type writeBenchmark struct {
@@ -479,12 +520,12 @@ func exitWithError(err error) {
479520 os .Exit (1 )
480521}
481522
482- func printTmps ( tmps []string , humanReadable * bool ) {
523+ func printFiles ( files []string , humanReadable * bool ) {
483524 tw := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
484525 defer tw .Flush ()
485526
486527 fmt .Fprintln (tw , "PATH\t SIZE\t DATE\t " )
487- for _ , path := range tmps {
528+ for _ , path := range files {
488529 f , e := os .Stat (path )
489530 if e != nil {
490531 exitWithError (e )
0 commit comments