From b34b8fd23356b8ea2216405ae3d7880c1184da91 Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:55:21 +0200 Subject: [PATCH 1/3] Add telemetry event for SSH tunnel connections --- experimental/ssh/internal/client/client.go | 40 ++++++++++++++++++ libs/telemetry/protos/frontend_log.go | 1 + libs/telemetry/protos/ssh_tunnel.go | 47 ++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 libs/telemetry/protos/ssh_tunnel.go diff --git a/experimental/ssh/internal/client/client.go b/experimental/ssh/internal/client/client.go index 5ab096d2929..655ad5fdd69 100644 --- a/experimental/ssh/internal/client/client.go +++ b/experimental/ssh/internal/client/client.go @@ -28,6 +28,8 @@ import ( "github.com/databricks/cli/internal/build" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/telemetry" + "github.com/databricks/cli/libs/telemetry/protos" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/retries" "github.com/databricks/databricks-sdk-go/service/compute" @@ -300,6 +302,9 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt version := build.GetInfo().Version + isReconnect := opts.ServerMetadata != "" + var serverStartTimeMs int64 + if opts.ServerMetadata == "" { cmdio.LogString(ctx, "Uploading binaries...") sp := cmdio.NewSpinner(ctx, cmdio.WithElapsedTime()) @@ -309,14 +314,17 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt if err != nil { return fmt.Errorf("failed to upload ssh-tunnel binaries: %w", err) } + serverStartTime := time.Now() userName, serverPort, clusterID, err = ensureSSHServerIsRunning(ctx, client, version, secretScopeName, opts) if err != nil { + logSshTunnelEvent(ctx, opts, false, isReconnect, 0) if opts.IsServerlessMode() && opts.Accelerator == "" && errors.Is(err, errServerMetadata) { return fmt.Errorf("failed to ensure that ssh server is running: %w\n\n"+ cmdio.Yellow(ctx, "This may be because serverless compute without an accelerator is in private preview.\nContact your Databricks account team to enroll."), err) } return fmt.Errorf("failed to ensure that ssh server is running: %w", err) } + serverStartTimeMs = time.Since(serverStartTime).Milliseconds() } else { // Metadata format: ",," metadata := strings.Split(opts.ServerMetadata, ",") @@ -353,6 +361,8 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt cmdio.LogString(ctx, "Connected!") } + logSshTunnelEvent(ctx, opts, true, isReconnect, serverStartTimeMs) + if opts.ProxyMode { return runSSHProxy(ctx, client, serverPort, clusterID, opts) } else if opts.IDE != "" { @@ -726,3 +736,33 @@ func ensureSSHServerIsRunning(ctx context.Context, client *databricks.WorkspaceC return userName, serverPort, effectiveClusterID, nil } + +func logSshTunnelEvent(ctx context.Context, opts ClientOptions, isSuccess, isReconnect bool, serverStartTimeMs int64) { + computeType := protos.SshTunnelComputeTypeDedicated + if opts.IsServerlessMode() { + computeType = protos.SshTunnelComputeTypeServerless + } + + var clientMode protos.SshTunnelClientMode + switch { + case opts.ProxyMode: + clientMode = protos.SshTunnelClientModeProxy + case opts.IDE != "": + clientMode = protos.SshTunnelClientModeIDE + default: + clientMode = protos.SshTunnelClientModeSSH + } + + telemetry.Log(ctx, protos.DatabricksCliLog{ + SshTunnelEvent: &protos.SshTunnelEvent{ + ComputeType: computeType, + AcceleratorType: opts.Accelerator, + IdeType: opts.IDE, + ClientMode: clientMode, + IsReconnect: isReconnect, + AutoStartCluster: opts.AutoStartCluster, + ServerStartTimeMs: serverStartTimeMs, + IsSuccess: isSuccess, + }, + }) +} diff --git a/libs/telemetry/protos/frontend_log.go b/libs/telemetry/protos/frontend_log.go index 7e6ab1012b7..816297a8eef 100644 --- a/libs/telemetry/protos/frontend_log.go +++ b/libs/telemetry/protos/frontend_log.go @@ -19,4 +19,5 @@ type DatabricksCliLog struct { CliTestEvent *CliTestEvent `json:"cli_test_event,omitempty"` BundleInitEvent *BundleInitEvent `json:"bundle_init_event,omitempty"` BundleDeployEvent *BundleDeployEvent `json:"bundle_deploy_event,omitempty"` + SshTunnelEvent *SshTunnelEvent `json:"ssh_tunnel_event,omitempty"` } diff --git a/libs/telemetry/protos/ssh_tunnel.go b/libs/telemetry/protos/ssh_tunnel.go new file mode 100644 index 00000000000..7eddd896198 --- /dev/null +++ b/libs/telemetry/protos/ssh_tunnel.go @@ -0,0 +1,47 @@ +package protos + +type SshTunnelComputeType string + +const ( + SshTunnelComputeTypeUnspecified SshTunnelComputeType = "TYPE_UNSPECIFIED" + SshTunnelComputeTypeDedicated SshTunnelComputeType = "DEDICATED" + SshTunnelComputeTypeServerless SshTunnelComputeType = "SERVERLESS" +) + +type SshTunnelClientMode string + +const ( + SshTunnelClientModeUnspecified SshTunnelClientMode = "TYPE_UNSPECIFIED" + SshTunnelClientModeSSH SshTunnelClientMode = "SSH_CLIENT" + SshTunnelClientModeProxy SshTunnelClientMode = "PROXY" + SshTunnelClientModeIDE SshTunnelClientMode = "IDE" +) + +// SshTunnelEvent is emitted when a user establishes an SSH tunnel connection +// via the Databricks CLI. +type SshTunnelEvent struct { + // Type of compute: dedicated cluster or serverless. + ComputeType SshTunnelComputeType `json:"compute_type,omitempty"` + + // GPU accelerator type for serverless compute. + AcceleratorType string `json:"accelerator_type,omitempty"` + + // IDE that initiated the connection (e.g., "vscode", "cursor"). + IdeType string `json:"ide_type,omitempty"` + + // How the client is used: SSH client, proxy mode, or IDE mode. + ClientMode SshTunnelClientMode `json:"client_mode,omitempty"` + + // Whether this is a reconnection to an existing session. + IsReconnect bool `json:"is_reconnect,omitempty"` + + // Whether the cluster was auto-started by the CLI. + AutoStartCluster bool `json:"auto_start_cluster,omitempty"` + + // Time in milliseconds spent starting the SSH server. + // Zero if server was already running. + ServerStartTimeMs int64 `json:"server_start_time_ms"` + + // Whether the connection was successful. + IsSuccess bool `json:"is_success,omitempty"` +} From 0d5be274a79b46fa58199cd8bcfedb56b9a21a4a Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 8 May 2026 16:26:04 +0200 Subject: [PATCH 2/3] Emit SSH tunnel telemetry on every invocation using defer Move telemetry event emission to a deferred closure so it fires on all exit paths (success, server-start failure, binary-upload failure, cluster-state check failure, etc.), not only on the two explicit call sites that existed before. Co-authored-by: Isaac --- experimental/ssh/internal/client/client.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/experimental/ssh/internal/client/client.go b/experimental/ssh/internal/client/client.go index 655ad5fdd69..6f5f6dc646f 100644 --- a/experimental/ssh/internal/client/client.go +++ b/experimental/ssh/internal/client/client.go @@ -265,6 +265,13 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt } } + isReconnect := opts.ServerMetadata != "" + var serverStartTimeMs int64 + isSuccess := false + defer func() { + logSshTunnelEvent(ctx, opts, isSuccess, isReconnect, serverStartTimeMs) + }() + // Only check cluster state for dedicated clusters if !opts.IsServerlessMode() { cmdio.LogString(ctx, "Checking cluster state...") @@ -302,9 +309,6 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt version := build.GetInfo().Version - isReconnect := opts.ServerMetadata != "" - var serverStartTimeMs int64 - if opts.ServerMetadata == "" { cmdio.LogString(ctx, "Uploading binaries...") sp := cmdio.NewSpinner(ctx, cmdio.WithElapsedTime()) @@ -317,7 +321,6 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt serverStartTime := time.Now() userName, serverPort, clusterID, err = ensureSSHServerIsRunning(ctx, client, version, secretScopeName, opts) if err != nil { - logSshTunnelEvent(ctx, opts, false, isReconnect, 0) if opts.IsServerlessMode() && opts.Accelerator == "" && errors.Is(err, errServerMetadata) { return fmt.Errorf("failed to ensure that ssh server is running: %w\n\n"+ cmdio.Yellow(ctx, "This may be because serverless compute without an accelerator is in private preview.\nContact your Databricks account team to enroll."), err) @@ -361,7 +364,7 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt cmdio.LogString(ctx, "Connected!") } - logSshTunnelEvent(ctx, opts, true, isReconnect, serverStartTimeMs) + isSuccess = true if opts.ProxyMode { return runSSHProxy(ctx, client, serverPort, clusterID, opts) From 25f0d85465fb32db095aadc5ff90ff9cf853a12c Mon Sep 17 00:00:00 2001 From: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Date: Fri, 8 May 2026 16:37:27 +0200 Subject: [PATCH 3/3] Fix gofmt alignment in SshTunnelClientMode const block Co-authored-by: Isaac --- libs/telemetry/protos/ssh_tunnel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/telemetry/protos/ssh_tunnel.go b/libs/telemetry/protos/ssh_tunnel.go index 7eddd896198..42be8233b7f 100644 --- a/libs/telemetry/protos/ssh_tunnel.go +++ b/libs/telemetry/protos/ssh_tunnel.go @@ -12,9 +12,9 @@ type SshTunnelClientMode string const ( SshTunnelClientModeUnspecified SshTunnelClientMode = "TYPE_UNSPECIFIED" - SshTunnelClientModeSSH SshTunnelClientMode = "SSH_CLIENT" - SshTunnelClientModeProxy SshTunnelClientMode = "PROXY" - SshTunnelClientModeIDE SshTunnelClientMode = "IDE" + SshTunnelClientModeSSH SshTunnelClientMode = "SSH_CLIENT" + SshTunnelClientModeProxy SshTunnelClientMode = "PROXY" + SshTunnelClientModeIDE SshTunnelClientMode = "IDE" ) // SshTunnelEvent is emitted when a user establishes an SSH tunnel connection