diff --git a/README.md b/README.md index 1d283a4..aaf14a2 100644 --- a/README.md +++ b/README.md @@ -480,14 +480,17 @@ ilm - manage index lifecycle list - list index lifecycle policies show - show details about an index lifecycle policy create - create a index lifecycle policy + forecast - calculate index phase movements + list - list index rollover config + show - show rollover forecast over all indices index - manage indicies list - list indicies show - show details about an index create - create a new index + modify - modify anindex delete - delete an index close - close an index allocation - explain index allocation - modify - modify an index fields - show info about field capabilities ilm - show ilm status alias - manage index aliases @@ -522,10 +525,10 @@ version - show esctl version information debug - developer only help-jsonpath - show jsonpath help completion - Output shell completion script for bash, zsh, fish, or Powershell + pwsh - Output pwsh completion script bash - Output bash completion script zsh - Output zsh completion script fish - Output fish completion script - pwsh - Output pwsh completion script ``` # Development diff --git a/cmd/ilm_forecast.go b/cmd/ilm_forecast.go index 12e2f22..1a89a8d 100644 --- a/cmd/ilm_forecast.go +++ b/cmd/ilm_forecast.go @@ -34,6 +34,7 @@ func IlmForecast(conf *cfg.Config) *cli.Command { Commands: []*cli.Command{ IlmForecastList(conf), + IlmForecastShow(conf), }, } } @@ -69,3 +70,35 @@ func IlmForecastList(conf *cfg.Config) *cli.Command { }, } } + +func IlmForecastShow(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "show", + Aliases: []string{"sh"}, + Usage: "show rollover forecast over all indices", + + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from-phase", + Usage: "which ilm phase to forecast from", + Destination: &conf.Ilm.FromPhase, + Aliases: []string{"f"}, + }, + &cli.StringFlag{ + Name: "within", + Usage: "duration withing which to forecast", + Destination: &conf.Ilm.Within, + Aliases: []string{"w"}, + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + valid := []string{"hot", "warm", "cold", "frozen"} + if !slices.Contains(valid, conf.Ilm.FromPhase) { + return errors.New("invalid from phase, allowed: hot, warm, cold, frozen") + } + + return es.IlmForecastShow(conf) + }, + } +} diff --git a/pkg/es/ilm_forecast.go b/pkg/es/ilm_forecast.go index f90f52f..dc29014 100644 --- a/pkg/es/ilm_forecast.go +++ b/pkg/es/ilm_forecast.go @@ -84,6 +84,83 @@ types.Lifecycle{ hot for 7 days or 25Gig, then Rollover => warm 14 days => frozen 60 days => delete */ + +func IlmForecastList(conf *cfg.Config) error { + phaseData, err := getIlmPhaseData(conf) + if err != nil { + return err + } + + table := printer.NewTable(conf, 8, 0) + table.Addheaders("index", "current size", "current age", "virtual age", "min age", "min size", "current phase", "next phase") + + for _, phase := range phaseData { + virtualAge := virtualAge(&phase) + + table.AddRow( + phase.index, + humanize.Bytes(uint64(phase.size)), + fmt.Sprintf("%s", phase.age), + + fmt.Sprintf("%s", virtualAge), + + fmt.Sprintf("%s", phase.minage), + humanize.Bytes(uint64(phase.minsize)), + + phase.currentPhase, + phase.nextPhase, + ) + } + + table.Sort() + return table.Print() +} + +// Loop over all current index phases of the current phase +// (conf.Ilm.FromPhase). Calculate virtualAge if phase.minsize >0. If +// the actual age is smaller than the virtual age, use this as a base, +// otherwise use virtual age. Then look if the rollover would happen +// within the phase.minage window (conf.Ilm.Within + current age) and +// add the size. +func IlmForecastShow(conf *cfg.Config) error { + phaseData, err := getIlmPhaseData(conf) + if err != nil { + return err + } + + within := parseDuration(conf.Ilm.Within) + var toBeFreed int64 = 0 + + for _, phase := range phaseData { + age := virtualAge(&phase) + if age < phase.age { + age = phase.age + } + + if age+within >= phase.minage { + toBeFreed += phase.size + } + } + + fmt.Printf("%s bytes of data will be rolled within %s\n", humanize.Bytes(uint64(toBeFreed)), within) + + return nil +} + +func virtualAge(phase *PhaseData) time.Duration { + if phase.minsize == 0 { + return time.Duration(0) + } + + oneMinAge := phase.minage.Seconds() / 100 + oneMinSize := float64(phase.minsize) / 100 + + percentSize := float64(phase.size) / oneMinSize + virtualAge := percentSize * oneMinAge + + return time.Duration(virtualAge) * time.Second +} + func getIlmPhaseData(conf *cfg.Config) ([]PhaseData, error) { responses := make(chan apiResponse, 3) wg := &sync.WaitGroup{} @@ -177,33 +254,6 @@ func getIlmPhaseData(conf *cfg.Config) ([]PhaseData, error) { return list, nil } -func IlmForecastList(conf *cfg.Config) error { - phaseData, err := getIlmPhaseData(conf) - if err != nil { - return err - } - - table := printer.NewTable(conf, 7, 0) - table.Addheaders("index", "current size", "current age", "min age", "min size", "current phase", "next phase") - - for _, phase := range phaseData { - table.AddRow( - phase.index, - humanize.Bytes(uint64(phase.size)), - fmt.Sprintf("%s", phase.age), - - fmt.Sprintf("%s", phase.minage), - humanize.Bytes(uint64(phase.minsize)), - - phase.currentPhase, - phase.nextPhase, - ) - } - - table.Sort() - return table.Print() -} - func findNextPhase(policy types.IlmPolicy, currentPhase string) *NextPhase { // phase list to determine which comes next order := []string{"hot", "warm", "cold", "frozen", "delete"} @@ -265,10 +315,7 @@ func findNextPhase(policy types.IlmPolicy, currentPhase string) *NextPhase { if phase == "hot" { // if we're starting here, do not look for the next phase - maxage, err := time.ParseDuration(phases[phase].Actions.Rollover.MaxAge.(string)) - if err != err { - maxage = 0 - } + maxage := parseDuration(phases[phase].Actions.Rollover.MaxAge.(string)) var maxsize int64 = 0