Skip to content

Commit

Permalink
Merge pull request #6569 from timvaillancourt/add-tablet-stats-to-vtc…
Browse files Browse the repository at this point in the history
…tld-api-keyspace

Add realtime health stats to vtctld /api/keyspace/ks/tablets API
  • Loading branch information
deepthi authored Aug 17, 2020
2 parents cac2ca7 + 1c65304 commit 9696e74
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 38 deletions.
93 changes: 68 additions & 25 deletions go/vt/vtctld/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (

"vitess.io/vitess/go/vt/mysqlctl"
logutilpb "vitess.io/vitess/go/vt/proto/logutil"
querypb "vitess.io/vitess/go/vt/proto/query"
topodatapb "vitess.io/vitess/go/vt/proto/topodata"
"vitess.io/vitess/go/vt/proto/vttime"
)
Expand All @@ -60,8 +61,16 @@ const (
jsonContentType = "application/json; charset=utf-8"
)

// TabletWithURL wraps topo.Tablet and adds a URL property.
type TabletWithURL struct {
// TabletStats represents realtime stats from a discovery.LegacyTabletStats struct.
type TabletStats struct {
LastError string `json:"last_error,omitempty"`
Realtime *querypb.RealtimeStats `json:"realtime,omitempty"`
Serving bool `json:"serving"`
Up bool `json:"up"`
}

// TabletWithStatsAndURL wraps topo.Tablet, adding a URL property and optional realtime stats.
type TabletWithStatsAndURL struct {
Alias *topodatapb.TabletAlias `json:"alias,omitempty"`
Hostname string `json:"hostname,omitempty"`
PortMap map[string]int32 `json:"port_map,omitempty"`
Expand All @@ -74,9 +83,48 @@ type TabletWithURL struct {
MysqlHostname string `json:"mysql_hostname,omitempty"`
MysqlPort int32 `json:"mysql_port,omitempty"`
MasterTermStartTime *vttime.Time `json:"master_term_start_time,omitempty"`
Stats *TabletStats `json:"stats,omitempty"`
URL string `json:"url,omitempty"`
}

func newTabletWithStatsAndURL(t *topodatapb.Tablet, realtimeStats *realtimeStats) *TabletWithStatsAndURL {
tablet := &TabletWithStatsAndURL{
Alias: t.Alias,
Hostname: t.Hostname,
PortMap: t.PortMap,
Keyspace: t.Keyspace,
Shard: t.Shard,
KeyRange: t.KeyRange,
Type: t.Type,
DbNameOverride: t.DbNameOverride,
Tags: t.Tags,
MysqlHostname: t.MysqlHostname,
MysqlPort: t.MysqlPort,
MasterTermStartTime: t.MasterTermStartTime,
}

if *proxyTablets {
tablet.URL = fmt.Sprintf("/vttablet/%s-%d/debug/status", t.Alias.Cell, t.Alias.Uid)
} else {
tablet.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"])
}

if realtimeStats != nil {
if stats, err := realtimeStats.tabletStats(tablet.Alias); err == nil {
tablet.Stats = &TabletStats{
Realtime: stats.Stats,
Serving: stats.Serving,
Up: stats.Up,
}
if stats.LastError != nil {
tablet.Stats.LastError = stats.LastError.Error()
}
}
}

return tablet
}

func httpErrorf(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
errMsg := fmt.Sprintf(format, args...)
log.Errorf("HTTP error on %v: %v, request: %#v", r.URL.Path, errMsg, r)
Expand Down Expand Up @@ -218,10 +266,23 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
return nil, err
}
}
tablets := [](*topodatapb.Tablet){}

if err := r.ParseForm(); err != nil {
return nil, err
}
cell := r.FormValue("cell")
cells := r.FormValue("cells")
filterCells := []string{} // empty == all cells
if cell != "" {
filterCells = []string{cell} // single cell
} else if cells != "" {
filterCells = strings.Split(cells, ",") // list of cells
}

tablets := [](*TabletWithStatsAndURL){}
for _, shard := range shardNames {
// Get tablets for this shard.
tabletAliases, err := ts.FindAllTabletAliasesInShard(ctx, keyspace, shard)
tabletAliases, err := ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, filterCells)
if err != nil && !topo.IsErrType(err, topo.PartialResult) {
return nil, err
}
Expand All @@ -230,7 +291,8 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
if err != nil {
return nil, err
}
tablets = append(tablets, t.Tablet)
tablet := newTabletWithStatsAndURL(t.Tablet, realtimeStats)
tablets = append(tablets, tablet)
}
}
return tablets, nil
Expand Down Expand Up @@ -402,26 +464,7 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
return nil, err
}

tab := &TabletWithURL{
Alias: t.Alias,
Hostname: t.Hostname,
PortMap: t.PortMap,
Keyspace: t.Keyspace,
Shard: t.Shard,
KeyRange: t.KeyRange,
Type: t.Type,
DbNameOverride: t.DbNameOverride,
Tags: t.Tags,
MysqlHostname: t.MysqlHostname,
MysqlPort: t.MysqlPort,
MasterTermStartTime: t.MasterTermStartTime,
}
if *proxyTablets {
tab.URL = fmt.Sprintf("/vttablet/%s-%d/debug/status", t.Alias.Cell, t.Alias.Uid)
} else {
tab.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"])
}
return tab, nil
return newTabletWithStatsAndURL(t.Tablet, nil), nil
})

// Healthcheck real time status per (cell, keyspace, tablet type, metric).
Expand Down
142 changes: 129 additions & 13 deletions go/vt/vtctld/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,28 @@ func TestAPI(t *testing.T) {
ts.SaveVSchema(ctx, "ks1", vs)

tablet1 := topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{Cell: "cell1", Uid: 100},
Keyspace: "ks1",
Shard: "-80",
Type: topodatapb.TabletType_REPLICA,
KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
PortMap: map[string]int32{"vt": 100},
Alias: &topodatapb.TabletAlias{Cell: "cell1", Uid: 100},
Keyspace: "ks1",
Shard: "-80",
Type: topodatapb.TabletType_REPLICA,
KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
PortMap: map[string]int32{"vt": 100},
Hostname: "mysql1-cell1.test.net",
MysqlHostname: "mysql1-cell1.test.net",
MysqlPort: int32(3306),
}
ts.CreateTablet(ctx, &tablet1)

tablet2 := topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{Cell: "cell2", Uid: 200},
Keyspace: "ks1",
Shard: "-80",
Type: topodatapb.TabletType_REPLICA,
KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
PortMap: map[string]int32{"vt": 200},
Alias: &topodatapb.TabletAlias{Cell: "cell2", Uid: 200},
Keyspace: "ks1",
Shard: "-80",
Type: topodatapb.TabletType_REPLICA,
KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
PortMap: map[string]int32{"vt": 200},
Hostname: "mysql2-cell2.test.net",
MysqlHostname: "mysql2-cell2.test.net",
MysqlPort: int32(3306),
}
ts.CreateTablet(ctx, &tablet2)

Expand Down Expand Up @@ -126,6 +132,55 @@ func TestAPI(t *testing.T) {
realtimeStats.StatsUpdate(ts5)
realtimeStats.StatsUpdate(ts6)

// all-tablets response for keyspace/ks1/tablets/ endpoints
keyspaceKs1AllTablets := `[
{
"alias": {
"cell": "cell1",
"uid": 100
},
"hostname": "mysql1-cell1.test.net",
"port_map": {
"vt": 100
},
"keyspace": "ks1",
"shard": "-80",
"key_range": {
"end": "gA=="
},
"type": 2,
"mysql_hostname": "mysql1-cell1.test.net",
"mysql_port": 3306,
"stats": {
"realtime": {
"seconds_behind_master": 100
},
"serving": true,
"up": true
},
"url": "http://mysql1-cell1.test.net:100"
},
{
"alias": {
"cell": "cell2",
"uid": 200
},
"hostname": "mysql2-cell2.test.net",
"port_map": {
"vt": 200
},
"keyspace": "ks1",
"shard": "-80",
"key_range": {
"end": "gA=="
},
"type": 2,
"mysql_hostname": "mysql2-cell2.test.net",
"mysql_port": 3306,
"url": "http://mysql2-cell2.test.net:200"
}
]`

// Test cases.
table := []struct {
method, path, body, want string
Expand All @@ -139,6 +194,64 @@ func TestAPI(t *testing.T) {
// Cells
{"GET", "cells", "", `["cell1","cell2"]`},

// Keyspace
{"GET", "keyspace/doesnt-exist/tablets/", "", ``},
{"GET", "keyspace/ks1/tablets/", "", keyspaceKs1AllTablets},
{"GET", "keyspace/ks1/tablets/-80", "", keyspaceKs1AllTablets},
{"GET", "keyspace/ks1/tablets/80-", "", `[]`},
{"GET", "keyspace/ks1/tablets/?cells=cell1,cell2", "", keyspaceKs1AllTablets},
{"GET", "keyspace/ks1/tablets/?cells=cell1", "", `[
{
"alias": {
"cell": "cell1",
"uid": 100
},
"hostname": "mysql1-cell1.test.net",
"port_map": {
"vt": 100
},
"keyspace": "ks1",
"shard": "-80",
"key_range": {
"end": "gA=="
},
"type": 2,
"mysql_hostname": "mysql1-cell1.test.net",
"mysql_port": 3306,
"stats": {
"realtime": {
"seconds_behind_master": 100
},
"serving": true,
"up": true
},
"url": "http://mysql1-cell1.test.net:100"
}
]`},
{"GET", "keyspace/ks1/tablets/?cells=cell3", "", `[]`},
{"GET", "keyspace/ks1/tablets/?cell=cell2", "", `[
{
"alias": {
"cell": "cell2",
"uid": 200
},
"hostname": "mysql2-cell2.test.net",
"port_map": {
"vt": 200
},
"keyspace": "ks1",
"shard": "-80",
"key_range": {
"end": "gA=="
},
"type": 2,
"mysql_hostname": "mysql2-cell2.test.net",
"mysql_port": 3306,
"url": "http://mysql2-cell2.test.net:200"
}
]`},
{"GET", "keyspace/ks1/tablets/?cell=cell3", "", `[]`},

// Keyspaces
{"GET", "keyspaces", "", `["ks1", "ks3"]`},
{"GET", "keyspaces/ks1", "", `{
Expand Down Expand Up @@ -193,14 +306,17 @@ func TestAPI(t *testing.T) {
{"GET", "tablets/?shard=ks1%2F80-&cell=cell1", "", `[]`},
{"GET", "tablets/cell1-100", "", `{
"alias": {"cell": "cell1", "uid": 100},
"hostname": "mysql1-cell1.test.net",
"port_map": {"vt": 100},
"keyspace": "ks1",
"shard": "-80",
"key_range": {
"end": "gA=="
},
"type": 2,
"url":"http://:100"
"mysql_hostname": "mysql1-cell1.test.net",
"mysql_port": 3306,
"url":"http://mysql1-cell1.test.net:100"
}`},
{"GET", "tablets/nonexistent-999", "", "404 page not found"},
{"POST", "tablets/cell1-100?action=TestTabletAction", "", `{
Expand Down

0 comments on commit 9696e74

Please sign in to comment.