From 71a6287d4d972a0346a9638d47503e9fcf6c67ce Mon Sep 17 00:00:00 2001 From: Gianluca Arbezzano Date: Mon, 9 Dec 2019 11:09:13 +0100 Subject: [PATCH] feat(kubectl): add get commands Now you can do `kubectl get`: * profiles: to retrieve profiles * profile-types: to retrieve the list of supported profiles Signed-off-by: Gianluca Arbezzano --- go.sum | 1 + pkg/cmd/get.go | 17 +++++++++ pkg/cmd/get_profile_types.go | 20 +++++++++++ pkg/cmd/get_profiles.go | 68 ++++++++++++++++++++++++++++++++++++ pkg/cmd/profefe.go | 1 + pkg/profefe/client.go | 63 +++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 pkg/cmd/get.go create mode 100644 pkg/cmd/get_profile_types.go create mode 100644 pkg/cmd/get_profiles.go diff --git a/go.sum b/go.sum index 2600312..994c98d 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/pkg/cmd/get.go b/pkg/cmd/get.go new file mode 100644 index 0000000..f89a01f --- /dev/null +++ b/pkg/cmd/get.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func NewGetCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "get", + Short: "", + Run: func(cmd *cobra.Command, args []string) {}, + } + + cmd.AddCommand(NewGetProfilesCmd()) + cmd.AddCommand(NewGetProfileTypesCmd()) + return cmd +} diff --git a/pkg/cmd/get_profile_types.go b/pkg/cmd/get_profile_types.go new file mode 100644 index 0000000..9872d61 --- /dev/null +++ b/pkg/cmd/get_profile_types.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/gianarb/kube-profefe/pkg/profefe" + "github.com/spf13/cobra" +) + +func NewGetProfileTypesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "profile-types", + Short: "Retrieve supported profile types", + RunE: func(cmd *cobra.Command, args []string) error { + for _, v := range profefe.AllProfileTypes() { + println(v) + } + return nil + }, + } + return cmd +} diff --git a/pkg/cmd/get_profiles.go b/pkg/cmd/get_profiles.go new file mode 100644 index 0000000..2e9bbc2 --- /dev/null +++ b/pkg/cmd/get_profiles.go @@ -0,0 +1,68 @@ +package cmd + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/gianarb/kube-profefe/pkg/profefe" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + service string + profileType string + from time.Duration + to time.Duration +) + +func NewGetProfilesCmd() *cobra.Command { + flags := pflag.NewFlagSet("kprofefe", pflag.ExitOnError) + + cmd := &cobra.Command{ + Use: "profiles", + Short: "Retrieve profiles from profefe", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + pClient := profefe.NewClient(profefe.Config{ + HostPort: ProfefeHostPort, + }, http.Client{}) + + req := profefe.GetProfilesRequest{} + req.Service = service + + if profileType != "" { + pt := profefe.NewProfileTypeFromString(profileType) + if pt != profefe.UnknownProfile { + req.Type = pt + } + } + + req.To = time.Now() + req.From = req.To.Add(-from) + + resp, err := pClient.GetProfiles(ctx, req) + if err != nil { + return err + } + + for _, v := range resp.Body { + println(fmt.Sprintf("ID: %s Type: %s Service: %s CreateAt: %s", v.ID, v.Type, v.Service, v.CreatedAt.Format(time.RFC1123))) + } + return nil + }, + } + + flags.AddFlagSet(cmd.PersistentFlags()) + flags.StringVar(&ProfefeHostPort, "profefe-hostport", "http://localhost:10100", `where profefe is located`) + flags.StringVar(&profileType, "profile-type", "cpu", `The pprof profiles to retrieve`) + flags.StringVar(&service, "service", "", ``) + flags.DurationVar(&from, "from", 24*time.Hour, ``) + flags.DurationVar(&to, "to", 0, ``) + + cmd.Flags().AddFlagSet(flags) + + return cmd +} diff --git a/pkg/cmd/profefe.go b/pkg/cmd/profefe.go index a999d6e..6f5b0b4 100644 --- a/pkg/cmd/profefe.go +++ b/pkg/cmd/profefe.go @@ -39,6 +39,7 @@ func NewProfefeCmd(streams genericclioptions.IOStreams) *cobra.Command { flagsCapture.StringVar(&OutputDir, "output-dir", "/tmp", "Directory where to place the profiles") captureCmd.Flags().AddFlagSet(flagsCapture) rootCmd.AddCommand(captureCmd) + rootCmd.AddCommand(NewGetCmd()) return rootCmd } diff --git a/pkg/profefe/client.go b/pkg/profefe/client.go index 25c5f29..0bb79ed 100644 --- a/pkg/profefe/client.go +++ b/pkg/profefe/client.go @@ -14,6 +14,8 @@ import ( type ProfileType int8 +var timeFormat = "2006-01-02T15:04:05" + const ( UnknownProfile ProfileType = iota CPUProfile @@ -26,6 +28,18 @@ const ( OtherProfile = 127 ) +func AllProfileTypes() []string { + return []string{ + UnknownProfile.String(), + CPUProfile.String(), + HeapProfile.String(), + BlockProfile.String(), + MutexProfile.String(), + GoroutineProfile.String(), + ThreadcreateProfile.String(), + } +} + func (ptype ProfileType) String() string { switch ptype { case UnknownProfile: @@ -100,6 +114,55 @@ func GetProfileType() []string { } } +// GET +// /api/0/profiles?service=&type=from=&to=&labels= +func (c *Client) GetProfiles(ctx context.Context, req GetProfilesRequest) (*GetProfilesResponse, error) { + buf := bytes.NewBuffer([]byte{}) + r, err := http.NewRequestWithContext(ctx, "GET", c.HostPort+"/api/0/profiles", buf) + + q := r.URL.Query() + q.Add("from", req.From.Format(timeFormat)) + q.Add("to", req.To.Format(timeFormat)) + q.Add("type", req.Type.String()) + q.Add("service", req.Service) + r.URL.RawQuery = q.Encode() + + resp, err := c.Do(r) + defer resp.Body.Close() + rr := &GetProfilesResponse{} + + err = json.NewDecoder(resp.Body).Decode(rr) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusOK { + return rr, nil + } + return nil, fmt.Errorf(rr.Error) +} + +type GetProfilesRequest struct { + // service name + Service string + // cpu, heap, block, mutex, or goroutine + Type ProfileType + // a set of key-value pairs, e.g. "region=europe-west3,dc=fra,ip=1.2.3.4,version=1.0" + Labels map[string]string + + From, To time.Time +} + +type GetProfilesResponse struct { + Code int `json:"code"` + Error string `json:"error"` + Body []struct { + ID string `json:"id"` + Type string `json:"type"` + Service string `json:"service"` + CreatedAt time.Time `json:"created_at"` + } `json:"body"` +} + // https://github.com/profefe/profefe#save-pprof-data // POST /api/0/profiles?service=&instance_id=&type=[cpu|heap]&labels= // body pprof.pb.gz