@@ -16,6 +16,8 @@ package collector
16
16
17
17
import (
18
18
"context"
19
+ "errors"
20
+ "fmt"
19
21
"net/http"
20
22
"net/url"
21
23
"sync"
@@ -24,10 +26,26 @@ import (
24
26
"github.com/go-kit/log"
25
27
"github.com/go-kit/log/level"
26
28
"github.com/prometheus/client_golang/prometheus"
29
+ "gopkg.in/alecthomas/kingpin.v2"
27
30
)
28
31
29
- // Namespace defines the common namespace to be used by all metrics.
30
- const namespace = "elasticsearch"
32
+ const (
33
+ // Namespace defines the common namespace to be used by all metrics.
34
+ namespace = "elasticsearch"
35
+
36
+ defaultEnabled = true
37
+ // defaultDisabled = false
38
+ )
39
+
40
+ type factoryFunc func (logger log.Logger , u * url.URL , hc * http.Client ) (Collector , error )
41
+
42
+ var (
43
+ factories = make (map [string ]factoryFunc )
44
+ initiatedCollectorsMtx = sync.Mutex {}
45
+ initiatedCollectors = make (map [string ]Collector )
46
+ collectorState = make (map [string ]* bool )
47
+ forcedCollectors = map [string ]bool {} // collectors which have been explicitly enabled or disabled
48
+ )
31
49
32
50
var (
33
51
scrapeDurationDesc = prometheus .NewDesc (
@@ -50,16 +68,92 @@ type Collector interface {
50
68
Update (context.Context , chan <- prometheus.Metric ) error
51
69
}
52
70
71
+ func registerCollector (name string , isDefaultEnabled bool , createFunc factoryFunc ) {
72
+ var helpDefaultState string
73
+ if isDefaultEnabled {
74
+ helpDefaultState = "enabled"
75
+ } else {
76
+ helpDefaultState = "disabled"
77
+ }
78
+
79
+ // Create flag for this collector
80
+ flagName := fmt .Sprintf ("collector.%s" , name )
81
+ flagHelp := fmt .Sprintf ("Enable the %s collector (default: %s)." , name , helpDefaultState )
82
+ defaultValue := fmt .Sprintf ("%v" , isDefaultEnabled )
83
+
84
+ flag := kingpin .Flag (flagName , flagHelp ).Default (defaultValue ).Action (collectorFlagAction (name )).Bool ()
85
+ collectorState [name ] = flag
86
+
87
+ // Register the create function for this collector
88
+ factories [name ] = createFunc
89
+ }
90
+
53
91
type ElasticsearchCollector struct {
54
92
Collectors map [string ]Collector
55
93
logger log.Logger
94
+ esURL * url.URL
95
+ httpClient * http.Client
56
96
}
57
97
98
+ type Option func (* ElasticsearchCollector ) error
99
+
58
100
// NewElasticsearchCollector creates a new ElasticsearchCollector
59
- func NewElasticsearchCollector (logger log.Logger , httpClient * http.Client , esURL * url.URL ) (* ElasticsearchCollector , error ) {
101
+ func NewElasticsearchCollector (logger log.Logger , filters []string , options ... Option ) (* ElasticsearchCollector , error ) {
102
+ e := & ElasticsearchCollector {logger : logger }
103
+ // Apply options to customize the collector
104
+ for _ , o := range options {
105
+ if err := o (e ); err != nil {
106
+ return nil , err
107
+ }
108
+ }
109
+
110
+ f := make (map [string ]bool )
111
+ for _ , filter := range filters {
112
+ enabled , exist := collectorState [filter ]
113
+ if ! exist {
114
+ return nil , fmt .Errorf ("missing collector: %s" , filter )
115
+ }
116
+ if ! * enabled {
117
+ return nil , fmt .Errorf ("disabled collector: %s" , filter )
118
+ }
119
+ f [filter ] = true
120
+ }
60
121
collectors := make (map [string ]Collector )
122
+ initiatedCollectorsMtx .Lock ()
123
+ defer initiatedCollectorsMtx .Unlock ()
124
+ for key , enabled := range collectorState {
125
+ if ! * enabled || (len (f ) > 0 && ! f [key ]) {
126
+ continue
127
+ }
128
+ if collector , ok := initiatedCollectors [key ]; ok {
129
+ collectors [key ] = collector
130
+ } else {
131
+ collector , err := factories [key ](log .With (logger , "collector" , key ), e .esURL , e .httpClient )
132
+ if err != nil {
133
+ return nil , err
134
+ }
135
+ collectors [key ] = collector
136
+ initiatedCollectors [key ] = collector
137
+ }
138
+ }
139
+
140
+ e .Collectors = collectors
141
+
142
+ return e , nil
143
+ }
61
144
62
- return & ElasticsearchCollector {Collectors : collectors , logger : logger }, nil
145
+ func WithElasticsearchURL (esURL * url.URL ) Option {
146
+ return func (e * ElasticsearchCollector ) error {
147
+ e .esURL = esURL
148
+ return nil
149
+ }
150
+ }
151
+
152
+ func WithHTTPClient (hc * http.Client ) Option {
153
+ return func (e * ElasticsearchCollector ) error {
154
+ e .httpClient = hc
155
+ return nil
156
+ }
63
157
}
64
158
65
159
// Describe implements the prometheus.Collector interface.
@@ -89,7 +183,11 @@ func execute(ctx context.Context, name string, c Collector, ch chan<- prometheus
89
183
var success float64
90
184
91
185
if err != nil {
92
- _ = level .Error (logger ).Log ("msg" , "collector failed" , "name" , name , "duration_seconds" , duration .Seconds (), "err" , err )
186
+ if IsNoDataError (err ) {
187
+ _ = level .Debug (logger ).Log ("msg" , "collector returned no data" , "name" , name , "duration_seconds" , duration .Seconds (), "err" , err )
188
+ } else {
189
+ _ = level .Error (logger ).Log ("msg" , "collector failed" , "name" , name , "duration_seconds" , duration .Seconds (), "err" , err )
190
+ }
93
191
success = 0
94
192
} else {
95
193
_ = level .Debug (logger ).Log ("msg" , "collector succeeded" , "name" , name , "duration_seconds" , duration .Seconds ())
@@ -98,3 +196,22 @@ func execute(ctx context.Context, name string, c Collector, ch chan<- prometheus
98
196
ch <- prometheus .MustNewConstMetric (scrapeDurationDesc , prometheus .GaugeValue , duration .Seconds (), name )
99
197
ch <- prometheus .MustNewConstMetric (scrapeSuccessDesc , prometheus .GaugeValue , success , name )
100
198
}
199
+
200
+ // collectorFlagAction generates a new action function for the given collector
201
+ // to track whether it has been explicitly enabled or disabled from the command line.
202
+ // A new action function is needed for each collector flag because the ParseContext
203
+ // does not contain information about which flag called the action.
204
+ // See: https://github.com/alecthomas/kingpin/issues/294
205
+ func collectorFlagAction (collector string ) func (ctx * kingpin.ParseContext ) error {
206
+ return func (ctx * kingpin.ParseContext ) error {
207
+ forcedCollectors [collector ] = true
208
+ return nil
209
+ }
210
+ }
211
+
212
+ // ErrNoData indicates the collector found no data to collect, but had no other error.
213
+ var ErrNoData = errors .New ("collector returned no data" )
214
+
215
+ func IsNoDataError (err error ) bool {
216
+ return err == ErrNoData
217
+ }
0 commit comments