diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 1fbbba6..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,152 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" - name = "github.com/beorn7/perks" - packages = ["quantile"] - pruneopts = "UT" - revision = "3a771d992973f24aa725d07868b467d1ddfceafb" - -[[projects]] - digest = "1:db2e37856e0f3e8ad322792ca2ede9bcf821f4d482ca0ef300e6a9d85776a99a" - name = "github.com/go-kit/kit" - packages = [ - "log", - "log/level", - ] - pruneopts = "UT" - revision = "ca4112baa34cb55091301bdc13b1420a122b1b9e" - version = "v0.7.0" - -[[projects]] - digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" - name = "github.com/go-logfmt/logfmt" - packages = ["."] - pruneopts = "UT" - revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" - version = "v0.3.0" - -[[projects]] - digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" - name = "github.com/go-stack/stack" - packages = ["."] - pruneopts = "UT" - revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" - version = "v1.8.0" - -[[projects]] - digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d" - name = "github.com/golang/protobuf" - packages = ["proto"] - pruneopts = "UT" - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" - -[[projects]] - digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a" - name = "github.com/google/go-cmp" - packages = [ - "cmp", - "cmp/internal/diff", - "cmp/internal/function", - "cmp/internal/value", - ] - pruneopts = "UT" - revision = "3af367b6b30c263d47e8895973edcca9a49cf029" - version = "v0.2.0" - -[[projects]] - branch = "master" - digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" - name = "github.com/kr/logfmt" - packages = ["."] - pruneopts = "UT" - revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" - -[[projects]] - digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" - name = "github.com/matttproud/golang_protobuf_extensions" - packages = ["pbutil"] - pruneopts = "UT" - revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" - version = "v1.0.1" - -[[projects]] - digest = "1:9ec6cf1df5ad1d55cf41a43b6b1e7e118a91bade4f68ff4303379343e40c0e25" - name = "github.com/oklog/run" - packages = ["."] - pruneopts = "UT" - revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39" - version = "v1.0.0" - -[[projects]] - digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" - name = "github.com/pkg/errors" - packages = ["."] - pruneopts = "UT" - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" - -[[projects]] - digest = "1:6c94c57e53f0e6f57ff74cdb2e5d7813918ff1be2e0fff69303daa506ede46bd" - name = "github.com/prometheus/client_golang" - packages = [ - "prometheus", - "prometheus/internal", - "prometheus/promauto", - "prometheus/promhttp", - ] - pruneopts = "UT" - revision = "1cafe34db7fdec6022e17e00e1c1ea501022f3e4" - version = "v0.9.0" - -[[projects]] - branch = "master" - digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" - name = "github.com/prometheus/client_model" - packages = ["go"] - pruneopts = "UT" - revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" - -[[projects]] - branch = "master" - digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4" - name = "github.com/prometheus/common" - packages = [ - "expfmt", - "internal/bitbucket.org/ww/goautoneg", - "model", - ] - pruneopts = "UT" - revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6" - -[[projects]] - branch = "master" - digest = "1:ef74914912f99c79434d9c09658274678bc85080ebe3ab32bec3940ebce5e1fc" - name = "github.com/prometheus/procfs" - packages = [ - ".", - "internal/util", - "nfs", - "xfs", - ] - pruneopts = "UT" - revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/go-kit/kit/log", - "github.com/go-kit/kit/log/level", - "github.com/google/go-cmp/cmp", - "github.com/oklog/run", - "github.com/pkg/errors", - "github.com/prometheus/client_golang/prometheus", - "github.com/prometheus/client_golang/prometheus/promauto", - "github.com/prometheus/client_golang/prometheus/promhttp", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 8b0da79..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,24 +0,0 @@ - -[[constraint]] - name = "github.com/go-kit/kit" - version = "0.7.0" - -[[constraint]] - name = "github.com/google/go-cmp" - version = "0.2.0" - -[[constraint]] - name = "github.com/oklog/run" - version = "1.0.0" - -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" - -[[constraint]] - name = "github.com/prometheus/client_golang" - version = "0.9.0" - -[prune] - go-tests = true - unused-packages = true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2a4cfdd --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/peterbourgon/fastly-exporter + +go 1.12 + +require ( + github.com/go-kit/kit v0.8.0 + github.com/google/go-cmp v0.2.0 + github.com/oklog/run v1.0.0 + github.com/peterbourgon/usage v1.0.1 + github.com/pkg/errors v0.8.0 + github.com/prometheus/client_golang v1.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d782e7a --- /dev/null +++ b/go.sum @@ -0,0 +1,56 @@ +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/peterbourgon/usage v1.0.1 h1:Clwpoi24ldPcZdlNreEdkh/1RyV5mEnJh0A8t13d/Rk= +github.com/peterbourgon/usage v1.0.1/go.mod h1:KBC4Y1vQmbn4b8YHk7r0TlVkByTasbseRBMSOz5o0d0= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 0d4fd2a..9809349 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,12 @@ import ( "os" "os/signal" "strings" - "text/tabwriter" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/oklog/run" + "github.com/peterbourgon/usage" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -31,7 +31,7 @@ func main() { debug = fs.Bool("debug", false, "Log debug information") ) fs.Var(&serviceIDs, "service", "Specific Fastly service ID (optional, repeatable)") - fs.Usage = usageFor(fs, "fastly-exporter [flags]") + fs.Usage = usage.For(fs, "fastly-exporter [flags]") fs.Parse(os.Args[1:]) var logger log.Logger @@ -187,25 +187,3 @@ func (ss *stringslice) String() string { type httpClient interface { Do(*http.Request) (*http.Response, error) } - -func usageFor(fs *flag.FlagSet, short string) func() { - return func() { - fmt.Fprintf(os.Stderr, "USAGE\n") - fmt.Fprintf(os.Stderr, " %s\n", short) - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "FLAGS\n") - w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0) - fs.VisitAll(func(f *flag.Flag) { - def := f.DefValue - if def == "" { - def = "..." - } - fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, def, f.Usage) - }) - w.Flush() - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "VERSION\n") - fmt.Fprintf(os.Stderr, " %s\n", version) - fmt.Fprintf(os.Stderr, "\n") - } -} diff --git a/monitor.go b/monitor.go index 5d058d1..00ec421 100644 --- a/monitor.go +++ b/monitor.go @@ -11,6 +11,10 @@ import ( "github.com/go-kit/kit/log/level" ) +// monitor polls the Fastly real-time stats API for the provided service ID on a +// regular cadence. All received metrics are processed and given to the +// Prometheus metrics struct. The service name label is regularly updated via +// the name resolver. The function exits when the context is canceled. func monitor(ctx context.Context, client httpClient, token string, serviceID string, resolver nameResolver, metrics prometheusMetrics, postprocess func(), logger log.Logger) error { var ts uint64 for { @@ -23,6 +27,7 @@ func monitor(ctx context.Context, client httpClient, token string, serviceID str serviceName = resolver.resolve(serviceID) logger = log.With(logger, "service_name", serviceName) ) + // rt.fastly.com blocks until it has data to return. // It's safe to call in a (single-threaded!) hot loop. u := fmt.Sprintf("https://rt.fastly.com/v1/channel/%s/ts/%d", serviceID, ts) @@ -30,39 +35,46 @@ func monitor(ctx context.Context, client httpClient, token string, serviceID str if err != nil { return err // fatal for sure } + req.Header.Set("User-Agent", "Fastly-Exporter ("+version+")") req.Header.Set("Fastly-Key", token) req.Header.Set("Accept", "application/json") resp, err := client.Do(req.WithContext(ctx)) if err != nil { - level.Error(logger).Log("err", err) + level.Error(logger).Log("during", "execute request", "err", err) contextSleep(ctx, time.Second) continue } + var rt realtimeResponse if err := json.NewDecoder(resp.Body).Decode(&rt); err != nil { resp.Body.Close() - level.Error(logger).Log("err", err) + level.Error(logger).Log("during", "decode response", "err", err) contextSleep(ctx, time.Second) continue } resp.Body.Close() + rterr := rt.Error if rterr == "" { rterr = "" } + switch resp.StatusCode { case http.StatusOK: level.Debug(logger).Log("status_code", resp.StatusCode, "response_ts", rt.Timestamp, "err", rterr) process(rt, serviceID, serviceName, metrics) postprocess() + case http.StatusUnauthorized, http.StatusForbidden: level.Error(logger).Log("status_code", resp.StatusCode, "response_ts", rt.Timestamp, "err", rterr, "msg", "token may be invalid") contextSleep(ctx, 15*time.Second) + default: level.Error(logger).Log("status_code", resp.StatusCode, "response_ts", rt.Timestamp, "err", rterr) contextSleep(ctx, 5*time.Second) } + ts = rt.Timestamp } } diff --git a/monitor_manager.go b/monitor_manager.go index 5b9a9a5..4e7b4db 100644 --- a/monitor_manager.go +++ b/monitor_manager.go @@ -8,6 +8,11 @@ import ( "github.com/go-kit/kit/log/level" ) +// monitorManager maintains the goroutines running the monitor function for each +// service ID. It's possible that a service ID is added or removed during +// runtime; for example, if we're configured to monitor all service IDs +// accessible to a token, and the admin for that account adds or removes a +// service. In that case, the monitor manager updates the monitors accordingly. type monitorManager struct { mtx sync.Mutex running map[string]interrupt @@ -29,6 +34,7 @@ type nameResolver interface { resolve(id string) (name string) } +// newMonitorManager returns an empty, usable monitor manager. func newMonitorManager(client httpClient, token string, resolver nameResolver, metrics prometheusMetrics, postprocess func(), logger log.Logger) *monitorManager { return &monitorManager{ running: map[string]interrupt{}, @@ -42,6 +48,10 @@ func newMonitorManager(client httpClient, token string, resolver nameResolver, m } } +// update the set of service IDs that the monitor manager should be managing. +// New service IDs spawn new monitors; existing service IDs leave their monitors +// unchanged; service IDs that were in the manager but aren't in the incoming +// set of IDs have their monitors canceled and reaped. func (m *monitorManager) update(ids []string) { m.mtx.Lock() defer m.mtx.Unlock() @@ -66,6 +76,8 @@ func (m *monitorManager) update(ids []string) { m.running = nextgen } +// spawn a new monitor watching the provided service ID. Return an interrupt, +// which allows the monitor to be canceled and reaped. func (m *monitorManager) spawn(id string) interrupt { var ( ctx, cancel = context.WithCancel(context.Background()) @@ -78,6 +90,8 @@ func (m *monitorManager) spawn(id string) interrupt { return interrupt{cancel, done} } +// stopAll cancels and reaps all running monitors in sequence. +// When it returns, the monitor manager is empty. func (m *monitorManager) stopAll() { m.mtx.Lock() defer m.mtx.Unlock() @@ -90,6 +104,7 @@ func (m *monitorManager) stopAll() { } } +// currentRunning returns all service IDs that are currently being monitored. func (m *monitorManager) currentlyRunning() (ids []string) { m.mtx.Lock() defer m.mtx.Unlock() diff --git a/name_cache.go b/name_cache.go index d78c811..1b77828 100644 --- a/name_cache.go +++ b/name_cache.go @@ -2,20 +2,25 @@ package main import "sync/atomic" +// nameCache is a simple wrapper around a map of service ID to service name. type nameCache struct { v atomic.Value } +// newNameCache returns an empty and usable name cache. func newNameCache() *nameCache { var v atomic.Value v.Store(map[string]string{}) return &nameCache{v} } +// update the complete mapping of service IDs to names. func (c *nameCache) update(names map[string]string) { c.v.Store(names) } +// resolve a service ID to its corresponding name. +// If the service ID isn't found, the ID itself is returned as the name. func (c *nameCache) resolve(id string) (name string) { if name, ok := c.v.Load().(map[string]string)[id]; ok { return name diff --git a/process.go b/process.go index 6a3d24f..e1812ce 100644 --- a/process.go +++ b/process.go @@ -6,6 +6,8 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +// process interprets the data in the realtime response, and feeds the +// interpreted results to the Prometheus metrics as observations. func process(src realtimeResponse, serviceID string, serviceName string, dst prometheusMetrics) { for _, d := range src.Data { for datacenter, stats := range d.Datacenter { diff --git a/prometheus_metrics.go b/prometheus_metrics.go index f9e38be..23531ab 100644 --- a/prometheus_metrics.go +++ b/prometheus_metrics.go @@ -5,6 +5,9 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) +// prometheusMetrics are the concrete Prometheus metrics that get updated with +// data retrieved from the Fastly real-time stats API. The same set of metrics +// are updated for all service IDs, only the labels change. type prometheusMetrics struct { requestsTotal *prometheus.CounterVec tlsTotal *prometheus.CounterVec diff --git a/service_queryer.go b/service_queryer.go index 1b2eda9..784f58c 100644 --- a/service_queryer.go +++ b/service_queryer.go @@ -7,30 +7,51 @@ import ( "github.com/pkg/errors" ) +// serviceQueryer asks the Fastly API to resolve a set of service IDs to their +// names. This is necessary because names can be changed dynamically, and the +// fastly-exporter should reflect the most recent name. type serviceQueryer struct { - token string - ids []string // optional - resolver nameUpdater - manager idUpdater + token string + whitelist map[string]bool // service IDs to use (optional; if not specified, allow all) + resolver nameUpdater + manager idUpdater } +// nameUpdater is a consumer contract for the write side of the name cache. +// Whenever the service queryer gets a new mapping of service IDs to names, +// it will call this method to save that latest mapping. type nameUpdater interface { update(names map[string]string) } +// idUpdater is a consumer contract for the write side of the monitor manager. +// Whenever the service queryer gets a new set of service IDs that should be +// monitored, it will call this method to save those latest IDs. +// +// Note that while this method is called regardless, it only has a meaningful +// effect when the fastly-exporter and service queryer are configured without +// any explicit service IDs, and thus should monitor *all* service IDs available +// to a Fastly token. type idUpdater interface { update(ids []string) } func newServiceQueryer(token string, ids []string, resolver nameUpdater, manager idUpdater) *serviceQueryer { + whitelist := map[string]bool{} + for _, id := range ids { + whitelist[id] = true + } + return &serviceQueryer{ - token: token, - ids: ids, - resolver: resolver, - manager: manager, + token: token, + whitelist: whitelist, + resolver: resolver, + manager: manager, } } +// refresh the service ID to name mapping, updating the name updater (the name +// cache) and the ID updater (the monitor manager). func (q *serviceQueryer) refresh(client httpClient) error { req, err := http.NewRequest("GET", "https://api.fastly.com/service", nil) if err != nil { @@ -43,15 +64,11 @@ func (q *serviceQueryer) refresh(client httpClient) error { if err != nil { return errors.Wrap(err, "error making API services request") } + defer resp.Body.Close() var response serviceResponse if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return errors.Wrap(err, "error decuding API services response") - } - - filter := map[string]bool{} - for _, id := range q.ids { - filter[id] = true + return errors.Wrap(err, "error decoding API services response") } var ( @@ -60,8 +77,8 @@ func (q *serviceQueryer) refresh(client httpClient) error { ) for _, pair := range response { var ( - allowAll = len(filter) == 0 - allowThis = filter[pair.ID] + allowAll = len(q.whitelist) == 0 + allowThis = q.whitelist[pair.ID] ) if allowAll || allowThis { names[pair.ID] = pair.Name