diff --git a/go.mod b/go.mod index efa2c5f..23438a1 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,14 @@ module github.com/timescale/timescaledb-tune -go 1.12 +go 1.18 require ( github.com/fatih/color v1.9.0 github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4 ) + +require ( + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect + golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect +) diff --git a/pkg/pgtune/background_writer.go b/pkg/pgtune/background_writer.go index b18ede5..bb030df 100644 --- a/pkg/pgtune/background_writer.go +++ b/pkg/pgtune/background_writer.go @@ -1,21 +1,16 @@ package pgtune -import "github.com/timescale/timescaledb-tune/internal/parse" - const ( - BgwriterDelayKey = "bgwriter_delay" - BgwriterLRUMaxPagesKey = "bgwriter_lru_maxpages" + BgwriterFlushAfterKey = "bgwriter_flush_after" - promscaleDefaultBgwriterDelay = "10ms" - promscaleDefaultBgwriterLRUMaxPages = "100000" + promscaleDefaultBgwriterFlushAfter = "0" ) // BgwriterLabel is the label used to refer to the background writer settings group const BgwriterLabel = "background writer" var BgwriterKeys = []string{ - BgwriterDelayKey, - BgwriterLRUMaxPagesKey, + BgwriterFlushAfterKey, } // PromscaleBgwriterRecommender gives recommendations for the background writer for the promscale profile @@ -30,10 +25,8 @@ func (r *PromscaleBgwriterRecommender) IsAvailable() bool { // file for a given key. func (r *PromscaleBgwriterRecommender) Recommend(key string) string { switch key { - case BgwriterDelayKey: - return promscaleDefaultBgwriterDelay - case BgwriterLRUMaxPagesKey: - return promscaleDefaultBgwriterLRUMaxPages + case BgwriterFlushAfterKey: + return promscaleDefaultBgwriterFlushAfter default: return NoRecommendation } @@ -61,23 +54,3 @@ func (sg *BgwriterSettingsGroup) GetRecommender(profile Profile) Recommender { return &NullRecommender{} } } - -type BgwriterFloatParser struct{} - -func (v *BgwriterFloatParser) ParseFloat(key string, s string) (float64, error) { - switch key { - case BgwriterDelayKey: - val, units, err := parse.PGFormatToTime(s, parse.Milliseconds, parse.VarTypeInteger) - if err != nil { - return val, err - } - conv, err := parse.TimeConversion(units, parse.Milliseconds) - if err != nil { - return val, err - } - return val * conv, nil - default: - bfp := &numericFloatParser{} - return bfp.ParseFloat(key, s) - } -} diff --git a/pkg/pgtune/background_writer_test.go b/pkg/pgtune/background_writer_test.go index f5cc0b1..83f3c56 100644 --- a/pkg/pgtune/background_writer_test.go +++ b/pkg/pgtune/background_writer_test.go @@ -3,8 +3,6 @@ package pgtune import ( "fmt" "testing" - - "github.com/timescale/timescaledb-tune/internal/parse" ) func TestBgwriterSettingsGroup_GetRecommender(t *testing.T) { @@ -31,20 +29,14 @@ func TestBgwriterSettingsGroupRecommend(t *testing.T) { // the default profile should provide no recommendations r := sg.GetRecommender(DefaultProfile) - if val := r.Recommend(BgwriterDelayKey); val != NoRecommendation { - t.Errorf("Expected no recommendation for key %s but got %s", BgwriterDelayKey, val) - } - if val := r.Recommend(BgwriterLRUMaxPagesKey); val != NoRecommendation { - t.Errorf("Expected no recommendation for key %s but got %s", BgwriterLRUMaxPagesKey, val) + if val := r.Recommend(BgwriterFlushAfterKey); val != NoRecommendation { + t.Errorf("Expected no recommendation for key %s but got %s", BgwriterFlushAfterKey, val) } // the promscale profile should have recommendations r = sg.GetRecommender(PromscaleProfile) - if val := r.Recommend(BgwriterDelayKey); val != promscaleDefaultBgwriterDelay { - t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterDelay, BgwriterDelayKey, val) - } - if val := r.Recommend(BgwriterLRUMaxPagesKey); val != promscaleDefaultBgwriterLRUMaxPages { - t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterLRUMaxPages, BgwriterLRUMaxPagesKey, val) + if val := r.Recommend(BgwriterFlushAfterKey); val != promscaleDefaultBgwriterFlushAfter { + t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterFlushAfter, BgwriterFlushAfterKey, val) } } @@ -53,35 +45,7 @@ func TestPromscaleBgwriterRecommender(t *testing.T) { if !r.IsAvailable() { t.Error("PromscaleBgwriterRecommender should always be available") } - if val := r.Recommend(BgwriterDelayKey); val != promscaleDefaultBgwriterDelay { - t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterDelay, BgwriterDelayKey, val) - } - if val := r.Recommend(BgwriterLRUMaxPagesKey); val != promscaleDefaultBgwriterLRUMaxPages { - t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterLRUMaxPages, BgwriterLRUMaxPagesKey, val) - } -} - -func TestBgwriterFloatParserParseFloat(t *testing.T) { - v := &BgwriterFloatParser{} - - s := "100" - want := 100.0 - got, err := v.ParseFloat(BgwriterLRUMaxPagesKey, s) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if got != want { - t.Errorf("incorrect result: got %f want %f", got, want) - } - - s = "33" + parse.Minutes.String() - conversion, _ := parse.TimeConversion(parse.Minutes, parse.Milliseconds) - want = 33.0 * conversion - got, err = v.ParseFloat(BgwriterDelayKey, s) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if got != want { - t.Errorf("incorrect result: got %f want %f", got, want) + if val := r.Recommend(BgwriterFlushAfterKey); val != promscaleDefaultBgwriterFlushAfter { + t.Errorf("Expected %s for key %s but got %s", promscaleDefaultBgwriterFlushAfter, BgwriterFlushAfterKey, val) } } diff --git a/pkg/pgtune/float_parser.go b/pkg/pgtune/float_parser.go index 95a70aa..6b9ffa2 100644 --- a/pkg/pgtune/float_parser.go +++ b/pkg/pgtune/float_parser.go @@ -1,11 +1,17 @@ package pgtune import ( + "fmt" "strconv" + "strings" "github.com/timescale/timescaledb-tune/internal/parse" ) +const ( + errUnrecognizedBoolValue = "unrecognized bool value: %s" +) + type FloatParser interface { ParseFloat(string, string) (float64, error) } @@ -23,6 +29,34 @@ func (v *numericFloatParser) ParseFloat(key string, s string) (float64, error) { return strconv.ParseFloat(s, 64) } +type boolFloatParser struct{} + +func (v *boolFloatParser) ParseFloat(key string, s string) (float64, error) { + s = strings.ToLower(s) + s = strings.TrimLeft(s, `"'`) + s = strings.TrimRight(s, `"'`) + switch s { + case "on": + return 1.0, nil + case "off": + return 0.0, nil + case "true": + return 1.0, nil + case "false": + return 0.0, nil + case "yes": + return 1.0, nil + case "no": + return 0.0, nil + case "1": + return 1.0, nil + case "0": + return 0.0, nil + default: + return 0.0, fmt.Errorf(errUnrecognizedBoolValue, s) + } +} + // GetFloatParser returns the correct FloatParser for a given Recommender. func GetFloatParser(r Recommender) FloatParser { switch r.(type) { @@ -33,7 +67,7 @@ func GetFloatParser(r Recommender) FloatParser { case *PromscaleWALRecommender: return &WALFloatParser{} case *PromscaleBgwriterRecommender: - return &BgwriterFloatParser{} + return &numericFloatParser{} case *ParallelRecommender: return &numericFloatParser{} default: diff --git a/pkg/pgtune/float_parser_test.go b/pkg/pgtune/float_parser_test.go index 390e9cf..50032fb 100644 --- a/pkg/pgtune/float_parser_test.go +++ b/pkg/pgtune/float_parser_test.go @@ -32,6 +32,119 @@ func TestNumericFloatParserParseFloat(t *testing.T) { } } +func Test_boolFloatParser_ParseFloat(t *testing.T) { + tests := []struct { + name string + arg string + want float64 + wantErr bool + }{ + { + name: "on", + arg: "on", + want: 1.0, + wantErr: false, + }, + { + name: "oN", + arg: "oN", + want: 1.0, + wantErr: false, + }, + { + name: "'ON'", + arg: "'ON'", + want: 1.0, + wantErr: false, + }, + { + name: "off", + arg: "off", + want: 0.0, + wantErr: false, + }, + { + name: "OfF", + arg: "OfF", + want: 0.0, + wantErr: false, + }, + { + name: "'OFF'", + arg: "'OFF'", + want: 0.0, + wantErr: false, + }, + { + name: "true", + arg: "true", + want: 1.0, + wantErr: false, + }, + { + name: "false", + arg: "false", + want: 0.0, + wantErr: false, + }, + { + name: "yes", + arg: "yes", + want: 1.0, + wantErr: false, + }, + { + name: "no", + arg: "no", + want: 0.0, + wantErr: false, + }, + { + name: "1", + arg: "1", + want: 1.0, + wantErr: false, + }, + { + name: "0", + arg: "0", + want: 0.0, + wantErr: false, + }, + { + name: "bob", + arg: "bob", + want: 0.0, + wantErr: true, + }, + { + name: "99", + arg: "99", + want: 0.0, + wantErr: true, + }, + { + name: "0.1", + arg: "0.1", + want: 0.0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &boolFloatParser{} + got, err := v.ParseFloat("", tt.arg) + if (err != nil) != tt.wantErr { + t.Errorf("ParseFloat() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseFloat() got = %v, want %v", got, tt.want) + } + }) + } +} + func TestGetFloatParser(t *testing.T) { switch x := (GetFloatParser(&MemoryRecommender{})).(type) { case *bytesFloatParser: @@ -58,7 +171,7 @@ func TestGetFloatParser(t *testing.T) { } switch x := (GetFloatParser(&PromscaleBgwriterRecommender{})).(type) { - case *BgwriterFloatParser: + case *numericFloatParser: default: t.Errorf("wrong validator type for PromscaleBgwriterRecommender: got %T", x) } diff --git a/pkg/pgtune/wal.go b/pkg/pgtune/wal.go index d9ea7e4..1d2d63b 100644 --- a/pkg/pgtune/wal.go +++ b/pkg/pgtune/wal.go @@ -10,6 +10,7 @@ const ( MinWALKey = "min_wal_size" MaxWALKey = "max_wal_size" CheckpointTimeoutKey = "checkpoint_timeout" + WALCompressionKey = "wal_compression" walMaxDiskPct = 60 // max_wal_size should be 60% of the WAL disk walBuffersThreshold = 2 * parse.Gigabyte @@ -17,6 +18,7 @@ const ( defaultMaxWALBytes = 1 * parse.Gigabyte promscaleDefaultMaxWALBytes = 4 * parse.Gigabyte promscaleDefaultCheckpointTimeout = "900" // 15 minutes expressed in seconds + promscaleDefaultWALCompression = "1" ) // WALLabel is the label used to refer to the WAL settings group @@ -28,6 +30,7 @@ var WALKeys = []string{ MinWALKey, MaxWALKey, CheckpointTimeoutKey, + WALCompressionKey, } // WALRecommender gives recommendations for WALKeys based on system resources @@ -146,6 +149,8 @@ func (r *PromscaleWALRecommender) Recommend(key string) string { return parse.BytesToPGFormat(temp) case CheckpointTimeoutKey: return promscaleDefaultCheckpointTimeout + case WALCompressionKey: + return promscaleDefaultWALCompression default: return r.WALRecommender.Recommend(key) } @@ -173,6 +178,9 @@ type WALFloatParser struct{} func (v *WALFloatParser) ParseFloat(key string, s string) (float64, error) { switch key { + case WALCompressionKey: + bfp := &boolFloatParser{} + return bfp.ParseFloat(key, s) case CheckpointTimeoutKey: val, units, err := parse.PGFormatToTime(s, parse.Milliseconds, parse.VarTypeInteger) if err != nil { diff --git a/pkg/pgtune/wal_test.go b/pkg/pgtune/wal_test.go index ee98aec..dd6dc92 100644 --- a/pkg/pgtune/wal_test.go +++ b/pkg/pgtune/wal_test.go @@ -53,6 +53,7 @@ func init() { walSettingsMatrix[memory][walSize][MaxWALKey] = parse.BytesToPGFormat(walDiskToMaxBytes[walSize]) walSettingsMatrix[memory][walSize][WALBuffersKey] = parse.BytesToPGFormat(walBuffers) walSettingsMatrix[memory][walSize][CheckpointTimeoutKey] = NoRecommendation + walSettingsMatrix[memory][walSize][WALCompressionKey] = NoRecommendation } } @@ -64,6 +65,7 @@ func init() { promscaleWalSettingsMatrix[memory][walSize][MaxWALKey] = parse.BytesToPGFormat(promscaleWALDiskToMaxBytes[walSize]) promscaleWalSettingsMatrix[memory][walSize][WALBuffersKey] = parse.BytesToPGFormat(walBuffers) promscaleWalSettingsMatrix[memory][walSize][CheckpointTimeoutKey] = promscaleDefaultCheckpointTimeout + promscaleWalSettingsMatrix[memory][walSize][WALCompressionKey] = promscaleDefaultWALCompression } } } diff --git a/pkg/tstune/shared_preload_libs_test.go b/pkg/tstune/shared_preload_libs_test.go index 1f45504..bbf6c53 100644 --- a/pkg/tstune/shared_preload_libs_test.go +++ b/pkg/tstune/shared_preload_libs_test.go @@ -9,7 +9,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { want *sharedLibResult }{ { - desc: "initial config value", + desc: "initial config value", input: "#shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: true, @@ -18,7 +18,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "extra commented out", + desc: "extra commented out", input: "###shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: true, @@ -27,7 +27,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "commented with space after", + desc: "commented with space after", input: "# shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: true, @@ -36,7 +36,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "extra commented with space after", + desc: "extra commented with space after", input: "## shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: true, @@ -45,7 +45,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "initial config value, uncommented", + desc: "initial config value, uncommented", input: "shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: false, @@ -54,7 +54,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "initial config value, uncommented with leading space", + desc: "initial config value, uncommented with leading space", input: " shared_preload_libraries = '' # (change requires restart)", want: &sharedLibResult{ commented: false, @@ -63,7 +63,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "timescaledb already there but commented", + desc: "timescaledb already there but commented", input: "#shared_preload_libraries = 'timescaledb' # (change requires restart)", want: &sharedLibResult{ commented: true, @@ -90,7 +90,7 @@ func TestParseLineForSharedLibResult(t *testing.T) { }, }, { - desc: "don't be greedy with things between single quotes", + desc: "don't be greedy with things between single quotes", input: "#shared_preload_libraries = 'timescaledb' # comment with single quote ' test", want: &sharedLibResult{ commented: true, diff --git a/pkg/tstune/tune_settings_test.go b/pkg/tstune/tune_settings_test.go index 6a6484e..f0a2623 100644 --- a/pkg/tstune/tune_settings_test.go +++ b/pkg/tstune/tune_settings_test.go @@ -143,23 +143,23 @@ func TestParseWithRegex(t *testing.T) { }, }, { - desc: "correct, comment at end tabs", + desc: "correct, comment at end tabs", input: testKey + " = 50.0 # do not change!", want: &tunableParseResult{ commented: false, key: testKey, value: "50.0", - extra: " # do not change!", + extra: " # do not change!", }, }, { - desc: "correct, tabs at the end", + desc: "correct, tabs at the end", input: testKey + " = 50.0 ", want: &tunableParseResult{ commented: false, key: testKey, value: "50.0", - extra: " ", + extra: " ", }, }, { @@ -173,7 +173,7 @@ func TestParseWithRegex(t *testing.T) { }, }, { - desc: "commented with spaces", + desc: "commented with spaces", input: " # " + testKey + " = 50.0", want: &tunableParseResult{ commented: true, @@ -183,13 +183,13 @@ func TestParseWithRegex(t *testing.T) { }, }, { - desc: "commented with ending comment", + desc: "commented with ending comment", input: "# " + testKey + " = 50.0 # do not change", want: &tunableParseResult{ commented: true, key: testKey, value: "50.0", - extra: " # do not change", + extra: " # do not change", }, }, { diff --git a/pkg/tstune/tuner.go b/pkg/tstune/tuner.go index f860cab..38884ef 100644 --- a/pkg/tstune/tuner.go +++ b/pkg/tstune/tuner.go @@ -22,7 +22,7 @@ import ( const ( // Version is the version of this library - Version = "0.14.2" + Version = "0.14.3" errCouldNotExecuteFmt = "could not execute `%s --version`: %v" errUnsupportedMajorFmt = "unsupported major PG version: %s" diff --git a/pkg/tstune/tuner_test.go b/pkg/tstune/tuner_test.go index 7aee854..2084526 100644 --- a/pkg/tstune/tuner_test.go +++ b/pkg/tstune/tuner_test.go @@ -1240,20 +1240,46 @@ func TestTunerProcessSettingsGroup(t *testing.T) { input: "y\n", wantStatements: 3, // intro remark + current label + recommend label wantPrompts: 1, - wantPrints: 6, // one for initial newline + 3 for recommendations - wantErrors: 3, // 3 are missing + wantPrints: 7, + wantErrors: 4, successMsg: "WAL settings will be updated", shouldErr: false, }, { - desc: "bgwriter", + desc: "wal - wal_compression promscale", + ts: pgtune.GetSettingsGroup(pgtune.WALLabel, config), + profile: pgtune.PromscaleProfile, + lines: []string{"wal_compression = off"}, + input: "y\n", + wantStatements: 3, // intro remark + current label + recommend label + wantPrompts: 1, + wantPrints: 7, + wantErrors: 4, + successMsg: "WAL settings will be updated", + shouldErr: false, + }, + { + desc: "bgwriter wrong", + ts: pgtune.GetSettingsGroup(pgtune.BgwriterLabel, config), + profile: pgtune.PromscaleProfile, + lines: []string{"bgwriter_flush_after = 100"}, + input: "y\n", + wantStatements: 3, // intro remark + current label + recommend label + wantPrompts: 1, + wantPrints: 3, + wantErrors: 0, + successMsg: "background writer settings will be updated", + shouldErr: false, + }, + { + desc: "bgwriter correct", ts: pgtune.GetSettingsGroup(pgtune.BgwriterLabel, config), profile: pgtune.PromscaleProfile, - lines: []string{"bgwriter_delay = 13s", "bgwriter_lru_maxpages = 1000"}, + lines: []string{"bgwriter_flush_after = 0"}, input: "y\n", wantStatements: 3, // intro remark + current label + recommend label wantPrompts: 1, - wantPrints: 5, + wantPrints: 3, wantErrors: 0, successMsg: "background writer settings will be updated", shouldErr: false,