* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* 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 debug
import (
"bufio"
"fmt"
"io"
"net/http"
"reflect"
"strings"
"time"
"github.com/olekukonko/tablewriter"
"google.golang.org/protobuf/proto"
"cli/internal/pb"
"cli/pkg/cmdio"
"cli/utils"
"cli/utils/colorprint"
)
type InstanceInfo struct {
InstanceID string `json:"instanceID"`
PID int32 `json:"pid"`
DebugServer string `json:"debugServer"`
Status string `json:"status"`
Language string `json:"language" json:"language"`
}
var debugInstanceInfosMap map[string]InstanceInfo
var httpClient *http.Client
var pageSize = 5
var printedFields = []string{"InstanceID", "Status", "Language"}
func init() {
httpClient = &http.Client{
Timeout: 30 * time.Minute,
Transport: &http.Transport{
MaxIdleConns: 10,
MaxIdleConnsPerHost: 5,
MaxConnsPerHost: 10,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 30 * time.Minute,
},
}
}
const listDebugInstPath = "/instance-manager/query-debug-instances"
func pageOutDebugInstanceInfos(cmdIO *cmdio.CmdIO) {
err := queryDebugInstanceInfo(cmdIO)
if err != nil {
return
}
var values []InstanceInfo
for _, v := range debugInstanceInfosMap {
values = append(values, v)
}
dataStr, err := transStructsToString(values, printedFields)
if err != nil {
return
}
printTable(dataStr, cmdIO, printedFields)
}
func queryDebugInstanceInfo(cmdIO *cmdio.CmdIO) error {
url := "http://" + MasterInfo.MasterIP + ":" + MasterInfo.GlobalSchedulerPort
body, err := requestFunctionMaster(url, listDebugInstPath)
if err != nil {
colorprint.PrintFail(cmdIO.Out, "request master to get debug info failed: ", err.Error(), "\n")
return err
}
var debugInst pb.QueryDebugInstanceInfosResponse
err = proto.Unmarshal(body, &debugInst)
if err != nil {
colorprint.PrintFail(cmdIO.Out, "response body to QueryDebugInstancesInfoResponse failed: ", err.Error(), "\n")
return err
}
pbToInstanceInfos(debugInst.DebugInstanceInfos)
return nil
}
func requestFunctionMaster(url string, path string) ([]byte, error) {
req, err := http.NewRequest("GET", url+path, nil)
if err != nil {
return nil, err
}
req.Header.Set("Type", "protobuf")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func pbToInstanceInfos(pbInfos []*pb.DebugInstanceInfo) {
debugInstanceInfosMap = make(map[string]InstanceInfo)
for _, pbInfo := range pbInfos {
instId := pbInfo.InstanceID
info := InstanceInfo{
InstanceID: instId,
PID: pbInfo.Pid,
DebugServer: pbInfo.DebugServer,
Status: pbInfo.Status,
Language: pbInfo.Language,
}
debugInstanceInfosMap[instId] = info
}
}
func transStructsToString(arr interface{}, fieldNames []string) (string, error) {
val := reflect.ValueOf(arr)
if val.Kind() != reflect.Slice {
return "", fmt.Errorf("expected a slice, got %s", val.Kind())
}
var result []string
for i := 0; i < val.Len(); i++ {
structVal := val.Index(i)
if structVal.Kind() != reflect.Struct {
return "", fmt.Errorf("expected struct element, got %s", structVal.Kind())
}
var fields []string
for _, fieldName := range fieldNames {
field := structVal.FieldByName(fieldName)
if !field.IsValid() {
return "", fmt.Errorf("field %s not found in struct", fieldName)
}
fields = append(fields, fmt.Sprintf("%v", field.Interface()))
}
result = append(result, strings.Join(fields, ","))
}
return strings.Join(result, "\n"), nil
}
func printTable(dataStr string, cmdIO *cmdio.CmdIO, tableHeader []string) {
dataLines := strings.Split(strings.TrimSpace(dataStr), "\n")
pageNum := (len(dataLines) + pageSize - 1) / pageSize
table := utils.NewTable(cmdIO.Out, true, true, true)
table.SetAlignment(tablewriter.ALIGN_CENTER)
table.SetHeader(tableHeader)
input := "c"
var err error
reader := bufio.NewReader(cmdIO.In)
for i := 0; i < pageNum; i++ {
pageStart := i * pageSize
pageEnd := pageStart + pageSize
if pageEnd > len(dataLines) {
pageEnd = len(dataLines)
}
if input == "c" || input == "continue" {
for j := pageStart; j < pageEnd; j++ {
table.Append(strings.Split(dataLines[j], ","))
}
table.Render()
table.ClearRows()
} else if input == "q" || input == "quit" {
break
} else {
colorprint.PrintInteractive(cmdIO.Out, "Invalid cmd. Type q or quit to quit, "+
"c or continue to show next page.\n")
i--
}
if pageEnd == len(dataLines) {
break
}
msg := fmt.Sprintf("Page [%d/%d]. Type q or quit to quit, c or continue to show next page.\n", i, pageNum)
colorprint.PrintInteractive(cmdIO.Out, msg)
input, err = reader.ReadString('\n')
if err != nil {
colorprint.PrintFail(cmdIO.Out, "Failed to read input: ", err.Error(), "\n")
break
}
input = strings.ToLower(strings.TrimSpace(input))
}
}