From 18819254e669e2f54a6576f6da1a8ba63c95176f Mon Sep 17 00:00:00 2001 From: Florent Poinsard <35779988+frouioui@users.noreply.github.com> Date: Wed, 15 Nov 2023 20:40:22 -0600 Subject: [PATCH] Add HealthCheck's `healthy` map to the VTGate UI (#14521) --- go/cmd/vtcombo/cli/status.go | 5 +- go/cmd/vtgate/cli/status.go | 5 +- go/vt/discovery/fake_healthcheck.go | 11 +++ go/vt/discovery/healthcheck.go | 67 +++++++++++++++---- go/vt/discovery/healthcheck_test.go | 4 +- go/vt/vtgate/tabletgateway.go | 5 ++ .../txthrottler/mock_healthcheck_test.go | 8 +++ 7 files changed, 87 insertions(+), 18 deletions(-) diff --git a/go/cmd/vtcombo/cli/status.go b/go/cmd/vtcombo/cli/status.go index 8069fc72606..80176d4a11a 100644 --- a/go/cmd/vtcombo/cli/status.go +++ b/go/cmd/vtcombo/cli/status.go @@ -41,7 +41,10 @@ func addStatusParts(vtg *vtgate.VTGate) { servenv.AddStatusPart("Gateway Status", vtgate.StatusTemplate, func() any { return vtg.GetGatewayCacheStatus() }) - servenv.AddStatusPart("Health Check Cache", discovery.HealthCheckTemplate, func() any { + servenv.AddStatusPart("Health Check - Cache", discovery.HealthCheckCacheTemplate, func() any { return vtg.Gateway().TabletsCacheStatus() }) + servenv.AddStatusPart("Health Check - Healthy Tablets", discovery.HealthCheckHealthyTemplate, func() any { + return vtg.Gateway().TabletsHealthyStatus() + }) } diff --git a/go/cmd/vtgate/cli/status.go b/go/cmd/vtgate/cli/status.go index 2fdab073d5a..9652392dc65 100644 --- a/go/cmd/vtgate/cli/status.go +++ b/go/cmd/vtgate/cli/status.go @@ -37,7 +37,10 @@ func addStatusParts(vtg *vtgate.VTGate) { servenv.AddStatusPart("Gateway Status", vtgate.StatusTemplate, func() any { return vtg.GetGatewayCacheStatus() }) - servenv.AddStatusPart("Health Check Cache", discovery.HealthCheckTemplate, func() any { + servenv.AddStatusPart("Health Check - Cache", discovery.HealthCheckCacheTemplate, func() any { return vtg.Gateway().TabletsCacheStatus() }) + servenv.AddStatusPart("Health Check - Healthy Tablets", discovery.HealthCheckHealthyTemplate, func() any { + return vtg.Gateway().TabletsHealthyStatus() + }) } diff --git a/go/vt/discovery/fake_healthcheck.go b/go/vt/discovery/fake_healthcheck.go index cb959902c19..1c83de5b149 100644 --- a/go/vt/discovery/fake_healthcheck.go +++ b/go/vt/discovery/fake_healthcheck.go @@ -252,6 +252,17 @@ func (fhc *FakeHealthCheck) CacheStatus() TabletsCacheStatusList { return tcsl } +// HealthyStatus returns the status for each healthy tablet +func (fhc *FakeHealthCheck) HealthyStatus() TabletsCacheStatusList { + tcsMap := fhc.CacheStatusMap() + tcsl := make(TabletsCacheStatusList, 0, len(tcsMap)) + for _, tcs := range tcsMap { + tcsl = append(tcsl, tcs) + } + sort.Sort(tcsl) + return tcsl +} + // CacheStatusMap returns a map of the health check cache. func (fhc *FakeHealthCheck) CacheStatusMap() map[string]*TabletsCacheStatus { tcsMap := make(map[string]*TabletsCacheStatus) diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index 9d17005d0ad..20e16875748 100644 --- a/go/vt/discovery/healthcheck.go +++ b/go/vt/discovery/healthcheck.go @@ -92,6 +92,14 @@ var ( // How much to sleep between each check. waitAvailableTabletInterval = 100 * time.Millisecond + + // HealthCheckCacheTemplate uses healthCheckTemplate with the `HealthCheck Tablet - Cache` title to create the + // HTML code required to render the cache of the HealthCheck. + HealthCheckCacheTemplate = fmt.Sprintf(healthCheckTemplate, "HealthCheck - Cache") + + // HealthCheckHealthyTemplate uses healthCheckTemplate with the `HealthCheck Tablet - Healthy Tablets` title to + // create the HTML code required to render the list of healthy tablets from the HealthCheck. + HealthCheckHealthyTemplate = fmt.Sprintf(healthCheckTemplate, "HealthCheck - Healthy Tablets") ) // See the documentation for NewHealthCheck below for an explanation of these parameters. @@ -104,8 +112,9 @@ const ( // DefaultTopologyWatcherRefreshInterval is used as the default value for // the refresh interval of a topology watcher. DefaultTopologyWatcherRefreshInterval = 1 * time.Minute - // HealthCheckTemplate is the HTML code to display a TabletsCacheStatusList - HealthCheckTemplate = ` + // healthCheckTemplate is the HTML code to display a TabletsCacheStatusList, it takes a parameter for the title + // as the template can be used for both HealthCheck's cache and healthy tablets list. + healthCheckTemplate = `
HealthCheck Tablet Cache | +%s | ||||||||
---|---|---|---|---|---|---|---|---|---|
Cell | @@ -193,6 +202,9 @@ type HealthCheck interface { // CacheStatus returns a displayable version of the health check cache. CacheStatus() TabletsCacheStatusList + // HealthyStatus returns a displayable version of the health check healthy list. + HealthyStatus() TabletsCacheStatusList + // CacheStatusMap returns a map of the health check cache. CacheStatusMap() map[string]*TabletsCacheStatus @@ -622,28 +634,55 @@ func (hc *HealthCheckImpl) CacheStatus() TabletsCacheStatusList { return tcsl } +// HealthyStatus returns a displayable version of the cache. +func (hc *HealthCheckImpl) HealthyStatus() TabletsCacheStatusList { + tcsMap := hc.HealthyStatusMap() + tcsl := make(TabletsCacheStatusList, 0, len(tcsMap)) + for _, tcs := range tcsMap { + tcsl = append(tcsl, tcs) + } + sort.Sort(tcsl) + return tcsl +} + func (hc *HealthCheckImpl) CacheStatusMap() map[string]*TabletsCacheStatus { tcsMap := make(map[string]*TabletsCacheStatus) hc.mu.Lock() defer hc.mu.Unlock() for _, ths := range hc.healthData { for _, th := range ths { - key := fmt.Sprintf("%v.%v.%v.%v", th.Tablet.Alias.Cell, th.Target.Keyspace, th.Target.Shard, th.Target.TabletType.String()) - var tcs *TabletsCacheStatus - var ok bool - if tcs, ok = tcsMap[key]; !ok { - tcs = &TabletsCacheStatus{ - Cell: th.Tablet.Alias.Cell, - Target: th.Target, - } - tcsMap[key] = tcs - } - tcs.TabletsStats = append(tcs.TabletsStats, th) + tabletHealthToTabletCacheStatus(th, tcsMap) } } return tcsMap } +func (hc *HealthCheckImpl) HealthyStatusMap() map[string]*TabletsCacheStatus { + tcsMap := make(map[string]*TabletsCacheStatus) + hc.mu.Lock() + defer hc.mu.Unlock() + for _, ths := range hc.healthy { + for _, th := range ths { + tabletHealthToTabletCacheStatus(th, tcsMap) + } + } + return tcsMap +} + +func tabletHealthToTabletCacheStatus(th *TabletHealth, tcsMap map[string]*TabletsCacheStatus) { + key := fmt.Sprintf("%v.%v.%v.%v", th.Tablet.Alias.Cell, th.Target.Keyspace, th.Target.Shard, th.Target.TabletType.String()) + var tcs *TabletsCacheStatus + var ok bool + if tcs, ok = tcsMap[key]; !ok { + tcs = &TabletsCacheStatus{ + Cell: th.Tablet.Alias.Cell, + Target: th.Target, + } + tcsMap[key] = tcs + } + tcs.TabletsStats = append(tcs.TabletsStats, th) +} + // Close stops the healthcheck. func (hc *HealthCheckImpl) Close() error { hc.mu.Lock() diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index 5fadc57eb2e..9563d9bfdc5 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -1267,7 +1267,7 @@ func TestTemplate(t *testing.T) { TabletsStats: ts, } templ := template.New("") - templ, err := templ.Parse(HealthCheckTemplate) + templ, err := templ.Parse(healthCheckTemplate) require.Nil(t, err, "error parsing template: %v", err) wr := &bytes.Buffer{} err = templ.Execute(wr, []*TabletsCacheStatus{tcs}) @@ -1295,7 +1295,7 @@ func TestDebugURLFormatting(t *testing.T) { TabletsStats: ts, } templ := template.New("") - templ, err := templ.Parse(HealthCheckTemplate) + templ, err := templ.Parse(healthCheckTemplate) require.Nil(t, err, "error parsing template") wr := &bytes.Buffer{} err = templ.Execute(wr, []*TabletsCacheStatus{tcs}) diff --git a/go/vt/vtgate/tabletgateway.go b/go/vt/vtgate/tabletgateway.go index de63da87907..9d62be2d357 100644 --- a/go/vt/vtgate/tabletgateway.go +++ b/go/vt/vtgate/tabletgateway.go @@ -428,6 +428,11 @@ func (gw *TabletGateway) TabletsCacheStatus() discovery.TabletsCacheStatusList { return gw.hc.CacheStatus() } +// TabletsHealthyStatus returns a displayable version of the health check healthy list. +func (gw *TabletGateway) TabletsHealthyStatus() discovery.TabletsCacheStatusList { + return gw.hc.HealthyStatus() +} + func (gw *TabletGateway) updateDefaultConnCollation(tablet *topodatapb.Tablet) { if atomic.CompareAndSwapUint32(&gw.defaultConnCollation, 0, tablet.DefaultConnCollation) { return diff --git a/go/vt/vttablet/tabletserver/txthrottler/mock_healthcheck_test.go b/go/vt/vttablet/tabletserver/txthrottler/mock_healthcheck_test.go index 1e503dc7020..3b298cacddf 100644 --- a/go/vt/vttablet/tabletserver/txthrottler/mock_healthcheck_test.go +++ b/go/vt/vttablet/tabletserver/txthrottler/mock_healthcheck_test.go @@ -59,6 +59,14 @@ func (m *MockHealthCheck) CacheStatus() discovery.TabletsCacheStatusList { return ret0 } +// HealthyStatus mocks base method. +func (m *MockHealthCheck) HealthyStatus() discovery.TabletsCacheStatusList { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthyStatus") + ret0, _ := ret[0].(discovery.TabletsCacheStatusList) + return ret0 +} + // CacheStatus indicates an expected call of CacheStatus. func (mr *MockHealthCheckMockRecorder) CacheStatus() *gomock.Call { mr.mock.ctrl.T.Helper()