diff --git a/go/vt/dbconnpool/connection_pool.go b/go/vt/dbconnpool/connection_pool.go index 9865efdada3..4814ae3aedf 100644 --- a/go/vt/dbconnpool/connection_pool.go +++ b/go/vt/dbconnpool/connection_pool.go @@ -36,8 +36,16 @@ import ( // PooledDBConnection objects. type ConnectionPool struct { *smartconnpool.ConnPool[*DBConnection] + + name string } +// usedNames is for preventing expvar from panicking. Tests +// create pool objects multiple time. If a name was previously +// used, expvar initialization is skipped. +// through non-test code. +var usedNames = make(map[string]bool) + // NewConnectionPool creates a new ConnectionPool. The name is used // to publish stats only. func NewConnectionPool(name string, stats *servenv.Exporter, capacity int, idleTimeout time.Duration, maxLifetime time.Duration, dnsResolutionFrequency time.Duration) *ConnectionPool { @@ -47,7 +55,18 @@ func NewConnectionPool(name string, stats *servenv.Exporter, capacity int, idleT MaxLifetime: maxLifetime, RefreshInterval: dnsResolutionFrequency, } - return &ConnectionPool{ConnPool: smartconnpool.NewPool(&config)} + cp := &ConnectionPool{ConnPool: smartconnpool.NewPool(&config), name: name} + if name == "" || usedNames[name] { + return cp + } + usedNames[name] = true + + if stats == nil { + // This is unnamed exported so it will use the stats functions directly when adding to the expvar. + stats = servenv.NewExporter("", "") + } + cp.ConnPool.RegisterStats(stats, name) + return cp } // Open must be called before starting to use the pool. diff --git a/go/vt/vttablet/endtoend/config_test.go b/go/vt/vttablet/endtoend/config_test.go index b1dc7f5dcb9..b2c70c94188 100644 --- a/go/vt/vttablet/endtoend/config_test.go +++ b/go/vt/vttablet/endtoend/config_test.go @@ -279,6 +279,29 @@ func TestQueryTimeout(t *testing.T) { compareIntDiff(t, vend, "Kills/Connections", vstart, 1) } +// TestHeartbeatMetric validates the heartbeat metrics exists from the connection pool. +func TestHeartbeatMetric(t *testing.T) { + tcases := []struct { + metricName string + exp any + }{{ + metricName: "HeartbeatWriteAppPoolCapacity", + exp: 2, + }, { + metricName: "HeartbeatWriteAllPrivsPoolCapacity", + exp: 2, + }} + + metrics := framework.DebugVars() + for _, tcase := range tcases { + t.Run(tcase.metricName, func(t *testing.T) { + mValue, exists := metrics[tcase.metricName] + require.True(t, exists, "metric %s not found", tcase.metricName) + require.EqualValues(t, tcase.exp, mValue, "metric %s value is %d, want %d", tcase.metricName, mValue, tcase.exp) + }) + } +} + func changeVar(t *testing.T, name, value string) (revert func()) { t.Helper() diff --git a/go/vt/vttablet/endtoend/framework/server.go b/go/vt/vttablet/endtoend/framework/server.go index 95c8114fd9f..a05348f1d7b 100644 --- a/go/vt/vttablet/endtoend/framework/server.go +++ b/go/vt/vttablet/endtoend/framework/server.go @@ -129,6 +129,9 @@ func StartServer(ctx context.Context, connParams, connAppDebugParams mysql.ConnP config.EnableViews = true config.QueryCacheDoorkeeper = false config.SchemaReloadInterval = 5 * time.Second + config.ReplicationTracker.Mode = tabletenv.Heartbeat + config.ReplicationTracker.HeartbeatOnDemand = 1 * time.Second + config.ReplicationTracker.HeartbeatInterval = 1 * time.Second gotBytes, _ := yaml2.Marshal(config) log.Infof("Config:\n%s", gotBytes) return StartCustomServer(ctx, connParams, connAppDebugParams, dbName, config)