mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-21 14:35:22 +08:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user