add ilm forecast show

This commit is contained in:
2026-06-25 13:29:24 +02:00
parent 5f60696b01
commit e1291d1d92
3 changed files with 116 additions and 33 deletions

View File

@@ -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

View File

@@ -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)
},
}
}

View File

@@ -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