feat: 优化进程守护状态获取方式 (#1943)

This commit is contained in:
ssongliu 2023-08-14 14:02:11 +08:00 committed by GitHub
parent b78b0e67ac
commit 35b8532eaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 250 additions and 47 deletions

View File

@ -189,6 +189,16 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
helper.SuccessWithData(c, configs)
}
// @Tags Host tool
// @Summary Load Supervisor process status
// @Description 获取 Supervisor 进程状态
// @Success 200 {array} response.ProcessStatus
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process/load [post]
func (b *BaseApi) LoadProcessStatus(c *gin.Context) {
helper.SuccessWithData(c, hostToolService.LoadProcessStatus())
}
// @Tags Host tool
// @Summary Get Supervisor process config
// @Description 操作 Supervisor 进程文件

View File

@ -3,6 +3,14 @@ package service
import (
"bytes"
"fmt"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/buserr"
@ -14,11 +22,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
"gopkg.in/ini.v1"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
)
type HostToolService struct{}
@ -31,6 +34,7 @@ type IHostToolService interface {
GetToolLog(req request.HostToolLogReq) (string, error)
OperateSupervisorProcess(req request.SupervisorProcessConfig) error
GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error)
LoadProcessStatus() []response.ProcessStatus
OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error)
}
@ -364,6 +368,57 @@ func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcess
return nil
}
func (h *HostToolService) LoadProcessStatus() []response.ProcessStatus {
var datas []response.ProcessStatus
statuLines, err := cmd.Exec("supervisorctl status")
if err != nil {
return datas
}
lines := strings.Split(string(statuLines), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) > 1 {
datas = append(datas, response.ProcessStatus{Name: fields[0]})
}
}
var wg sync.WaitGroup
wg.Add(len(datas))
for i := 0; i < len(datas); i++ {
go func(index int) {
for t := 0; t < 3; t++ {
status, err := cmd.ExecWithTimeOut(fmt.Sprintf("supervisorctl status %s", datas[index].Name), 3*time.Second)
if err != nil {
time.Sleep(2 * time.Second)
continue
}
fields := strings.Fields(status)
if len(fields) < 5 {
time.Sleep(2 * time.Second)
continue
}
datas[index].Name = fields[0]
datas[index].Status = fields[1]
if fields[1] != "RUNNING" {
datas[index].Msg = strings.Join(fields[2:], " ")
time.Sleep(1 * time.Second)
continue
}
datas[index].PID = strings.TrimSuffix(fields[3], ",")
datas[index].Uptime = fields[5]
break
}
if len(datas[index].Status) == 0 {
datas[index].Status = "FATAL"
datas[index].Msg = "Timeout for getting process status"
}
wg.Done()
}(i)
}
wg.Wait()
return datas
}
func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) {
var (
result []response.SupervisorProcessConfig
@ -400,7 +455,6 @@ func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorPro
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
config.Numprocs = numprocs.Value()
}
_ = getProcessStatus(&config)
result = append(result, config)
}
}
@ -528,29 +582,3 @@ func getProcessName(name, numprocs string) []string {
}
return processNames
}
func getProcessStatus(config *response.SupervisorProcessConfig) error {
var (
processNames = []string{"status"}
)
processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...)
output, _ := exec.Command("supervisorctl", processNames...).Output()
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) >= 5 {
status := response.ProcessStatus{
Name: fields[0],
Status: fields[1],
}
if fields[1] == "RUNNING" {
status.PID = strings.TrimSuffix(fields[3], ",")
status.Uptime = fields[5]
} else {
status.Msg = strings.Join(fields[2:], " ")
}
config.Status = append(config.Status, status)
}
}
return nil
}

View File

@ -54,6 +54,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/tool/operate", baseApi.OperateTool)
hostRouter.POST("/tool/config", baseApi.OperateToolConfig)
hostRouter.POST("/tool/log", baseApi.GetToolLog)
hostRouter.POST("/tool/supervisor/process/load", baseApi.LoadProcessStatus)
hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess)
hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess)
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)

View File

@ -6340,6 +6340,31 @@ const docTemplate = `{
}
}
},
"/host/tool/supervisor/process/load": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 Supervisor 进程状态",
"tags": [
"Host tool"
],
"summary": "Load Supervisor process status",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/response.ProcessStatus"
}
}
}
}
}
},
"/hosts": {
"post": {
"security": [
@ -17024,6 +17049,26 @@ const docTemplate = `{
}
}
},
"response.ProcessStatus": {
"type": "object",
"properties": {
"PID": {
"type": "string"
},
"msg": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": "string"
},
"uptime": {
"type": "string"
}
}
},
"response.WebsiteAcmeAccountDTO": {
"type": "object",
"properties": {

View File

@ -6333,6 +6333,31 @@
}
}
},
"/host/tool/supervisor/process/load": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取 Supervisor 进程状态",
"tags": [
"Host tool"
],
"summary": "Load Supervisor process status",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/response.ProcessStatus"
}
}
}
}
}
},
"/hosts": {
"post": {
"security": [
@ -17017,6 +17042,26 @@
}
}
},
"response.ProcessStatus": {
"type": "object",
"properties": {
"PID": {
"type": "string"
},
"msg": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": "string"
},
"uptime": {
"type": "string"
}
}
},
"response.WebsiteAcmeAccountDTO": {
"type": "object",
"properties": {

View File

@ -3714,6 +3714,19 @@ definitions:
uploadMaxSize:
type: string
type: object
response.ProcessStatus:
properties:
PID:
type: string
msg:
type: string
name:
type: string
status:
type: string
uptime:
type: string
type: object
response.WebsiteAcmeAccountDTO:
properties:
createdAt:
@ -7880,6 +7893,21 @@ paths:
formatEN: '[operate] Supervisor Process Config file'
formatZH: '[operate] Supervisor 进程文件 '
paramKeys: []
/host/tool/supervisor/process/load:
post:
description: 获取 Supervisor 进程状态
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/response.ProcessStatus'
type: array
security:
- ApiKeyAuth: []
summary: Load Supervisor process status
tags:
- Host tool
/hosts:
post:
consumes:

View File

@ -29,6 +29,10 @@ export const OperateSupervisorProcess = (req: HostTool.ProcessReq) => {
return http.post<any>(`/hosts/tool/supervisor/process`, req, 100000);
};
export const LoadProcessStatus = () => {
return http.post<Array<HostTool.ProcessStatus>>(`/hosts/tool/supervisor/process/load`, {}, 40000);
};
export const GetSupervisorProcess = () => {
return http.get<HostTool.SupersivorProcess>(`/hosts/tool/supervisor/process`);
};

View File

@ -1671,10 +1671,10 @@ const message = {
config: 'Supervisor configuration',
primaryConfig: 'Main configuration file location',
notSupportCrl: 'The supervisorctl is not detected, please refer to the official document for installation',
user: 'start user',
command: 'start command',
dir: 'run directory',
numprocs: 'Number of processes',
user: 'User',
command: 'Command',
dir: 'Directory',
numprocs: 'Numprocs',
initWarn:
'The initialization operation needs to modify the [include] files parameter of the configuration file, the directory where the modified service configuration file is located: 1panel installation directory/1panel/tools/supervisord/supervisor.d/',
operatorHelper: 'Operation {1} will be performed on {0}, continue? ',

View File

@ -73,6 +73,8 @@ const handleReady = (payload) => {
};
let timer: NodeJS.Timer | null = null;
const em = defineEmits(['search']);
const getContent = () => {
loading.value = true;
OperateSupervisorProcessFile(req)
@ -114,8 +116,9 @@ const submit = () => {
loading.value = true;
OperateSupervisorProcessFile(updateReq)
.then(() => {
em('search');
open.value = false;
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
getContent();
})
.finally(() => {
loading.value = false;

View File

@ -20,28 +20,41 @@
</template>
<template #main v-if="showTable">
<ComplexTable :data="data" :class="{ mask: !supervisorStatus.isRunning }">
<el-table-column :label="$t('commons.table.name')" fix prop="name" width="150px"></el-table-column>
<el-table-column
:label="$t('commons.table.name')"
fix
prop="name"
min-width="80px"
show-overflow-tooltip
></el-table-column>
<el-table-column
:label="$t('tool.supervisor.command')"
prop="command"
min-width="100px"
fix
show-overflow-tooltip
></el-table-column>
<el-table-column
:label="$t('tool.supervisor.dir')"
prop="dir"
min-width="100px"
fix
show-overflow-tooltip
></el-table-column>
<el-table-column :label="$t('tool.supervisor.user')" prop="user" width="100px"></el-table-column>
<el-table-column
:label="$t('tool.supervisor.user')"
prop="user"
show-overflow-tooltip
min-width="50px"
></el-table-column>
<el-table-column
:label="$t('tool.supervisor.numprocs')"
prop="numprocs"
width="100px"
min-width="60px"
></el-table-column>
<el-table-column :label="$t('tool.supervisor.manage')" width="100px">
<el-table-column :label="$t('tool.supervisor.manage')" min-width="80px">
<template #default="{ row }">
<div v-if="row.status && row.status.length > 0">
<div v-if="row.status && row.status.length > 0 && row.hasLoad">
<el-button
v-if="checkStatus(row.status) === 'RUNNING'"
link
@ -70,11 +83,14 @@
{{ $t('commons.status.stopped') }}
</el-button>
</div>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" width="100px">
<el-table-column :label="$t('commons.table.status')" min-width="60px">
<template #default="{ row }">
<div v-if="row.status">
<div v-if="row.hasLoad">
<el-popover placement="bottom" :width="600" trigger="hover">
<template #reference>
<el-button type="primary" link v-if="row.status.length > 1">
@ -111,6 +127,9 @@
</el-table>
</el-popover>
</div>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
</div>
</template>
</el-table-column>
<fu-table-operations
@ -118,7 +137,7 @@
:buttons="buttons"
:label="$t('commons.table.operate')"
:fixed="mobile ? false : 'right'"
width="250px"
width="280px"
fix
/>
</ComplexTable>
@ -126,7 +145,7 @@
<ConfigSuperVisor v-if="setSuperVisor" />
</LayoutContent>
<Create ref="createRef" @close="search"></Create>
<File ref="fileRef"></File>
<File ref="fileRef" @search="search"></File>
</div>
</template>
@ -138,7 +157,7 @@ import ConfigSuperVisor from './config/index.vue';
import { computed, onMounted } from 'vue';
import Create from './create/index.vue';
import File from './file/index.vue';
import { GetSupervisorProcess, OperateSupervisorProcess } from '@/api/modules/host-tool';
import { GetSupervisorProcess, LoadProcessStatus, OperateSupervisorProcess } from '@/api/modules/host-tool';
import { GlobalStore } from '@/store';
import i18n from '@/lang';
import { HostTool } from '@/api/interface/host-tool';
@ -193,6 +212,7 @@ const openCreate = () => {
const search = async () => {
loading.value = true;
loadStatus();
try {
const res = await GetSupervisorProcess();
data.value = res.data;
@ -200,6 +220,25 @@ const search = async () => {
loading.value = false;
};
const loadStatus = async () => {
const res = await LoadProcessStatus();
let stats = res.data || [];
if (stats.length === 0) {
return;
}
for (const process of data.value) {
process.status = [];
for (const item of stats) {
if (process.name === item.name) {
process.status.push(item);
}
}
if (process.status.length !== 0) {
process.hasLoad = true;
}
}
};
const mobile = computed(() => {
return globalStore.isMobile();
});