Add latency diagnostic to tsh (#35679)

The new `tsh latency` command allows visualizing latency to
resources in real time. Currently, supported is limited to SSH
instances, but may be extended to anything that implements a
`latency.Pinger` to support other protocols in the future.
This commit is contained in:
rosstimothy
2023-12-19 20:55:17 +00:00
committed by GitHub
parent 830c5ac3fc
commit 9be0b678c3
7 changed files with 311 additions and 22 deletions
+13
View File
@@ -33,6 +33,7 @@ import (
"github.com/gravitational/teleport/api/breaker"
"github.com/gravitational/teleport/api/client"
authpb "github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/proxy/transport/transportv1"
"github.com/gravitational/teleport/api/defaults"
transportv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1"
@@ -416,3 +417,15 @@ func (c *Client) ClusterDetails(ctx context.Context) (ClusterDetails, error) {
return ClusterDetails{FIPS: details.FipsEnabled}, nil
}
// Ping measures the round trip latency of sending a message to the Proxy.
func (c *Client) Ping(ctx context.Context) error {
// TODO(tross): Update to call Ping when it is added to the transport service.
// For now we don't really care what method is used we just want to measure
// how long it takes to get a reply. This will always fail with a not implemented
// error since the Proxy gRPC server doesn't serve the auth service proto. However,
// we use it because it's already imported in the api package.
clt := authpb.NewAuthServiceClient(c.grpcConn)
_, _ = clt.Ping(ctx, &authpb.PingRequest{})
return nil
}
+19 -10
View File
@@ -29,6 +29,7 @@ require (
github.com/ClickHouse/ch-go v0.61.0
github.com/ClickHouse/clickhouse-go/v2 v2.16.0
github.com/HdrHistogram/hdrhistogram-go v1.1.2
github.com/Masterminds/sprig/v3 v3.2.3
github.com/Microsoft/go-winio v0.6.1
github.com/ThalesIgnite/crypto11 v1.2.5
github.com/alecthomas/kingpin/v2 v2.3.2 // replaced
@@ -60,12 +61,16 @@ require (
github.com/beevik/etree v1.2.0
github.com/bufbuild/connect-go v1.10.0
github.com/buildkite/bintest/v3 v3.2.0
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.9.1
github.com/coreos/go-oidc v2.2.1+incompatible // replaced
github.com/coreos/go-semver v0.3.1
github.com/creack/pty v1.1.21
github.com/crewjam/saml v0.4.14
github.com/datastax/go-cassandra-native-protocol v0.0.0-20220706104457-5e8aad05cf90
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352
github.com/distribution/reference v0.5.0
github.com/dustin/go-humanize v1.0.1
github.com/elastic/go-elasticsearch/v8 v8.11.1
github.com/flynn/hid v0.0.0-20190502022136-f1b9b6cc019a
@@ -87,6 +92,7 @@ require (
github.com/google/btree v1.1.2
github.com/google/go-attestation v0.5.1
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.17.0
github.com/google/go-querystring v1.1.0
github.com/google/go-tpm-tools v0.4.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@@ -101,6 +107,7 @@ require (
github.com/gravitational/trace v1.3.1
github.com/gravitational/ttlmap v0.0.0-20171116003245-91fd36b9004c
github.com/grpc-ecosystem/go-grpc-middleware/providers/openmetrics/v2 v2.0.0-rc.3
github.com/guptarohit/asciigraph v0.5.6
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/icza/mjpeg v0.0.0-20220812133530-f79265a232f2
@@ -128,6 +135,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/moby/term v0.5.0
github.com/okta/okta-sdk-golang/v2 v2.20.0
github.com/opencontainers/go-digest v1.0.0
github.com/opensearch-project/opensearch-go/v2 v2.3.0
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
github.com/pelletier/go-toml v1.9.5
@@ -143,6 +151,8 @@ require (
github.com/schollz/progressbar/v3 v3.14.1
github.com/segmentio/parquet-go v0.0.0-20230622230624-510764ae9e80
github.com/sethvargo/go-diceware v0.3.0
github.com/sigstore/cosign/v2 v2.2.2
github.com/sigstore/sigstore v1.7.6
github.com/sijms/go-ora/v2 v2.7.9
github.com/sirupsen/logrus v1.9.3
github.com/snowflakedb/gosnowflake v1.7.1
@@ -208,15 +218,6 @@ require (
github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect
)
require (
github.com/Masterminds/sprig/v3 v3.2.3
github.com/distribution/reference v0.5.0
github.com/google/go-containerregistry v0.17.0
github.com/opencontainers/go-digest v1.0.0
github.com/sigstore/cosign/v2 v2.2.2
github.com/sigstore/sigstore v1.7.6
)
require (
cloud.google.com/go v0.110.10 // indirect
cloud.google.com/go/longrunning v0.5.4 // indirect
@@ -249,6 +250,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
@@ -256,6 +258,7 @@ require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect
@@ -360,12 +363,14 @@ require (
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lithammer/dedent v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
@@ -381,6 +386,10 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
+28 -2
View File
@@ -277,6 +277,8 @@ github.com/aws/aws-sigv4-auth-cassandra-gocql-driver-plugin v0.0.0-2022033116504
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw=
github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
@@ -319,6 +321,12 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -339,6 +347,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -793,6 +803,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/guptarohit/asciigraph v0.5.6 h1:0tra3HEhfdj1sP/9IedrCpfSiXYTtHdCgBhBL09Yx6E=
github.com/guptarohit/asciigraph v0.5.6/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -1023,6 +1035,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -1064,10 +1078,13 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -1134,6 +1151,14 @@ github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -1293,6 +1318,7 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+3 -9
View File
@@ -226,23 +226,17 @@ type SSHClient interface {
// SSH connection. To calculate round trip time, a keepalive@openssh.com request
// is sent.
type SSHPinger struct {
clt SSHClient
clock clockwork.Clock
clt SSHClient
}
// NewSSHPinger creates a new [SSHPinger] with the provided configuration.
func NewSSHPinger(clock clockwork.Clock, clt SSHClient) (*SSHPinger, error) {
func NewSSHPinger(clt SSHClient) (*SSHPinger, error) {
if clt == nil {
return nil, trace.BadParameter("ssh client not provided to SSHPinger")
}
if clock == nil {
clock = clockwork.NewRealClock()
}
return &SSHPinger{
clt: clt,
clock: clock,
clt: clt,
}, nil
}
+1 -1
View File
@@ -712,7 +712,7 @@ func monitorSessionLatency(ctx context.Context, clock clockwork.Clock, stream *W
return trace.Wrap(err, "creating websocket pinger")
}
sshPinger, err := latency.NewSSHPinger(clock, sshClient)
sshPinger, err := latency.NewSSHPinger(sshClient)
if err != nil {
return trace.Wrap(err, "creating ssh pinger")
}
+196
View File
@@ -0,0 +1,196 @@
// Teleport
// Copyright (C) 2023 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package common
import (
"context"
"fmt"
"time"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/gravitational/trace"
"github.com/guptarohit/asciigraph"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/diagnostics/latency"
)
// keyMap is used to display the help text at
// the bottom of the screen.
type keyMap struct {
quit key.Binding
}
func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.quit}
}
func (k keyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{{k.quit}}
}
var helpKeys = keyMap{
quit: key.NewBinding(
key.WithKeys("q", "esc", "ctrl+c"),
key.WithHelp("q", "quit"),
),
}
// latencyModel is a [tea.Model] that contains the state required
// to display the latency graph.
type latencyModel struct {
ready bool
h, w int
help help.Model
clientLabel, serverLabel string
clientLegend, serverLegend viewport.Model
clientMax, serverMax int64
clientLatency, serverLatency *utils.CircularBuffer
}
// newLatencyModel constructs a new latencyModel.
func newLatencyModel(clientLabel, serverLabel string) (*latencyModel, error) {
clientStats, err := utils.NewCircularBuffer(50)
if err != nil {
return nil, trace.Wrap(err)
}
serverStats, err := utils.NewCircularBuffer(50)
if err != nil {
return nil, trace.Wrap(err)
}
return &latencyModel{
clientLabel: clientLabel,
serverLabel: serverLabel,
help: help.New(),
clientLegend: viewport.New(1, 2),
serverLegend: viewport.New(1, 2),
clientLatency: clientStats,
serverLatency: serverStats,
}, nil
}
// Init is a noop but required to implement [tea.Model].
func (m *latencyModel) Init() tea.Cmd {
return nil
}
// Update reacts to screen resizing and new latency statistics.
func (m *latencyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
case tea.WindowSizeMsg:
m.ready = true
m.h = msg.Height
m.w = msg.Width
m.clientLegend.Width = m.w / 2
m.serverLegend.Width = m.clientLegend.Width
case latency.Statistics:
m.clientLatency.Add(float64(msg.Client))
m.serverLatency.Add(float64(msg.Server))
m.clientMax = max(m.clientMax, msg.Client)
m.serverMax = max(m.serverMax, msg.Server)
m.clientLegend.SetContent(fmt.Sprintf("%s: last recorded %dms (max %dms)\t", m.clientLabel, msg.Client, m.clientMax))
m.serverLegend.SetContent(fmt.Sprintf("%s: last recorded %dms (max %dms)", m.serverLabel, msg.Server, m.serverMax))
}
return m, nil
}
var (
// clientColor is the color of the client plot and the client information in the legend.
clientColor = asciigraph.Blue
// serverColor is the color of the server plot and the client information in the legend.
serverColor = asciigraph.Goldenrod
clientStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("%d", clientColor)))
serverStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("%d", serverColor)))
)
// View renders the current latency graph to be displayed.
func (m *latencyModel) View() string {
if !m.ready {
return ""
}
clientData := m.clientLatency.Data(150)
serverData := m.serverLatency.Data(150)
if clientData == nil || serverData == nil {
return ""
}
legend := lipgloss.JoinHorizontal(
lipgloss.Top,
clientStyle.Render(m.clientLegend.View()),
serverStyle.Render(m.serverLegend.View()),
)
plot := asciigraph.PlotMany(
[][]float64{clientData, serverData},
asciigraph.Height(m.h-4),
asciigraph.Width(m.w),
asciigraph.SeriesColors(clientColor, serverColor),
asciigraph.Caption(legend),
)
return lipgloss.JoinVertical(lipgloss.Top, plot, lipgloss.PlaceHorizontal(m.w, lipgloss.Bottom, m.help.View(helpKeys)))
}
// showLatency creates a [latency.Monitor] using the provided [latency.Pinger]s to capture latency and
// render it to the terminal via a plot graph.
func showLatency(ctx context.Context, clientPinger, serverPinger latency.Pinger, clientLabel, serverLabel string) error {
m, err := newLatencyModel(clientLabel, serverLabel)
if err != nil {
return trace.Wrap(err)
}
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithContext(ctx), tea.WithoutSignalHandler())
monitor, err := latency.NewMonitor(latency.MonitorConfig{
InitialPingInterval: time.Millisecond,
InitialReportInterval: 500 * time.Millisecond,
PingInterval: time.Second,
ReportInterval: time.Second,
ClientPinger: clientPinger,
ServerPinger: serverPinger,
Reporter: latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error {
p.Send(stats)
return nil
}),
})
if err != nil {
return trace.Wrap(err)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go monitor.Run(ctx)
if _, err := p.Run(); err != nil {
return trace.Wrap(err)
}
return nil
}
+51
View File
@@ -86,6 +86,7 @@ import (
"github.com/gravitational/teleport/lib/sshutils/x11"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/diagnostics/latency"
"github.com/gravitational/teleport/lib/utils/mlock"
"github.com/gravitational/teleport/tool/common"
)
@@ -952,6 +953,13 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {
// logout deletes obtained session certificates in ~/.tsh
logout := app.Command("logout", "Delete a cluster certificate.")
// latency
latency := app.Command("latency", "Run latency diagnostics.").Hidden()
latencySSH := latency.Command("ssh", "Measure latency to a particular SSH host.")
latencySSH.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cf.UserHost)
latencySSH.Flag("cluster", clusterHelp).Short('c').StringVar(&cf.SiteName)
// bench
bench := app.Command("bench", "Run Teleport benchmark tests.").Hidden()
bench.Flag("cluster", clusterHelp).Short('c').StringVar(&cf.SiteName)
@@ -1243,6 +1251,8 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {
err = onVersion(&cf)
case ssh.FullCommand():
err = onSSH(&cf)
case latencySSH.FullCommand():
err = onSSHLatency(&cf)
case benchSSH.FullCommand():
err = onBenchmark(
&cf,
@@ -3283,6 +3293,47 @@ func retryWithAccessRequest(
return trace.Wrap(fn())
}
func onSSHLatency(cf *CLIConf) error {
tc, err := makeClient(cf)
if err != nil {
return trace.Wrap(err)
}
clt, err := tc.ConnectToCluster(cf.Context)
if err != nil {
return trace.Wrap(err)
}
defer clt.Close()
// detect the common error when users use host:port address format
_, port, err := net.SplitHostPort(tc.Host)
// client has used host:port notation
if err == nil {
return trace.BadParameter("please use ssh subcommand with '--port=%v' flag instead of semicolon", port)
}
addr := net.JoinHostPort(tc.Host, strconv.Itoa(tc.HostPort))
nodeClient, err := tc.ConnectToNode(
cf.Context,
clt,
client.NodeDetails{Addr: addr, Namespace: tc.Namespace, Cluster: tc.SiteName},
tc.Config.HostLogin,
)
if err != nil {
tc.ExitStatus = 1
return trace.Wrap(err)
}
defer nodeClient.Close()
targetPinger, err := latency.NewSSHPinger(nodeClient.Client)
if err != nil {
return trace.Wrap(err)
}
return trace.Wrap(showLatency(cf.Context, clt.ProxyClient, targetPinger, "Proxy", tc.Host))
}
// onSSH executes 'tsh ssh' command
func onSSH(cf *CLIConf) error {
tc, err := makeClient(cf)