goc/pkg/server/api.go

402 lines
8.5 KiB
Go
Raw Normal View History

2021-09-02 09:48:11 +00:00
/*
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.
*/
2021-06-10 12:03:47 +00:00
package server
import (
2021-06-19 08:17:29 +00:00
"bytes"
2021-09-09 03:49:49 +00:00
"fmt"
2021-06-10 12:03:47 +00:00
"net/http"
2021-09-09 03:49:49 +00:00
"regexp"
"strings"
2021-06-19 08:17:29 +00:00
"sync"
"time"
2021-06-10 12:03:47 +00:00
2023-08-28 04:34:44 +00:00
"github.com/RickLeee/goc-v2/pkg/log"
2021-06-10 12:03:47 +00:00
"github.com/gin-gonic/gin"
2021-06-19 08:17:29 +00:00
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
2021-06-10 12:03:47 +00:00
)
2021-09-09 03:49:49 +00:00
// listAgents return all service informations
func (gs *gocServer) listAgents(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
2021-09-09 03:49:49 +00:00
agents := make([]*gocCoveredAgent, 0)
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
return true
}
2021-09-09 03:49:49 +00:00
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
2021-09-09 03:49:49 +00:00
agents = append(agents, agent)
return true
})
2021-09-09 03:49:49 +00:00
c.JSON(http.StatusOK, gin.H{
"items": agents,
})
}
2021-09-09 03:49:49 +00:00
func (gs *gocServer) removeAgents(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
2021-09-10 06:47:03 +00:00
errs := ""
2021-09-05 12:27:29 +00:00
gs.agents.Range(func(key, value interface{}) bool {
2021-09-09 03:49:49 +00:00
// check if id is in the query ids
2021-09-10 06:47:03 +00:00
id := key.(string)
if !ifInIdMap(id) {
return true
}
agent, ok := value.(*gocCoveredAgent)
2021-06-10 12:03:47 +00:00
if !ok {
return false
}
2021-09-09 03:49:49 +00:00
2021-09-10 06:47:03 +00:00
err := gs.removeAgentFromStore(id)
if err != nil {
log.Errorf("fail to remove agent: %v", id)
err := fmt.Errorf("fail to remove agent: %v, err: %v", id, err)
errs = errs + err.Error()
return true
}
2021-09-09 03:49:49 +00:00
agent.closeConnection()
gs.agents.Delete(key)
2021-06-10 12:03:47 +00:00
return true
})
2021-09-10 06:47:03 +00:00
if errs != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": errs,
})
} else {
c.JSON(http.StatusOK, nil)
}
2021-06-10 12:03:47 +00:00
}
2021-06-17 11:53:00 +00:00
2021-06-19 08:17:29 +00:00
// getProfiles get and merge all agents' informations
//
// it is synchronous
2021-06-17 11:53:00 +00:00
func (gs *gocServer) getProfiles(c *gin.Context) {
2021-09-09 03:49:49 +00:00
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
skippatternRaw := c.Query("skippattern")
var skippattern []string
if skippatternRaw != "" {
skippattern = strings.Split(skippatternRaw, ",")
}
2023-07-28 03:48:05 +00:00
neerpatternRaw := c.Query("needpattern")
var neerpattern []string
if neerpatternRaw != "" {
neerpattern = strings.Split(neerpatternRaw, ",")
}
2021-09-09 03:49:49 +00:00
extra := c.Query("extra")
isExtra := filterExtra(extra)
2021-06-19 08:17:29 +00:00
var mu sync.Mutex
var wg sync.WaitGroup
mergedProfiles := make([][]*cover.Profile, 0)
2021-09-05 12:27:29 +00:00
gs.agents.Range(func(key, value interface{}) bool {
2021-09-09 03:49:49 +00:00
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
agent, ok := value.(*gocCoveredAgent)
2021-06-17 11:53:00 +00:00
if !ok {
return false
}
2021-09-09 03:49:49 +00:00
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
2021-06-19 08:17:29 +00:00
wg.Add(1)
// 并发 rpc且每个 rpc 设超时时间 10 second
go func() {
defer wg.Done()
timeout := time.Duration(10 * time.Second)
done := make(chan error, 1)
var req ProfileReq = "getprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
done <- nil
return
}
2021-06-19 08:17:29 +00:00
err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
if err != nil {
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
}
done <- err
}()
select {
// rpc 超时
case <-time.After(timeout):
log.Warnf("rpc call timeout: %v", agent.Hostname)
// 关闭链接
2021-09-05 12:27:29 +00:00
agent.closeRpcConnOnce()
2021-06-19 08:17:29 +00:00
case err := <-done:
// 调用 rpc 发生错误
if err != nil {
// 关闭链接
2021-09-05 12:27:29 +00:00
agent.closeRpcConnOnce()
2021-06-19 08:17:29 +00:00
}
}
// append profile
profile, err := convertProfile([]byte(res))
if err != nil {
log.Errorf("fail to convert the received profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
2021-09-05 12:27:29 +00:00
agent.closeRpcConnOnce()
2021-06-19 08:17:29 +00:00
return
}
2021-09-09 03:49:49 +00:00
// check if skippattern matches
2023-07-28 03:48:05 +00:00
newProfile := filterProfileByPattern(skippattern, neerpattern, profile)
2021-09-09 03:49:49 +00:00
2021-06-19 08:17:29 +00:00
mu.Lock()
defer mu.Unlock()
2021-09-09 03:49:49 +00:00
mergedProfiles = append(mergedProfiles, newProfile)
2021-06-19 08:17:29 +00:00
}()
return true
})
// 一直等待并发的 rpc 都回应
wg.Wait()
merged, err := cov.MergeMultipleProfiles(mergedProfiles)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
var buff bytes.Buffer
err = cov.DumpProfile(merged, &buff)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"profile": buff.String(),
})
}
// resetProfiles reset all profiles in agent
//
// it is async, the function will return immediately
func (gs *gocServer) resetProfiles(c *gin.Context) {
2021-09-09 03:49:49 +00:00
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
extra := c.Query("extra")
isExtra := filterExtra(extra)
2021-09-05 12:27:29 +00:00
gs.agents.Range(func(key, value interface{}) bool {
2021-09-09 03:49:49 +00:00
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
agent, ok := value.(*gocCoveredAgent)
2021-06-19 08:17:29 +00:00
if !ok {
return false
2021-06-17 11:53:00 +00:00
}
2021-06-19 08:17:29 +00:00
2021-09-09 03:49:49 +00:00
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
2021-06-19 08:17:29 +00:00
var req ProfileReq = "resetprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
return
}
err := rpc.Call("GocAgent.ResetProfile", req, &res)
2021-06-19 08:17:29 +00:00
if err != nil {
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
2021-09-05 12:27:29 +00:00
agent.closeRpcConnOnce()
2021-06-19 08:17:29 +00:00
}
}()
2021-06-17 11:53:00 +00:00
return true
})
}
2021-06-24 07:22:24 +00:00
// watchProfileUpdate watch the profile change
//
// any profile change will be updated on this websocket connection.
func (gs *gocServer) watchProfileUpdate(c *gin.Context) {
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with watch client: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
log.Infof("watch client connected")
id := time.Now().String()
gwc := &gocWatchClient{
ws: ws,
exitCh: make(chan int),
}
gs.watchClients.Store(id, gwc)
// send close msg and close ws connection
defer func() {
gs.watchClients.Delete(id)
ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) })
log.Infof("watch client disconnected")
}()
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
break
}
}
gwc.once.Do(func() { close(gwc.exitCh) })
2021-06-24 07:22:24 +00:00
}()
<-gwc.exitCh
}
2021-09-05 12:27:29 +00:00
2023-07-28 03:48:05 +00:00
func filterProfileByPattern(skippattern []string, needpattern []string, profiles []*cover.Profile) []*cover.Profile {
var out = make([]*cover.Profile, 0)
2023-08-25 09:40:13 +00:00
var skipOut = make([]*cover.Profile, 0)
2021-09-05 12:27:29 +00:00
2023-08-25 08:49:07 +00:00
if len(skippattern) != 0 {
for _, profile := range profiles {
skip := false
for _, pattern := range skippattern {
if strings.Contains(profile.FileName, pattern) {
skip = true
break
}
}
2023-08-25 08:49:07 +00:00
if !skip {
2023-08-25 09:40:13 +00:00
skipOut = append(skipOut, profile)
2023-08-25 08:49:07 +00:00
}
2021-09-09 03:49:49 +00:00
}
2023-09-04 10:13:42 +00:00
} else {
return profiles
2021-09-05 12:27:29 +00:00
}
2023-08-25 09:40:13 +00:00
log.Infof("skipOut len: %v", len(skipOut))
if len(needpattern) == 0 {
return skipOut
}
2023-08-25 08:49:07 +00:00
2023-08-25 09:40:13 +00:00
for _, profile := range skipOut {
need := false
for _, pattern := range needpattern {
if strings.Contains(profile.FileName, pattern) {
need = true
break
2023-07-28 03:48:05 +00:00
}
}
2023-08-25 09:40:13 +00:00
if need {
out = append(out, profile)
}
2023-07-28 03:48:05 +00:00
}
2023-08-25 09:40:13 +00:00
log.Infof("need out len: %v", len(out))
2021-09-05 12:27:29 +00:00
return out
2021-09-05 12:27:29 +00:00
}
2021-09-09 03:49:49 +00:00
func idMaps(idQuery string) func(key string) bool {
idMap := make(map[string]bool)
if len(strings.TrimSpace(idQuery)) == 0 {
} else {
ids := strings.Split(idQuery, ",")
for _, id := range ids {
idMap[id] = true
}
}
2021-09-09 03:49:49 +00:00
inIdMaps := func(key string) bool {
// if no id in query, then all id agent will be return
if len(idMap) == 0 {
return true
}
2021-09-09 03:49:49 +00:00
// other
_, ok := idMap[key]
2021-09-05 12:27:29 +00:00
if !ok {
return false
2021-09-09 03:49:49 +00:00
} else {
return true
2021-09-05 12:27:29 +00:00
}
2021-09-09 03:49:49 +00:00
}
2021-09-05 12:27:29 +00:00
2021-09-09 03:49:49 +00:00
return inIdMaps
}
2021-09-05 12:27:29 +00:00
2021-09-09 03:49:49 +00:00
func filterExtra(extraPattern string) func(string) bool {
2021-09-05 12:27:29 +00:00
2021-09-09 03:49:49 +00:00
re := regexp.MustCompile(extraPattern)
return func(extra string) bool {
return re.Match([]byte(extra))
}
2021-09-05 12:27:29 +00:00
}