From 281ba5884526395eadd7041bcd49f6f3225b2874 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 12 Apr 2016 15:09:56 +0100 Subject: [PATCH] Add /api/probes endpoint --- app/api_report.go | 18 +++++++++++++++ app/router.go | 2 ++ probe/probe.go | 9 +++++++- probe/probe_internal_test.go | 14 ++++++++++-- prog/probe.go | 2 +- report/probes.go | 43 ++++++++++++++++++++++++++++++++++++ report/report.go | 6 +++++ 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 report/probes.go diff --git a/app/api_report.go b/app/api_report.go index 54ad023523..bf17046351 100644 --- a/app/api_report.go +++ b/app/api_report.go @@ -4,6 +4,8 @@ import ( "net/http" "golang.org/x/net/context" + + "github.com/weaveworks/scope/report" ) // Raw report handler @@ -17,3 +19,19 @@ func makeRawReportHandler(rep Reporter) CtxHandlerFunc { respondWith(w, http.StatusOK, report) } } + +// Probe handler +func makeProbeHandler(rep Reporter) CtxHandlerFunc { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + rpt, err := rep.Report(ctx) + if err != nil { + respondWith(w, http.StatusInternalServerError, err.Error()) + return + } + result := []report.Probe{} + for _, p := range rpt.Probes { + result = append(result, p) + } + respondWith(w, http.StatusOK, result) + } +} diff --git a/app/router.go b/app/router.go index 0f9f197804..318d7e00c7 100644 --- a/app/router.go +++ b/app/router.go @@ -95,6 +95,8 @@ func RegisterTopologyRoutes(router *mux.Router, r Reporter) { gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters(r, handleNode)))) get.HandleFunc("/api/report", gzipHandler(requestContextDecorator(makeRawReportHandler(r)))) + get.HandleFunc("/api/probes", + gzipHandler(requestContextDecorator(makeProbeHandler(r)))) } // RegisterReportPostHandler registers the handler for report submission diff --git a/probe/probe.go b/probe/probe.go index b8321c6dc1..b66ac32386 100644 --- a/probe/probe.go +++ b/probe/probe.go @@ -7,6 +7,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/armon/go-metrics" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/probe/appclient" "github.com/weaveworks/scope/report" ) @@ -17,6 +18,7 @@ const ( // Probe sits there, generating and publishing reports. type Probe struct { + id string spyInterval, publishInterval time.Duration publisher *appclient.ReportPublisher @@ -52,8 +54,9 @@ type Ticker interface { } // New makes a new Probe. -func New(spyInterval, publishInterval time.Duration, publisher appclient.Publisher) *Probe { +func New(id string, spyInterval, publishInterval time.Duration, publisher appclient.Publisher) *Probe { result := &Probe{ + id: id, spyInterval: spyInterval, publishInterval: publishInterval, publisher: appclient.NewReportPublisher(publisher), @@ -150,6 +153,10 @@ func (p *Probe) report() report.Report { for i := 0; i < cap(reports); i++ { result = result.Merge(<-reports) } + result.Probes[p.id] = report.Probe{ + ID: p.id, + LastSeen: mtime.Now(), + } return result } diff --git a/probe/probe_internal_test.go b/probe/probe_internal_test.go index 8b0451fada..d19a1bdb5b 100644 --- a/probe/probe_internal_test.go +++ b/probe/probe_internal_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ugorji/go/codec" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/test/reflect" @@ -18,7 +19,7 @@ func TestApply(t *testing.T) { endpointNode = report.MakeNodeWith(map[string]string{"5": "6"}) ) - p := New(0, 0, nil) + p := New("", 0, 0, nil) p.AddTagger(NewTopologyTagger()) r := report.MakeReport() @@ -71,11 +72,20 @@ func TestProbe(t *testing.T) { // marshalling->unmarshaling is not idempotent due to `json:"omitempty"` // tags, transforming empty slices into nils. So, we make DeepEqual // happy by setting empty `json:"omitempty"` entries to nil + const probeID = "probeid" + now := time.Now() + mtime.NowForce(now) + defer mtime.NowReset() want := report.MakeReport() node := report.MakeNodeWith(map[string]string{"b": "c"}) node.Metrics = nil // omitempty want.Endpoint.AddNode("a", node) + want.Probes[probeID] = report.Probe{ + ID: probeID, + LastSeen: now, + } + pub := mockPublisher{make(chan report.Report)} // omitempty @@ -88,7 +98,7 @@ func TestProbe(t *testing.T) { want.Host.Controls = nil want.Overlay.Controls = nil - p := New(10*time.Millisecond, 100*time.Millisecond, pub) + p := New(probeID, 10*time.Millisecond, 100*time.Millisecond, pub) p.AddReporter(mockReporter{want}) p.Start() defer p.Stop() diff --git a/prog/probe.go b/prog/probe.go index 7ae166747f..459f36de46 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -138,7 +138,7 @@ func probeMain() { endpointReporter := endpoint.NewReporter(hostID, hostName, *spyProcs, *useConntrack, scanner) defer endpointReporter.Stop() - p := probe.New(*spyInterval, *publishInterval, clients) + p := probe.New(probeID, *spyInterval, *publishInterval, clients) p.AddTicker(processCache) hostReporter := host.NewReporter(hostID, hostName, probeID, clients) defer hostReporter.Stop() diff --git a/report/probes.go b/report/probes.go new file mode 100644 index 0000000000..3d320bf639 --- /dev/null +++ b/report/probes.go @@ -0,0 +1,43 @@ +package report + +import ( + "time" +) + +// Probes contains details of the probe(s) which generated a report. +type Probes map[string]Probe + +// Copy produces a copy of the Probes +func (ps Probes) Copy() Probes { + result := Probes{} + for id, probe := range ps { + result[id] = probe.Copy() + } + return result +} + +// Merge two sets of Probes, keeping the records with the latest LastSeen +func (ps Probes) Merge(other Probes) Probes { + result := ps.Copy() + for id, probe := range other { + o, ok := result[id] + if !ok || o.LastSeen.Before(probe.LastSeen) { + result[id] = probe + } + } + return result +} + +// Probe is the details for a single probe that generated a report. +type Probe struct { + ID string `json:"id"` + LastSeen time.Time `json:"lastSeen"` +} + +// Copy produces a copy of the Probe +func (p Probe) Copy() Probe { + return Probe{ + ID: p.ID, + LastSeen: p.LastSeen, + } +} diff --git a/report/report.go b/report/report.go index 7be47d92a2..74cecad565 100644 --- a/report/report.go +++ b/report/report.go @@ -81,6 +81,9 @@ type Report struct { // must be equal, but we don't require that equal reports have // the same id. ID string `deepequal:"skip"` + + // Probes is the details of the probes who reported this report + Probes Probes } // MakeReport makes a clean report, ready to Merge() other reports into. @@ -97,6 +100,7 @@ func MakeReport() Report { Sampling: Sampling{}, Window: 0, ID: fmt.Sprintf("%d", rand.Int63()), + Probes: Probes{}, } } @@ -114,6 +118,7 @@ func (r Report) Copy() Report { Sampling: r.Sampling, Window: r.Window, ID: fmt.Sprintf("%d", rand.Int63()), + Probes: r.Probes.Copy(), } } @@ -130,6 +135,7 @@ func (r Report) Merge(other Report) Report { cp.Service = r.Service.Merge(other.Service) cp.Overlay = r.Overlay.Merge(other.Overlay) cp.Sampling = r.Sampling.Merge(other.Sampling) + cp.Probes = r.Probes.Merge(other.Probes) cp.Window += other.Window return cp }