From 4a683f377f7776988184b72bb76db3f9446eb73e Mon Sep 17 00:00:00 2001 From: Matthias Loibl Date: Fri, 20 Sep 2019 16:39:52 +0200 Subject: [PATCH 01/12] Add metalmatze to list of maintainers (#1547) Signed-off-by: Matthias Loibl --- MAINTAINERS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 7cb90239f04..8803a0c0bdd 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -7,10 +7,11 @@ | Frederic Branczyk | fbranczyk@gmail.com | `@brancz` | [@brancz](https://github.com/brancz) | | Giedrius Statkevičius | giedriuswork@gmail.com | `@Giedrius Statkevičius` | [@GiedriusS](https://github.com/GiedriusS) | | Povilas Versockas | p.versockas@gmail.com | `@povilasv` | [@povilasv](https://github.com/povilasv) | +| Matthias Loibl | mail@matthiasloibl.com | `@metalmatze` | [@metalmatze](https://github.com/metalmatze) | We are bunch of people from different companies with various interests and skills. We are from different parts of Europe: Germany, Lithuania, Poland and UK. -We have something in common though: We all share the love for OpenSource, Golang, Prometheus, :coffee: and Observability topics. +We have something in common though: We all share the love for OpenSource, Go, Prometheus, :coffee: and Observability topics. As either Software Developers or SRE (or both!) we've chosen to maintain (mostly in our free time) Thanos, the de facto way to scale awesome [Prometheus](https://prometheus.io) project. @@ -96,4 +97,5 @@ maintainer team. ## Initial authors -Fabian Reinartz @fabxc and Bartłomiej Płotka @bwplotka \ No newline at end of file +Fabian Reinartz @fabxc and Bartłomiej Płotka @bwplotka + From 3a6f8e15fc24aa96b328744802beea07911e44e9 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Fri, 20 Sep 2019 17:10:23 +0200 Subject: [PATCH 02/12] receive: Add liveness and readiness probe (#1537) * Add prober to receive Signed-off-by: Kemal Akkoyun * Add changelog entries Signed-off-by: Kemal Akkoyun * Update README Signed-off-by: Kemal Akkoyun * Remove default Signed-off-by: Kemal Akkoyun * Wait hashring to be ready Signed-off-by: Kemal Akkoyun --- CHANGELOG.md | 8 +++++--- cmd/thanos/compact.go | 4 ++-- cmd/thanos/main.go | 2 +- cmd/thanos/query.go | 4 ++-- cmd/thanos/receive.go | 36 +++++++++++++++++++++++++----------- cmd/thanos/sidecar.go | 4 ++-- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ffcf972c7..825efa655f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,14 @@ We use *breaking* word for marking changes that are not backward compatible (rel ## Added - [#1538](https://github.com/thanos-io/thanos/pull/1538) Added `/-/ready` and `/-/healthy` endpoints to Thanos Rule. --[1533](https://github.com/thanos-io/thanos/pull/1533) Thanos inspect now supports the timeout flag. +- [#1537](https://github.com/thanos-io/thanos/pull/1537) Added `/-/ready` and `/-/healthy` endpoints to Thanos Receive. +- [#1534](https://github.com/thanos-io/thanos/pull/1534) Added `/-/ready` endpoint to Thanos Query. +- [#1533](https://github.com/thanos-io/thanos/pull/1533) Thanos inspect now supports the timeout flag. ### Fixed --[#1525](https://github.com/thanos-io/thanos/pull/1525) Thanos now deletes block's file in correct order allowing to detect partial blocks without problems. --[#1505](https://github.com/thanos-io/thanos/pull/1505) Thanos store now removes invalid local cache blocks. +- [#1525](https://github.com/thanos-io/thanos/pull/1525) Thanos now deletes block's file in correct order allowing to detect partial blocks without problems. +- [#1505](https://github.com/thanos-io/thanos/pull/1505) Thanos store now removes invalid local cache blocks. ## v0.7.0 - 2019.09.02 diff --git a/cmd/thanos/compact.go b/cmd/thanos/compact.go index 5b2e277fc66..01347caba2a 100644 --- a/cmd/thanos/compact.go +++ b/cmd/thanos/compact.go @@ -168,9 +168,9 @@ func runCompact( downsampleMetrics := newDownsampleMetrics(reg) statusProber := prober.NewProber(component, logger, prometheus.WrapRegistererWithPrefix("thanos_", reg)) - // Initiate default HTTP listener providing metrics endpoint and readiness/liveness probes. + // Initiate HTTP listener providing metrics endpoint and readiness/liveness probes. if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, nil, component); err != nil { - return errors.Wrap(err, "create default HTTP server with readiness prober") + return errors.Wrap(err, "schedule HTTP server with probes") } confContentYaml, err := objStoreConfig.Content() diff --git a/cmd/thanos/main.go b/cmd/thanos/main.go index a5143be3710..a7a583915f8 100644 --- a/cmd/thanos/main.go +++ b/cmd/thanos/main.go @@ -80,7 +80,7 @@ func main() { registerCompact(cmds, app) registerBucket(cmds, app, "bucket") registerDownsample(cmds, app, "downsample") - registerReceive(cmds, app, "receive") + registerReceive(cmds, app) registerChecks(cmds, app, "check") cmd, err := app.Parse(os.Args[1:]) diff --git a/cmd/thanos/query.go b/cmd/thanos/query.go index 515c57de61f..594ac90de31 100644 --- a/cmd/thanos/query.go +++ b/cmd/thanos/query.go @@ -414,9 +414,9 @@ func runQuery( api.Register(router.WithPrefix(path.Join(webRoutePrefix, "/api/v1")), tracer, logger, ins) - // Initiate default HTTP listener providing metrics endpoint and readiness/liveness probes. + // Initiate HTTP listener providing metrics endpoint and readiness/liveness probes. if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, router, comp); err != nil { - return errors.Wrap(err, "create default HTTP server with readiness prober") + return errors.Wrap(err, "schedule HTTP server with probes") } } // Start query (proxy) gRPC StoreAPI. diff --git a/cmd/thanos/receive.go b/cmd/thanos/receive.go index ed6115425cc..72dd8752b0a 100644 --- a/cmd/thanos/receive.go +++ b/cmd/thanos/receive.go @@ -20,6 +20,7 @@ import ( "github.com/thanos-io/thanos/pkg/block/metadata" "github.com/thanos-io/thanos/pkg/component" "github.com/thanos-io/thanos/pkg/objstore/client" + "github.com/thanos-io/thanos/pkg/prober" "github.com/thanos-io/thanos/pkg/receive" "github.com/thanos-io/thanos/pkg/runutil" "github.com/thanos-io/thanos/pkg/shipper" @@ -28,11 +29,12 @@ import ( kingpin "gopkg.in/alecthomas/kingpin.v2" ) -func registerReceive(m map[string]setupFunc, app *kingpin.Application, name string) { - cmd := app.Command(name, "Accept Prometheus remote write API requests and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)") +func registerReceive(m map[string]setupFunc, app *kingpin.Application) { + comp := component.Receive + cmd := app.Command(comp.String(), "Accept Prometheus remote write API requests and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)") grpcBindAddr, cert, key, clientCA := regGRPCFlags(cmd) - httpMetricsBindAddr := regHTTPAddrFlag(cmd) + httpBindAddr := regHTTPAddrFlag(cmd) remoteWriteAddress := cmd.Flag("remote-write.address", "Address to listen on for remote write requests."). Default("0.0.0.0:19291").String() @@ -62,7 +64,7 @@ func registerReceive(m map[string]setupFunc, app *kingpin.Application, name stri tsdbBlockDuration := modelDuration(cmd.Flag("tsdb.block-duration", "Duration for local TSDB blocks").Default("2h").Hidden()) - m[name] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ bool) error { + m[comp.String()] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ bool) error { lset, err := parseFlagLabels(*labelStrs) if err != nil { return errors.Wrap(err, "parse labels") @@ -97,7 +99,7 @@ func registerReceive(m map[string]setupFunc, app *kingpin.Application, name stri *cert, *key, *clientCA, - *httpMetricsBindAddr, + *httpBindAddr, *remoteWriteAddress, *dataDir, objStoreConfig, @@ -109,6 +111,7 @@ func registerReceive(m map[string]setupFunc, app *kingpin.Application, name stri *replicaHeader, *replicationFactor, *tsdbBlockDuration, + comp, ) } } @@ -122,7 +125,7 @@ func runReceive( cert string, key string, clientCA string, - httpMetricsBindAddr string, + httpBindAddr string, remoteWriteAddress string, dataDir string, objStoreConfig *pathOrContent, @@ -134,6 +137,7 @@ func runReceive( replicaHeader string, replicationFactor uint64, tsdbBlockDuration model.Duration, + comp component.Component, ) error { logger = log.With(logger, "component", "receive") level.Warn(logger).Log("msg", "setting up receive; the Thanos receive component is EXPERIMENTAL, it may break significantly without notice") @@ -159,6 +163,8 @@ func runReceive( ReplicationFactor: replicationFactor, }) + statusProber := prober.NewProber(comp, logger, prometheus.WrapRegistererWithPrefix("thanos_", reg)) + // Start all components while we wait for TSDB to open but only load // initial config and mark ourselves as ready after it completed. dbOpen := make(chan struct{}) @@ -198,6 +204,7 @@ func runReceive( ) } + hashringReady := make(chan struct{}) level.Debug(logger).Log("msg", "setting up hashring") { updates := make(chan receive.Hashring) @@ -227,15 +234,20 @@ func runReceive( func() error { select { case h := <-updates: + close(hashringReady) webHandler.Hashring(h) + statusProber.SetReady() case <-cancel: + close(hashringReady) return nil } select { // If any new hashring is received, then mark the handler as unready, but keep it alive. case <-updates: + msg := "hashring has changed; server is not ready to receive web requests." webHandler.Hashring(nil) - level.Info(logger).Log("msg", "hashring has changed; server is not ready to receive web requests.") + statusProber.SetNotReady(errors.New(msg)) + level.Info(logger).Log("msg", msg) case <-cancel: return nil } @@ -248,9 +260,10 @@ func runReceive( ) } - level.Debug(logger).Log("msg", "setting up metric http listen-group") - if err := metricHTTPListenGroup(g, logger, reg, httpMetricsBindAddr); err != nil { - return err + level.Debug(logger).Log("msg", "setting up http server") + // Initiate HTTP listener providing metrics endpoint and readiness/liveness probes. + if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, nil, comp); err != nil { + return errors.Wrap(err, "schedule HTTP server with probes") } level.Debug(logger).Log("msg", "setting up grpc server") @@ -277,6 +290,8 @@ func runReceive( } s := newStoreGRPCServer(logger, reg, tracer, tsdbStore, opts) + // Wait hashring to be ready before start serving metrics + <-hashringReady level.Info(logger).Log("msg", "listening for StoreAPI gRPC", "address", grpcBindAddr) return errors.Wrap(s.Serve(l), "serve gRPC") }, func(error) { @@ -343,6 +358,5 @@ func runReceive( } level.Info(logger).Log("msg", "starting receiver") - return nil } diff --git a/cmd/thanos/sidecar.go b/cmd/thanos/sidecar.go index 68afd666e4e..aab6be8e55a 100644 --- a/cmd/thanos/sidecar.go +++ b/cmd/thanos/sidecar.go @@ -120,9 +120,9 @@ func runSidecar( } statusProber := prober.NewProber(comp, logger, prometheus.WrapRegistererWithPrefix("thanos_", reg)) - // Initiate default HTTP listener providing metrics endpoint and readiness/liveness probes. + // Initiate HTTP listener providing metrics endpoint and readiness/liveness probes. if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, nil, comp); err != nil { - return errors.Wrap(err, "create default HTTP server with readiness prober") + return errors.Wrap(err, "schedule HTTP server with probes") } // Setup all the concurrent groups. From a5696418c9e9b0215c4216160fd13c233c70667f Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Fri, 20 Sep 2019 18:53:16 +0200 Subject: [PATCH 03/12] downsample: Add liveness and readiness probe (#1540) * Add readiness and liveness probes for downsampler Signed-off-by: Kemal Akkoyun * Add changelog entry Signed-off-by: Kemal Akkoyun * Remove default Signed-off-by: Kemal Akkoyun * Set ready Signed-off-by: Kemal Akkoyun * Update CHANGELOG Signed-off-by: Kemal Akkoyun * Clean CHANGELOG Signed-off-by: Kemal Akkoyun --- CHANGELOG.md | 1 + cmd/thanos/downsample.go | 19 ++++++++++++------- cmd/thanos/main.go | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 825efa655f8..dd3b4dd277a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ We use *breaking* word for marking changes that are not backward compatible (rel ## Unreleased ## Added +- [#1540](https://github.com/thanos-io/thanos/pull/1540) Added `/-/ready` and `/-/healthy` endpoints to Thanos Downsample. - [#1538](https://github.com/thanos-io/thanos/pull/1538) Added `/-/ready` and `/-/healthy` endpoints to Thanos Rule. - [#1537](https://github.com/thanos-io/thanos/pull/1537) Added `/-/ready` and `/-/healthy` endpoints to Thanos Receive. - [#1534](https://github.com/thanos-io/thanos/pull/1534) Added `/-/ready` endpoint to Thanos Query. diff --git a/cmd/thanos/downsample.go b/cmd/thanos/downsample.go index ac5ae31bbb9..2f90f368aca 100644 --- a/cmd/thanos/downsample.go +++ b/cmd/thanos/downsample.go @@ -22,12 +22,14 @@ import ( "github.com/thanos-io/thanos/pkg/component" "github.com/thanos-io/thanos/pkg/objstore" "github.com/thanos-io/thanos/pkg/objstore/client" + "github.com/thanos-io/thanos/pkg/prober" "github.com/thanos-io/thanos/pkg/runutil" kingpin "gopkg.in/alecthomas/kingpin.v2" ) -func registerDownsample(m map[string]setupFunc, app *kingpin.Application, name string) { - cmd := app.Command(name, "continuously downsamples blocks in an object store bucket") +func registerDownsample(m map[string]setupFunc, app *kingpin.Application) { + comp := component.Downsample + cmd := app.Command(comp.String(), "continuously downsamples blocks in an object store bucket") httpAddr := regHTTPAddrFlag(cmd) @@ -36,8 +38,8 @@ func registerDownsample(m map[string]setupFunc, app *kingpin.Application, name s objStoreConfig := regCommonObjStoreFlags(cmd, "", true) - m[name] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ bool) error { - return runDownsample(g, logger, reg, *httpAddr, *dataDir, objStoreConfig) + m[comp.String()] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ bool) error { + return runDownsample(g, logger, reg, *httpAddr, *dataDir, objStoreConfig, comp) } } @@ -71,6 +73,7 @@ func runDownsample( httpBindAddr string, dataDir string, objStoreConfig *pathOrContent, + comp component.Component, ) error { confContentYaml, err := objStoreConfig.Content() if err != nil { @@ -90,13 +93,14 @@ func runDownsample( }() metrics := newDownsampleMetrics(reg) - + statusProber := prober.NewProber(comp, logger, prometheus.WrapRegistererWithPrefix("thanos_", reg)) // Start cycle of syncing blocks from the bucket and garbage collecting the bucket. { ctx, cancel := context.WithCancel(context.Background()) g.Add(func() error { defer runutil.CloseWithLogOnErr(logger, bkt, "bucket client") + statusProber.SetReady() level.Info(logger).Log("msg", "start first pass of downsampling") @@ -116,8 +120,9 @@ func runDownsample( }) } - if err := metricHTTPListenGroup(g, logger, reg, httpBindAddr); err != nil { - return err + // Initiate HTTP listener providing metrics endpoint and readiness/liveness probes. + if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, nil, comp); err != nil { + return errors.Wrap(err, "schedule HTTP server with probe") } level.Info(logger).Log("msg", "starting downsample node") diff --git a/cmd/thanos/main.go b/cmd/thanos/main.go index a7a583915f8..3f27527efdf 100644 --- a/cmd/thanos/main.go +++ b/cmd/thanos/main.go @@ -79,7 +79,7 @@ func main() { registerRule(cmds, app) registerCompact(cmds, app) registerBucket(cmds, app, "bucket") - registerDownsample(cmds, app, "downsample") + registerDownsample(cmds, app) registerReceive(cmds, app) registerChecks(cmds, app, "check") From 7e1d151807bdb254a4e606b0fcd1689ddd516909 Mon Sep 17 00:00:00 2001 From: Antonio Santos Date: Sat, 21 Sep 2019 11:08:17 +0200 Subject: [PATCH 04/12] Document the dnssrvnoa option (#1551) Signed-off-by: Antonio Santos --- docs/service-discovery.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/service-discovery.md b/docs/service-discovery.md index 62c5dde6ca7..3931f2f0b1f 100644 --- a/docs/service-discovery.md +++ b/docs/service-discovery.md @@ -90,12 +90,18 @@ An example using this lookup with a static flag: --store=dns+stores.thanos.mycompany.org:9090 ``` -* `dnssrv+` - the domain name after this prefix will be looked up as a SRV query. You do not need to specify a port as the -one from the query results will be used. An example: +* `dnssrv+` - the domain name after this prefix will be looked up as a SRV query, and then each SRV record will be looked up as an A/AAAA query. You do not need to specify a port as the one from the query results will be used. An example: + ``` --store=dnssrv+_thanosstores._tcp.mycompany.org ``` +* `dnssrvnoa+` - the domain name after this prefix will be looked up as a SRV query, with no A/AAAA lookup made after that. Similar to the `dnssrv+` case, you do not need to specify a port. An example: + +``` +--store=dnssrvnoa+_thanosstores._tcp.mycompany.org +``` + The default interval between DNS lookups is 30s. You can change it using the `store.sd-dns-interval` flag for `StoreAPI` configuration in `Thanos Query`, or `query.sd-dns-interval` for `QueryAPI` configuration in `Thanos Rule`. From 7a2f54e831fa46536cfd9c248108feda58f27fbb Mon Sep 17 00:00:00 2001 From: Martin Chodur Date: Mon, 23 Sep 2019 09:31:14 +0200 Subject: [PATCH 05/12] feat store: added readiness and livenes prober (#1460) Signed-off-by: Martin Chodur --- CHANGELOG.md | 9 +- cmd/thanos/main.go | 23 +-- cmd/thanos/store.go | 172 +++++++++--------- .../manifests/thanos-store-gateway.yaml | 8 + 4 files changed, 102 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3b4dd277a..f43d8514997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,11 @@ We use *breaking* word for marking changes that are not backward compatible (rel ## Unreleased ## Added -- [#1540](https://github.com/thanos-io/thanos/pull/1540) Added `/-/ready` and `/-/healthy` endpoints to Thanos Downsample. -- [#1538](https://github.com/thanos-io/thanos/pull/1538) Added `/-/ready` and `/-/healthy` endpoints to Thanos Rule. -- [#1537](https://github.com/thanos-io/thanos/pull/1537) Added `/-/ready` and `/-/healthy` endpoints to Thanos Receive. -- [#1534](https://github.com/thanos-io/thanos/pull/1534) Added `/-/ready` endpoint to Thanos Query. +- [#1540](https://github.com/thanos-io/thanos/pull/1540) Thanos Downsample added `/-/ready` and `/-/healthy` endpoints. +- [#1538](https://github.com/thanos-io/thanos/pull/1538) Thanos Rule added `/-/ready` and `/-/healthy` endpoints. +- [#1537](https://github.com/thanos-io/thanos/pull/1537) Thanos Receive added `/-/ready` and `/-/healthy` endpoints. +- [#1460](https://github.com/thanos-io/thanos/pull/1460) Thanos Store Added `/-/ready` and `/-/healthy` endpoints. +- [#1534](https://github.com/thanos-io/thanos/pull/1534) Thanos Query Added `/-/ready` and `/-/healthy` endpoints. - [#1533](https://github.com/thanos-io/thanos/pull/1533) Thanos inspect now supports the timeout flag. ### Fixed diff --git a/cmd/thanos/main.go b/cmd/thanos/main.go index 3f27527efdf..5300aa1985b 100644 --- a/cmd/thanos/main.go +++ b/cmd/thanos/main.go @@ -74,7 +74,7 @@ func main() { cmds := map[string]setupFunc{} registerSidecar(cmds, app) - registerStore(cmds, app, "store") + registerStore(cmds, app) registerQuery(cmds, app) registerRule(cmds, app) registerCompact(cmds, app) @@ -333,27 +333,6 @@ func newStoreGRPCServer(logger log.Logger, reg *prometheus.Registry, tracer open return s } -// TODO Remove once all components are migrated to the new scheduleHTTPServer. -// metricHTTPListenGroup is a run.Group that servers HTTP endpoint with only Prometheus metrics. -func metricHTTPListenGroup(g *run.Group, logger log.Logger, reg *prometheus.Registry, httpBindAddr string) error { - mux := http.NewServeMux() - registerMetrics(mux, reg) - registerProfile(mux) - - l, err := net.Listen("tcp", httpBindAddr) - if err != nil { - return errors.Wrap(err, "listen metrics address") - } - - g.Add(func() error { - level.Info(logger).Log("msg", "Listening for metrics", "address", httpBindAddr) - return errors.Wrap(http.Serve(l, mux), "serve metrics") - }, func(error) { - runutil.CloseWithLogOnErr(logger, l, "metric listener") - }) - return nil -} - // scheduleHTTPServer starts a run.Group that servers HTTP endpoint with default endpoints providing Prometheus metrics, // profiling and liveness/readiness probes. func scheduleHTTPServer(g *run.Group, logger log.Logger, reg *prometheus.Registry, readinessProber *prober.Prober, httpBindAddr string, handler http.Handler, comp component.Component) error { diff --git a/cmd/thanos/store.go b/cmd/thanos/store.go index c3f7ccb8710..163cc64bac1 100644 --- a/cmd/thanos/store.go +++ b/cmd/thanos/store.go @@ -8,20 +8,22 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/oklog/run" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" + "github.com/thanos-io/thanos/pkg/component" "github.com/thanos-io/thanos/pkg/model" "github.com/thanos-io/thanos/pkg/objstore/client" + "github.com/thanos-io/thanos/pkg/prober" "github.com/thanos-io/thanos/pkg/runutil" "github.com/thanos-io/thanos/pkg/store" storecache "github.com/thanos-io/thanos/pkg/store/cache" - kingpin "gopkg.in/alecthomas/kingpin.v2" + "gopkg.in/alecthomas/kingpin.v2" ) // registerStore registers a store command. -func registerStore(m map[string]setupFunc, app *kingpin.Application, name string) { - cmd := app.Command(name, "store node giving access to blocks in a bucket provider. Now supported GCS, S3, Azure, Swift and Tencent COS.") +func registerStore(m map[string]setupFunc, app *kingpin.Application) { + cmd := app.Command(component.Store.String(), "store node giving access to blocks in a bucket provider. Now supported GCS, S3, Azure, Swift and Tencent COS.") grpcBindAddr, httpBindAddr, cert, key, clientCA := regCommonServerFlags(cmd) @@ -54,7 +56,7 @@ func registerStore(m map[string]setupFunc, app *kingpin.Application, name string maxTime := model.TimeOrDuration(cmd.Flag("max-time", "End of time range limit to serve. Thanos Store serves only blocks, which happened eariler than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y."). Default("9999-12-31T23:59:59Z")) - m[name] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, debugLogging bool) error { + m[component.Store.String()] = func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, debugLogging bool) error { if minTime.PrometheusTimestamp() > maxTime.PrometheusTimestamp() { return errors.Errorf("invalid argument: --min-time '%s' can't be greater than --max-time '%s'", minTime, maxTime) @@ -75,7 +77,7 @@ func registerStore(m map[string]setupFunc, app *kingpin.Application, name string uint64(*chunkPoolSize), uint64(*maxSampleCount), int(*maxConcurrent), - name, + component.Store, debugLogging, *syncInterval, *blockSyncConcurrency, @@ -104,102 +106,104 @@ func runStore( chunkPoolSizeBytes uint64, maxSampleCount uint64, maxConcurrent int, - component string, + component component.Component, verbose bool, syncInterval time.Duration, blockSyncConcurrency int, filterConf *store.FilterConfig, ) error { - { - confContentYaml, err := objStoreConfig.Content() - if err != nil { - return err - } + statusProber := prober.NewProber(component, logger, prometheus.WrapRegistererWithPrefix("thanos_", reg)) + + confContentYaml, err := objStoreConfig.Content() + if err != nil { + return err + } + + bkt, err := client.NewBucket(logger, confContentYaml, reg, component.String()) + if err != nil { + return errors.Wrap(err, "create bucket client") + } - bkt, err := client.NewBucket(logger, confContentYaml, reg, component) + // Ensure we close up everything properly. + defer func() { if err != nil { - return errors.Wrap(err, "create bucket client") + runutil.CloseWithLogOnErr(logger, bkt, "bucket client") } + }() - // Ensure we close up everything properly. - defer func() { - if err != nil { - runutil.CloseWithLogOnErr(logger, bkt, "bucket client") - } - }() + // TODO(bwplotka): Add as a flag? + maxItemSizeBytes := indexCacheSizeBytes / 2 - // TODO(bwplotka): Add as a flag? - maxItemSizeBytes := indexCacheSizeBytes / 2 + indexCache, err := storecache.NewIndexCache(logger, reg, storecache.Opts{ + MaxSizeBytes: indexCacheSizeBytes, + MaxItemSizeBytes: maxItemSizeBytes, + }) + if err != nil { + return errors.Wrap(err, "create index cache") + } - indexCache, err := storecache.NewIndexCache(logger, reg, storecache.Opts{ - MaxSizeBytes: indexCacheSizeBytes, - MaxItemSizeBytes: maxItemSizeBytes, - }) - if err != nil { - return errors.Wrap(err, "create index cache") - } + bs, err := store.NewBucketStore( + logger, + reg, + bkt, + dataDir, + indexCache, + chunkPoolSizeBytes, + maxSampleCount, + maxConcurrent, + verbose, + blockSyncConcurrency, + filterConf, + ) + if err != nil { + return errors.Wrap(err, "create object storage store") + } - bs, err := store.NewBucketStore( - logger, - reg, - bkt, - dataDir, - indexCache, - chunkPoolSizeBytes, - maxSampleCount, - maxConcurrent, - verbose, - blockSyncConcurrency, - filterConf, - ) - if err != nil { - return errors.Wrap(err, "create object storage store") - } + begin := time.Now() + level.Debug(logger).Log("msg", "initializing bucket store") + if err := bs.InitialSync(context.Background()); err != nil { + return errors.Wrap(err, "bucket store initial sync") + } + level.Debug(logger).Log("msg", "bucket store ready", "init_duration", time.Since(begin).String()) - begin := time.Now() - level.Debug(logger).Log("msg", "initializing bucket store") - if err := bs.InitialSync(context.Background()); err != nil { - return errors.Wrap(err, "bucket store initial sync") - } - level.Debug(logger).Log("msg", "bucket store ready", "init_duration", time.Since(begin).String()) - - ctx, cancel := context.WithCancel(context.Background()) - g.Add(func() error { - defer runutil.CloseWithLogOnErr(logger, bkt, "bucket client") - - err := runutil.Repeat(syncInterval, ctx.Done(), func() error { - if err := bs.SyncBlocks(ctx); err != nil { - level.Warn(logger).Log("msg", "syncing blocks failed", "err", err) - } - return nil - }) - - runutil.CloseWithLogOnErr(logger, bs, "bucket store") - return err - }, func(error) { - cancel() + ctx, cancel := context.WithCancel(context.Background()) + g.Add(func() error { + defer runutil.CloseWithLogOnErr(logger, bkt, "bucket client") + + err := runutil.Repeat(syncInterval, ctx.Done(), func() error { + if err := bs.SyncBlocks(ctx); err != nil { + level.Warn(logger).Log("msg", "syncing blocks failed", "err", err) + } + return nil }) - l, err := net.Listen("tcp", grpcBindAddr) - if err != nil { - return errors.Wrap(err, "listen API address") - } + runutil.CloseWithLogOnErr(logger, bs, "bucket store") + return err + }, func(error) { + cancel() + }) - opts, err := defaultGRPCServerOpts(logger, cert, key, clientCA) - if err != nil { - return errors.Wrap(err, "grpc server options") - } - s := newStoreGRPCServer(logger, reg, tracer, bs, opts) + l, err := net.Listen("tcp", grpcBindAddr) + if err != nil { + return errors.Wrap(err, "listen API address") + } - g.Add(func() error { - level.Info(logger).Log("msg", "Listening for StoreAPI gRPC", "address", grpcBindAddr) - return errors.Wrap(s.Serve(l), "serve gRPC") - }, func(error) { - s.Stop() - }) + opts, err := defaultGRPCServerOpts(logger, cert, key, clientCA) + if err != nil { + return errors.Wrap(err, "grpc server options") } - if err := metricHTTPListenGroup(g, logger, reg, httpBindAddr); err != nil { - return err + s := newStoreGRPCServer(logger, reg, tracer, bs, opts) + + g.Add(func() error { + level.Info(logger).Log("msg", "Listening for StoreAPI gRPC", "address", grpcBindAddr) + statusProber.SetReady() + return errors.Wrap(s.Serve(l), "serve gRPC") + }, func(error) { + s.Stop() + }) + + if err := scheduleHTTPServer(g, logger, reg, statusProber, httpBindAddr, nil, component); err != nil { + return errors.Wrap(err, "schedule HTTP server") } level.Info(logger).Log("msg", "starting store node") diff --git a/tutorials/kubernetes-demo/manifests/thanos-store-gateway.yaml b/tutorials/kubernetes-demo/manifests/thanos-store-gateway.yaml index c080cf58c0e..100d094242a 100644 --- a/tutorials/kubernetes-demo/manifests/thanos-store-gateway.yaml +++ b/tutorials/kubernetes-demo/manifests/thanos-store-gateway.yaml @@ -40,6 +40,14 @@ spec: containerPort: 10902 - name: grpc containerPort: 10901 + livenessProbe: + httpGet: + port: 10902 + path: /-/healthy + readinessProbe: + httpGet: + port: 10902 + path: /-/ready resources: limits: cpu: "1" From a6c0f2c46abe0aa2c9fb07b16f98b12c7c2188f6 Mon Sep 17 00:00:00 2001 From: Goutham Veeramachaneni Date: Mon, 23 Sep 2019 17:59:22 +0530 Subject: [PATCH 06/12] Add Hotstar to adopters. (#1553) It's the largest streaming service in India that does cricket and GoT for India. They have insane scale and are using Thanos to scale their Prometheus. Spoke to them offline about adding the logo and will get a signoff here too. Signed-off-by: Goutham Veeramachaneni --- website/data/adopters.yml | 3 +++ website/static/logos/hotstar.jpg | Bin 0 -> 34524 bytes 2 files changed, 3 insertions(+) create mode 100644 website/static/logos/hotstar.jpg diff --git a/website/data/adopters.yml b/website/data/adopters.yml index b259011d8a5..41e95504235 100644 --- a/website/data/adopters.yml +++ b/website/data/adopters.yml @@ -45,3 +45,6 @@ adopters: - name: Untab url: https://www.untab.io/ logo: untab.png +- name: Hotstar + url: https://www.hotstar.com/ + logo: hotstar.jpg diff --git a/website/static/logos/hotstar.jpg b/website/static/logos/hotstar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c83e89e4de9daf4236c7423eedc9447a47dd5b37 GIT binary patch literal 34524 zcmeFZ2Ut`|(=d95C|M+f3L-&3az+8kk|iTi7zP+%7??m5FzX^giAq$`NLI2$MF9mx z$-)o>L~@Q2?-@|n-FM&cz4yEKx%c_EhjUJKb#-@jb*k#X-lx6Kz+oLtZA}0IfdJ>h zAF#Iz*f=6lPB0XgBU(X>%Mq$@Qf%LGc@g|xpdYsHxL^c7?Due}yMnZsn3I@&znA#PFC{kg z!oZQ9XfT|GK&j@1@%&K;u#k?ZpB`fuwEj1ccaJCLcaNT<2l{7-feXgxr$+;6Bm~g66<&l`I+F|cUmaVf554F{3D$z z%E{Es6Qg<3*n?2FKb@+F6W3qijnN*Mef$MKk8{R9$titcPz(~K?tpP1lwhC93oaMX zKUxI=CJ=*P0^5Kd%=sXhKV@qQw(-Bp)&%NtfWLr(p1Vj$Vc#8wqL5w|t}w82!4?Yl zbon7ZT0(#^$df7(gFzxZke)8zqfq|{A+YcZp5_PM81CZwYXH@c0Fbwz^n|`bJbSwxHe+SPsRc|g?J84U3N-(>q3$al=+hY3G15?}}fumayd zXr%Wsg#Pdyv!CV$=m!Y>?SSh)zlkRZ!GFdc`2X*O?aSg2A#H#Oe86@^bnf6Gz*rEq zPjMUs0APPi-1oB+yu|L{>!1t-FF+y(0OVIdfCH>4BshMc!N&m{Rk#2T(C-31T>Bv? z01gCzKF|Gd4?q|&2DAWGFeaGiKe%6TgwzSS{L%eF@H61|p$728dJslL!stxqh{Sq2 zq5ss=p%`&6Y`<`X?(wq?g2+F$VLa3CtN)T0xeE%3_4)&f42gofz<-Xj8YYC)4fkvZ5!WD@MQ1O7f{E!sgLE+E;K!YVGcY--PVE2b$3Lh8> z^B3giKhS?9r*d>rLwX=lzX+H9AbnM>p8;SLzyRq<;ZXxOh~9Hj?182$s6 z+HrsI{0BT0SjDb?Cg1OOrwAE=b2ZqUl^6EmgZ|054-=X Q@0yxwPz6hP;5%_>3 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 literal 0 HcmV?d00001 From f14c9985054dfd91a7383e28d291aea0ed48baee Mon Sep 17 00:00:00 2001 From: Karthik Vijayaraju <50951771+karthik-vijayaraju@users.noreply.github.com> Date: Tue, 24 Sep 2019 11:41:45 +0530 Subject: [PATCH 07/12] Fix hotstar logo in the adoptor's list (#1558) Signed-off-by: Karthik Vijayaraju --- website/static/logos/hotstar.jpg | Bin 34524 -> 28253 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/static/logos/hotstar.jpg b/website/static/logos/hotstar.jpg index c83e89e4de9daf4236c7423eedc9447a47dd5b37..2a58d89b11b1a4075ed69367f1ee6e73ac8670ee 100644 GIT binary patch literal 28253 zcmeFYc|4Ti`#(BDvXmr5G1+C!o^>iAgb<>b>^s>qwizk=PAJNhB}q*7-Pna>-^19m z&m?1*?L2+H+wXPG?|XiqbN)HM*Y|vHp7D6*dG7nVuKT{P>wPT`b()F+o!8gV(*e=Y zfIxSF9}sm3^i&(><_rQE8iK?@AkY~Q9nEPFEpSEyIt6@O_}6pb0UELYxE|vMqWi}^ zAdn{T7X+exPxBvlzCZPE-_nBL)Bf8zjm_U1sY{Ift^om0Rb^zn{iW?3ee9j29eliG zV0KSsWTmgmfYc$dr*;k=P61c#om||!H3asX&;nQ895n~FO3eynyN9U@*`QOVj0$2ZWOMr)lz(d3P zSG9cnoUSTLD@b1#0G{@9bXGOJqw}xN0{>|U{40?`K|#_%^3pzjE;6zzDk?J9|j#f{(}G3!yPAo2S2x`0d79tSO4~CXYUgjpdlb21B@W^uS5Jj{dZb5 zul{%YZwdUj1pZqB|1E+4mcV~Y;QxOT_z$$>^&KhA~!ae987H*qEfR7tlP!T5TIa2rl-1F@Z-myL$R&56mqrA6i*EI667IxVpLf2LwI~f(3`Xco`KP6B`$wp7AO(EBp1Eocw~q zqL0O&N9~~Q?n4FrPnO$05SzTM-__eu(`+e}|5Pw8C zKKaWR06G7KE#UKCIQu{G#Rl+oijIz!j^Qs~G^c|8GR{Uv&o6uW!Yva9J70DIx#wpt z-cI{a(atC+Z;Iux_Zwm26jE3c#{FgOADsPjj79#x;_TlU`wzb6Kw1Dk|JzOhzqF@L z0aHZ_G&+CO6ql<*2hD z7Fxg;V54ONK|oXx-pqn0MPbB#mQ_w;Xhuv~3E;*wio>%4Hy#k+9n0q2t#`~l@m z9~DIR0LQf}OLT%|9pRJ4p41ckv5&fxd5r46Ue6h`n*1W{$m7XQQwhXZ4$7w;$+_;U z!An!Wlf_ry+?X9Sck+)4lY|e34_-+(%x3sTnzSAYH8_m4me}M)gPEc1H#%MgJwqEq z>M4PTRM06{X@`N6a{QmBBo)QWuzktdEzalOUhBjR9Y>Q!5egSr=3cVLQ0Ykk74#w< zfhj4|P@;foUe|r#t@UlGm83I0$L4dd9;8x1 zvVrsMGm~CjVX`lVtwlzdPnRLmMTsp zF|gzK*rO`l-_(jo`;A_YBz%65m0Ao|(Vq$t>sHY#%vJqm@-8I)E!?Nl#&~>x8iCg! z6cEpZv0#sO-e7GZUnx_N>K-J1(BE956&jPrqN8gZMgn?z5WHcrl3{nIqFLLB3^rCAmcV$Zol~nIsy@x81>*dJHa7}2U2wPt? zyCWna#;!kiZ_Y8X7@I*13}YvMfuE*~XgMca zxL~G(t=d`D%6X$gxJii^s{Lwyyaes$sd%yX|Lnm^o%;WJF-tg0s7Mz+^jenUze;!i zVY-p*@5J=Mwo@&OE0CQAq89u|?d&}?ABhG9yYAu3{RM12R_^y8yZUVzTHJeY-cwCYMJnwe{Gy4?sei$t<#o5QlU2(t z-b0<5+?82Wkjc2HL8!+g*sNYr=A6AiexyMKBp`4(dgW5X;LKG`V7EsGQ#vUkWHiFf zO9E5-Lg3fz*)OK<3u04FS08Qi@O~T%3YAdq=YFu8;!o#hMKj$LOZf>uM~J}(VP!gB zBQQnii)GLbNR>~Umxv7*Qyl$Hy`g;Mc^$GV{gyPJm%+o=3Oqi@Dz6#lj?}9 zh;}svM9czh12A|%Zv9U6_6;v}bK+e>BlqN`GqJ%Zh)5})`y~&vMC7(i1-3$SinYY3KLHL&$+G=6o`FUo1)(x{E^ja|19#P4m!A@faZcgRP~ zQ(VVLb9OUvx7dKrr(?H>+(Ws&$~T&6FkfMd_@ z(|pryW~-L12hFSVBflT+c$AZqME9f+T_=4f$UxGas10&bvxKIc*G3iMeLeDF;>tE2 z9{yBfiHCd%N1xV8P7nam;(2AXnonG;a4ae(6avfCh6duH<$_EfIOb`Mk*mXIaiPaJ zG`xrkssjwpW8E!&?Thzm{>g*kpLarr;}?fA?X`b?_ft9)oW8)Y3B5z>U}xHDnj+IYok8Sl)VZ z3w{wR6ZuDi3Sx8TLB|dM8W4ULmAiO%a`MPYy8K1g%RAWHBeKLFYVf;L#0W(D12%RF zV-!(WJc5BFP6cscHL497sUX%9kXU~F|MU<)$sZ8!DM{~*Y#1UopOXK>L4HpKwTnV9 zqhvLF9~nvFAU=YRAvtn&v8co^mJh0jo8{fhJ}JIM`+gjk(bDn}YZt!oV@;L{l9C`Q zkQ33E7$o){c-pG$5c+(PsX7;Z-ulu{j}yR9dAjF2QbAc2#?OQ}%XQ6blIb;H(NN@q zlu%v@wRP94{n~7VbS^oyx6z%y#NmB8NHGPPkHlK)Ir$L8Q+90UN2%BcsS=XtnkcM(;|7n z%I9%10#;XXjS9+pKtpkTYNhalD7YX1R=HKrk}Bt$*)HmiNqL>@!H1q0pfE2HuTcge zm0YCrf-A@u5NGfyS2*2RVM6CM$q=bT3op$u-J%k=b!}F^HJFryyhEf1slXMKA20Tw z9kjD>29kLr0gOX-K)4l}#1t>5oY}VE`EHTz&cj$Zw&&2mruTL)so(2~e`@a(^3P8k zXD*Q+(H@4trFlDb86GVb`PdCm_=UNZ~mrMAoaYhMjYfFz~QDwJ|wdYIhf1M#ObH=7V z)&t{Yl5DRyKX!`fz{xN@2spS$>z_YXCpXg1`zqHmAw+p^VBUMtD$~JjsaX6{W{5xF zKfl>n-cy$ja6~{YDNXjtjHC%Cw z{>jji&4Gz zzh-x*Be}yG6n5`~=hM3meb2jx+2OB$n#<|{EhHE&TR@kgXC<9nLnWO!Kg1SQMQIm2 z3;Fb8u(!&HtJ&(J^!vT-W;;A;#BPrexMsD=Z#!5Pt@c&cZ*y0x%*DWhSCN9oyf zi{^wn+_|l7ZGwEkmI)Pgpr2K+g=f{;o#uCtN%)-kfn= zdf1Dzj3?=eU*g8DbL#n{78=6DnJu|%K7X8QvD)83KPwX-PAB)qRCzWj7PG)FJRS-4 z3SF3khR(~V{V{x-oj~Hi5_@MgfqAFvBxo!*{6RcNGAHHgaj_i5)Np=+TOCP41=N)6;DxR4vyn$a$=FDaBc^I*isGintVkNG-IP^L670` zu_UFYhona@orL2D-3%y*erO1=lI8%6jZ!BU{%3~ie|DV;sEvOrB>E53>92+)h`F?R zW>7)GTpf!a%RCuYZ*Wjn%_)}=SR?51U6(c=qF5_{hsIiJ5DmhhFkt!8n^Hlq>?k9P zcmt9h74-41`Q$*oMANp)hlS$oVV5z`mx@RpkK`s`i~J!b-QLU~E?;(C4R8ITqftuk z2lR)}i&j2ze(NdN93FwI=^p%;v{J@whQ)tSERYOhn|05sv^K)3MQaD@>D}I%)=1G` zMmrf#AK+ccvG9vlVI1Tv61W(qb*uKlJbgZ#vbl0ee%|OfZ_7`0K)wlSJ5EEN0LitnoZ|q=NjH-pO&ixQDxF z!Y|i-wmQ_lc3dgtXEVS3o9B95-%}02$N9Ntbb~AjSw?5tg|4i80Il+!QJ~4zcAsl= zN@F`OuXNMoC>D=NtbJB~+D|QrLIQYrFOg}0R%{vA?nxhqpXv@|p`)W*k;o7kmXJahnH!h=QvRY&}4fH6@;}ibTCjIEN)|ED-2J6Ruu^)9w>hIN33L00Cjq< zm7AnYfMB*|Vi1gBkdK5%ohRdJHuINniqMt-1Ed+ELRerlgMt z)BS1s<^{2^9yRN&3A(n5^gC}+**C#!641LO2ssVyZX8eHO6i3#Q+QydU5%3ExbhE$ zu60wj$sB<(wN-YJ?s$1etPH~yTm0#TfH&BM45JS1&yW~UU1}q#|Qi0|~6ws>1SajT9yFiyu zU|s@W+RV1rEGi}#O-kfSO!Li#2A_XhnA%5d`*}o`IJ_@FKMm@=_pGSxhnp)c$qSA7 zgs8CGOPMyE_=e^pmKet1)*O3>~>*XX3guk=B;D7L}O+~zH z+LGAh^_V7^L7$=lEV)Aw0H{XVku;MSnW-RVEh9|8%AHAH(jW-&Ix2uiDd98h#e*2_ zVb9gp#`-4}THZ;L$>C|rvXFb0e*lX>M+a=rP(=jc8Ucw|m8FuLsrx1x?WT^Novc6j9wGjq$eC!n#ThVO1RDltBxh;z{aQF(ef^7> zUh$k*Q{&w5yluU5kSXi_kYFA9A`Rt6D`5X4?<3lc)DbK}a8`0&tKh<|YsW5B&`TGy zWI*g*LUj+RAl%mOA(|ktLE=E(d}(6~{{cO31KFIr)!YC%`Y!v;gIxZNE>c(jx_r}> zVFrRM$sB;085r9}b$MJ>2hY@t8o34{UqkWZ%`uuS!G=4CL63;hKTW#`v)T~_1DB?j z<{Y(9Sia4WWIFE$j+=eX&NNRoGbxAQ^YF_gbwbUO8X8109}d`OceUKE+%TFbvumk- z6D|}P2>Tf-mqP&{1*ZP5P9}&@K|8|40C+z|2~dL&NMy4=j&@n+jnsNt(N7m`%1{Nm zM(%Nfp0Z-$km6wD?b=aYhOEl8GfpBA5Kf?%NJs^G*9SXCVW!2ZbuY5PIbfYPM|KoT zW9rf43Wm2Q7wCy+`@PfMJ|rt%p1CzthA7LL5EVN2nj|(I^L78^u=qa1k}~Pa8;tQ4 zk|7{lX_6C`tMlCjDyYkPf(jxDE;^6>X>#+CUYCWvFnqNWc`*g9@v4dDgt=fA}M@n*Kff7=jLo? zb5?fh@h72_Fm@roYfeu(HO#YXo|+TqsUQYbSqSzqI{7{F*y}xFOXH*EA{6U6L}EkW zg#q$jAv(-)P56&E!R?JU>qMm?4D&UiDmg9nTbR~y>9m(aUJ}P6U+xF%1_Xv$CK$cud)-Vq%yw|d|-t?iyX&DW`TV!i|>TvaA<%Jj@W znfT)~bI^VM4i)65p>spw7<~yPQuhje4Z0(Ub;0d!;`l?r@wzNZlw;wmu!_T$)$wZDs)G!os|qv}v1-e0 z6p3Su!YGO>1wM#cz1W0hHR-0Mq!5*Tb(v+?0@n}YWddpD6U%DNrA=P_(suM6F*>HE zcb<{r-?Nra;c3Z@Z?uZrjd@eakma{PiiXfFL%PnoJ5dM07Lr1N-M3ys1$~>KtXba@ zHLR2BljSGfElmNx&U@tO(ulhWUh*KS!vQ5*6SM14_R|$vCE5u+PuL69(QZ%`dFLO* zQ36N0X5E+-yGbnU9~kn7IuVX2X8^rQ|B~R7&w2rtf)B+7;EQYDxaSlu9<3OM#-V@n zBEF*A!Oy{GATF#?jk@iK=X_VbV(X$5E@d%G$s4`YqI16<^i(q_NJP)s^V|MGll%pb z*~jH$=^$Rc&6{A1&8J_gSurIB1s)cak8EbYVTV}yN1FY*DV53=Ozp3*{lP<^^@iEM zPEzCvd^QhEZa`o@Q9&14C14k=m7^6BUSWTv__|hzwN#HH{H9XG#XO=Ka|d&{g@m&> z<7no1;z@$b-4NAmL1-QtWlI_DdNSYvq0#x1KOx)~3Br8uuvE~J4Cx|+ zRs(jw4VtvgSB#m5Z@G%rjG|-&_43mSqxC2QKF@?K@@DPWbp|&myLX7$lymn0%JfiC ziW*uJCS{_^=I5Me1!BN#Y#7wtS5Jt0`+m|v`}m&n1v_bd^31fsw_p~yF)(fvxv7u`h+K>O`EVXJ;8Gk{#Nb%L{!aDY^Xgo!R^?5HE+Xliw9Ed9Rq_TEWa&qd?mKAZ{?K*It8JR;y1>_@DBS29qUW7c z*;GRXN%mZOA}nq`QCLj{MMEBd6U?25gv}&cl7uxx3Nic_XfHoLq>;RKS_Y$$@M{*)Y8%b?C~j(FAnaxoOG2 z_D4UV#4P_|cc47?N_EnAzlwLKm#@>f2^z;xK^}{9&o=N*BVRN{Tth4Y1<2FW`@y#Zvz8so&y3cV74FdaBGW+`7o$v4{cwS*q2EkvsZM!W zQSGx)a_%0&9?9bLckxmtbKYfrz!VSvL9k2MFYd5Hq5|y%1I2KPpNFmu-EpH2Fa84W zS*(@}u^Q0?hxt#|oYk1_KQZolr)-StLEcb5)~>l^x_qYhs8iqfcnZtg&|(?%jwJ>-pd`-9v3NsDCatPyXWDuN-5wBPEym!g38| zNc4Q>EUh0oief(R&TJv{(QFV5GEZ`x&tNDCT*=jmjywNp!<1mM%#5bD$wv(7DG)pH zmf4gyo!<2DXJLlqC}5SnN?QQ@6T(p!@)E?E_f9q#=Nl%X*dk}8CS22#{`Fozq!~9g zhNy}mU3n*xDct&o_#o_t)e6JLMA$X4_ygQgAdcv zxg}-q$Sbz)o|^&>AAkN<%4nayhd3;@J2r+yfG-0)rX#SXkePKWvt__4T%2w9Ixq~f za||>P0`J;v0zTn$-AN$6raSnE!bm1wC6t<0v`%R9x`o)Rzw-%lpxu({<4o<$nb*A|)KJ?TPo%DQE(?>OFlWBs zOaoY{5pCOlfvh=-`I7JkrP=G;nA4-ylYMGfhk~8?rVZC3S^m{5@kcpGk{}SJH{MCW z$>SYyeDBxj_o6fgW=qeNr*3K9Tle_!s2h_BI4f`>7H-V zh?<-n-zv*$3{mj|;!HOvss+bJ`DjBlCLcnN#=HeH)u5Rd0j;E_DdCR3P~GAzQ9B`d zSf=D5(IV6}LVjoKCiP`>shCytXRPP%->@{<;|%Dem4$<`@<566gOUdyixJPtR7O92 znRUOPlUVS1blcpyJjZ3o{NtwKScdV)Paytq^yBIOO+fbF0=2Z%f3|!^695X406YC! znzV=+Z7iDdZnu=&CqYt%VBR8sr!Gb;;!T>RsG#Cdz@hR}1%1ofV!o8KyxE}>|FG%& z2lHF%Sy$~Wh2s=SHv!h~ATYS0(K zBI`@WluY}a0O7FWp4faCOL>^{}QZPf#WaRN1;sRY*8C zrf2kmH;s^(Ji|H>%jd8J#b z`5WVmy1ayzT!0l@gqklBo7aZ|0)W6!(lzssEC5PvVN?3!#ld!?t-@RPXG>)T#1{2O zogDAO%;nTnXhBu%{yh+5SnCk*nab`Loo{Y zBn&YKcj&CYIk~=7VRQMQ198T=&W3Yg$^Yy|oXA-Pxwnzr0l!l3^#>m~*QGv9W<7cK z^443LA?v?x?+Xuz%bd&%k#c4Vn>?y^cL|M)YFSKBWX!*$e8Ig=O0nvzsBm{|ZH#F3 z&16|<%Q($(0f6$v0s23}zk{&`kjT_9J96P6b9DWL&F&u!1|vr4lG4Yz)s1sq>k4dl zzVHRFrQcXKeO@J$)%AVQq!HU` zsLmmRiXgrhXT0kVjAl>BbqNlX0c_t{MaD3;0;^VYubw2qcKGxa=g|FX{l_eo7ftWp zN@<(lO=1w1aDg`YEtSCkUP2cGmY zyc05cA(X1e;urknTb{kHMDd?{b}v)AFJ`L6(7s4&;3KSTLNOy)m*w6rKvK)a(G;aH z>2a_=PBbd;vW=O3Q%Y9!G9z6@y=`)XVR`Y@7ccTMDXYjT%br0Ad^-7EGY#?fuk$r6 zDJkk|MTGb>dP2RN#U8w~{F)`}^gfdjkk|VJH6R-oUH~6QS6*DKW9YO|d9=*cKIfcH z#@rNc#r8ReetJ_Ixj3`7aHyrk39iO)W_lcd2Dk*mr0ql2b)*Z6c;zvOqn8?{%T_VP zXxsb+y@sD4(0M4BLl=>t)(hqYf*3|75Ti>TT2IOzSJ zLF`RsuIU+Zo58t6g6BEVYcw1WZddYct91S58ST zX^(q3Ch~r}H&&kjzZK0Cx5=xq%3KxYY!e_dYs&nB)Of2HthmE+fgYDvbuEwh7#rTdIXd6J_O7tL zI_KJsW0tez*!S9wWgQhaMitb?*oq8^n=z~;s02QQ0H@7SYy$S#emTi*uP<87-(?`3 z{(hff551!ekf9E+E)DVGHDx`$2+KKGvW%Cu5==1075Z8|8kBgl%zOKqNHu@%s(H@C zA_OKG9pgig7A0K_fh3`^j{*8-89F;Of;!Q3HDu-6!@Cn5y8c|zsC!4^m0eCO*{UhC z3o5z~ZPk6_572WE%Go-AqjF03EJtr*pbuR;j=59Gr0Vfj9z&j?75`1W)RtWB)A=Qw zqmKUWb_wZwH3S(UPpg9a#@T7ZKo)vI{dwC;q#U3?{*(Y61pu_qb48almW<$sZB1wKe?b}c5{QwZ=dTuR1Ng_ zAjA@P1f%)Nzsggq~p+DTO&Uh*V)vIB+xlb#m=r zkv>3pf*VM4 zB5Q(T(C)|==~aXFJYyDPR^OkxH{KZn&w9E@yp{ZT{o5-0mCY;fy>IGvaw;k3wER@! z@s#onR4eq=zQE<;qINsVq`pa(>2rZ1`7i7dMPP=veK-gZ9l|j{m`-!^SQH_CYpRXM z@#E-{{Zo*k{n386P{aD<^q08bd+)9^-=#SjAlf3@e?qWEsEEb03Wh!y-r6FZOSk6L zu@B>BI#o^;FO0Zkh4SajTeDOyT#h}GQ&;Nq`!6$BRWsH<;`=@y57T$R*${$+PY*Kk1OsKG}Tu@(i*a37ima=z<44+yd8a- zG6&|ZmYFfeS7OcUI#Haka@Zd-`@^yF)m;6iac94ZnF0EaxLu*l_-@+ZG07@6@Ln7c zfpy@25WshcbLmYXh%WBv5%1*>0FhusjAEG%L$XKsJ*%SHQV#`31;0n`N&~#RRRv<= z*zaG?(6Pl6O(P&I2+Y#rbX_O77D2jDuX{CUX|oRRO;Wh_?R(n%uhc&Ek=|sz z`YQH0hbC3_qg&vWQs_P}QDykf(DG1o$VcQ5Az<+?Sk!Z+~ zB-VVw$Hk7Is?DhdQQVb`pH>StbLcd=8LJsX&)rk~N@acZntr5_F0KoDSgCyypE=PG z21F#i2?^MyW#!Iy^18V4mi&c>u+T0zS5tj;!o8LTV{x_UswR&(tEGi8p#=Y^G(lrg z4_Vam=(3)0W}c4Z!_{;@y`xWct;26_(4}e&LDz1cVdTB`uP-G?7VWP3eHrQB`lTgt z`fB94TMQLL5Npa0fWO6A_I3!?7B!`4GCQr_d#Zon{`oYtM zuuw<#6miAon{%(}TN4e0p8<2vco&uEJP1$5|ddp2TT~esp z;aZcMY*h03Xx(R*&1qNJr*O$in3Km=5yfU_EvHwm?;}~QgyvOd2Nm%pgP}D~$TOX~ zWjTMu95`8X0u`fLR*8t5dGIegqFC62VFacOS&2?WJQ*TflX-bIr5h2=mAXl}R?!mM zCel*v9I9bIxI9Z&_BC!L@_u>@zpsy9DfA_@mI{iMK|cxIMxP*>A|OrW{y2_JPsUrg{fK;kcX z!sH1lcWa(oA6Sf;(=E@6y-o;yl;nKbmF1H~uvfmFdoTG;(g9@G9J_%TB3=G;{Mb~q?iuJ8v^T> zjy_dn&SJ!?G|9{^T)4D&*2Ze=Q?`t7=JBSe^jpiXL%+Q)eIY=$k0pMNm-{l^bqzm>cPRXA*_U=4#@<=8^HQs@QfVN#?MJMpR8OprM}`ws>yGE z$L)GUsvH~Ar`46|3!pMI?6hm9w^weU@|%Ga9D~6&lu1*b5QFL6jl%Xz=}&deuHoN_ zR4u7AClnc|WyPwD?X9^Mz1nG?3p}HUt=mcS#%nSG>MM9#qIXtCQ($)WWI#PFWfFZ3 zD5$o*j5tG?U*xQoV~WCQ7Ce0qSdDq3DRIz_tT2Mjp;-mPr}^Ducv-!XJ=YwnZYgZ= zMf>6Q4ZnU1J(=K|kDYBR71NTB9=Nw!)P|tq{v0DW1&OkNma_*CdoHr3zPgIwgEKH3 zQ!ELqu>p4#Zt}$B%-bb-t2YI&YEb$lJJ1F-WpZ;?qfWq1irsN&{n}(K9s1~X^R`%VT=V;9>=8*PI9?q!JA9Zdgv#3UhU;wt5!lj#pXVCO)A=>Cs#Y{$rYM z3&L{|tB)WE#*i)!;Z1npH8?0fVVp#t&nig;bzELL;@P1GXY1+Yr@N)zfb`OrecK?r zqJAHBBk0sMG3Js+kGh5nOgwlvT29_gyyMsvXi_ot@c2e9l^(39ADSOx4V-sH^RkYE zQ^43eNP;kWmuYlp8U2#SC@NgwtqsAga5zsH(!FY>cabG{Tj4A;9>E6)14#3~R4sn< z3H}|)8K}V)@%@?YwTfiC%4H(jqmrnQ##&ZbLk<`3yJ)Y%`#L#VoA%f&n;x%yRE2!y zfV7o3W{n{^;N+uK95P)LgDq!r@fCGWJn?!!4s*?6b9)WOqT8z%R!vIzeL`;eTd!7o zL}EW^mkWDLcvI{K7r@>#>-N!XTAGV)tgT7+ujOb=e)yr`<=s^ge8Rvv3we=^6vPTI z;&V2MGZg=pz)!q^XxE^K{ep5Hs@+wD^1@uqh7}BXixL+Wpj#HdA88BSPyg=l{O6Dm zTKx8T5ymI1$JTZpl!!1!9CKW=&XRZoirsEpm?@dEhwQ6 zB8XC$02CzFNiIO|WD@`oe=j?b0V0^CKQ(Z{uFvFm%;+1 z30^sqbbyL1BmyX_@Zv*%cm9tr!v2RZA|Mq2s=p2;Xc$vW-(bK*VO2{CQn8;3GXDI3 zxt@2}5274GFeyBFZ^{wv*C5VhErX1~6bc7A`pGi}iuK%Ps+QQvm)HS<<)$J+27z^p z*|qr?1_$=I$tL)WXyYO(jDNyM9TI%`o2{9fzl1xd{CT50kEE^V8I1opgY40t^Z_S~ zb)O)vg#7dii8V#fUe=pHtgM6MY$dE1={Q9#8wMhSXQ`lI#Eee}K>&>cyiIuuW5NnL z0#a*pw7Y`Q5Z7zv3wv-~ujKKgp0F~tu4~+pcU?eA?V(o;e6N&sWwdl8(wjlQodszW z2K`tU6&2O2q0B(Ax?X~&BM{81#j3pAFVP1noyc?Ff+Cr|G=`n~bnA}=?Jo&Z1_bj4 zf;9%mBe7=2v)ceHt1|8&>W%hn0odbK>7Cqy(Lb>{kXqZjU7kNbSv^TS5c-~$_b`VF zaz~zqX>}r5N#Kt#a67(Y^F%4m&BwvP;|zJCr6&K(a+1cW{Lc}pAO?5P?y1ikuC(gF zQ-1}aS!Ra<)RrN_wZ%m*)48gJ@NStHD{O1N67IY^@`{*fNmPbUp{s*lq8(*sf_fLL zr;^{Deuwcock~c8PeN-Nz-BPD78G*V2fJJIZvlPF!paWIw!+24%8)FVugz`&`3$apX*pbd z0U4Jko^kGsWV-tlOCk-0>$Z@^Du>+*XkHe!TP-t@g~)dU9Gd!wmOeK*?-k7nKj3Nr zS~#*0K8?T{?8l9fjLBJHx3liM+uog2P+L`2)hxZD*8FL2H#2+VzR^qptl01e=S}V^ z@&vpGi4E4>F~M1P%-tbZg-Lx0yEZW$m3#lIKb<)O~qRCDlM8uO5Hn0{B!(0 z$oGIu@Yv($SJx?)X#1h7*&e}gfWmSuP>Mf7n8+^VNI)x~fK33N*6megqWoBZOa^N3 zEUaJ3=-^2_*s_*d1-x?HR%2LVhjKmfGk%+eMA-!*FtZBzbql~gIxPJ=2QP9Vm?-2K zihLL=&V_W*3~xWY3<$H!=k)C07efAzc22eMAft%W%}rUtOq}@H>r;Fo2fi1+Ri1v; zWl5AAk*PVWrCl{1DDkYiKIhWHL+(nuXOAl@_N#GLwyN0Tm}W1`yGQM|;_+%pG7l>A z`u9|Bih1n`xPCm_6v^T_!|+Ln?<@$BVoADO3M`Hy0)VjXnvi7W1xuokB>~vV?Tv-3 z1S#dVv%O%PdIL}(7Cw)tGzY~za#gPp~e8d*wQPSI;NRAAp@6(EV6?TSS>`d zY+H&1J$C!+{qN6;;}m-RYeIDj{FRR$Y{XMRSM@=Zax`XCW;&q*QVo<7Ty#EIL3S}; z99J-Yq53#vt=eA)#XaTy@x0sG#B=D+-TM02Lp}SrDHb`%_>udGK&$h5#P)4d^un~G4X|;LEzn(q}F5{!HJ{6QZFqxC^QzU0xP-jXcx*8n)hnN63q%fUu zAN@&h{>v+4~7G_37!hG@pJt8KJl&cBitm9x+q2oFUH zE4pgxxw;QdVg>b5oe_I*w`sCwUiV>jXMrrAySKpCM$->8PUIlQ|Az%(EN0IMY zc?rOz34wbGDDPE~lngy6W(>M71r*HP1wtb`v_J%4Qz4Ub7AO}WsX|DRUR2O_JVLz5 zm-2xM`l)E}Ke%k(kZ}kxIs(#W0Y#gPr;|^T|NV_^5%ZrnwO4V^`$T8amIxPPx`$yG zumR``%}>%EZJA0K=DzN`8&zRiCb#`VEbscs%zua+T$CGV*>9Q#>A1-o_5V_^EgJt*`=`#toLvy zO6%^;mH*0nfY{(u#7uAaCkJPa8FQSe*O$4wEY{+5yVxsy%WNU@k)G=e(Zc31>@<%` z_>I||xkkj`*5P`%_uORfE(3|vAFoD$5=}^=fRTv&3h&Vlv=Ozbd4yu0o5@cf7~YcN z3G09ro|d|GS2CGpbXPzJ(r<%sShzs=fZeAsZmhIG@GlA6PZvbUi4@3>A6Pa#l5nTS zqikfz$Cu{Y!xjDl*Ed>y-d|$mI-;RHtOW5z68lLf{w@LKg=1J!us>RgNqV;z;PF}y zwzKNnJj1Q&^^5%|tCG(t_Asp=WE~UM|IkxIJ%!DrKd-gNbo5zDw|@SnY1R*7QQ(Je zm7+&8L;k-{#gjG3uOXNWL{0Cm@{%!7ck}UK7=+-<$FtJYs?=o2emjm;M{iH}&5fhF zWMeI8DY7bPCl`?S4#5tkTLdU>5DvWCjuZG{SUG>@awmeddOOv((rr)2`<|yiag%3u zN%L2HYs`(6?U6ax2Q4)#_vt@cWMhZ493WdE)UCLM_*Em#DGJ_BQJVK{K zUTb}Ptn0=5b2u~J+3s@cDcQ$9o#_k%Y1@fof4n$}kH-Z^tDAxv8sByKw)0f!3BC>c z^qFy-UBIZ)(`Ix4*tK1!+2uXKpdEhpLhW+EK!>j-0E>x0xdhyvf@4xf!5|NRF|$`b0Usv{$xPgcy3ganu179Q zQ#%HL%m&Gy;evm?D$7`zfY&4e_{A-X=!Ckq&oaMa=9aa4NSaa{$5)+KE#E`G+l^`# z_^MlO6+*DLQ>K}_kk!d(qAAH|1aKx{30+*W3*i~&+0H56*zSv=lcQs<$Y8C^JgX(h zJgT+z8f5|s(xmBLdIOgwe4f$++@Jxggw7My*o~%eEsLyMTr>9{eIfWL+c$A0*|i_k zTCPAy92pemO@DyQ-vktE8zcsOmNhO+4^hzmx%0CJSM+TA1-JDi;~9s9A8(z3j8+!T z^4PsTqX&q6R=GW?w|ModqqNM&k(~J0#>yx^u(*G%?^=zto~V~fknS%JhIFS*^Dc$e zY#YS6m_JfW5M~&32`JRqZRWrp=vNI_dt9pUDR!&H&CEV3KC%!q7c|F%m~e=c#Tw*J z9Z|xq(gR$L?Bb9lY!y>Cb^cAivbPe(VY*ZF6;xQ7wR8v8?@MJK()X~s1Oa-JeaG9)xDm!|NN9^yDlmXrK;#E>Qv zD3O+?RGIlj^}XLpYEJd#TvB@42&h@HQ4+BKF6LM3xu)-AYpv({g$)5|Y6<(>|BLrl zS*d`l_s><&|7Sj-48qQb8>Y3?yNkkxE<;jKX7`4}AS(z~ck9g6h*!1M)#JPCIYG3d z%sf@+H6+MxkY#=-L6Cubg)0WVpRh>LdC&S8g82;nod}s#qx2yXA%FUUpi4yPHedtj z&j1=3fd1{mt^c6Si)bpk~Y=h~qqvclUaN(2X>zQkm- znQQs1S?|-v*oL*Sqnj`68Eq90lXQW`=>hz_mO8`Bj<~AvX6P zqjr5L0+#?%fzZuJ8!VJUXr_Wz-6^!dKwGSH>_8oVJp}W<^(>)*C;@97>dE8%N(e4? zV_&rTl5oLRx77WhkLJK3uCik2vI{S(HRw2-at6rQ(VL=yP9IQaInn3CY^T`66mYLh zHu+?$UN))D9!Jb=$@bg`_AHjfJjsgMcxhK;3YkIzTQNob8cw-gRktf8rm>~9;r;gH zQ+H0Dq*n0M+7(4cgfXi5KwVqtpU`n?nL>46zN!L@hRDt3O4t{=zz0D-rY zR)%8oK!jopS$Ao_YnRy=$M^ip?a8AP=MpcDwRhu&Nq|+9Yxeul*58==R03Pj1-MAD%}BBR2@}Z7%d4Ldj=-KPrZ7qoEjN{3m=fZ^ zoxIPWhbNXvCIRhSygDCR{;WSfA{vpxSMT9Y(uxu7H_=bX{zg0tDY&XOuB}k2tc~mI_t9d( z@21bm$Df4H+$67`p;dqD#n*uPHUtO=cq5-66tgp5VDJUDxKbB&=yKoOlOzvoB(Lb- zm>%AY#B}{c445Wtc@Wxxc?5zHb6{??+dF0x0+2bOql_McqgpSF+h4aKq@UcK>I#zO zQ?e%KSzR4VU8@rE8&C@?5{dZ$!lMal5DK&K(qf|O8=xOs_~2}p<%wtvYI3{xBT;SI1U~=@)o%f+XDr7z$G*tm?UhxRsf?57Cc_LT z8;EukqB{CH4yYmB$L`-6+v}R`?QGa@mMXo?k=>U*y0#~!>t|j~Ot4^x7-|42*R{DT$OELI?ac1X?u`5G%-x;I%*h|gdHe7EmFIc*N37a3+Y_)}PJ;*bi~HS-3Ee3NUoHnx z?4zTa97P(_bIj5Ss3jfHK?`hA46@N(!b4bDF0EE_ZO?UbsznRy;Yq}rwKE7O1J7mVk-!Fr| z4H}F;;iGYImLCj$_e{odWqmN@Ef)-FYPwM@ zduTtEj+j&O+)%-{0(+M=p*Vsg+bqfd)!?*t*>m(}0$OHGFk~g)-Kv(Yj!4viwejN` z`0yYzH=o8rDs6(Y92q6}PM-doMVKqQW~_H0-8^1)yVA8B(=YZOg^J}LGtbX@N3Jh6 z!q33Ld*`4ESImBu+>^r=Y#@Y5@w>fAP#u{L3lSc-aEM<@S9X9Kw6Eo9xUcv9BQjs4 zhOSl1cH^PEVbab#*2S-{v>XtHxFcHr*$<*U&kuUM5h*+_io5V=K2nBn8*rK`2->Vl z8Vf;=DMNX%uuQ=*)vW?X!KQacpg`*-2kmuXK5l;xe6$0?QJBxeYpQ!z-ntqwxdAim z*P`==9CWChm5|L18K4HVkE6-(*X=QCCVvErb8bm2L{Tb@Gv1Di6sFrvj$GV$QzP42 z^_KE+Q8|1o5V%3K08_8m(p}E-db&+?IJ;+t_AgyyD?d|2tqr!9Rr-QKE6X0mQ6kq4 z8IUVoH~!=rN6lr44GnR^Yv-Bxk9>OkV#=EA7ADg9!(Ayj|Ev8^Yzzck=J2p?c1%8(H^wF?9pRrS->9 zcsJ08TB=Y=mqL1taV?7XTO4IPH-RR5-frQ|(rRh^2*A34k>+}G4iteEkdvc0Pn{|i zQ|jW)vdcwGwS$sIhu)b_@{LqAJ2f9OkdfL1)8t|LpmhLC7W?4Wpn?o&sXj9)7ZrSl zb1l1GzKc9J5ji`ko=@1}2{N6qy`3^@TJ1VWG3~8Mbn~m$kY!dM%s3O5b*hrp_sGL@ zMONSD9@DEx37X+Vj7rh`>s;cm6! zd55+n9ZyTUE_LncJG9|>4Z?7X2VAsT&wJl6+c?<uUc_jo^uR&mnxsV6~U#@ZR5pE7jIBM1VuFxBGC4PA!4iR@;&|AA_4&c_6CYc#*5r`({cJmy2sRJ0j;6uh8MIixXb7Ju^f z+K+&F#64uL=zU(SNF8|67k=_7SPL?ynikgOf8ju0@6P+lm)&L0(dpX|{6*Ca1G$1V zGF(emR0Ce7v)Xui?+6Ml$uiyJ&{=JvK5@z#|Cx(e0YQS9khL*zxhD2vz;WAx+DC*H z2ycwsumRy5KgkoS^K{u&itmt<`y_9>=)4MDVcNNZ{?S9~XtbuZ3Rg!kqRQm^U%lb< zTCU9)#)EmEj(=!SILCoNd_z(_uZArwPtUVyJ>q&*SD;r3{R_nFBzlH z+UFtruQHjgESl>G@{?g;8PWC)hsH)AI{b7vs;q`ubRh2N$howT=Zgb__AzzBxU!=Z=G5u zcfcP&OJvE97VH416Efet_#%ZMtY-fve197@kP`d;;*+NHrB05@_#hWlDQP=?%K&!| zYc8caDj$v7zU=pBsav2-p&s>6T5!4Z;U1S*ODsLHTiwI@2bQcL8d-0fJF+i4d%kzF$N+XFmnZCz#gt4xGBQF zG310f2iYdgO$8kN`go*DBqsYBFD5Aaww~7TOkBH6nA$K*Z+g%6C2WHn+#fV7!Z`RU zz-j{>e*I9cq|Z2QD<^P3V;LgDnOU;x^{O6hm5Iv<4__^5a+kgTp+%g?_wLDEw}3wS zAbyy}ItL#xSzH9deSliiaibOy=Wl2&-4$j}4!3qqs)`#qr_ZISEd>v+6l2Fw+HBO@>(3G40qnu>C(UL{bNI?jZLB2`o zcZc>9YT*)GOVF1Xa&HKnYC`To(*qrG#S_YpZZ_ppI>$s@(W1uJZ$1kt%;XTkkOT?N zAV?pUI)uBj(Kr}FN0)3Z12MIHRUoJBRQRw`ny{es4P2v<{TQ+SZnYAAfVZU&xDFk> zZ|$iHnvd-GpL}K&YS&)(dG>gpuqse_CMx#FrRr(1*aS1`7n2Gwwv*~D4mKt`Shxha zDj;-@75BxShI_}>2lhS%Ag}K}JlxS%IGAQ47vX+)D60+}Jxv0NGY_(N?5^-X!5k%Nw)M!6f1KU%tGB%saN-n*K17_b;;r%iAXF9nSyN@*y0yiCw zjN(OGatp7%K01YRv&^vU{KN!?Dyt&~gU%#>pX}gKG`zApsG~6?7LvOEbxpU4hO?2r zhit%lJ7Yh0n>UT+pLbu8wZ<%BZPY&xXv)Va8#zB+XX;u`s?3(TLeGNEyR5Fj7@4CF zU4^sni%D8?QuEK68*7CWwSw-&>`Xfc2CePr=k$$YqD4*In_(8$+X?HdL}Ze^N$9YN zpqD#G^5K%;12+6fT{$<{&L6_%r@VAvbCDHf%hYY>^KN%@3FF;Cv8FnMhsi4jWh=Kn zf(kn3APfJu)UF^uzy47UA0HTm|z zW0WTwwR_`1C7X7)!Qu*{iQ+&SyKFRQfA6H=tuX-qtiZM zI}$3X8m7ZIMtd&&;{`u!{xH+#EJdFT%3rPcd_g+$Oelkgw=@jIUP>4Youm52f$c&k zEjH=hvO%^W)Je9gBs$F&9EPNdL6#ym`$edDd|=lIKv*;ATx(RyTN zPjH;w^b4~7(&KXr7oX2(?A?rzXML|UQRXlU7(5vYF_B`yw)gw)4vDRa`IHx84P{w>Sx-@TsCJ7S*QvQmuhNk`oMK*A(@^APb( z#3QwhqyS>&=8XlFC6~&9Eh;`!v6OqmGGTWNayu`rL!I%(TuDw%$OeLQvW_O~&$L-4 zKOWgG#1mj22qE~3!z-Pn7(>Mny)Hcjx77Fn5kMm$m+`$+NHGJT$dQ@?77_y1Pk(Cw7dCI5;A(80{vtQ^yQTc4o_d3^Ndfv1uur$2kBS zs0TzQLS%EQwx9(N3bKREH!U)XpPRpIunzQPA`#g4zsjVP*+_vR}-TMnIm- ziqeK{trsx*l{D~XL#BzXYJK*HfaW6*8rGz`OyljI=sd_q%(bu_#*0#t9}m?_MQ?c@ zs_TBdSX^LU9r7KO2B##Y4ucL`KB;t~u!}ZsH4(vBCGSOWN2s>cQM!0)6P0xu^1%6c zIsf^TpL`E>56JjNdM%4g%+7ZmG|BGcG@YTac!#`K^)$ymFM&vIy=)f_shk1Z1@@u) zJ!T%k+I3poe&Z3N-{A-Y6OjCJzOlFo0NW$WIo-1y8+AcX;c*$i>UwR zK1O(yP;Yn&?Oh~2^lh+Bz!d9#P|me-8mDDq;$=WC)$j=3HICWZ&oyudVbKp%>-V;R z%toVRb>(|6CG1G&KEUF3X2(n!1-y5>x7HBo`LYA7Ws}%q4$?t%z}{%x3Xpi zEiKmy&Z#2U7qg3AF%YEH5A+vxaITa)%lk}AHLZ!LzF*0g#5DE|6`xWxz&s7<2=XL< zMRJa8OT!z3Uc76t^mwi`4avgR#UPE%gC;7e@R+ec6gJz!c zi_2G9jJ0?>`_x^ku$x*PXe%slZT(atabZ{=i&j}cU~kS zF0$<-GGhDr7gXmAHwxFZQkIUmv7h)fT7r`Luj!1uhmuR$~vjj#g%6} zQQQpL%52;$_iz;-o>*>|;CVXx)j_PT8Q3Q?UEh#0<>y-uSqa@MzAUTo*lJd4X{oGzj4b!gB@#zcXrb*I zOLwDaCA#UPSe-UQx7sBoreOx$maQ_@Ag`3K%y!+ODCXQ)wV$pe2N122lHz%1L1H0q z0tN#4{#&WHe`gl{`%~cebqoKA%KqC{`bp~_GfhkR(|G~m;GRAByCT59eh2++HK)IA tH2;g)gZ~79-2d-R;lDa_@ZZLd{;Mtgo&SUXcIET$?A5>87X0VHzX3V+eH#D( literal 34524 zcmeFZ2Ut`|(=d95C|M+f3L-&3az+8kk|iTi7zP+%7??m5FzX^giAq$`NLI2$MF9mx z$-)o>L~@Q2?-@|n-FM&cz4yEKx%c_EhjUJKb#-@jb*k#X-lx6Kz+oLtZA}0IfdJ>h zAF#Iz*f=6lPB0XgBU(X>%Mq$@Qf%LGc@g|xpdYsHxL^c7?Due}yMnZsn3I@&znA#PFC{kg z!oZQ9XfT|GK&j@1@%&K;u#k?ZpB`fuwEj1ccaJCLcaNT<2l{7-feXgxr$+;6Bm~g66<&l`I+F|cUmaVf554F{3D$z z%E{Es6Qg<3*n?2FKb@+F6W3qijnN*Mef$MKk8{R9$titcPz(~K?tpP1lwhC93oaMX zKUxI=CJ=*P0^5Kd%=sXhKV@qQw(-Bp)&%NtfWLr(p1Vj$Vc#8wqL5w|t}w82!4?Yl zbon7ZT0(#^$df7(gFzxZke)8zqfq|{A+YcZp5_PM81CZwYXH@c0Fbwz^n|`bJbSwxHe+SPsRc|g?J84U3N-(>q3$al=+hY3G15?}}fumayd zXr%Wsg#Pdyv!CV$=m!Y>?SSh)zlkRZ!GFdc`2X*O?aSg2A#H#Oe86@^bnf6Gz*rEq zPjMUs0APPi-1oB+yu|L{>!1t-FF+y(0OVIdfCH>4BshMc!N&m{Rk#2T(C-31T>Bv? z01gCzKF|Gd4?q|&2DAWGFeaGiKe%6TgwzSS{L%eF@H61|p$728dJslL!stxqh{Sq2 zq5ss=p%`&6Y`<`X?(wq?g2+F$VLa3CtN)T0xeE%3_4)&f42gofz<-Xj8YYC)4fkvZ5!WD@MQ1O7f{E!sgLE+E;K!YVGcY--PVE2b$3Lh8> z^B3giKhS?9r*d>rLwX=lzX+H9AbnM>p8;SLzyRq<;ZXxOh~9Hj?182$s6 z+HrsI{0BT0SjDb?Cg1OOrwAE=b2ZqUl^6EmgZ|054-=X Q@0yxwPz6hP;5%_>3 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 From 5c59e44760cb72833d8b2d1299e9bb1b27cd2130 Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Tue, 24 Sep 2019 01:22:33 -0700 Subject: [PATCH 08/12] Fix typos, including 'fomrat' -> 'format' in tracing.config-file help text. (#1552) Signed-off-by: Callum Styan --- cmd/thanos/flags.go | 2 +- docs/components/bucket.md | 12 ++++++------ docs/components/check.md | 4 ++-- docs/components/compact.md | 2 +- docs/components/query.md | 2 +- docs/components/rule.md | 2 +- docs/components/sidecar.md | 8 ++++---- docs/components/store.md | 2 +- pkg/shipper/shipper.go | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/thanos/flags.go b/cmd/thanos/flags.go index 27bcd2266f3..12154bffc3d 100644 --- a/cmd/thanos/flags.go +++ b/cmd/thanos/flags.go @@ -118,7 +118,7 @@ func regCommonTracingFlags(app *kingpin.Application) *pathOrContent { fileFlagName := fmt.Sprintf("tracing.config-file") contentFlagName := fmt.Sprintf("tracing.config") - help := fmt.Sprintf("Path to YAML file that contains tracing configuration. See fomrat details: https://thanos.io/tracing.md/#configuration ") + help := fmt.Sprintf("Path to YAML file that contains tracing configuration. See format details: https://thanos.io/tracing.md/#configuration ") tracingConfFile := app.Flag(fileFlagName, help).PlaceHolder("").String() help = fmt.Sprintf("Alternative to '%s' flag. Tracing configuration in YAML. See format details: https://thanos.io/tracing.md/#configuration", fileFlagName) diff --git a/docs/components/bucket.md b/docs/components/bucket.md index ef08e2863fe..97f8d868427 100644 --- a/docs/components/bucket.md +++ b/docs/components/bucket.md @@ -24,7 +24,7 @@ config: ``` Bucket can be extended to add more subcommands that will be helpful when working with object storage buckets -by adding a new command within `/cmd/thanos/bucket.go` +by adding a new command within `/cmd/thanos/bucket.go`. ## Deployment @@ -44,7 +44,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing @@ -103,7 +103,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. @@ -151,7 +151,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing @@ -213,7 +213,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing @@ -256,7 +256,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing diff --git a/docs/components/check.md b/docs/components/check.md index f2b6af506b5..e10f8b561cb 100644 --- a/docs/components/check.md +++ b/docs/components/check.md @@ -25,7 +25,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing @@ -69,7 +69,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. Tracing diff --git a/docs/components/compact.md b/docs/components/compact.md index 3df32c5ea2f..a9405723f0b 100644 --- a/docs/components/compact.md +++ b/docs/components/compact.md @@ -44,7 +44,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. diff --git a/docs/components/query.md b/docs/components/query.md index 1bbb96e23b8..283d624c883 100644 --- a/docs/components/query.md +++ b/docs/components/query.md @@ -206,7 +206,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. diff --git a/docs/components/rule.md b/docs/components/rule.md index 0f09c093ac6..c6ecaf8320b 100644 --- a/docs/components/rule.md +++ b/docs/components/rule.md @@ -159,7 +159,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. diff --git a/docs/components/sidecar.md b/docs/components/sidecar.md index 1dc3798ee87..6c421ddfe70 100644 --- a/docs/components/sidecar.md +++ b/docs/components/sidecar.md @@ -21,7 +21,7 @@ In details: Prometheus servers connected to the Thanos cluster via the sidecar are subject to a few limitations and recommendations for safe operations: * The recommended Prometheus version is 2.2.1 or greater (including newest releases). This is due to Prometheus instability in previous versions as well as lack of `flags` endpoint. -* (!) The Prometheus `external_labels` section of the Prometheus configuration file has unique labels in the overall Thanos system. Those external labels will be used by sidecar and then Thanos in many places: +* (!) The Prometheus `external_labels` section of the Prometheus configuration file has unique labels in the overall Thanos system. Those external labels will be used by the sidecar and then Thanos in many places: * [Querier](./query.md) to filter out store APIs to touch during query requests. * Many object storage readers like [compactor](./compact.md) and [store gateway](./store.md) which groups the blocks by Prometheus source. Each produced TSDB block by Prometheus is labelled with external label by sidecar before upload to object storage. @@ -31,7 +31,7 @@ Prometheus servers connected to the Thanos cluster via the sidecar are subject t If you choose to use the sidecar to also upload to object storage: * The `--storage.tsdb.min-block-duration` and `--storage.tsdb.max-block-duration` must be set to equal values to disable local compaction on order to use Thanos sidecar upload, otherwise leave local compaction on if sidecar just exposes StoreAPI and your retention is normal. The default of `2h` is recommended. - Mentioned parameters set to equal values disable the internal Prometheus compaction, which is needed to avoid the uploaded data corruption when Thanos compactor does its job, this is critical for data consistency and should not be ignored if you plan to use Thanos compactor. Even though you set mentioned parameters equal, you might observe Prometheus internal metric `prometheus_tsdb_compactions_total` being incremented, don't be confused by that: Prometheus writes initial head block to filesytem via internal compaction mechanism, but if you have followed recommendations - data won't be modified by Prometheus before sidecar uploads it. Thanos sidecar will also check sanity of the flags set to Prometheus on the startup and log errors or warning if they have been configured improperly (#838). + Mentioned parameters set to equal values disable the internal Prometheus compaction, which is needed to avoid the uploaded data corruption when Thanos compactor does its job, this is critical for data consistency and should not be ignored if you plan to use Thanos compactor. Even though you set mentioned parameters equal, you might observe Prometheus internal metric `prometheus_tsdb_compactions_total` being incremented, don't be confused by that: Prometheus writes initial head block to filesytem via its internal compaction mechanism, but if you have followed recommendations - data won't be modified by Prometheus before the sidecar uploads it. Thanos sidecar will also check sanity of the flags set to Prometheus on the startup and log errors or warning if they have been configured improperly (#838). * The retention is recommended to not be lower than three times the min block duration, so 6 hours. This achieves resilience in the face of connectivity issues to the object storage since all local data will remain available within the Thanos cluster. If connectivity gets restored the backlog of blocks gets uploaded to the object storage. ## Reloader Configuration @@ -40,7 +40,7 @@ Thanos can watch changes in Prometheus configuration and refresh Prometheus conf You can configure watching for changes in directory via `--reloader.rule-dir=DIR_NAME` flag. -Thanos sidecar can watch `--reloader.config-file=CONFIG_FILE` configuration file, evaluate environment variables found in there and produce generated config in `--reloader.config-envsubst-file=OUT_CONFIG_FILE` file. +Thanos sidecar can watch `--reloader.config-file=CONFIG_FILE` configuration file, evaluate environment variables found in there, and produce generated config in `--reloader.config-envsubst-file=OUT_CONFIG_FILE` file. ## Example basic deployment @@ -83,7 +83,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. diff --git a/docs/components/store.md b/docs/components/store.md index 8520a22a581..46ab29d5567 100644 --- a/docs/components/store.md +++ b/docs/components/store.md @@ -42,7 +42,7 @@ Flags: --log.format=logfmt Log format to use. --tracing.config-file= Path to YAML file that contains tracing - configuration. See fomrat details: + configuration. See format details: https://thanos.io/tracing.md/#configuration --tracing.config= Alternative to 'tracing.config-file' flag. diff --git a/pkg/shipper/shipper.go b/pkg/shipper/shipper.go index 3a83d2490ac..ef1216bf1ef 100644 --- a/pkg/shipper/shipper.go +++ b/pkg/shipper/shipper.go @@ -247,7 +247,7 @@ func (c *lazyOverlapChecker) IsOverlapping(ctx context.Context, newMeta tsdb.Blo // Sync performs a single synchronization, which ensures all non-compacted local blocks have been uploaded // to the object bucket once. // -// If updload +// If uploaded. // // It is not concurrency-safe, however it is compactor-safe (running concurrently with compactor is ok) func (s *Shipper) Sync(ctx context.Context) (uploaded int, err error) { From 749b378d408094c4472c11d0255bbda30d99e513 Mon Sep 17 00:00:00 2001 From: Jamie Poole Date: Tue, 24 Sep 2019 14:03:01 +0100 Subject: [PATCH 09/12] Compactor: Fix for #844 - Ignore object if it is the current directory (#1544) * Ignore object if it is the current directory Signed-off-by: Jamie Poole * Add full-stop Signed-off-by: Jamie Poole --- pkg/objstore/s3/s3.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/objstore/s3/s3.go b/pkg/objstore/s3/s3.go index 96e0bf00824..a8efda1b4b8 100644 --- a/pkg/objstore/s3/s3.go +++ b/pkg/objstore/s3/s3.go @@ -239,6 +239,10 @@ func (b *Bucket) Iter(ctx context.Context, dir string, f func(string) error) err if object.Key == "" { continue } + // The s3 client can also return the directory itself in the ListObjects call above. + if object.Key == dir { + continue + } if err := f(object.Key); err != nil { return err } From 3fe208fbd2beed468abad051b6bc88ed317c95d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Meira=20Vital?= Date: Wed, 25 Sep 2019 05:28:07 -0300 Subject: [PATCH 10/12] Adding doc explaining the importance of groups for compactor (#1555) Signed-off-by: Leo Meira Vital --- docs/components/compact.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/components/compact.md b/docs/components/compact.md index a9405723f0b..ea1d9cd657d 100644 --- a/docs/components/compact.md +++ b/docs/components/compact.md @@ -28,6 +28,17 @@ config: The compactor needs local disk space to store intermediate data for its processing. Generally, about 100GB are recommended for it to keep working as the compacted time ranges grow over time. On-disk data is safe to delete between restarts and should be the first attempt to get crash-looping compactors unstuck. +## Groups + +The compactor groups blocks using the [external_labels](https://thanos.io/getting-started.md/#external-labels) added by the +Prometheus who produced the block. The labels must be both _unique_ and _persistent_ across different Prometheus instances. + +By _unique_, we mean that the set of labels in a Prometheus instance must be different from all other sets of labels of +your Prometheus instances, so that the compactor will be able to group blocks by Prometheus instance. + +By _persistent_, we mean that one Prometheus instance must keep the same labels if it restarts, so that the compactor will keep +compacting blocks from an instance even when a Prometheus instance goes down for some time. + ## Flags [embedmd]:# (flags/compact.txt $) From b788fb4b19848ec7d67d3acc7399e30e414de661 Mon Sep 17 00:00:00 2001 From: dongwenjuan Date: Wed, 25 Sep 2019 16:28:31 +0800 Subject: [PATCH 11/12] Add blank line for list (#1566) The format of these files is wrong in the web. Signed-off-by: dongwenjuan --- docs/proposals/201809_gossip-removal.md | 2 ++ docs/proposals/201901-read-write-operations-bucket.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/proposals/201809_gossip-removal.md b/docs/proposals/201809_gossip-removal.md index 3e7d84e3120..91a0de97c87 100644 --- a/docs/proposals/201809_gossip-removal.md +++ b/docs/proposals/201809_gossip-removal.md @@ -18,12 +18,14 @@ that allows changing `StoreAPI`s on-the-fly. [Gossip](https://en.wikipedia.org/wiki/Gossip_protocol) protocol (with the [membership](https://github.com/hashicorp/memberlist) implementation) was built into Thanos from the very beginning. The main advantages over other solution to connect components were: + * Auto-join and auto-drop of components based on health checks. * Propagation of tiny metadata. After a couple of month of maintaining Thanos project and various discussions with different users, we realized that those advantages are not outstanding anymore and are not worth keeping, compared to the issues gossip causes. There are numerous reasons why we should deprecate gossip: + * Gossip has been proven to be extremely confusing for the new users. Peer logic and really confusing `cluster.advertise-address` that was sometimes able to magically deduce private IP address (and sometimes not!) were leading to lots of questions and issues. Something that was made for quick ramp-up into Thanos ("just click the button and it auto-joins everything") created lots of confusion diff --git a/docs/proposals/201901-read-write-operations-bucket.md b/docs/proposals/201901-read-write-operations-bucket.md index 3c2a8c60503..bb58acbe79e 100644 --- a/docs/proposals/201901-read-write-operations-bucket.md +++ b/docs/proposals/201901-read-write-operations-bucket.md @@ -26,12 +26,14 @@ every X minutes instead of watch and react to changes immediately) ## Motivation Thanos performs similar operations as Prometheus do on TSDB blocks with the following differences: + * It operates on Object Storage API instead of local filesystem. * Operations are done from multiple processes that lack coordination. Moving from lock-based logic to coordination free and from strongly consistent local filesystem to potentially eventually consistent remote simplified "filesystem" in form of Object Storage API, causes additional cases that we need to consider in Thanos system, like: + * Thanos sidecar or compactor crashes during the process of uploading the block. It uploaded index, 2 chunk files and crashed. How to ensure readers (e.g compactor, store gateway) will handle this gracefully? * Thanos compactor uploads compacted block and deletes source blocks. After next sync iteration it does not see a new block (read after write eventual consistency). It sees gap, wrongly plans next compaction and causes non-resolvable overlap. * Thanos compactor uploads compacted block and deletes source blocks. Thanos Store Gateway syncs every 3m so it missed that fact. Next query that hits store gateway tries to fetch deleted source blocks and fails. From 99bc7d29ffe836224afcd6ed5f0efe91d3458035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Wed, 25 Sep 2019 11:29:16 +0300 Subject: [PATCH 12/12] Refactor compactor constants, fix bucket column (#1561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * compact: unify different time constants Use downsample.* constants where possible. Move the downsampling time ranges into constants and use them as well. Signed-off-by: Giedrius Statkevičius * bucket: refactor column calculation into compact Fix the column's name and name it UNTIL-DOWN because that is what it actually shows - time until the next downsampling. Move out the calculation into a separate function into the compact package. Ideally we could use the retention policies in this calculation as well but the `bucket` subcommand knows nothing about them :-( Signed-off-by: Giedrius Statkevičius * compact: fix issues with naming Reorder the constants and fix mistakes. Signed-off-by: Giedrius Statkevičius --- cmd/thanos/bucket.go | 16 +++++++--------- cmd/thanos/downsample.go | 18 +++++++++--------- pkg/compact/compact.go | 17 ++++++++++++++++- pkg/compact/downsample/downsample.go | 6 ++++++ 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/cmd/thanos/bucket.go b/cmd/thanos/bucket.go index 9c68d9b2b27..6ab198fbc7b 100644 --- a/cmd/thanos/bucket.go +++ b/cmd/thanos/bucket.go @@ -14,6 +14,7 @@ import ( "github.com/thanos-io/thanos/pkg/block" "github.com/thanos-io/thanos/pkg/block/metadata" + "github.com/thanos-io/thanos/pkg/compact" extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http" "github.com/thanos-io/thanos/pkg/objstore" "github.com/thanos-io/thanos/pkg/objstore/client" @@ -50,7 +51,7 @@ var ( sort.Strings(s) return s } - inspectColumns = []string{"ULID", "FROM", "UNTIL", "RANGE", "UNTIL-COMP", "#SERIES", "#SAMPLES", "#CHUNKS", "COMP-LEVEL", "COMP-FAILED", "LABELS", "RESOLUTION", "SOURCE"} + inspectColumns = []string{"ULID", "FROM", "UNTIL", "RANGE", "UNTIL-DOWN", "#SERIES", "#SAMPLES", "#CHUNKS", "COMP-LEVEL", "COMP-FAILED", "LABELS", "RESOLUTION", "SOURCE"} ) func registerBucket(m map[string]setupFunc, app *kingpin.Application, name string) { @@ -420,13 +421,10 @@ func printTable(blockMetas []*metadata.Meta, selectorLabels labels.Labels, sortB } timeRange := time.Duration((blockMeta.MaxTime - blockMeta.MinTime) * int64(time.Millisecond)) - // Calculate how long it takes until the next compaction. - untilComp := "-" - if blockMeta.Thanos.Downsample.Resolution == 0 { // data currently raw, downsample if range >= 40 hours - untilComp = (time.Duration(40*60*60*1000*time.Millisecond) - timeRange).String() - } - if blockMeta.Thanos.Downsample.Resolution == 5*60*1000 { // data currently 5m resolution, downsample if range >= 10 days - untilComp = (time.Duration(10*24*60*60*1000*time.Millisecond) - timeRange).String() + + untilDown := "-" + if until, err := compact.UntilNextDownsampling(blockMeta); err == nil { + untilDown = until.String() } var labels []string for _, key := range getKeysAlphabetically(blockMeta.Thanos.Labels) { @@ -438,7 +436,7 @@ func printTable(blockMetas []*metadata.Meta, selectorLabels labels.Labels, sortB line = append(line, time.Unix(blockMeta.MinTime/1000, 0).Format("02-01-2006 15:04:05")) line = append(line, time.Unix(blockMeta.MaxTime/1000, 0).Format("02-01-2006 15:04:05")) line = append(line, timeRange.String()) - line = append(line, untilComp) + line = append(line, untilDown) line = append(line, p.Sprintf("%d", blockMeta.Stats.NumSeries)) line = append(line, p.Sprintf("%d", blockMeta.Stats.NumSamples)) line = append(line, p.Sprintf("%d", blockMeta.Stats.NumChunks)) diff --git a/cmd/thanos/downsample.go b/cmd/thanos/downsample.go index 2f90f368aca..cb5a6765336 100644 --- a/cmd/thanos/downsample.go +++ b/cmd/thanos/downsample.go @@ -170,13 +170,13 @@ func downsampleBucket( for _, m := range metas { switch m.Thanos.Downsample.Resolution { - case 0: + case downsample.ResLevel0: continue - case 5 * 60 * 1000: + case downsample.ResLevel1: for _, id := range m.Compaction.Sources { sources5m[id] = struct{}{} } - case 60 * 60 * 1000: + case downsample.ResLevel2: for _, id := range m.Compaction.Sources { sources1h[id] = struct{}{} } @@ -187,7 +187,7 @@ func downsampleBucket( for _, m := range metas { switch m.Thanos.Downsample.Resolution { - case 0: + case downsample.ResLevel0: missing := false for _, id := range m.Compaction.Sources { if _, ok := sources5m[id]; !ok { @@ -201,16 +201,16 @@ func downsampleBucket( // Only downsample blocks once we are sure to get roughly 2 chunks out of it. // NOTE(fabxc): this must match with at which block size the compactor creates downsampled // blocks. Otherwise we may never downsample some data. - if m.MaxTime-m.MinTime < 40*60*60*1000 { + if m.MaxTime-m.MinTime < downsample.DownsampleRange0 { continue } - if err := processDownsampling(ctx, logger, bkt, m, dir, 5*60*1000); err != nil { + if err := processDownsampling(ctx, logger, bkt, m, dir, downsample.ResLevel1); err != nil { metrics.downsampleFailures.WithLabelValues(compact.GroupKey(*m)).Inc() return errors.Wrap(err, "downsampling to 5 min") } metrics.downsamples.WithLabelValues(compact.GroupKey(*m)).Inc() - case 5 * 60 * 1000: + case downsample.ResLevel1: missing := false for _, id := range m.Compaction.Sources { if _, ok := sources1h[id]; !ok { @@ -224,10 +224,10 @@ func downsampleBucket( // Only downsample blocks once we are sure to get roughly 2 chunks out of it. // NOTE(fabxc): this must match with at which block size the compactor creates downsampled // blocks. Otherwise we may never downsample some data. - if m.MaxTime-m.MinTime < 10*24*60*60*1000 { + if m.MaxTime-m.MinTime < downsample.DownsampleRange1 { continue } - if err := processDownsampling(ctx, logger, bkt, m, dir, 60*60*1000); err != nil { + if err := processDownsampling(ctx, logger, bkt, m, dir, downsample.ResLevel2); err != nil { metrics.downsampleFailures.WithLabelValues(compact.GroupKey(*m)) return errors.Wrap(err, "downsampling to 60 min") } diff --git a/pkg/compact/compact.go b/pkg/compact/compact.go index 506d2dada81..87f7dbd11be 100644 --- a/pkg/compact/compact.go +++ b/pkg/compact/compact.go @@ -162,10 +162,25 @@ func (c *Syncer) SyncMetas(ctx context.Context) error { } c.metrics.syncMetas.Inc() c.metrics.syncMetaDuration.Observe(time.Since(begin).Seconds()) - return err } +// UntilNextDownsampling calculates how long it will take until the next downsampling operation. +// Returns an error if there will be no downsampling. +func UntilNextDownsampling(m *metadata.Meta) (time.Duration, error) { + timeRange := time.Duration((m.MaxTime - m.MinTime) * int64(time.Millisecond)) + switch m.Thanos.Downsample.Resolution { + case downsample.ResLevel2: + return time.Duration(0), errors.New("no downsampling") + case downsample.ResLevel1: + return time.Duration(downsample.DownsampleRange1*time.Millisecond) - timeRange, nil + case downsample.ResLevel0: + return time.Duration(downsample.DownsampleRange0*time.Millisecond) - timeRange, nil + default: + panic(fmt.Errorf("invalid resolution %v", m.Thanos.Downsample.Resolution)) + } +} + func (c *Syncer) syncMetas(ctx context.Context) error { var wg sync.WaitGroup defer wg.Wait() diff --git a/pkg/compact/downsample/downsample.go b/pkg/compact/downsample/downsample.go index 3c1deb7ef2b..7bd59b3e782 100644 --- a/pkg/compact/downsample/downsample.go +++ b/pkg/compact/downsample/downsample.go @@ -28,6 +28,12 @@ const ( ResLevel2 = int64(60 * 60 * 1000) // 1 hour in milliseconds ) +// Downsampling ranges i.e. after what time we start to downsample blocks (in seconds). +const ( + DownsampleRange0 = 40 * 60 * 60 * 1000 // 40 hours + DownsampleRange1 = 10 * 24 * 60 * 60 * 1000 // 10 days +) + // Downsample downsamples the given block. It writes a new block into dir and returns its ID. func Downsample( logger log.Logger,