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

Modify the Stat API to handle requests for resource type "all" #928

Merged
merged 14 commits into from
May 11, 2018
Merged
69 changes: 55 additions & 14 deletions cli/cmd/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/runconduit/conduit/controller/api/util"
pb "github.com/runconduit/conduit/controller/gen/public"
"github.com/runconduit/conduit/pkg/k8s"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
Expand All @@ -38,6 +39,7 @@ var statCmd = &cobra.Command{
* deploy/my-deploy
* deploy my-deploy
* ns/my-ns
* all

Valid resource types include:

Expand All @@ -46,6 +48,7 @@ Valid resource types include:
* pods
* replicationcontrollers
* services (only supported if a "--from" is also specified, or as a "--to")
* all (all resource types, not supported in --from or --to)

This command will hide resources that have completed, such as pods that are in the Succeeded or Failed phases.
If no resource name is specified, displays stats about all resources of the specified RESOURCETYPE`,
Expand Down Expand Up @@ -118,13 +121,13 @@ func requestStatsFromAPI(client pb.ApiClient, req *pb.StatSummaryRequest) (strin
return "", fmt.Errorf("StatSummary API response error: %v", e.Error)
}

return renderStats(resp), nil
return renderStats(resp, req.Selector.Resource.Type), nil
}

func renderStats(resp *pb.StatSummaryResponse) string {
func renderStats(resp *pb.StatSummaryResponse, resourceType string) string {
var buffer bytes.Buffer
w := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', tabwriter.AlignRight)
writeStatsToBuffer(resp, w)
writeStatsToBuffer(resp, resourceType, w)
w.Flush()

// strip left padding on the first column
Expand All @@ -149,35 +152,48 @@ type row struct {
*rowStats
}

func writeStatsToBuffer(resp *pb.StatSummaryResponse, w *tabwriter.Writer) {
nameHeader := "NAME"
var (
nameHeader = "NAME"
namespaceHeader = "NAMESPACE"
)

func writeStatsToBuffer(resp *pb.StatSummaryResponse, reqResourceType string, w *tabwriter.Writer) {
maxNameLength := len(nameHeader)
namespaceHeader := "NAMESPACE"
maxNamespaceLength := len(namespaceHeader)

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

for _, statTable := range resp.GetOk().StatTables {
table := statTable.GetPodGroup()

for _, r := range table.Rows {
name := r.Resource.Name
nameWithPrefix := name
if reqResourceType == k8s.All {
nameWithPrefix = getNamePrefix(r.Resource.Type) + nameWithPrefix
}

namespace := r.Resource.Namespace
key := fmt.Sprintf("%s/%s", namespace, name)
resourceKey := r.Resource.Type

if len(name) > maxNameLength {
maxNameLength = len(name)
if _, ok := statTables[resourceKey]; !ok {
statTables[resourceKey] = make(map[string]*row)
}

if len(nameWithPrefix) > maxNameLength {
maxNameLength = len(nameWithPrefix)
}

if len(namespace) > maxNamespaceLength {
maxNamespaceLength = len(namespace)
}

stats[key] = &row{
statTables[resourceKey][key] = &row{
meshed: fmt.Sprintf("%d/%d", r.MeshedPodCount, r.RunningPodCount),
}

if r.Stats != nil {
stats[key].rowStats = &rowStats{
statTables[resourceKey][key].rowStats = &rowStats{
requestRate: getRequestRate(*r),
successRate: getSuccessRate(*r),
latencyP50: r.Stats.LatencyMsP50,
Expand All @@ -188,11 +204,26 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, w *tabwriter.Writer) {
}
}

if len(stats) == 0 {
if len(statTables) == 0 {
fmt.Fprintln(os.Stderr, "No traffic found.")
os.Exit(0)
}

lastDisplayedStat := true // don't print a newline after the final stat
for resourceType, stats := range statTables {
if !lastDisplayedStat {
fmt.Fprint(w, "\n")
}
lastDisplayedStat = false
if reqResourceType == k8s.All {
printStatTable(stats, resourceType, w, maxNameLength, maxNamespaceLength)
} else {
printStatTable(stats, "", w, maxNameLength, maxNamespaceLength)
}
}
}

func printStatTable(stats map[string]*row, resourceType string, w *tabwriter.Writer, maxNameLength int, maxNamespaceLength int) {
headers := make([]string, 0)
if allNamespaces {
headers = append(headers,
Expand All @@ -210,11 +241,13 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, w *tabwriter.Writer) {

fmt.Fprintln(w, strings.Join(headers, "\t"))

namePrefix := getNamePrefix(resourceType)

sortedKeys := sortStatsKeys(stats)
for _, key := range sortedKeys {
parts := strings.Split(key, "/")
namespace := parts[0]
name := parts[1]
name := namePrefix + parts[1]
values := make([]interface{}, 0)
templateString := "%s\t%s\t%.2f%%\t%.1frps\t%dms\t%dms\t%dms\t\n"
templateStringEmpty := "%s\t%s\t-\t-\t-\t-\t-\t\n"
Expand Down Expand Up @@ -246,6 +279,14 @@ func writeStatsToBuffer(resp *pb.StatSummaryResponse, w *tabwriter.Writer) {
}
}

func getNamePrefix(resourceType string) string {
if resourceType == "" {
return ""
} else {
return k8s.ShortNameFromCanonicalKubernetesName(resourceType) + "/"
}
}

func buildStatSummaryRequest(
timeWindow string, allNamespaces bool,
resource []string, namespace string,
Expand Down
114 changes: 89 additions & 25 deletions controller/api/public/stat_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ type promResult struct {
err error
}

type resourceResult struct {
res *pb.StatTable
err error
}

const (
reqQuery = "sum(increase(response_total%s[%s])) by (%s, classification)"
latencyQuantileQuery = "histogram_quantile(%s, sum(irate(response_latency_ms_bucket%s[%s])) by (le, %s))"
Expand All @@ -41,6 +46,9 @@ const (

var promTypes = []promType{promRequests, promLatencyP50, promLatencyP95, promLatencyP99}

// resources to query when Resource.Type is "all"
var resourceTypes = []string{k8s.Pods, k8s.Deployments, k8s.ReplicationControllers, k8s.Services}

type podCount struct {
inMesh uint64
total uint64
Expand All @@ -50,19 +58,83 @@ type podCount struct {
func (s *grpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest) (*pb.StatSummaryResponse, error) {
// special case to check for services as outbound only
if isInvalidServiceRequest(req) {
return &pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Error{
Error: &pb.ResourceError{
Resource: req.Selector.Resource,
Error: "service only supported as a target on 'from' queries, or as a destination on 'to' queries",
return statSummaryError(req, "service only supported as a target on 'from' queries, or as a destination on 'to' queries"), nil
}

switch req.Outbound.(type) {
case *pb.StatSummaryRequest_ToResource:
if req.Outbound.(*pb.StatSummaryRequest_ToResource).ToResource.Type == k8s.All {
return statSummaryError(req, "resource type 'all' is not supported as a filter"), nil
}
case *pb.StatSummaryRequest_FromResource:
if req.Outbound.(*pb.StatSummaryRequest_FromResource).FromResource.Type == k8s.All {
return statSummaryError(req, "resource type 'all' is not supported as a filter"), nil
}
}

statTables := make([]*pb.StatTable, 0)

var resourcesToQuery []string
if req.Selector.Resource.Type == k8s.All {
resourcesToQuery = resourceTypes
} else {
resourcesToQuery = []string{req.Selector.Resource.Type}
}

// request stats for the resourcesToQuery, in parallel
resultChan := make(chan resourceResult)

for _, resource := range resourcesToQuery {
statReq := &pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: resource,
Namespace: req.Selector.Resource.Namespace,
},
LabelSelector: req.Selector.LabelSelector,
},
}, nil
TimeWindow: req.TimeWindow,
}

go func() {
resultChan <- s.resourceQuery(ctx, statReq)
}()
}

for i := 0; i < len(resourcesToQuery); i++ {
result := <-resultChan
if result.err != nil {
return nil, util.GRPCError(result.err)
}
statTables = append(statTables, result.res)
}

rsp := pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205
Ok: &pb.StatSummaryResponse_Ok{
StatTables: statTables,
},
},
}

return &rsp, nil
}

func statSummaryError(req *pb.StatSummaryRequest, message string) *pb.StatSummaryResponse {
return &pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Error{
Error: &pb.ResourceError{
Resource: req.Selector.Resource,
Error: message,
},
},
}
}

func (s *grpcServer) resourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {
objects, err := s.lister.GetObjects(req.Selector.Resource.Namespace, req.Selector.Resource.Type, req.Selector.Resource.Name)
if err != nil {
return nil, util.GRPCError(err)
return resourceResult{res: nil, err: err}
}

// TODO: make these one struct:
Expand All @@ -73,36 +145,36 @@ func (s *grpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest
for _, object := range objects {
key, err := cache.MetaNamespaceKeyFunc(object)
if err != nil {
return nil, util.GRPCError(err)
return resourceResult{res: nil, err: err}
}
metaObj, err := meta.Accessor(object)
if err != nil {
return nil, util.GRPCError(err)
return resourceResult{res: nil, err: err}
}

objectMap[key] = metaObj

meshCount, err := s.getMeshedPodCount(object)
if err != nil {
return nil, util.GRPCError(err)
return resourceResult{res: nil, err: err}
}
meshCountMap[key] = meshCount
}

res, err := s.objectQuery(ctx, req, objectMap, meshCountMap)
if err != nil {
return nil, util.GRPCError(err)
return resourceResult{res: nil, err: err}
}

return res, nil
return resourceResult{res: res, err: nil}
}

func (s *grpcServer) objectQuery(
ctx context.Context,
req *pb.StatSummaryRequest,
objects map[string]metav1.Object,
meshCount map[string]*podCount,
) (*pb.StatSummaryResponse, error) {
) (*pb.StatTable, error) {
rows := make([]*pb.StatTable_PodGroup_Row, 0)

requestMetrics, err := s.getRequests(ctx, req, req.TimeWindow)
Expand Down Expand Up @@ -149,18 +221,10 @@ func (s *grpcServer) objectQuery(
rows = append(rows, &row)
}

rsp := pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205
Ok: &pb.StatSummaryResponse_Ok{
StatTables: []*pb.StatTable{
&pb.StatTable{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: rows,
},
},
},
},
rsp := pb.StatTable{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: rows,
},
},
}
Expand Down
Loading