prober web page improvements

Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann 2024-11-18 10:21:36 -06:00
parent 888e3cb097
commit 8d25ad0c0a
No known key found for this signature in database
GPG Key ID: 29D8CDEB4C13D48B
5 changed files with 86 additions and 34 deletions

View File

@ -106,7 +106,7 @@ func getOverallStatus(p *prober.Prober) (o overallStatus) {
// Do not show probes that have not finished yet.
continue
}
if i.Result {
if i.Status == prober.ProbeStatusSucceeded {
o.addGoodf("%s: %s", p, i.Latency)
} else {
o.addBadf("%s: %s", p, i.Error)

View File

@ -345,6 +345,11 @@ func (p *Probe) run() (pi ProbeInfo, err error) {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
} else {
// For continuous probes, reset last error
p.mu.Lock()
p.lastErr = nil
p.mu.Unlock()
}
err = p.probeClass.Probe(ctx)
@ -385,6 +390,16 @@ func (p *Probe) recordEnd(err error) {
p.successHist = p.successHist.Next()
}
// ProbeStatus indicates the status of a probe.
type ProbeStatus string
const (
ProbeStatusUnknown = "unknown"
ProbeStatusRunning = "running"
ProbeStatusFailed = "failed"
ProbeStatusSucceeded = "succeeded"
)
// ProbeInfo is a snapshot of the configuration and state of a Probe.
type ProbeInfo struct {
Name string
@ -394,7 +409,7 @@ type ProbeInfo struct {
Start time.Time
End time.Time
Latency time.Duration
Result bool
Status ProbeStatus
Error string
RecentResults []bool
RecentLatencies []time.Duration
@ -422,6 +437,10 @@ func (pb ProbeInfo) RecentMedianLatency() time.Duration {
return pb.RecentLatencies[len(pb.RecentLatencies)/2]
}
func (pb ProbeInfo) Continuous() bool {
return pb.Interval < 0
}
// ProbeInfo returns the state of all probes.
func (p *Prober) ProbeInfo() map[string]ProbeInfo {
out := map[string]ProbeInfo{}
@ -449,9 +468,14 @@ func (probe *Probe) probeInfoLocked() ProbeInfo {
Labels: probe.metricLabels,
Start: probe.start,
End: probe.end,
Result: probe.succeeded,
}
if probe.lastErr != nil {
inf.Status = ProbeStatusUnknown
if probe.end.Before(probe.start) {
inf.Status = ProbeStatusRunning
} else if probe.succeeded {
inf.Status = ProbeStatusSucceeded
} else if probe.lastErr != nil {
inf.Status = ProbeStatusFailed
inf.Error = probe.lastErr.Error()
}
if probe.latency > 0 {

View File

@ -316,7 +316,7 @@ func TestProberProbeInfo(t *testing.T) {
Interval: probeInterval,
Labels: map[string]string{"class": "", "name": "probe1"},
Latency: 500 * time.Millisecond,
Result: true,
Status: ProbeStatusSucceeded,
RecentResults: []bool{true},
RecentLatencies: []time.Duration{500 * time.Millisecond},
},
@ -324,6 +324,7 @@ func TestProberProbeInfo(t *testing.T) {
Name: "probe2",
Interval: probeInterval,
Labels: map[string]string{"class": "", "name": "probe2"},
Status: ProbeStatusFailed,
Error: "error2",
RecentResults: []bool{false},
RecentLatencies: nil, // no latency for failed probes
@ -349,7 +350,7 @@ type probeResult struct {
}{
{
name: "no_runs",
wantProbeInfo: ProbeInfo{},
wantProbeInfo: ProbeInfo{Status: ProbeStatusUnknown},
wantRecentSuccessRatio: 0,
wantRecentMedianLatency: 0,
},
@ -358,7 +359,7 @@ type probeResult struct {
results: []probeResult{{latency: 100 * time.Millisecond, err: nil}},
wantProbeInfo: ProbeInfo{
Latency: 100 * time.Millisecond,
Result: true,
Status: ProbeStatusSucceeded,
RecentResults: []bool{true},
RecentLatencies: []time.Duration{100 * time.Millisecond},
},
@ -369,7 +370,7 @@ type probeResult struct {
name: "single_failure",
results: []probeResult{{latency: 100 * time.Millisecond, err: errors.New("error123")}},
wantProbeInfo: ProbeInfo{
Result: false,
Status: ProbeStatusFailed,
RecentResults: []bool{false},
RecentLatencies: nil,
Error: "error123",
@ -390,7 +391,7 @@ type probeResult struct {
{latency: 80 * time.Millisecond, err: nil},
},
wantProbeInfo: ProbeInfo{
Result: true,
Status: ProbeStatusSucceeded,
Latency: 80 * time.Millisecond,
RecentResults: []bool{false, true, true, false, true, true, false, true},
RecentLatencies: []time.Duration{
@ -420,7 +421,7 @@ type probeResult struct {
{latency: 110 * time.Millisecond, err: nil},
},
wantProbeInfo: ProbeInfo{
Result: true,
Status: ProbeStatusSucceeded,
Latency: 110 * time.Millisecond,
RecentResults: []bool{true, true, true, true, true, true, true, true, true, true},
RecentLatencies: []time.Duration{
@ -483,7 +484,7 @@ func TestProberRunHandler(t *testing.T) {
ProbeInfo: ProbeInfo{
Name: "success",
Interval: probeInterval,
Result: true,
Status: ProbeStatusSucceeded,
RecentResults: []bool{true, true},
},
PreviousSuccessRatio: 1,
@ -498,7 +499,7 @@ func TestProberRunHandler(t *testing.T) {
ProbeInfo: ProbeInfo{
Name: "failure",
Interval: probeInterval,
Result: false,
Status: ProbeStatusFailed,
Error: "error123",
RecentResults: []bool{false, false},
},

View File

@ -62,8 +62,9 @@ func (p *Prober) StatusHandler(opts ...statusHandlerOpt) tsweb.ReturnHandlerFunc
return func(w http.ResponseWriter, r *http.Request) error {
type probeStatus struct {
ProbeInfo
TimeSinceLast time.Duration
Links map[string]template.URL
TimeSinceLastStart time.Duration
TimeSinceLastEnd time.Duration
Links map[string]template.URL
}
vars := struct {
Title string
@ -81,12 +82,15 @@ type probeStatus struct {
for name, info := range p.ProbeInfo() {
vars.TotalProbes++
if !info.Result {
if info.Error != "" {
vars.UnhealthyProbes++
}
s := probeStatus{ProbeInfo: info}
if !info.Start.IsZero() {
s.TimeSinceLastStart = time.Since(info.Start).Truncate(time.Second)
}
if !info.End.IsZero() {
s.TimeSinceLast = time.Since(info.End).Truncate(time.Second)
s.TimeSinceLastEnd = time.Since(info.End).Truncate(time.Second)
}
for textTpl, urlTpl := range params.probeLinks {
text, err := renderTemplate(textTpl, info)

View File

@ -73,8 +73,9 @@
<th>Name</th>
<th>Probe Class & Labels</th>
<th>Interval</th>
<th>Last Attempt</th>
<th>Success</th>
<th>Last Finished</th>
<th>Last Started</th>
<th>Status</th>
<th>Latency</th>
<th>Last Error</th>
</tr></thead>
@ -85,9 +86,11 @@
{{$name}}
{{range $text, $url := $probeInfo.Links}}
<br/>
<button onclick="location.href='{{$url}}';" type="button">
{{$text}}
</button>
{{if not $probeInfo.Continuous}}
<button onclick="location.href='{{$url}}';" type="button">
{{$text}}
</button>
{{end}}
{{end}}
</td>
<td>{{$probeInfo.Class}}<br/>
@ -97,28 +100,48 @@
{{end}}
</div>
</td>
<td>{{$probeInfo.Interval}}</td>
<td data-sort="{{$probeInfo.TimeSinceLast.Milliseconds}}">
{{if $probeInfo.TimeSinceLast}}
{{$probeInfo.TimeSinceLast.String}} ago<br/>
<td>
{{if $probeInfo.Continuous}}
Continuous
{{else}}
{{$probeInfo.Interval}}
{{end}}
</td>
<td data-sort="{{$probeInfo.TimeSinceLastEnd.Milliseconds}}">
{{if $probeInfo.TimeSinceLastEnd}}
{{$probeInfo.TimeSinceLastEnd.String}} ago<br/>
<span class="small">{{$probeInfo.End.Format "2006-01-02T15:04:05Z07:00"}}</span>
{{else}}
Never
{{end}}
</td>
<td>
{{if $probeInfo.Result}}
{{$probeInfo.Result}}
<td data-sort="{{$probeInfo.TimeSinceLastStart.Milliseconds}}">
{{if $probeInfo.TimeSinceLastStart}}
{{$probeInfo.TimeSinceLastStart.String}} ago<br/>
<span class="small">{{$probeInfo.Start.Format "2006-01-02T15:04:05Z07:00"}}</span>
{{else}}
<span class="error">{{$probeInfo.Result}}</span>
Never
{{end}}
</td>
<td>
{{if $probeInfo.Error}}
<span class="error">{{$probeInfo.Status}}</span>
{{else}}
{{$probeInfo.Status}}
{{end}}<br/>
<div class="small">Recent: {{$probeInfo.RecentResults}}</div>
<div class="small">Mean: {{$probeInfo.RecentSuccessRatio}}</div>
{{if not $probeInfo.Continuous}}
<div class="small">Recent: {{$probeInfo.RecentResults}}</div>
<div class="small">Mean: {{$probeInfo.RecentSuccessRatio}}</div>
{{end}}
</td>
<td data-sort="{{$probeInfo.Latency.Milliseconds}}">
{{$probeInfo.Latency.String}}
<div class="small">Recent: {{$probeInfo.RecentLatencies}}</div>
<div class="small">Median: {{$probeInfo.RecentMedianLatency}}</div>
{{if $probeInfo.Continuous}}
n/a
{{else}}
{{$probeInfo.Latency.String}}
<div class="small">Recent: {{$probeInfo.RecentLatencies}}</div>
<div class="small">Median: {{$probeInfo.RecentMedianLatency}}</div>
{{end}}
</td>
<td class="small">{{$probeInfo.Error}}</td>
</tr>