blob: b37df8e5313c1f8c6e0ff34877a6ac12630ecea2 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(hinoka): Remove this file after migrating to the new archival tasking pipeline.
// These settings are used for adjusting how much traffic goes into the new archival tasking pipeline.
// 3 Settings are available for the new pipeline:
// * Optimistic archival delay - Controls the delay to set for optimistic archival.
// * Optimistic archival tasking percentage - Controls what % of terminated streams are tasked to the new pipeline
// * Pessimistic archival tasking percentage - Controls what % of registered streams are tasked to the new pipeline.
package coordinator
import (
"context"
"fmt"
"html/template"
"strconv"
"time"
"go.chromium.org/luci/common/clock/clockflag"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/server/portal"
"go.chromium.org/luci/server/settings"
)
const (
settingDisabled = "disabled"
settingEnabled = "enabled"
baseName = "archivist"
)
type Settings struct {
// OptimisticArchivalDelay controls the delay to set for optimistic archival.
OptimisticArchivalDelay time.Duration
// OptimisticArchivalPercent (0-100) Controls what percent of terminated streams
// are tasked to the new pipeline
OptimisticArchivalPercent uint32
// PessimisticArchivalPercent (0-100) Controls what percent of registered
// streams are tasked to the new pipeline after 47 hours.
PessimisticArchivalPercent uint32
// ArchivistBatchSize controls the batchsize per lease cycle.
ArchivistBatchSize int64
// ArchivistLeaseTime controls the lease time per cycle.
ArchivistLeaseTime time.Duration
}
var defaultSettings = Settings{
OptimisticArchivalDelay: 5 * time.Minute,
ArchivistBatchSize: 50,
ArchivistLeaseTime: 15 * time.Minute,
}
// settingsPage is a UI page to configure a static Tumble configuration.
type settingsPage struct {
portal.BasePage
}
func (settingsPage) Title(c context.Context) (string, error) {
return "LogDog Archivist Settings", nil
}
func (settingsPage) Overview(c context.Context) (template.HTML, error) {
return template.HTML(`<p>Configuration parameters for the Archivist tasking pipeline.</p>.`), nil
}
func (settingsPage) Fields(c context.Context) ([]portal.Field, error) {
return []portal.Field{
{
ID: "OptimisticArchivalDelay",
Title: "The delay, of how the optimistic archival delay. " +
"This should be set to compensate for potential collector pipeline delays.",
Type: portal.FieldText,
Placeholder: defaultSettings.OptimisticArchivalDelay.String(),
Validator: validateDuration,
},
{
ID: "OptimisticArchivalPercent",
Title: "Percentage (0-100) of tasks to go to the new optimistic pipeline.",
Type: portal.FieldText,
Placeholder: "0",
Validator: validatePercent,
},
{
ID: "PessimisticArchivalPercent",
Title: "Percentage (0-100) of tasks to go to the new pessimistic pipeline (47hr delay from registration).",
Type: portal.FieldText,
Placeholder: "0",
Validator: validatePercent,
},
{
ID: "ArchivistBatchSize",
Title: "Number of Archive Tasks to lease in each LeaseTasks request. " +
"The theoretical max Task processed per second is this number * 10, " +
"due to the limitations of GAE TaskQueues.",
Type: portal.FieldText,
Placeholder: fmt.Sprintf("%d", defaultSettings.ArchivistBatchSize),
Validator: validateInt,
},
{
ID: "ArchivistLeaseTime",
Title: "Lease time per cycle for the Archivist.",
Type: portal.FieldText,
Placeholder: defaultSettings.ArchivistLeaseTime.String(),
Validator: validateDuration,
},
}, nil
}
func (settingsPage) ReadSettings(c context.Context) (map[string]string, error) {
var set Settings
switch err := settings.GetUncached(c, baseName, &set); err {
case nil:
break
case settings.ErrNoSettings:
logging.WithError(err).Infof(c, "No settings available, using defaults.")
set = defaultSettings
default:
return nil, err
}
values := map[string]string{}
// Only render values if they differ from our default config.
if set.OptimisticArchivalDelay != defaultSettings.OptimisticArchivalDelay {
values["OptimisticArchivalDelay"] = set.OptimisticArchivalDelay.String()
}
if set.OptimisticArchivalPercent != defaultSettings.OptimisticArchivalPercent {
values["OptimisticArchivalPercent"] = fmt.Sprintf("%d", set.OptimisticArchivalPercent)
}
if set.PessimisticArchivalPercent != defaultSettings.PessimisticArchivalPercent {
values["PessimisticArchivalPercent"] = fmt.Sprintf("%d", set.PessimisticArchivalPercent)
}
if set.ArchivistBatchSize != defaultSettings.ArchivistBatchSize {
values["ArchivistBatchSize"] = fmt.Sprintf("%d", set.ArchivistBatchSize)
}
if set.ArchivistLeaseTime != defaultSettings.ArchivistLeaseTime {
values["ArchivistLeaseTime"] = set.ArchivistLeaseTime.String()
}
return values, nil
}
func (settingsPage) WriteSettings(c context.Context, values map[string]string, who, why string) error {
// Start with our default config and shape it with populated values.
set := defaultSettings
if v := values["OptimisticArchivalDelay"]; v != "" {
t, err := clockflag.ParseDuration(v)
if err != nil {
return fmt.Errorf("could not parse OptimisticArchivalDelay: %v", err)
}
set.OptimisticArchivalDelay = time.Duration(t)
}
if v := values["OptimisticArchivalPercent"]; v != "" {
i, err := strconv.Atoi(v)
if err != nil {
return fmt.Errorf("could not parse OptimisticArchivalPercent: %v", err)
}
set.OptimisticArchivalPercent = uint32(i)
}
if v := values["PessimisticArchivalPercent"]; v != "" {
i, err := strconv.Atoi(v)
if err != nil {
return fmt.Errorf("could not parse PessimisticArchivalPercent: %v", err)
}
set.PessimisticArchivalPercent = uint32(i)
}
if v := values["ArchivistBatchSize"]; v != "" {
i, err := strconv.Atoi(v)
if err != nil {
return fmt.Errorf("could not parse ArchivistBatchSize : %v", err)
}
set.ArchivistBatchSize = int64(i)
}
if v := values["ArchivistLeaseTime"]; v != "" {
t, err := clockflag.ParseDuration(v)
if err != nil {
return fmt.Errorf("could not parse ArchivistLeaseTime : %v", err)
}
set.ArchivistLeaseTime = time.Duration(t)
}
return settings.SetIfChanged(c, baseName, &set, who, why)
}
// validatePercent validates a string is empty or an integer between 0-100.
func validatePercent(v string) error {
if v == "" {
return nil
}
i, err := strconv.Atoi(v)
if err != nil {
return fmt.Errorf("invalid integer %q - %s", v, err)
}
if i < 0 || i > 100 {
return fmt.Errorf("%d is out of range (0-100)", i)
}
return nil
}
// validateInt validates that a string is empty or an integer.
func validateInt(v string) error {
if v == "" {
return nil
}
_, err := strconv.Atoi(v)
return err
}
func validateDuration(v string) error {
if v == "" {
return nil
}
var cf clockflag.Duration
if err := cf.Set(v); err != nil {
return fmt.Errorf("bad duration %q - %s", v, err)
}
if cf <= 0 {
return fmt.Errorf("duration %q must be positive", v)
}
return nil
}
// GetSettings returns the current settings.
//
// It first tries to load it from settings. If no settings is installed, or if
// there is no configuration in settings, defaultSettings is returned.
func GetSettings(c context.Context) *Settings {
set := Settings{}
switch err := settings.Get(c, baseName, &set); err {
case nil:
break
case settings.ErrNoSettings:
// Defaults.
set = defaultSettings
default:
panic(fmt.Errorf("could not fetch Archivist settings - %s", err))
}
return &set
}
func init() {
portal.RegisterPage(baseName, settingsPage{})
}