✨ feat: 新增 profile get/delete, agent get/delete 接口
This commit is contained in:
parent
547016fc9b
commit
51a137411b
@ -16,6 +16,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"github.com/qiniu/goc/v2/pkg/client"
|
"github.com/qiniu/goc/v2/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var profileCmd = &cobra.Command{
|
var profileCmd = &cobra.Command{
|
||||||
@ -28,20 +29,54 @@ goc profile
|
|||||||
# Get coverage counter from specified register center, the result output to specified file.
|
# Get coverage counter from specified register center, the result output to specified file.
|
||||||
goc profile --host=http://192.168.1.1:8080 --output=./coverage.cov
|
goc profile --host=http://192.168.1.1:8080 --output=./coverage.cov
|
||||||
`,
|
`,
|
||||||
Run: profile,
|
//Run: profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
profileHost string
|
profileHost string
|
||||||
profileoutput string // --output flag
|
profileOutput string // --output flag
|
||||||
|
profileIds []string
|
||||||
|
profilePackages string
|
||||||
|
profileExtra string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
profileCmd.Flags().StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
|
|
||||||
profileCmd.Flags().StringVarP(&profileoutput, "output", "o", "", "download cover profile")
|
add1Flags := func(f *pflag.FlagSet) {
|
||||||
|
f.StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
|
||||||
|
f.StringSliceVar(&profileIds, "id", nil, "specify the ids of the services")
|
||||||
|
f.StringVar(&profileExtra, "extra", "", "specify the regex expression of extra, only profile with extra information will be downloaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
add2Flags := func(f *pflag.FlagSet) {
|
||||||
|
f.StringVarP(&profileOutput, "output", "o", "", "download cover profile")
|
||||||
|
f.StringVar(&profilePackages, "packages", "", "specify the regex expression of packages, only profile of these packages will be downloaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
add1Flags(getProfileCmd.Flags())
|
||||||
|
add2Flags(getProfileCmd.Flags())
|
||||||
|
|
||||||
|
add1Flags(clearProfileCmd.Flags())
|
||||||
|
|
||||||
|
profileCmd.AddCommand(getProfileCmd)
|
||||||
|
profileCmd.AddCommand(clearProfileCmd)
|
||||||
rootCmd.AddCommand(profileCmd)
|
rootCmd.AddCommand(profileCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func profile(cmd *cobra.Command, args []string) {
|
var getProfileCmd = &cobra.Command{
|
||||||
client.NewWorker("http://" + profileHost).Profile(profileoutput)
|
Use: "get",
|
||||||
|
Run: getProfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfile(cmd *cobra.Command, args []string) {
|
||||||
|
client.GetProfile(profileHost, profileIds, profilePackages, profileExtra, profileOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearProfileCmd = &cobra.Command{
|
||||||
|
Use: "clear",
|
||||||
|
Run: clearProfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearProfile(cmd *cobra.Command, args []string) {
|
||||||
|
client.ClearProfile(profileHost, profileIds, profileExtra)
|
||||||
}
|
}
|
||||||
|
@ -19,27 +19,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var listCmd = &cobra.Command{
|
var listCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "service",
|
||||||
Short: "Lists all the registered services",
|
Short: "Lists all the registered services",
|
||||||
Long: "Lists all the registered services",
|
Long: "Lists all the registered services",
|
||||||
Example: `
|
|
||||||
goc list [flags]
|
|
||||||
`,
|
|
||||||
|
|
||||||
Run: list,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
listHost string
|
listHost string
|
||||||
listWide bool
|
listWide bool
|
||||||
|
listIds []string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
listCmd.Flags().StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
|
listCmd.PersistentFlags().StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
|
||||||
listCmd.Flags().BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
|
listCmd.PersistentFlags().BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
|
||||||
|
listCmd.PersistentFlags().StringSliceVar(&listIds, "id", nil, "specify the ids of the services")
|
||||||
|
|
||||||
|
listCmd.AddCommand(getServiceCmd)
|
||||||
|
listCmd.AddCommand(deleteServiceCmd)
|
||||||
rootCmd.AddCommand(listCmd)
|
rootCmd.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(cmd *cobra.Command, args []string) {
|
func list(cmd *cobra.Command, args []string) {
|
||||||
client.NewWorker("http://" + listHost).ListAgents(listWide)
|
client.ListAgents(listHost, listIds, listWide)
|
||||||
|
}
|
||||||
|
|
||||||
|
var getServiceCmd = &cobra.Command{
|
||||||
|
Use: "get",
|
||||||
|
Run: getAgents,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAgents(cmd *cobra.Command, args []string) {
|
||||||
|
client.ListAgents(listHost, listIds, listWide)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteServiceCmd = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Run: deleteAgents,
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAgents(cmd *cobra.Command, args []string) {
|
||||||
|
client.DeleteAgents(listHost, listIds)
|
||||||
}
|
}
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.7.2
|
github.com/gin-gonic/gin v1.7.2
|
||||||
|
github.com/go-resty/resty/v2 v2.6.0
|
||||||
github.com/gofrs/flock v0.8.1
|
github.com/gofrs/flock v0.8.1
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
|
||||||
|
2
go.sum
2
go.sum
@ -472,6 +472,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
|||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
||||||
|
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||||
github.com/go-sql-driver/mysql v0.0.0-20160411075031-7ebe0a500653/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v0.0.0-20160411075031-7ebe0a500653/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
82
pkg/client/agent.go
Normal file
82
pkg/client/agent.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/client/rest"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DISCONNECT = 1 << iota
|
||||||
|
RPCCONNECT = 1 << iota
|
||||||
|
WATCHCONNECT = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListAgents(host string, ids []string, wide bool) {
|
||||||
|
gocClient := rest.NewV2Client(host)
|
||||||
|
|
||||||
|
agents, err := gocClient.Agent().Get(ids)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot get agent list from goc server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
|
table.SetCenterSeparator("")
|
||||||
|
table.SetColumnSeparator("")
|
||||||
|
table.SetRowSeparator("")
|
||||||
|
table.SetHeaderLine(false)
|
||||||
|
table.SetBorder(false)
|
||||||
|
table.SetTablePadding(" ") // pad with 3 blank spaces
|
||||||
|
table.SetNoWhiteSpace(true)
|
||||||
|
table.SetReflowDuringAutoWrap(false)
|
||||||
|
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||||
|
table.SetAutoWrapText(false)
|
||||||
|
if wide {
|
||||||
|
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "HOSTNAME", "PID", "CMD", "EXTRA"})
|
||||||
|
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||||
|
} else {
|
||||||
|
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "CMD"})
|
||||||
|
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||||
|
}
|
||||||
|
for _, agent := range agents {
|
||||||
|
var status string
|
||||||
|
if agent.Status == DISCONNECT {
|
||||||
|
status = "DISCONNECT"
|
||||||
|
} else if agent.Status&(RPCCONNECT|WATCHCONNECT) > 0 {
|
||||||
|
status = "CONNECT"
|
||||||
|
}
|
||||||
|
if wide {
|
||||||
|
table.Append([]string{agent.Id, status, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine, agent.Extra})
|
||||||
|
} else {
|
||||||
|
preLen := len(agent.Id) + len(agent.RemoteIP) + 9
|
||||||
|
table.Append([]string{agent.Id, status, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAgents(host string, ids []string) {
|
||||||
|
gocClient := rest.NewV2Client(host)
|
||||||
|
|
||||||
|
err := gocClient.Agent().Delete(ids)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot delete agents from goc server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
73
pkg/client/profie.go
Normal file
73
pkg/client/profie.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/qiniu/goc/v2/pkg/client/rest"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/client/rest/profile"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetProfile(host string, ids []string, packages string, extra string, output string) {
|
||||||
|
gocClient := rest.NewV2Client(host)
|
||||||
|
|
||||||
|
profiles, err := gocClient.Profile().Get(ids,
|
||||||
|
profile.WithPackagePattern(packages),
|
||||||
|
profile.WithExtraPattern(extra))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("fail to get profile from the goc server: %v, response: %v", err, profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output == "" {
|
||||||
|
fmt.Fprint(os.Stdout, profiles)
|
||||||
|
} else {
|
||||||
|
var dir, filename string = filepath.Split(output)
|
||||||
|
if dir != "" {
|
||||||
|
err = os.MkdirAll(dir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create directory %s, err:%v", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filename == "" {
|
||||||
|
output += "coverage.cov"
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(output)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create file %s, err:%v", output, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(f, bytes.NewReader([]byte(profiles)))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to write file: %v, err: %v", output, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearProfile(host string, ids []string, extra string) {
|
||||||
|
gocClient := rest.NewV2Client(host)
|
||||||
|
|
||||||
|
err := gocClient.Profile().Delete(ids,
|
||||||
|
profile.WithExtraPattern(extra))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("fail to clear the profile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
95
pkg/client/rest/agent/agent.go
Normal file
95
pkg/client/rest/agent/agent.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Agent struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
RemoteIP string `json:"rpc_remoteip"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
CmdLine string `json:"cmdline"`
|
||||||
|
Pid string `json:"pid"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Extra string `json:"extra"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
agentsAPI = "/v2/agents"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AgentInterface interface {
|
||||||
|
Get(ids []string) ([]Agent, error)
|
||||||
|
Delete(ids []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentsClient struct {
|
||||||
|
c *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAgentsClient(c *resty.Client) *agentsClient {
|
||||||
|
return &agentsClient{
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentOption func(*agentsClient)
|
||||||
|
|
||||||
|
func (a *agentsClient) Get(ids []string) ([]Agent, error) {
|
||||||
|
|
||||||
|
req := a.c.R()
|
||||||
|
|
||||||
|
idQuery := strings.Join(ids, ",")
|
||||||
|
|
||||||
|
req.QueryParam.Add("id", idQuery)
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
Items []Agent `json:"items"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
resp, err := req.
|
||||||
|
Get(agentsAPI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(resp.Body(), &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *agentsClient) Delete(ids []string) error {
|
||||||
|
|
||||||
|
req := a.c.R()
|
||||||
|
|
||||||
|
idQuery := strings.Join(ids, ",")
|
||||||
|
|
||||||
|
req.QueryParam.Add("id", idQuery)
|
||||||
|
|
||||||
|
_, err := req.
|
||||||
|
Delete(agentsAPI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
39
pkg/client/rest/client.go
Normal file
39
pkg/client/rest/client.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/client/rest/agent"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/client/rest/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V2Client provides methods contact with the covered agent under test
|
||||||
|
type V2Client struct {
|
||||||
|
rest *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV2Client(host string) *V2Client {
|
||||||
|
return &V2Client{
|
||||||
|
rest: resty.New().SetHostURL("http://" + host),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *V2Client) Agent() agent.AgentInterface {
|
||||||
|
return agent.NewAgentsClient(c.rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *V2Client) Profile() profile.ProfileInterface {
|
||||||
|
return profile.NewProfileClient(c.rest)
|
||||||
|
}
|
120
pkg/client/rest/profile/profile.go
Normal file
120
pkg/client/rest/profile/profile.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
profileAPI = "/v2/cover/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProfileInterface interface {
|
||||||
|
Get(ids []string, opts ...profileOption) (string, error)
|
||||||
|
Delete(ids []string, opts ...profileOption) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type profileClient struct {
|
||||||
|
c *resty.Client
|
||||||
|
packagePattern string
|
||||||
|
extraPattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProfileClient(c *resty.Client) *profileClient {
|
||||||
|
return &profileClient{
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type profileOption func(*profileClient)
|
||||||
|
|
||||||
|
func WithPackagePattern(pattern string) profileOption {
|
||||||
|
return func(pc *profileClient) {
|
||||||
|
pc.packagePattern = pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithExtraPattern(pattern string) profileOption {
|
||||||
|
return func(pc *profileClient) {
|
||||||
|
pc.extraPattern = pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *profileClient) Get(ids []string, opts ...profileOption) (string, error) {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := p.c.R()
|
||||||
|
|
||||||
|
idQuery := strings.Join(ids, ",")
|
||||||
|
|
||||||
|
req.QueryParam.Add("id", idQuery)
|
||||||
|
req.QueryParam.Add("pattern", p.packagePattern)
|
||||||
|
req.QueryParam.Add("extra", p.extraPattern)
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
Data string `json:"profile,omitempty"`
|
||||||
|
Msg string `jaon:"msg,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
resp, err := req.
|
||||||
|
Get(profileAPI)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(resp.Body(), &res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return res.Msg, fmt.Errorf("status code not 200")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *profileClient) Delete(ids []string, opts ...profileOption) error {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := p.c.R()
|
||||||
|
|
||||||
|
idQuery := strings.Join(ids, ",")
|
||||||
|
|
||||||
|
req.QueryParam.Add("id", idQuery)
|
||||||
|
req.QueryParam.Add("pattern", p.packagePattern)
|
||||||
|
req.QueryParam.Add("extra", p.extraPattern)
|
||||||
|
|
||||||
|
_, err := req.
|
||||||
|
Delete(profileAPI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -16,6 +16,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,11 +26,46 @@ import (
|
|||||||
"k8s.io/test-infra/gopherage/pkg/cov"
|
"k8s.io/test-infra/gopherage/pkg/cov"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func idMaps(idQuery string) func(key string) bool {
|
||||||
|
idMap := make(map[string]bool)
|
||||||
|
if strings.Contains(idQuery, ",") == false {
|
||||||
|
} else {
|
||||||
|
ids := strings.Split(idQuery, ",")
|
||||||
|
for _, id := range ids {
|
||||||
|
idMap[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inIdMaps := func(key string) bool {
|
||||||
|
// if no id in query, then all id agent will be return
|
||||||
|
if len(idMap) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// other
|
||||||
|
_, ok := idMap[key]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inIdMaps
|
||||||
|
}
|
||||||
|
|
||||||
// listAgents return all service informations
|
// listAgents return all service informations
|
||||||
func (gs *gocServer) listAgents(c *gin.Context) {
|
func (gs *gocServer) listAgents(c *gin.Context) {
|
||||||
|
idQuery := c.Query("id")
|
||||||
|
ifInIdMap := idMaps(idQuery)
|
||||||
|
|
||||||
agents := make([]*gocCoveredAgent, 0)
|
agents := make([]*gocCoveredAgent, 0)
|
||||||
|
|
||||||
gs.agents.Range(func(key, value interface{}) bool {
|
gs.agents.Range(func(key, value interface{}) bool {
|
||||||
|
// check if id is in the query ids
|
||||||
|
if !ifInIdMap(key.(string)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
agent, ok := value.(*gocCoveredAgent)
|
agent, ok := value.(*gocCoveredAgent)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -53,6 +89,7 @@ func (gs *gocServer) getProfiles(c *gin.Context) {
|
|||||||
mergedProfiles := make([][]*cover.Profile, 0)
|
mergedProfiles := make([][]*cover.Profile, 0)
|
||||||
|
|
||||||
gs.agents.Range(func(key, value interface{}) bool {
|
gs.agents.Range(func(key, value interface{}) bool {
|
||||||
|
|
||||||
agent, ok := value.(*gocCoveredAgent)
|
agent, ok := value.(*gocCoveredAgent)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -69,6 +106,12 @@ func (gs *gocServer) getProfiles(c *gin.Context) {
|
|||||||
var req ProfileReq = "getprofile"
|
var req ProfileReq = "getprofile"
|
||||||
var res ProfileRes
|
var res ProfileRes
|
||||||
go func() {
|
go func() {
|
||||||
|
// lock-free
|
||||||
|
rpc := agent.rpc
|
||||||
|
if rpc == nil || agent.Status == DISCONNECT {
|
||||||
|
done <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
|
err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
|
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
|
||||||
@ -134,8 +177,10 @@ func (gs *gocServer) getProfiles(c *gin.Context) {
|
|||||||
//
|
//
|
||||||
// it is async, the function will return immediately
|
// it is async, the function will return immediately
|
||||||
func (gs *gocServer) resetProfiles(c *gin.Context) {
|
func (gs *gocServer) resetProfiles(c *gin.Context) {
|
||||||
|
|
||||||
gs.agents.Range(func(key, value interface{}) bool {
|
gs.agents.Range(func(key, value interface{}) bool {
|
||||||
agent, ok := value.(gocCoveredAgent)
|
|
||||||
|
agent, ok := value.(*gocCoveredAgent)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -143,7 +188,12 @@ func (gs *gocServer) resetProfiles(c *gin.Context) {
|
|||||||
var req ProfileReq = "resetprofile"
|
var req ProfileReq = "resetprofile"
|
||||||
var res ProfileRes
|
var res ProfileRes
|
||||||
go func() {
|
go func() {
|
||||||
err := agent.rpc.Call("GocAgent.ResetProfile", req, &res)
|
// lock-free
|
||||||
|
rpc := agent.rpc
|
||||||
|
if rpc == nil || agent.Status == DISCONNECT {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := rpc.Call("GocAgent.ResetProfile", req, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
|
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
|
||||||
// 关闭链接
|
// 关闭链接
|
||||||
@ -207,7 +257,7 @@ func (gs *gocServer) watchProfileUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gs *gocServer) removeAgentById(c *gin.Context) {
|
func (gs *gocServer) removeAgentById(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Query("id")
|
||||||
|
|
||||||
rawagent, ok := gs.agents.Load(id)
|
rawagent, ok := gs.agents.Load(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -234,7 +284,16 @@ func (gs *gocServer) removeAgentById(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gs *gocServer) removeAgents(c *gin.Context) {
|
func (gs *gocServer) removeAgents(c *gin.Context) {
|
||||||
|
idQuery := c.Query("id")
|
||||||
|
ifInIdMap := idMaps(idQuery)
|
||||||
|
|
||||||
gs.agents.Range(func(key, value interface{}) bool {
|
gs.agents.Range(func(key, value interface{}) bool {
|
||||||
|
|
||||||
|
// check if id is in the query ids
|
||||||
|
if !ifInIdMap(key.(string)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
agent, ok := value.(*gocCoveredAgent)
|
agent, ok := value.(*gocCoveredAgent)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
|
@ -92,6 +92,8 @@ func (gs *gocServer) serveRpcStream(c *gin.Context) {
|
|||||||
|
|
||||||
ws.Close()
|
ws.Close()
|
||||||
log.Infof("close rpc connection, %v", agent.Hostname)
|
log.Infof("close rpc connection, %v", agent.Hostname)
|
||||||
|
// reset rpc client
|
||||||
|
agent.rpc = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// set pong handler
|
// set pong handler
|
||||||
|
@ -123,7 +123,6 @@ func RunGocServerUntilExit(host string, path string) {
|
|||||||
v2.GET("/cover/profile", gs.getProfiles)
|
v2.GET("/cover/profile", gs.getProfiles)
|
||||||
v2.DELETE("/cover/profile", gs.resetProfiles)
|
v2.DELETE("/cover/profile", gs.resetProfiles)
|
||||||
v2.GET("/agents", gs.listAgents)
|
v2.GET("/agents", gs.listAgents)
|
||||||
v2.DELETE("/agents/:id", gs.removeAgentById)
|
|
||||||
v2.DELETE("/agents", gs.removeAgents)
|
v2.DELETE("/agents", gs.removeAgents)
|
||||||
|
|
||||||
v2.GET("/cover/ws/watch", gs.watchProfileUpdate)
|
v2.GET("/cover/ws/watch", gs.watchProfileUpdate)
|
||||||
|
@ -64,19 +64,19 @@ var _ = Describe("1 [基础测试]", func() {
|
|||||||
basicC.Run()
|
basicC.Run()
|
||||||
defer basicC.Stop()
|
defer basicC.Stop()
|
||||||
|
|
||||||
By("使用 goc list 获取服务列表")
|
By("使用 goc service get 获取服务列表")
|
||||||
output, err = RunShortRunCmd([]string{"goc", "list"}, dir, nil)
|
output, err = RunShortRunCmd([]string{"goc", "service", "get"}, dir, nil)
|
||||||
Expect(err).To(BeNil(), "goc list 运行错误")
|
Expect(err).To(BeNil(), "goc servive get 运行错误")
|
||||||
Expect(output).To(ContainSubstring("127.0.0.1 ./basic2"), "goc list 输出应该包含 basic 服务")
|
Expect(output).To(ContainSubstring("127.0.0.1 ./basic2"), "goc service get 输出应该包含 basic 服务")
|
||||||
|
|
||||||
By("使用 goc profile 获取覆盖率")
|
By("使用 goc profile get 获取覆盖率")
|
||||||
profileStr := `mode: count
|
profileStr := `mode: count
|
||||||
basic2/main.go:8.13,9.6 1 1
|
basic2/main.go:8.13,9.6 1 1
|
||||||
basic2/main.go:9.6,12.3 2 2`
|
basic2/main.go:9.6,12.3 2 2`
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
output, err = RunShortRunCmd([]string{"goc", "profile"}, dir, nil)
|
output, err = RunShortRunCmd([]string{"goc", "profile", "get"}, dir, nil)
|
||||||
Expect(err).To(BeNil(), "goc profile 运行错误")
|
Expect(err).To(BeNil(), "goc profile get运行错误")
|
||||||
Expect(output).To(ContainSubstring(profileStr), "goc profile 获取的覆盖率有误")
|
Expect(output).To(ContainSubstring(profileStr), "goc profile get 获取的覆盖率有误")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user