From 1ebae299d580948c5830041c133a365d56e410f3 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 30 Jun 2026 16:42:57 +0800 Subject: [PATCH 1/4] feat: Optimize SSH session performance on Ubuntu 25. --- agent/utils/websocket/process_data.go | 86 ++++++++++++++++++- frontend/src/views/host/ssh/session/index.vue | 2 + 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/agent/utils/websocket/process_data.go b/agent/utils/websocket/process_data.go index 3e4f52573ec8..ff94d1d5399f 100644 --- a/agent/utils/websocket/process_data.go +++ b/agent/utils/websocket/process_data.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "os" + "os/exec" + "strconv" "strings" "time" @@ -250,8 +252,14 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() + result = loadLoginctlSSHSessions(ctx, config) + if len(result) > 0 { + res, err = json.Marshal(result) + return + } + users, err = host.UsersWithContext(ctx) - if err != nil { + if err != nil || len(users) == 0 { res, err = json.Marshal(result) return } @@ -286,12 +294,12 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { if name != "sshd" || proc.Pid == 0 { continue } - connections, _ := proc.Connections() + connections, _ := proc.ConnectionsWithContext(ctx) if len(connections) == 0 { continue } - cmdline, cmdErr := proc.Cmdline() + cmdline, cmdErr := proc.CmdlineWithContext(ctx) if cmdErr != nil { continue } @@ -320,6 +328,78 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { return } +func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) []sshSession { + if _, err := exec.LookPath("loginctl"); err != nil { + return nil + } + output, err := exec.CommandContext(ctx, "loginctl", "list-sessions", "--no-legend", "--no-pager").Output() + if err != nil { + return nil + } + var result []sshSession + for _, line := range strings.Split(string(output), "\n") { + fields := strings.Fields(line) + if len(fields) == 0 { + continue + } + sessionOutput, err := exec.CommandContext(ctx, "loginctl", "show-session", fields[0], "--no-pager", + "-p", "Name", "-p", "Remote", "-p", "RemoteHost", "-p", "TTY", "-p", "Timestamp", "-p", "Leader", "-p", "Service").Output() + if err != nil { + continue + } + session, ok := parseLoginctlSSHSession(string(sessionOutput)) + if !ok { + continue + } + if config.LoginUser != "" && !strings.Contains(session.Username, config.LoginUser) { + continue + } + if config.LoginIP != "" && !strings.Contains(session.Host, config.LoginIP) { + continue + } + result = append(result, session) + } + return result +} + +func parseLoginctlSSHSession(output string) (sshSession, bool) { + props := map[string]string{} + for _, line := range strings.Split(output, "\n") { + key, value, ok := strings.Cut(line, "=") + if ok { + props[key] = strings.TrimSpace(value) + } + } + service := props["Service"] + if props["Remote"] != "yes" || props["Name"] == "" || props["RemoteHost"] == "" || (service != "sshd" && service != "ssh") { + return sshSession{}, false + } + pid, _ := strconv.ParseInt(props["Leader"], 10, 32) + return sshSession{ + Username: props["Name"], + Host: props["RemoteHost"], + Terminal: props["TTY"], + PID: int32(pid), + LoginTime: parseLoginctlTimestamp(props["Timestamp"]), + }, true +} + +func parseLoginctlTimestamp(value string) string { + fields := strings.Fields(value) + for i := 0; i < len(fields)-1; i++ { + if strings.Count(fields[i], "-") != 2 || strings.Count(fields[i+1], ":") != 2 { + continue + } + candidate := fields[i] + " " + fields[i+1] + t, err := time.ParseInLocation("2006-01-02 15:04:05", candidate, time.Local) + if err != nil { + return candidate + } + return t.Format("2006-1-2 15:04:05") + } + return value +} + func getNetConnections(config NetConfig) (res []byte, err error) { result := make([]ProcessConnect, 0, 1024) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) diff --git a/frontend/src/views/host/ssh/session/index.vue b/frontend/src/views/host/ssh/session/index.vue index 6d2606153f18..5d66fc5f1c5f 100644 --- a/frontend/src/views/host/ssh/session/index.vue +++ b/frontend/src/views/host/ssh/session/index.vue @@ -134,6 +134,8 @@ const stop = async (PID: number) => { .then(async () => { try { await stopProcess({ PID: PID }); + data.value = data.value.filter((item: any) => item.PID !== PID); + search(); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); } catch (error) { MsgError(error); From 2653f6fe4cfd4a87c22497ff192481b826f9f760 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 30 Jun 2026 16:50:14 +0800 Subject: [PATCH 2/4] feat: Optimize SSH session performance on Ubuntu 25. --- agent/utils/websocket/process_data.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/agent/utils/websocket/process_data.go b/agent/utils/websocket/process_data.go index ff94d1d5399f..3b1c5d3f4f75 100644 --- a/agent/utils/websocket/process_data.go +++ b/agent/utils/websocket/process_data.go @@ -252,8 +252,7 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() - result = loadLoginctlSSHSessions(ctx, config) - if len(result) > 0 { + if result, ok := loadLoginctlSSHSessions(ctx, config); ok { res, err = json.Marshal(result) return } @@ -328,13 +327,13 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { return } -func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) []sshSession { +func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) ([]sshSession, bool) { if _, err := exec.LookPath("loginctl"); err != nil { - return nil + return nil, false } output, err := exec.CommandContext(ctx, "loginctl", "list-sessions", "--no-legend", "--no-pager").Output() if err != nil { - return nil + return nil, false } var result []sshSession for _, line := range strings.Split(string(output), "\n") { @@ -359,7 +358,7 @@ func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) []ssh } result = append(result, session) } - return result + return result, true } func parseLoginctlSSHSession(output string) (sshSession, bool) { From 043fd596ed63cb30b55a0de127c3a26745e0f524 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 30 Jun 2026 17:00:38 +0800 Subject: [PATCH 3/4] feat: Optimize SSH session performance on Ubuntu 25. --- agent/utils/websocket/process_data.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/utils/websocket/process_data.go b/agent/utils/websocket/process_data.go index 3b1c5d3f4f75..e1e0a56888d3 100644 --- a/agent/utils/websocket/process_data.go +++ b/agent/utils/websocket/process_data.go @@ -342,7 +342,7 @@ func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) ([]ss continue } sessionOutput, err := exec.CommandContext(ctx, "loginctl", "show-session", fields[0], "--no-pager", - "-p", "Name", "-p", "Remote", "-p", "RemoteHost", "-p", "TTY", "-p", "Timestamp", "-p", "Leader", "-p", "Service").Output() + "-p", "Name", "-p", "Remote", "-p", "RemoteHost", "-p", "TTY", "-p", "Timestamp", "-p", "Leader", "-p", "Service", "-p", "State").Output() if err != nil { continue } @@ -370,7 +370,7 @@ func parseLoginctlSSHSession(output string) (sshSession, bool) { } } service := props["Service"] - if props["Remote"] != "yes" || props["Name"] == "" || props["RemoteHost"] == "" || (service != "sshd" && service != "ssh") { + if props["Remote"] != "yes" || props["Name"] == "" || props["RemoteHost"] == "" || props["State"] != "active" || (service != "sshd" && service != "ssh") { return sshSession{}, false } pid, _ := strconv.ParseInt(props["Leader"], 10, 32) From 8127bec66df03ff02c39f720edd9e6a609318142 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 30 Jun 2026 17:19:45 +0800 Subject: [PATCH 4/4] feat: Optimize SSH session performance on Ubuntu 25. --- agent/utils/websocket/process_data.go | 34 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/agent/utils/websocket/process_data.go b/agent/utils/websocket/process_data.go index e1e0a56888d3..392326075fb2 100644 --- a/agent/utils/websocket/process_data.go +++ b/agent/utils/websocket/process_data.go @@ -252,8 +252,8 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() - if result, ok := loadLoginctlSSHSessions(ctx, config); ok { - res, err = json.Marshal(result) + if sessions, ok := loadLoginctlSSHSessions(ctx); ok && len(sessions) > 0 { + res, err = json.Marshal(filterSSHSessions(sessions, config)) return } @@ -282,6 +282,22 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { return } + connections, err := net.ConnectionsMaxWithContext(ctx, "all", 32768) + if err != nil { + res, err = json.Marshal(result) + return + } + pidConnections := make(map[int32][]net.ConnectionStat, 256) + for _, conn := range connections { + if conn.Pid == 0 || conn.Raddr.IP == "" { + continue + } + if _, ok := usersByHost[conn.Raddr.IP]; !ok { + continue + } + pidConnections[conn.Pid] = append(pidConnections[conn.Pid], conn) + } + processes, err = process.ProcessesWithContext(ctx) if err != nil { res, err = json.Marshal(result) @@ -293,7 +309,7 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { if name != "sshd" || proc.Pid == 0 { continue } - connections, _ := proc.ConnectionsWithContext(ctx) + connections := pidConnections[proc.Pid] if len(connections) == 0 { continue } @@ -327,7 +343,7 @@ func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { return } -func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) ([]sshSession, bool) { +func loadLoginctlSSHSessions(ctx context.Context) ([]sshSession, bool) { if _, err := exec.LookPath("loginctl"); err != nil { return nil, false } @@ -350,6 +366,14 @@ func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) ([]ss if !ok { continue } + result = append(result, session) + } + return result, true +} + +func filterSSHSessions(sessions []sshSession, config SSHSessionConfig) []sshSession { + result := make([]sshSession, 0, len(sessions)) + for _, session := range sessions { if config.LoginUser != "" && !strings.Contains(session.Username, config.LoginUser) { continue } @@ -358,7 +382,7 @@ func loadLoginctlSSHSessions(ctx context.Context, config SSHSessionConfig) ([]ss } result = append(result, session) } - return result, true + return result } func parseLoginctlSSHSession(output string) (sshSession, bool) {