mirror of
https://codeberg.org/scip/esctl.git
synced 2026-08-24 09:54:18 +02:00
enhance/ilm-forecast (#51)
This commit is contained in:
@@ -39,11 +39,28 @@ func IlmForecast(conf *cfg.Config) *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IlmForeCastListReference = `<duration> must be a string which consists one or more of these elements:
|
||||||
|
d - days
|
||||||
|
h - hours
|
||||||
|
m - minutes
|
||||||
|
s - seconds
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
58d12h - 58 days and 12 hours
|
||||||
|
122h30m - 122 hours and 30 minutes
|
||||||
|
|
||||||
|
Specs may be mixed:
|
||||||
|
|
||||||
|
8h12d - 12 days and 8 hours`
|
||||||
|
|
||||||
func IlmForecastList(conf *cfg.Config) *cli.Command {
|
func IlmForecastList(conf *cfg.Config) *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "list index rollover config",
|
Usage: "list index rollover config",
|
||||||
|
UsageText: "list [options] [<filter>]",
|
||||||
|
CustomHelpTemplate: addReference(IlmForeCastListReference),
|
||||||
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
@@ -54,10 +71,22 @@ func IlmForecastList(conf *cfg.Config) *cli.Command {
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "within",
|
Name: "within",
|
||||||
Usage: "duration withing which to forecast",
|
Usage: "<duration> withing which to forecast",
|
||||||
Destination: &conf.Ilm.Within,
|
Destination: &conf.Ilm.Within,
|
||||||
Aliases: []string{"w"},
|
Aliases: []string{"w"},
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "min-age",
|
||||||
|
Usage: "show only indices older than <duration>",
|
||||||
|
Destination: &conf.Ilm.MinAge,
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Usage: "show ilm policy as well",
|
||||||
|
Destination: &conf.Verbose,
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
@@ -66,7 +95,7 @@ func IlmForecastList(conf *cfg.Config) *cli.Command {
|
|||||||
return errors.New("invalid from phase, allowed: hot, warm, cold, frozen")
|
return errors.New("invalid from phase, allowed: hot, warm, cold, frozen")
|
||||||
}
|
}
|
||||||
|
|
||||||
return es.IlmForecastList(conf)
|
return es.IlmForecastList(conf, cmd.Args().Get(0))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type Ilm struct {
|
|||||||
DeleteMinAge string
|
DeleteMinAge string
|
||||||
DeleteSearchableSnapshots bool
|
DeleteSearchableSnapshots bool
|
||||||
|
|
||||||
FromPhase, Within string // forecast
|
MinAge, FromPhase, Within string // forecast
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Ilm) HaveHot() bool {
|
func (cfg *Ilm) HaveHot() bool {
|
||||||
|
|||||||
126
pkg/es/duration.go
Normal file
126
pkg/es/duration.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2026 Thomas von Dein
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package es
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
matchDuration = regexp.MustCompile(`(\d+)([dhms])`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type esDuration struct {
|
||||||
|
days, hours, minutes, seconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We could use time.ParseDuration(), but this doesn't support days.
|
||||||
|
|
||||||
|
We could also use github.com/xhit/go-str2duration/v2, which does
|
||||||
|
the job, but it's just another dependency, just for this little
|
||||||
|
gem. And we don't need a time.Time value. And int is good enough
|
||||||
|
for duration comparison.
|
||||||
|
|
||||||
|
Convert a duration into an integer. Valid time units are "s", "m", "h" and "d".
|
||||||
|
|
||||||
|
via https://codeberg.org/scip/tablizer/src/branch/main/lib/sort.go#L113
|
||||||
|
*/
|
||||||
|
func parseDuration(duration string) time.Duration {
|
||||||
|
|
||||||
|
seconds := 0
|
||||||
|
|
||||||
|
for _, match := range matchDuration.FindAllStringSubmatch(duration, -1) {
|
||||||
|
if len(match) == 3 {
|
||||||
|
durationvalue, _ := strconv.Atoi(match[1])
|
||||||
|
|
||||||
|
switch match[2][0] {
|
||||||
|
case 'd':
|
||||||
|
seconds += durationvalue * 86400
|
||||||
|
case 'h':
|
||||||
|
seconds += durationvalue * 3600
|
||||||
|
case 'm':
|
||||||
|
seconds += durationvalue * 60
|
||||||
|
case 's':
|
||||||
|
seconds += durationvalue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(seconds) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// pretty much the same as above, but return a struct filled with int duration values
|
||||||
|
func extractDuration(duration string) esDuration {
|
||||||
|
esd := esDuration{}
|
||||||
|
|
||||||
|
for _, match := range matchDuration.FindAllStringSubmatch(duration, -1) {
|
||||||
|
if len(match) == 3 {
|
||||||
|
durationvalue, _ := strconv.Atoi(match[1])
|
||||||
|
|
||||||
|
switch match[2][0] {
|
||||||
|
case 'd':
|
||||||
|
esd.days = durationvalue
|
||||||
|
case 'h':
|
||||||
|
esd.hours = durationvalue
|
||||||
|
case 'm':
|
||||||
|
esd.minutes = durationvalue
|
||||||
|
case 's':
|
||||||
|
esd.seconds = durationvalue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return esd
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert a duration like 156h6m15.095s-6d/12h/6m => 6d:12h:6m
|
||||||
|
func formatDuration(val time.Duration) string {
|
||||||
|
esd := extractDuration(val.String())
|
||||||
|
|
||||||
|
daysF := float64(esd.hours) / 24.0
|
||||||
|
days := math.Floor(daysF)
|
||||||
|
hoursLeft := (daysF - days) * 24
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case val.Minutes() < 1:
|
||||||
|
return fmt.Sprintf("%ds", int64(esd.seconds))
|
||||||
|
|
||||||
|
case val.Hours() < 1:
|
||||||
|
return fmt.Sprintf("%dm", int64(esd.minutes))
|
||||||
|
|
||||||
|
case val.Hours() < 24:
|
||||||
|
if esd.minutes == 0 {
|
||||||
|
return fmt.Sprintf("%dh", esd.hours)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dh:%dm", esd.hours, int64(esd.minutes))
|
||||||
|
|
||||||
|
default:
|
||||||
|
if esd.minutes == 0 && hoursLeft == 0 {
|
||||||
|
return fmt.Sprintf("%dd", int64(days))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dd:%dh:%dm",
|
||||||
|
int64(days),
|
||||||
|
int64(hoursLeft),
|
||||||
|
esd.minutes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
matchDuration = regexp.MustCompile(`(\d+)([dhms])`)
|
|
||||||
IlmPhaseOrder = []string{"hot", "warm", "cold", "frozen", "delete"}
|
IlmPhaseOrder = []string{"hot", "warm", "cold", "frozen", "delete"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +46,7 @@ type NextPhase struct {
|
|||||||
|
|
||||||
type PhaseData struct {
|
type PhaseData struct {
|
||||||
index string
|
index string
|
||||||
|
policy string
|
||||||
size int64
|
size int64
|
||||||
age time.Duration
|
age time.Duration
|
||||||
minage time.Duration
|
minage time.Duration
|
||||||
@@ -86,30 +86,55 @@ types.Lifecycle{
|
|||||||
hot for 7 days or 25Gig, then Rollover => warm 14 days => frozen 60 days => delete
|
hot for 7 days or 25Gig, then Rollover => warm 14 days => frozen 60 days => delete
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func IlmForecastList(conf *cfg.Config) error {
|
func IlmForecastList(conf *cfg.Config, filter string) error {
|
||||||
phaseData, err := getIlmPhaseData(conf)
|
phaseData, err := getIlmPhaseData(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
table := printer.NewTable(conf, 8, 0)
|
flt := regexp.MustCompile(filter)
|
||||||
table.Addheaders("index", "current size", "current age", "virtual age", "min age", "min size", "current phase", "next phase")
|
|
||||||
|
minage := time.Duration(0)
|
||||||
|
if conf.Ilm.MinAge != "" {
|
||||||
|
minage = parseDuration(conf.Ilm.MinAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"index", "current size", "current age",
|
||||||
|
"virtual age", "min age", "min size", "current phase", "next phase"}
|
||||||
|
if conf.Verbose {
|
||||||
|
headers = append(headers, "ilm policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
table := printer.NewTable(conf, len(headers), 0)
|
||||||
|
table.Addheaders(headers...)
|
||||||
|
|
||||||
for _, phase := range phaseData {
|
for _, phase := range phaseData {
|
||||||
|
if filter != "" && !flt.MatchString(phase.index) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Ilm.MinAge != "" && phase.age < minage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
virtualAge := virtualAge(&phase)
|
virtualAge := virtualAge(&phase)
|
||||||
|
|
||||||
table.AddRow(
|
row := []string{
|
||||||
phase.index,
|
phase.index,
|
||||||
humanize.Bytes(uint64(phase.size)),
|
humanize.Bytes(uint64(phase.size)),
|
||||||
phase.age.String(),
|
formatDuration(phase.age),
|
||||||
virtualAge.String(),
|
formatDuration(virtualAge),
|
||||||
|
formatDuration(phase.minage),
|
||||||
phase.minage.String(),
|
|
||||||
humanize.Bytes(uint64(phase.minsize)),
|
humanize.Bytes(uint64(phase.minsize)),
|
||||||
|
|
||||||
phase.currentPhase,
|
phase.currentPhase,
|
||||||
phase.nextPhase,
|
phase.nextPhase,
|
||||||
)
|
}
|
||||||
|
|
||||||
|
if conf.Verbose {
|
||||||
|
row = append(row, phase.policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.AddRow(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Sort()
|
table.Sort()
|
||||||
@@ -241,6 +266,7 @@ func getIlmPhaseData(conf *cfg.Config) ([]PhaseData, error) {
|
|||||||
|
|
||||||
list = append(list, PhaseData{
|
list = append(list, PhaseData{
|
||||||
index: indexName,
|
index: indexName,
|
||||||
|
policy: *explain.Policy,
|
||||||
size: size,
|
size: size,
|
||||||
age: age,
|
age: age,
|
||||||
minage: nextPhase.minage,
|
minage: nextPhase.minage,
|
||||||
@@ -365,39 +391,3 @@ func findNextPhase(policy types.IlmPolicy, currentPhase string) *NextPhase {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
We could use time.ParseDuration(), but this doesn't support days.
|
|
||||||
|
|
||||||
We could also use github.com/xhit/go-str2duration/v2, which does
|
|
||||||
the job, but it's just another dependency, just for this little
|
|
||||||
gem. And we don't need a time.Time value. And int is good enough
|
|
||||||
for duration comparison.
|
|
||||||
|
|
||||||
Convert a duration into an integer. Valid time units are "s", "m", "h" and "d".
|
|
||||||
|
|
||||||
via https://codeberg.org/scip/tablizer/src/branch/main/lib/sort.go#L113
|
|
||||||
*/
|
|
||||||
func parseDuration(duration string) time.Duration {
|
|
||||||
|
|
||||||
seconds := 0
|
|
||||||
|
|
||||||
for _, match := range matchDuration.FindAllStringSubmatch(duration, -1) {
|
|
||||||
if len(match) == 3 {
|
|
||||||
durationvalue, _ := strconv.Atoi(match[1])
|
|
||||||
|
|
||||||
switch match[2][0] {
|
|
||||||
case 'd':
|
|
||||||
seconds += durationvalue * 86400
|
|
||||||
case 'h':
|
|
||||||
seconds += durationvalue * 3600
|
|
||||||
case 'm':
|
|
||||||
seconds += durationvalue * 60
|
|
||||||
case 's':
|
|
||||||
seconds += durationvalue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(seconds) * time.Second
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user