This commit shifts responsibility for maintaining targets from providers and pools to the target manager. Target groups have a source name that identifies them for updates.pull/645/head
parent
9042b4f081
commit
5015c2a0e8
@ -1,25 +0,0 @@ |
||||
// Copyright 2013 The Prometheus 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.
|
||||
|
||||
package retrieval |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestInterfaces(t *testing.T) { |
||||
var ( |
||||
_ Target = &target{} |
||||
_ TargetManager = &targetManager{} |
||||
) |
||||
} |
||||
@ -1,164 +0,0 @@ |
||||
// Copyright 2013 The Prometheus 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.
|
||||
|
||||
package retrieval |
||||
|
||||
import ( |
||||
"sort" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/golang/glog" |
||||
|
||||
"github.com/prometheus/prometheus/storage" |
||||
"github.com/prometheus/prometheus/utility" |
||||
) |
||||
|
||||
const ( |
||||
targetAddQueueSize = 100 |
||||
targetReplaceQueueSize = 1 |
||||
) |
||||
|
||||
// TargetPool is a pool of targets for the same job.
|
||||
type TargetPool struct { |
||||
sync.RWMutex |
||||
|
||||
manager TargetManager |
||||
targetsByURL map[string]Target |
||||
interval time.Duration |
||||
sampleAppender storage.SampleAppender |
||||
addTargetQueue chan Target |
||||
|
||||
targetProvider TargetProvider |
||||
|
||||
stopping, stopped chan struct{} |
||||
} |
||||
|
||||
// NewTargetPool creates a TargetPool, ready to be started by calling Run.
|
||||
func NewTargetPool(p TargetProvider, app storage.SampleAppender, i time.Duration) *TargetPool { |
||||
return &TargetPool{ |
||||
interval: i, |
||||
sampleAppender: app, |
||||
targetsByURL: make(map[string]Target), |
||||
addTargetQueue: make(chan Target, targetAddQueueSize), |
||||
targetProvider: p, |
||||
stopping: make(chan struct{}), |
||||
stopped: make(chan struct{}), |
||||
} |
||||
} |
||||
|
||||
// Run starts the target pool. It returns when the target pool has stopped
|
||||
// (after calling Stop). Run is usually called as a goroutine.
|
||||
func (p *TargetPool) Run() { |
||||
ticker := time.NewTicker(p.interval) |
||||
defer ticker.Stop() |
||||
|
||||
for { |
||||
select { |
||||
case <-ticker.C: |
||||
if p.targetProvider != nil { |
||||
targets, err := p.targetProvider.Targets() |
||||
if err != nil { |
||||
glog.Warningf("Error looking up targets, keeping old list: %s", err) |
||||
} else { |
||||
p.ReplaceTargets(targets) |
||||
} |
||||
} |
||||
case newTarget := <-p.addTargetQueue: |
||||
p.addTarget(newTarget) |
||||
case <-p.stopping: |
||||
p.ReplaceTargets([]Target{}) |
||||
close(p.stopped) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Stop stops the target pool and returns once the shutdown is complete.
|
||||
func (p *TargetPool) Stop() { |
||||
close(p.stopping) |
||||
<-p.stopped |
||||
} |
||||
|
||||
// AddTarget adds a target by queuing it in the target queue.
|
||||
func (p *TargetPool) AddTarget(target Target) { |
||||
p.addTargetQueue <- target |
||||
} |
||||
|
||||
func (p *TargetPool) addTarget(target Target) { |
||||
p.Lock() |
||||
defer p.Unlock() |
||||
|
||||
p.targetsByURL[target.URL()] = target |
||||
go target.RunScraper(p.sampleAppender, p.interval) |
||||
} |
||||
|
||||
// ReplaceTargets replaces the old targets by the provided new ones but reuses
|
||||
// old targets that are also present in newTargets to preserve scheduling and
|
||||
// health state. Targets no longer present are stopped.
|
||||
func (p *TargetPool) ReplaceTargets(newTargets []Target) { |
||||
p.Lock() |
||||
defer p.Unlock() |
||||
|
||||
newTargetURLs := make(utility.Set) |
||||
for _, newTarget := range newTargets { |
||||
newTargetURLs.Add(newTarget.URL()) |
||||
oldTarget, ok := p.targetsByURL[newTarget.URL()] |
||||
if ok { |
||||
oldTarget.SetBaseLabelsFrom(newTarget) |
||||
} else { |
||||
p.targetsByURL[newTarget.URL()] = newTarget |
||||
go newTarget.RunScraper(p.sampleAppender, p.interval) |
||||
} |
||||
} |
||||
|
||||
var wg sync.WaitGroup |
||||
for k, oldTarget := range p.targetsByURL { |
||||
if !newTargetURLs.Has(k) { |
||||
wg.Add(1) |
||||
go func(k string, oldTarget Target) { |
||||
defer wg.Done() |
||||
glog.V(1).Infof("Stopping scraper for target %s...", k) |
||||
oldTarget.StopScraper() |
||||
glog.V(1).Infof("Scraper for target %s stopped.", k) |
||||
}(k, oldTarget) |
||||
delete(p.targetsByURL, k) |
||||
} |
||||
} |
||||
wg.Wait() |
||||
} |
||||
|
||||
type targetsByURL []Target |
||||
|
||||
func (s targetsByURL) Len() int { |
||||
return len(s) |
||||
} |
||||
func (s targetsByURL) Swap(i, j int) { |
||||
s[i], s[j] = s[j], s[i] |
||||
} |
||||
func (s targetsByURL) Less(i, j int) bool { |
||||
return s[i].URL() < s[j].URL() |
||||
} |
||||
|
||||
// Targets returns a sorted copy of the current target list.
|
||||
func (p *TargetPool) Targets() []Target { |
||||
p.RLock() |
||||
defer p.RUnlock() |
||||
|
||||
targets := make(targetsByURL, 0, len(p.targetsByURL)) |
||||
for _, v := range p.targetsByURL { |
||||
targets = append(targets, v) |
||||
} |
||||
sort.Sort(targets) |
||||
return targets |
||||
} |
||||
@ -1,164 +0,0 @@ |
||||
// Copyright 2013 The Prometheus 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.
|
||||
|
||||
package retrieval |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func testTargetPool(t testing.TB) { |
||||
type expectation struct { |
||||
size int |
||||
} |
||||
|
||||
type input struct { |
||||
url string |
||||
scheduledFor time.Time |
||||
} |
||||
|
||||
type output struct { |
||||
url string |
||||
} |
||||
|
||||
var scenarios = []struct { |
||||
name string |
||||
inputs []input |
||||
outputs []output |
||||
}{ |
||||
{ |
||||
name: "empty", |
||||
inputs: []input{}, |
||||
outputs: []output{}, |
||||
}, |
||||
{ |
||||
name: "single element", |
||||
inputs: []input{ |
||||
{ |
||||
url: "single1", |
||||
}, |
||||
}, |
||||
outputs: []output{ |
||||
{ |
||||
url: "single1", |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
name: "plural schedules", |
||||
inputs: []input{ |
||||
{ |
||||
url: "plural1", |
||||
}, |
||||
{ |
||||
url: "plural2", |
||||
}, |
||||
}, |
||||
outputs: []output{ |
||||
{ |
||||
url: "plural1", |
||||
}, |
||||
{ |
||||
url: "plural2", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for i, scenario := range scenarios { |
||||
pool := NewTargetPool(nil, nopAppender{}, time.Duration(1)) |
||||
|
||||
for _, input := range scenario.inputs { |
||||
target := target{ |
||||
url: input.url, |
||||
httpClient: &http.Client{}, |
||||
} |
||||
pool.addTarget(&target) |
||||
} |
||||
|
||||
if len(pool.targetsByURL) != len(scenario.outputs) { |
||||
t.Errorf("%s %d. expected TargetPool size to be %d but was %d", scenario.name, i, len(scenario.outputs), len(pool.targetsByURL)) |
||||
} else { |
||||
for j, output := range scenario.outputs { |
||||
if target, ok := pool.targetsByURL[output.url]; !ok { |
||||
t.Errorf("%s %d.%d. expected Target url to be %s but was %s", scenario.name, i, j, output.url, target.URL()) |
||||
} |
||||
} |
||||
|
||||
if len(pool.targetsByURL) != len(scenario.outputs) { |
||||
t.Errorf("%s %d. expected to repopulated with %d elements, got %d", scenario.name, i, len(scenario.outputs), len(pool.targetsByURL)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestTargetPool(t *testing.T) { |
||||
testTargetPool(t) |
||||
} |
||||
|
||||
func TestTargetPoolReplaceTargets(t *testing.T) { |
||||
pool := NewTargetPool(nil, nopAppender{}, time.Duration(1)) |
||||
oldTarget1 := &target{ |
||||
url: "example1", |
||||
state: Unhealthy, |
||||
scraperStopping: make(chan struct{}), |
||||
scraperStopped: make(chan struct{}), |
||||
httpClient: &http.Client{}, |
||||
} |
||||
oldTarget2 := &target{ |
||||
url: "example2", |
||||
state: Unhealthy, |
||||
scraperStopping: make(chan struct{}), |
||||
scraperStopped: make(chan struct{}), |
||||
httpClient: &http.Client{}, |
||||
} |
||||
newTarget1 := &target{ |
||||
url: "example1", |
||||
state: Healthy, |
||||
scraperStopping: make(chan struct{}), |
||||
scraperStopped: make(chan struct{}), |
||||
httpClient: &http.Client{}, |
||||
} |
||||
newTarget2 := &target{ |
||||
url: "example3", |
||||
state: Healthy, |
||||
scraperStopping: make(chan struct{}), |
||||
scraperStopped: make(chan struct{}), |
||||
httpClient: &http.Client{}, |
||||
} |
||||
|
||||
pool.addTarget(oldTarget1) |
||||
pool.addTarget(oldTarget2) |
||||
|
||||
pool.ReplaceTargets([]Target{newTarget1, newTarget2}) |
||||
|
||||
if len(pool.targetsByURL) != 2 { |
||||
t.Errorf("Expected 2 elements in pool, had %d", len(pool.targetsByURL)) |
||||
} |
||||
|
||||
if pool.targetsByURL["example1"].State() != oldTarget1.State() { |
||||
t.Errorf("target1 channel has changed") |
||||
} |
||||
if pool.targetsByURL["example3"].State() == oldTarget2.State() { |
||||
t.Errorf("newTarget2 channel same as oldTarget2's") |
||||
} |
||||
|
||||
} |
||||
|
||||
func BenchmarkTargetPool(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
testTargetPool(b) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue