Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trafficsplit metrics to CLI #3176

Merged
merged 21 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Using Alex's proxy branch
  • Loading branch information
Carol Scott committed Aug 14, 2019
commit 77539318ca3f6c135fdc9f6ffa04483675144575
132 changes: 65 additions & 67 deletions cli/cmd/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ func newStatOptions() *statOptions {
}
}

var isTrafficSplit = false

func newCmdStat() *cobra.Command {
options := newStatOptions()

Expand Down Expand Up @@ -133,9 +131,8 @@ If no resource name is specified, displays stats about all resources of the spec
client := checkPublicAPIClientOrExit()
c := make(chan indexedResults, len(reqs))
for num, req := range reqs {
if req.Selector.Resource.Type == "trafficsplit" {
isTrafficSplit = true
}
// if response is empty, isTrafficSplit determines whether returned message includes "trafficsplit" or "traffic"
isTrafficSplit = req.Selector.Resource.Type == k8s.TrafficSplit

go func(num int, req *pb.StatSummaryRequest) {
resp, err := requestStatsFromAPI(client, req)
Expand Down Expand Up @@ -221,36 +218,39 @@ type rowStats struct {
tcpWriteBytes float64
}

type tsStats struct {
apex string
leaf string
weight string
}

type row struct {
meshed string
status string
*rowStats
*tsStats
}

type tsStats struct {
apex string
leaf string
weight string
}

var (
nameHeader = "NAME"
namespaceHeader = "NAMESPACE"
apexHeader = "APEX"
leafHeader = "LEAF"
weightHeader = "WEIGHT"
isTrafficSplit = false
)

var key, leafName, apexName, weight string

func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer, options *statOptions) {
maxNameLength := len(nameHeader)
maxNamespaceLength := len(namespaceHeader)
statTables := make(map[string]map[string]*row)

maxApexLength := len(apexHeader)
maxLeafLength := len(leafHeader)
maxWeightLength := len(weightHeader)

statTables := make(map[string]map[string]*row)

prefixTypes := make(map[string]bool)
for _, r := range rows {
prefixTypes[r.Resource.Type] = true
Expand All @@ -268,20 +268,15 @@ func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer,
}

namespace := r.Resource.Namespace
key := fmt.Sprintf("%s/%s", namespace, name)
resourceType := r.Resource.Type
leaf := ""
apex := ""
weight := ""

var key string
if resourceType == "trafficsplit" {
leaf = r.TsStats.Leaf
apex = r.TsStats.Apex
if resourceType == k8s.TrafficSplit {
leafName = r.TsStats.Leaf
apexName = r.TsStats.Apex
weight = r.TsStats.Weight
key = fmt.Sprintf("%s/%s/%s", namespace, name, leaf)
} else {
key = fmt.Sprintf("%s/%s", namespace, name)
key = fmt.Sprintf("%s/%s/%s", namespace, name, leafName)
}

resourceKey := r.Resource.Type

if _, ok := statTables[resourceKey]; !ok {
Expand All @@ -296,12 +291,12 @@ func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer,
maxNamespaceLength = len(namespace)
}

if len(leaf) > maxLeafLength {
maxLeafLength = len(leaf)
if len(leafName) > maxLeafLength {
maxLeafLength = len(leafName)
}

if len(apex) > maxApexLength {
maxApexLength = len(apex)
if len(apexName) > maxApexLength {
maxApexLength = len(apexName)
}

if len(weight) > maxWeightLength {
Expand Down Expand Up @@ -331,9 +326,9 @@ func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer,
}
if r.TsStats != nil {
scottcarol marked this conversation as resolved.
Show resolved Hide resolved
statTables[resourceKey][key].tsStats = &tsStats{
apex: r.TsStats.Apex,
leaf: r.TsStats.Leaf,
weight: r.TsStats.Weight,
apex: apexName,
leaf: leafName,
weight: weight,
}
}
}
Expand Down Expand Up @@ -387,30 +382,30 @@ func showTCPConns(resourceType string) bool {

func printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType string, w *tabwriter.Writer, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxWeightLength int, options *statOptions) {
headers := make([]string, 0)
nameTemplate := fmt.Sprintf("%%-%ds", maxNameLength)
namespaceTemplate := fmt.Sprintf("%%-%ds", maxNamespaceLength)
apexTemplate := fmt.Sprintf("%%-%ds", maxApexLength)
leafTemplate := fmt.Sprintf("%%-%ds", maxLeafLength)
weightTemplate := fmt.Sprintf("%%-%ds", maxWeightLength)

if options.allNamespaces {
headers = append(headers,
namespaceHeader+strings.Repeat(" ", maxNamespaceLength-len(namespaceHeader)))
fmt.Sprintf(namespaceTemplate, namespaceHeader))
}

headers = append(headers, nameHeader+strings.Repeat(" ", maxNameLength-len(nameHeader)))
headers = append(headers,
fmt.Sprintf(nameTemplate, nameHeader))

if resourceType == k8s.Pod {
headers = append(headers, "STATUS")
}

if resourceType == k8s.TrafficSplit {

apexTemplate := fmt.Sprintf("%%-%ds", maxApexLength)
leafTemplate := fmt.Sprintf("%%-%ds", maxLeafLength)
weightTemplate := fmt.Sprintf("%%-%ds", maxWeightLength)

headers = append(headers,
fmt.Sprintf(apexTemplate, "APEX"),
fmt.Sprintf(leafTemplate, "LEAF"),
fmt.Sprintf(weightTemplate, "WEIGHT"))
}

if resourceType != k8s.TrafficSplit {
fmt.Sprintf(apexTemplate, apexHeader),
fmt.Sprintf(leafTemplate, leafHeader),
fmt.Sprintf(weightTemplate, weightHeader))
} else {
headers = append(headers, "MESHED")
}

Expand Down Expand Up @@ -446,7 +441,6 @@ func printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType
}

if resourceType == k8s.TrafficSplit {

templateString = "%s\t%s\t%s\t%s\t%.2f%%\t%.1frps\t%dms\t%dms\t%dms\t"
templateStringEmpty = "%s\t%s\t%s\t%s\t-\t-\t-\t-\t-\t-\t"
}
Expand Down Expand Up @@ -482,16 +476,16 @@ func printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType
leafPadding := 0

if stats[key].tsStats != nil {

if maxApexLength > len(stats[key].tsStats.apex) {
apexPadding = maxApexLength - len(stats[key].tsStats.apex)
}

if maxLeafLength > len(stats[key].tsStats.leaf) {
leafPadding = maxLeafLength - len(stats[key].tsStats.leaf)
}
}

values = append(values, name+strings.Repeat(" ", padding))

if resourceType == k8s.Pod {
values = append(values, stats[key].status)
}
Expand All @@ -500,8 +494,7 @@ func printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType
values = append(values, []interface{}{
stats[key].meshed,
}...)
}
if stats[key].tsStats != nil {
} else {
values = append(values,
stats[key].tsStats.apex+strings.Repeat(" ", apexPadding),
stats[key].tsStats.leaf+strings.Repeat(" ", leafPadding),
Expand Down Expand Up @@ -545,21 +538,25 @@ func namespaceName(resourceType string, key string) (string, string) {

// Using pointers where the value is NA and the corresponding json is null
type jsonStats struct {
Namespace string `json:"namespace"`
Kind string `json:"kind"`
Name string `json:"name"`
Meshed string `json:"meshed"`
Success *float64 `json:"success"`
Rps *float64 `json:"rps"`
LatencyMSp50 *uint64 `json:"latency_ms_p50"`
LatencyMSp95 *uint64 `json:"latency_ms_p95"`
LatencyMSp99 *uint64 `json:"latency_ms_p99"`
TCPConnections *uint64 `json:"tcp_open_connections"`
TCPReadBytes *float64 `json:"tcp_read_bytes_rate"`
TCPWriteBytes *float64 `json:"tcp_write_bytes_rate"`
Apex string `json:"apex"`
Leaf string `json:"leaf"`
Weight string `json:"weight"`
Namespace string `json:"namespace"`
Kind string `json:"kind"`
Name string `json:"name"`
Meshed string `json:"meshed"`
Success *float64 `json:"success"`
Rps *float64 `json:"rps"`
LatencyMSp50 *uint64 `json:"latency_ms_p50"`
LatencyMSp95 *uint64 `json:"latency_ms_p95"`
LatencyMSp99 *uint64 `json:"latency_ms_p99"`
TCPConnections *uint64 `json:"tcp_open_connections"`
TCPReadBytes *float64 `json:"tcp_read_bytes_rate"`
TCPWriteBytes *float64 `json:"tcp_write_bytes_rate"`
TsStats *jsonTsStats `json:"ts_stats"`
scottcarol marked this conversation as resolved.
Show resolved Hide resolved
}

type jsonTsStats struct {
Apex string `json:"apex"`
LeafName string `json:"leaf_name"`
scottcarol marked this conversation as resolved.
Show resolved Hide resolved
Weight string `json:"weight"`
}

func printStatJSON(statTables map[string]map[string]*row, w *tabwriter.Writer) {
Expand Down Expand Up @@ -590,9 +587,10 @@ func printStatJSON(statTables map[string]map[string]*row, w *tabwriter.Writer) {
}
}
if stats[key].tsStats != nil {
entry.Apex = stats[key].apex
entry.Leaf = stats[key].leaf
entry.Weight = stats[key].weight
entry.TsStats = &jsonTsStats{
Apex: stats[key].apex,
LeafName: stats[key].leaf,
Weight: stats[key].weight}
}
entries = append(entries, entry)
}
Expand Down
22 changes: 22 additions & 0 deletions cli/cmd/stat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ func TestStat(t *testing.T) {
}, k8s.Pod, t)
})

t.Run("Returns trafficsplit stats", func(t *testing.T) {
testStatCall(paramsExp{
options: options,
resNs: []string{"default"},
file: "stat_one_ts_output.golden",
}, k8s.TrafficSplit, t)
})

options.outputFormat = jsonOutput
t.Run("Returns namespace stats (json)", func(t *testing.T) {
testStatCall(paramsExp{
Expand All @@ -57,6 +65,14 @@ func TestStat(t *testing.T) {
}, k8s.Namespace, t)
})

t.Run("Returns trafficsplit stats (json)", func(t *testing.T) {
testStatCall(paramsExp{
options: options,
resNs: []string{"default"},
file: "stat_one_ts_output_json.golden",
}, k8s.TrafficSplit, t)
})

options = newStatOptions()
options.allNamespaces = true
t.Run("Returns all namespace stats", func(t *testing.T) {
Expand Down Expand Up @@ -167,10 +183,16 @@ func TestStat(t *testing.T) {
func testStatCall(exp paramsExp, resourceType string, t *testing.T) {
mockClient := &public.MockAPIClient{}
response := public.GenStatSummaryResponse("emoji", resourceType, exp.resNs, exp.counts, true, true)
if resourceType == k8s.TrafficSplit {
response = public.GenStatTsResponse("foo-split", resourceType, exp.resNs, true, true)
}

mockClient.StatSummaryResponseToReturn = &response

args := []string{"ns"}
if resourceType == k8s.TrafficSplit {
args = []string{"trafficsplit"}
}
reqs, err := buildStatSummaryRequests(args, exp.options)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
Expand Down
6 changes: 4 additions & 2 deletions cli/cmd/testdata/stat_all_output_json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"latency_ms_p99": 123,
"tcp_open_connections": 123,
"tcp_read_bytes_rate": 2.05,
"tcp_write_bytes_rate": 2.05
"tcp_write_bytes_rate": 2.05,
"ts_stats": null
},
{
"namespace": "emojivoto2",
Expand All @@ -25,6 +26,7 @@
"latency_ms_p99": 123,
"tcp_open_connections": 123,
"tcp_read_bytes_rate": 2.05,
"tcp_write_bytes_rate": 2.05
"tcp_write_bytes_rate": 2.05,
"ts_stats": null
}
]
3 changes: 2 additions & 1 deletion cli/cmd/testdata/stat_one_output_json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"latency_ms_p99": 123,
"tcp_open_connections": 123,
"tcp_read_bytes_rate": 2.05,
"tcp_write_bytes_rate": 2.05
"tcp_write_bytes_rate": 2.05,
"ts_stats": null
}
]
3 changes: 3 additions & 0 deletions cli/cmd/testdata/stat_one_ts_output.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NAME APEX LEAF WEIGHT SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
foo-split apex_name service-1 900m 100.00% 2.0rps 123ms 123ms 123ms 0
foo-split apex_name service-2 100m 100.00% 2.0rps 123ms 123ms 123ms 0
40 changes: 40 additions & 0 deletions cli/cmd/testdata/stat_one_ts_output_json.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[
{
"namespace": "default",
"kind": "trafficsplit",
"name": "foo-split",
"meshed": "0/0",
scottcarol marked this conversation as resolved.
Show resolved Hide resolved
"success": 1,
"rps": 2.05,
"latency_ms_p50": 123,
"latency_ms_p95": 123,
"latency_ms_p99": 123,
"tcp_open_connections": 0,
"tcp_read_bytes_rate": 0,
"tcp_write_bytes_rate": 0,
"ts_stats": {
scottcarol marked this conversation as resolved.
Show resolved Hide resolved
"apex": "apex_name",
"leaf_name": "service-1",
"weight": "900m"
}
},
{
"namespace": "default",
"kind": "trafficsplit",
"name": "foo-split",
"meshed": "0/0",
"success": 1,
"rps": 2.05,
"latency_ms_p50": 123,
"latency_ms_p95": 123,
"latency_ms_p99": 123,
"tcp_open_connections": 0,
"tcp_read_bytes_rate": 0,
"tcp_write_bytes_rate": 0,
"ts_stats": {
"apex": "apex_name",
"leaf_name": "service-2",
"weight": "100m"
}
}
]
Loading