-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The input speed of ConPTY / ConHost is very slow. #13594
Comments
I thought it was an issue of PowerShell/Win32-OpenSSH#1944 . Actually, it should be an issue of |
There is a minimal example.
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <windows.h>
int ReadTestData() {
unsigned int size = 0;
if (scanf("%u", &size) != 1) {
puts("invalid size");
return -11;
}
char buf[100];
unsigned int last_count = 0, read_count = 0;
struct timeval begin_time, last_time, current_time;
gettimeofday(&begin_time, NULL);
last_time = begin_time;
while (read_count < size) {
ssize_t n = read(0, buf, sizeof(buf));
if (n <= 0) {
puts("read error");
return -12;
}
read_count += n;
gettimeofday(¤t_time, NULL);
if (current_time.tv_sec - last_time.tv_sec > 1) {
long t = (current_time.tv_sec - last_time.tv_sec) * 1000 +
(current_time.tv_usec - last_time.tv_usec) / 1000;
printf("read %u bytes in %ldms, speed: %.2fKB/s\n",
read_count - last_count, t,
(read_count - last_count) / 1024.0 * 1000 / t);
last_time = current_time;
last_count = read_count;
}
}
gettimeofday(¤t_time, NULL);
long t = (current_time.tv_sec - begin_time.tv_sec) * 1000 +
(current_time.tv_usec - begin_time.tv_usec) / 1000;
printf("read %u bytes in %ldms, speed: %.2fKB/s\n", read_count, t,
read_count / 1024.0 * 1000 / t);
return 0;
}
int main() {
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin == INVALID_HANDLE_VALUE) {
puts("GetStdHandle failed");
return -1;
}
DWORD fdwSaveOldMode;
if (!GetConsoleMode(hStdin, &fdwSaveOldMode)) {
puts("GetConsoleMode failed");
return -2;
}
if (!SetConsoleMode(hStdin, 0)) {
puts("SetConsoleMode failed");
return -3;
}
int ret = ReadTestData();
SetConsoleMode(hStdin, fdwSaveOldMode);
return ret;
}
// gcc -o test_input test_input.c
package main
import (
"context"
"io"
"log"
"os"
"strconv"
"syscall"
"github.com/UserExistsError/conpty"
"golang.org/x/sys/windows"
)
func writeTestData(cpty *conpty.ConPty) {
const size int = 100
const count int = 0x10000
var data = make([]byte, size)
for i := 0; i < size; i++ {
data[i] = 'A'
}
data[size-2] = '\r'
data[size-1] = '\n'
cpty.Write([]byte(strconv.Itoa(size*count) + "\r\n"))
for i := 0; i < count; i++ {
cpty.Write(data)
}
// send more data to make sure exit
for i := 0; i < 1000; i++ {
cpty.Write(data)
}
}
func main() {
commandLine := "test_input"
var outMode uint32
outHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
if err != nil {
log.Fatalf("Failed to GetStdHandle: %v", err)
}
if err := windows.GetConsoleMode(windows.Handle(outHandle), &outMode); err != nil {
log.Fatalf("Failed to GetConsoleMode: %v", err)
}
if err := windows.SetConsoleMode(windows.Handle(outHandle),
windows.ENABLE_PROCESSED_OUTPUT|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|windows.DISABLE_NEWLINE_AUTO
_RETURN); err != nil {
log.Fatalf("Failed to SetConsoleMode: %v", err)
}
defer windows.SetConsoleMode(windows.Handle(outHandle), outMode)
cpty, err := conpty.Start(commandLine)
if err != nil {
log.Fatalf("Failed to spawn a pty for [%s]: %v", commandLine, err)
}
defer cpty.Close()
go func() {
go io.Copy(os.Stdout, cpty)
writeTestData(cpty)
}()
exitCode, err := cpty.Wait(context.Background())
if err != nil {
log.Fatalf("Error: %v", err)
}
log.Printf("ExitCode: %d", exitCode)
}
gcc -o test_input test_input.c
go mod init example.com/m/v2
go get github.com/UserExistsError/conpty
go run test_client.go
|
If change #include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <windows.h>
int ReadTestData(HANDLE hStdin) {
unsigned int size = 0;
if (scanf("%u", &size) != 1) {
puts("invalid size");
return -11;
}
DWORD cNumRead, i;
INPUT_RECORD irInBuf[128];
unsigned int last_count = 0, read_count = 0;
struct timeval begin_time, last_time, current_time;
gettimeofday(&begin_time, NULL);
last_time = begin_time;
while (read_count < size) {
if (!ReadConsoleInput(hStdin, // input buffer handle
irInBuf, // buffer to read into
128, // size of read buffer
&cNumRead)) // number of records read
{
puts("read error");
return -12;
}
for (i = 0; i < cNumRead; i++) {
if (irInBuf[i].EventType == KEY_EVENT &&
irInBuf[i].Event.KeyEvent.bKeyDown) {
read_count++;
}
}
gettimeofday(¤t_time, NULL);
if (current_time.tv_sec - last_time.tv_sec > 1) {
long t = (current_time.tv_sec - last_time.tv_sec) * 1000 +
(current_time.tv_usec - last_time.tv_usec) / 1000;
printf("read %u bytes in %ldms, speed: %.2fKB/s\n",
read_count - last_count, t,
(read_count - last_count) / 1024.0 * 1000 / t);
last_time = current_time;
last_count = read_count;
}
}
gettimeofday(¤t_time, NULL);
long t = (current_time.tv_sec - begin_time.tv_sec) * 1000 +
(current_time.tv_usec - begin_time.tv_usec) / 1000;
printf("read %u bytes in %ldms, speed: %.2fKB/s\n", read_count, t,
read_count / 1024.0 * 1000 / t);
return 0;
}
int main() {
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin == INVALID_HANDLE_VALUE) {
puts("GetStdHandle failed");
return -1;
}
DWORD fdwSaveOldMode;
if (!GetConsoleMode(hStdin, &fdwSaveOldMode)) {
puts("GetConsoleMode failed");
return -2;
}
if (!SetConsoleMode(hStdin, 0)) {
puts("SetConsoleMode failed");
return -3;
}
int ret = ReadTestData(hStdin);
SetConsoleMode(hStdin, fdwSaveOldMode);
return ret;
}
// gcc -o test_input test_input.c read 21825 bytes in 1499ms, spe ed: 14.22KB/s
|
Dunno how this fell out of the triage queue. That's weird. Thanks for the report! Someone will need to dig in and do some traces. ConPTY's input was definitely originally designed for user-driven input, not necessarily bulk input at the level that something like If someone wanted to be ambitious, I'd take a look at:
That's where the input comes in to conpty, attempts to be parsed into keys, and gets dumped to the input buffer. |
@zadjii-msft Thanks for your help. I wrote my own ssh client https://trzsz.github.io/ssh to avoid the issue. It works if the server is Linux. But the issue still exists if the server is Windows.
Is there other process between |
While testing this again I found that my recent performance improvements to our input handling have improved throughput by 2x already (~600kB/s). In particular, I think it was 5b44476. I bet we could make this another 100x faster though (assuming trzsz is not a bottleneck and doesn't use |
@lhecker Thanks. I'm new to conpty and conhost. One more question, how to specify which conhost.exe to use for conpty.dll? |
I know how to build conpty.dll and OpenConsole.exe. Is there a way to connect them? Or can I set the absolute path of conhost.exe in the conpty.dll source code? |
If you put |
Windows Terminal version
1.12.10982.0
Windows build number
10.0.19043.0
Other Software
https://github.com/UserExistsError/conpty v0.1.0
https://github.com/trzsz/trzsz-go v0.1.9
Steps to reproduce
Install trzsz-go, download trzsz_0.1.9_windows_x86_64.zip and unzip, you will get
trzsz.exe
,trz.exe
andtsz.exe
.open
cmd
execute
trzsz cmd
execute
trz
, a dialog will pop up.choose a file in the dialog, the file will be uploaded to the current directory.
Expected Behavior
The
trzsz
create aConPTY
process (cmd
), and send the file content as keystrokes.The
trz
receive the file content and save to the current directory.The upload speed should be
2MB/s
at least. Same as the download speed.Actual Behavior
The upload speed is only about
128KB/s
. Looks like the keystrokes are speed limited.The text was updated successfully, but these errors were encountered: