From dcf1c903a4edd6353f4d9feaea1d4505b7a46236 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 30 Sep 2026 11:51:50 +0000 Subject: [PATCH 1/8] Report PyDABs deploy telemetry per resource type Add a top-level `pydabs` section to the bundle deploy event with loader and mutator counts plus per-resource-type added/updated counts, covering only resources defined or modified in Python. The experimental.python_* fields are still populated and marked deprecated. Also fix loader and mutator counts being reported as 0 for bundles using the top-level `python` section; only `experimental/python` was read. Co-authored-by: Isaac --- .../telemetry-experimental/databricks.yml | 20 +++++++ .../python/telemetry-experimental/mutators.py | 11 ++++ .../telemetry-experimental/out.test.toml | 4 ++ .../python/telemetry-experimental/output.txt | 43 +++++++++++++++ .../telemetry-experimental/resources.py | 9 ++++ .../python/telemetry-experimental/script | 13 +++++ .../python/telemetry-experimental/test.toml | 4 ++ .../bundle/python/telemetry/databricks.yml | 19 +++++++ .../bundle/python/telemetry/mutators.py | 11 ++++ .../bundle/python/telemetry/out.test.toml | 4 ++ acceptance/bundle/python/telemetry/output.txt | 43 +++++++++++++++ .../bundle/python/telemetry/resources.py | 9 ++++ acceptance/bundle/python/telemetry/script | 13 +++++ acceptance/bundle/python/telemetry/test.toml | 4 ++ bundle/bundle.go | 18 ++++--- .../config/mutator/python/python_mutator.go | 15 +++++- .../mutator/python/python_mutator_test.go | 8 +-- bundle/phases/telemetry.go | 52 ++++++++++++++++--- bundle/phases/telemetry_test.go | 40 ++++++++++++++ libs/telemetry/protos/bundle_deploy.go | 44 ++++++++++++++-- 20 files changed, 359 insertions(+), 25 deletions(-) create mode 100644 acceptance/bundle/python/telemetry-experimental/databricks.yml create mode 100644 acceptance/bundle/python/telemetry-experimental/mutators.py create mode 100644 acceptance/bundle/python/telemetry-experimental/out.test.toml create mode 100644 acceptance/bundle/python/telemetry-experimental/output.txt create mode 100644 acceptance/bundle/python/telemetry-experimental/resources.py create mode 100644 acceptance/bundle/python/telemetry-experimental/script create mode 100644 acceptance/bundle/python/telemetry-experimental/test.toml create mode 100644 acceptance/bundle/python/telemetry/databricks.yml create mode 100644 acceptance/bundle/python/telemetry/mutators.py create mode 100644 acceptance/bundle/python/telemetry/out.test.toml create mode 100644 acceptance/bundle/python/telemetry/output.txt create mode 100644 acceptance/bundle/python/telemetry/resources.py create mode 100644 acceptance/bundle/python/telemetry/script create mode 100644 acceptance/bundle/python/telemetry/test.toml diff --git a/acceptance/bundle/python/telemetry-experimental/databricks.yml b/acceptance/bundle/python/telemetry-experimental/databricks.yml new file mode 100644 index 00000000000..f6deb34e0e8 --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/databricks.yml @@ -0,0 +1,20 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +experimental: + python: + resources: + - "resources:load_resources" + mutators: + - "mutators:update_job" + +resources: + jobs: + yaml_job: + name: "YAML Job" + schemas: + yaml_schema: + catalog_name: main + name: yaml_schema diff --git a/acceptance/bundle/python/telemetry-experimental/mutators.py b/acceptance/bundle/python/telemetry-experimental/mutators.py new file mode 100644 index 00000000000..9335dd63f19 --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/mutators.py @@ -0,0 +1,11 @@ +from dataclasses import replace + +from databricks.bundles.core import job_mutator +from databricks.bundles.jobs import Job + + +@job_mutator +def update_job(job: Job) -> Job: + assert isinstance(job.name, str) + + return replace(job, name=f"{job.name} (updated)") diff --git a/acceptance/bundle/python/telemetry-experimental/out.test.toml b/acceptance/bundle/python/telemetry-experimental/out.test.toml new file mode 100644 index 00000000000..d8dc0230d83 --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/out.test.toml @@ -0,0 +1,4 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = [""] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/telemetry-experimental/output.txt b/acceptance/bundle/python/telemetry-experimental/output.txt new file mode 100644 index 00000000000..d35cb6927a2 --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/output.txt @@ -0,0 +1,43 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/my_project/default/files... +Created jobs.python_job +Created jobs.yaml_job +Created pipelines.python_pipeline +Created schemas.yaml_schema +Files: 0 uploaded, 0 deleted +Resources: 4 created, 0 changed, 0 deleted, 0 unchanged + +=== PyDABs deploy telemetry +>>> cat [OUTPUT_DIR]/out.requests.txt +{ + "pydabs": { + "resource_loaders_count": 1, + "resource_mutators_count": 1, + "added_resources_count": 2, + "updated_resources_count": 2, + "resources": [ + { + "resource_type": "jobs", + "added_count": 1, + "updated_count": 2 + }, + { + "resource_type": "pipelines", + "added_count": 1, + "updated_count": 0 + } + ] + }, + "experimental": { + "python_added_resources_count": 2, + "python_updated_resources_count": 2, + "python_resource_loaders_count": 1, + "python_resource_mutators_count": 1 + }, + "all_resource_types": [ + "jobs", + "pipelines", + "schemas" + ] +} diff --git a/acceptance/bundle/python/telemetry-experimental/resources.py b/acceptance/bundle/python/telemetry-experimental/resources.py new file mode 100644 index 00000000000..9f9555ae9e0 --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/resources.py @@ -0,0 +1,9 @@ +from databricks.bundles.core import Resources + + +def load_resources() -> Resources: + resources = Resources() + resources.add_job("python_job", {"name": "Python Job"}) + resources.add_pipeline("python_pipeline", {"name": "Python Pipeline"}) + + return resources diff --git a/acceptance/bundle/python/telemetry-experimental/script b/acceptance/bundle/python/telemetry-experimental/script new file mode 100644 index 00000000000..1026f0f013e --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/script @@ -0,0 +1,13 @@ +trace uv run $UV_ARGS -q $CLI bundle deploy + +# pydabs covers only resources defined or modified in Python (jobs, pipelines), +# while resources_metadata covers every resource in the bundle (including schemas). +title "PyDABs deploy telemetry" +trace cat "$OUT_REQUESTS" | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { + pydabs, + experimental: .experimental | with_entries(select(.key | startswith("python_"))), + all_resource_types: [.resources_metadata.resources[].resource_type] +}' + +rm -fr .databricks __pycache__ +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/python/telemetry-experimental/test.toml b/acceptance/bundle/python/telemetry-experimental/test.toml new file mode 100644 index 00000000000..99957b14edb --- /dev/null +++ b/acceptance/bundle/python/telemetry-experimental/test.toml @@ -0,0 +1,4 @@ +RecordRequests = true + +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = [""] diff --git a/acceptance/bundle/python/telemetry/databricks.yml b/acceptance/bundle/python/telemetry/databricks.yml new file mode 100644 index 00000000000..9bf94c12524 --- /dev/null +++ b/acceptance/bundle/python/telemetry/databricks.yml @@ -0,0 +1,19 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +python: + resources: + - "resources:load_resources" + mutators: + - "mutators:update_job" + +resources: + jobs: + yaml_job: + name: "YAML Job" + schemas: + yaml_schema: + catalog_name: main + name: yaml_schema diff --git a/acceptance/bundle/python/telemetry/mutators.py b/acceptance/bundle/python/telemetry/mutators.py new file mode 100644 index 00000000000..9335dd63f19 --- /dev/null +++ b/acceptance/bundle/python/telemetry/mutators.py @@ -0,0 +1,11 @@ +from dataclasses import replace + +from databricks.bundles.core import job_mutator +from databricks.bundles.jobs import Job + + +@job_mutator +def update_job(job: Job) -> Job: + assert isinstance(job.name, str) + + return replace(job, name=f"{job.name} (updated)") diff --git a/acceptance/bundle/python/telemetry/out.test.toml b/acceptance/bundle/python/telemetry/out.test.toml new file mode 100644 index 00000000000..d8dc0230d83 --- /dev/null +++ b/acceptance/bundle/python/telemetry/out.test.toml @@ -0,0 +1,4 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = [""] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/telemetry/output.txt b/acceptance/bundle/python/telemetry/output.txt new file mode 100644 index 00000000000..d35cb6927a2 --- /dev/null +++ b/acceptance/bundle/python/telemetry/output.txt @@ -0,0 +1,43 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/my_project/default/files... +Created jobs.python_job +Created jobs.yaml_job +Created pipelines.python_pipeline +Created schemas.yaml_schema +Files: 0 uploaded, 0 deleted +Resources: 4 created, 0 changed, 0 deleted, 0 unchanged + +=== PyDABs deploy telemetry +>>> cat [OUTPUT_DIR]/out.requests.txt +{ + "pydabs": { + "resource_loaders_count": 1, + "resource_mutators_count": 1, + "added_resources_count": 2, + "updated_resources_count": 2, + "resources": [ + { + "resource_type": "jobs", + "added_count": 1, + "updated_count": 2 + }, + { + "resource_type": "pipelines", + "added_count": 1, + "updated_count": 0 + } + ] + }, + "experimental": { + "python_added_resources_count": 2, + "python_updated_resources_count": 2, + "python_resource_loaders_count": 1, + "python_resource_mutators_count": 1 + }, + "all_resource_types": [ + "jobs", + "pipelines", + "schemas" + ] +} diff --git a/acceptance/bundle/python/telemetry/resources.py b/acceptance/bundle/python/telemetry/resources.py new file mode 100644 index 00000000000..9f9555ae9e0 --- /dev/null +++ b/acceptance/bundle/python/telemetry/resources.py @@ -0,0 +1,9 @@ +from databricks.bundles.core import Resources + + +def load_resources() -> Resources: + resources = Resources() + resources.add_job("python_job", {"name": "Python Job"}) + resources.add_pipeline("python_pipeline", {"name": "Python Pipeline"}) + + return resources diff --git a/acceptance/bundle/python/telemetry/script b/acceptance/bundle/python/telemetry/script new file mode 100644 index 00000000000..1026f0f013e --- /dev/null +++ b/acceptance/bundle/python/telemetry/script @@ -0,0 +1,13 @@ +trace uv run $UV_ARGS -q $CLI bundle deploy + +# pydabs covers only resources defined or modified in Python (jobs, pipelines), +# while resources_metadata covers every resource in the bundle (including schemas). +title "PyDABs deploy telemetry" +trace cat "$OUT_REQUESTS" | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { + pydabs, + experimental: .experimental | with_entries(select(.key | startswith("python_"))), + all_resource_types: [.resources_metadata.resources[].resource_type] +}' + +rm -fr .databricks __pycache__ +rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/python/telemetry/test.toml b/acceptance/bundle/python/telemetry/test.toml new file mode 100644 index 00000000000..99957b14edb --- /dev/null +++ b/acceptance/bundle/python/telemetry/test.toml @@ -0,0 +1,4 @@ +RecordRequests = true + +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = [""] diff --git a/bundle/bundle.go b/bundle/bundle.go index ce158b3e26c..348604e5a81 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -71,14 +71,16 @@ const configSnapshotFilename = "resources-config-sync-snapshot.json" // This struct is used as a communication channel to collect metrics // from all over the bundle codebase to finally be emitted as telemetry. type Metrics struct { - ConfigurationFileCount int64 - TargetCount int64 - DeploymentId uuid.UUID - BoolValues []protos.BoolMapEntry - PythonAddedResourcesCount int64 - PythonUpdatedResourcesCount int64 - ExecutionTimes []protos.IntMapEntry - LocalCacheMeasurementsMs []protos.IntMapEntry // Local cache measurements stored as milliseconds + ConfigurationFileCount int64 + TargetCount int64 + DeploymentId uuid.UUID + BoolValues []protos.BoolMapEntry + ExecutionTimes []protos.IntMapEntry + LocalCacheMeasurementsMs []protos.IntMapEntry // Local cache measurements stored as milliseconds + + // Number of resources added and updated by the Python mutator, keyed by resource type. + PythonAddedResources map[string]int64 + PythonUpdatedResources map[string]int64 // StateEngine is the engine that ran (or would have run) the deploy. Set to the // requested engine as soon as it is resolved, then refined to the state's engine diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index 9112aaa808d..ba6da49a676 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -107,6 +107,17 @@ type runPythonMutatorOpts struct { authEnv map[string]string } +// addResourceCounts adds the number of resources in set to counts, per resource type. +func addResourceCounts(counts map[string]int64, set resourcemutator.ResourceKeySet) map[string]int64 { + for resourceType, names := range set { + if counts == nil { + counts = make(map[string]int64) + } + counts[resourceType] += int64(len(names)) + } + return counts +} + // getOpts adapts deprecated PyDABs and upcoming Python configuration // into a common structure. func getOpts(b *bundle.Bundle, phase phase) (opts, error) { @@ -290,8 +301,8 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno }) // we can precisely track resources that are added/updated, so sum doesn't double-count - b.Metrics.PythonUpdatedResourcesCount += int64(result.UpdatedResources.Size()) - b.Metrics.PythonAddedResourcesCount += int64(result.AddedResources.Size()) + b.Metrics.PythonUpdatedResources = addResourceCounts(b.Metrics.PythonUpdatedResources, result.UpdatedResources) + b.Metrics.PythonAddedResources = addResourceCounts(b.Metrics.PythonAddedResources, result.AddedResources) if err == mutateDiagsHasError { if !mutateDiags.HasError() { diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index 9b106f727bf..94d9487c794 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -156,8 +156,8 @@ workspace: { current_user: { userName: test }}`) }) assert.NoError(t, err) - assert.Equal(t, int64(2), b.Metrics.PythonAddedResourcesCount) - assert.Equal(t, int64(0), b.Metrics.PythonUpdatedResourcesCount) + assert.Equal(t, map[string]int64{"jobs": 1, "pipelines": 1}, b.Metrics.PythonAddedResources) + assert.Nil(t, b.Metrics.PythonUpdatedResources) assert.Len(t, diags, 1) assert.Equal(t, "job doesn't have any tasks", diags[0].Summary) @@ -237,8 +237,8 @@ resources: }) assert.NoError(t, err) - assert.Equal(t, int64(0), b.Metrics.PythonAddedResourcesCount) - assert.Equal(t, int64(1), b.Metrics.PythonUpdatedResourcesCount) + assert.Nil(t, b.Metrics.PythonAddedResources) + assert.Equal(t, map[string]int64{"jobs": 1}, b.Metrics.PythonUpdatedResources) } func TestPythonMutator_badOutput(t *testing.T) { diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 4b6d705eba9..0dd65b4ca79 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -3,6 +3,7 @@ package phases import ( "cmp" "context" + "maps" "math" "reflect" "slices" @@ -195,6 +196,38 @@ func aiRuntimeTaskMetrics(jobs map[string]*resources.Job) (present, scheduled, m return present, scheduled, multitask } +// pydabsMetrics reports usage of Python support. Loaders and mutators are read from +// 'python', falling back to 'experimental/python'; the Python mutator rejects +// configs where both are set and differ. +func pydabsMetrics(b *bundle.Bundle) *protos.BundleDeployPydabs { + pythonConfig := b.Config.Python + if reflect.DeepEqual(pythonConfig, config.Python{}) && b.Config.Experimental != nil { + pythonConfig = b.Config.Experimental.Python + } + + added := b.Metrics.PythonAddedResources + updated := b.Metrics.PythonUpdatedResources + + types := slices.Concat(slices.Collect(maps.Keys(added)), slices.Collect(maps.Keys(updated))) + slices.Sort(types) + types = slices.Compact(types) + + out := &protos.BundleDeployPydabs{ + ResourceLoadersCount: int64(len(pythonConfig.Resources)), + ResourceMutatorsCount: int64(len(pythonConfig.Mutators)), + } + for _, t := range types { + out.AddedResourcesCount += added[t] + out.UpdatedResourcesCount += updated[t] + out.Resources = append(out.Resources, protos.PydabsResourceTypeCount{ + ResourceType: t, + AddedCount: added[t], + UpdatedCount: updated[t], + }) + } + return out +} + // LogDeployTelemetry logs a telemetry event for a bundle deploy command. func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { errMsg = telemetry.ScrubErrorMessage(errMsg) @@ -349,9 +382,12 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { mode = protos.BundleModeProduction } - experimentalConfig := b.Config.Experimental - if experimentalConfig == nil { - experimentalConfig = &config.Experimental{} + pydabs := pydabsMetrics(b) + + // Omit the pydabs section for bundles that don't use Python support. + var pydabsSection *protos.BundleDeployPydabs + if pydabs.ResourceLoadersCount > 0 || pydabs.ResourceMutatorsCount > 0 { + pydabsSection = pydabs } uploadFileSizers := make([]sizer, len(b.Files)) @@ -386,6 +422,8 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { ResourcesMetadata: collectResourcesMetadata(ctx, b), + Pydabs: pydabsSection, + Experimental: &protos.BundleDeployExperimental{ BundleMode: mode, ConfigurationFileCount: b.Metrics.ConfigurationFileCount, @@ -395,10 +433,10 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { WorkspaceArtifactPathType: artifactPathType, BoolValues: b.Metrics.BoolValues, LocalCacheMeasurementsMs: b.Metrics.LocalCacheMeasurementsMs, - PythonAddedResourcesCount: b.Metrics.PythonAddedResourcesCount, - PythonUpdatedResourcesCount: b.Metrics.PythonUpdatedResourcesCount, - PythonResourceLoadersCount: int64(len(experimentalConfig.Python.Resources)), - PythonResourceMutatorsCount: int64(len(experimentalConfig.Python.Mutators)), + PythonAddedResourcesCount: pydabs.AddedResourcesCount, + PythonUpdatedResourcesCount: pydabs.UpdatedResourcesCount, + PythonResourceLoadersCount: pydabs.ResourceLoadersCount, + PythonResourceMutatorsCount: pydabs.ResourceMutatorsCount, VariableCount: int64(variableCount), ComplexVariableCount: complexVariableCount, LookupVariableCount: lookupVariableCount, diff --git a/bundle/phases/telemetry_test.go b/bundle/phases/telemetry_test.go index 0cea4df5794..7971fc70f90 100644 --- a/bundle/phases/telemetry_test.go +++ b/bundle/phases/telemetry_test.go @@ -3,7 +3,10 @@ package phases import ( "testing" + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/telemetry/protos" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" ) @@ -174,3 +177,40 @@ func TestAiRuntimeTaskMetrics(t *testing.T) { }) } } + +func TestPydabsMetrics(t *testing.T) { + pythonConfig := config.Python{ + Resources: []string{"resources:load_resources"}, + Mutators: []string{"mutators:m1", "mutators:m2"}, + } + metrics := bundle.Metrics{ + PythonAddedResources: map[string]int64{"jobs": 2, "pipelines": 1}, + PythonUpdatedResources: map[string]int64{"jobs": 3, "apps": 1}, + } + expected := &protos.BundleDeployPydabs{ + ResourceLoadersCount: 1, + ResourceMutatorsCount: 2, + AddedResourcesCount: 3, + UpdatedResourcesCount: 4, + Resources: []protos.PydabsResourceTypeCount{ + {ResourceType: "apps", AddedCount: 0, UpdatedCount: 1}, + {ResourceType: "jobs", AddedCount: 2, UpdatedCount: 3}, + {ResourceType: "pipelines", AddedCount: 1, UpdatedCount: 0}, + }, + } + + t.Run("python", func(t *testing.T) { + b := &bundle.Bundle{Config: config.Root{Python: pythonConfig}, Metrics: metrics} + assert.Equal(t, expected, pydabsMetrics(b)) + }) + + t.Run("experimental/python", func(t *testing.T) { + b := &bundle.Bundle{Config: config.Root{Experimental: &config.Experimental{Python: pythonConfig}}, Metrics: metrics} + assert.Equal(t, expected, pydabsMetrics(b)) + }) + + t.Run("not used", func(t *testing.T) { + b := &bundle.Bundle{} + assert.Equal(t, &protos.BundleDeployPydabs{}, pydabsMetrics(b)) + }) +} diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index 69e01a30715..0002e9211da 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -38,6 +38,10 @@ type BundleDeployEvent struct { // Per-resource-type metadata (counts and state-size statistics). ResourcesMetadata *BundleResourcesMetadata `json:"resources_metadata,omitempty"` + // Usage of Python support (PyDABs). Set only when the bundle declares + // Python resource loaders or mutators. + Pydabs *BundleDeployPydabs `json:"pydabs,omitempty"` + Experimental *BundleDeployExperimental `json:"experimental,omitempty"` } @@ -78,16 +82,16 @@ type BundleDeployExperimental struct { // Execution time per mutator for a selected subset of mutators. BundleMutatorExecutionTimeMs []IntMapEntry `json:"bundle_mutator_execution_time_ms,omitempty"` - // Number of resources added by PythonMutator + // Deprecated: superseded by Pydabs.AddedResourcesCount. PythonAddedResourcesCount int64 `json:"python_added_resources_count,omitempty"` - // Number of resources updated by PythonMutator + // Deprecated: superseded by Pydabs.UpdatedResourcesCount. PythonUpdatedResourcesCount int64 `json:"python_updated_resources_count,omitempty"` - // Number of resource loaders declared at 'python/resources' in databricks.yml + // Deprecated: superseded by Pydabs.ResourceLoadersCount. PythonResourceLoadersCount int64 `json:"python_resource_loaders_count,omitempty"` - // Number of resource mutators declared at 'python/mutators' in databricks.yml + // Deprecated: superseded by Pydabs.ResourceMutatorsCount. PythonResourceMutatorsCount int64 `json:"python_resource_mutators_count,omitempty"` // Number of files in the synced source tree considered for upload: the full @@ -154,6 +158,38 @@ type ResourceMetadata struct { StateSizeMedianBytes int64 `json:"state_size_median_bytes,omitempty"` } +// BundleDeployPydabs mirrors the universe proto. Usage of Python support +// (PyDABs) in one bundle deployment. +type BundleDeployPydabs struct { + // Number of resource loaders declared at 'python/resources' (or 'experimental/python/resources'). + ResourceLoadersCount int64 `json:"resource_loaders_count"` + + // Number of resource mutators declared at 'python/mutators' (or 'experimental/python/mutators'). + ResourceMutatorsCount int64 `json:"resource_mutators_count"` + + // Number of resources defined in Python. + AddedResourcesCount int64 `json:"added_resources_count"` + + // Number of resources modified by Python mutators, including resources + // defined in Python, so this can overlap with AddedResourcesCount. + UpdatedResourcesCount int64 `json:"updated_resources_count"` + + // One entry per resource type added or updated by Python, ordered by type name. + Resources []PydabsResourceTypeCount `json:"resources,omitempty"` +} + +// PydabsResourceTypeCount holds PyDABs counts for a single resource type. +type PydabsResourceTypeCount struct { + // Resource type name, same values as ResourceMetadata.ResourceType. + ResourceType string `json:"resource_type,omitempty"` + + // Number of resources of this type defined in Python. + AddedCount int64 `json:"added_count"` + + // Number of resources of this type modified by Python mutators. + UpdatedCount int64 `json:"updated_count"` +} + type BoolMapEntry struct { Key string `json:"key,omitempty"` Value bool `json:"value"` From 593c04ea34c4c5e1c1da96c4b153b36389049c05 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 30 Sep 2026 12:08:01 +0000 Subject: [PATCH 2/8] Report which config section declares PyDABs Add pydabs.config_section (PYTHON, EXPERIMENTAL_PYTHON, BOTH), recorded before the Python mutator copies 'python' into 'experimental/python'. Read loader and mutator counts from 'experimental/python' only, as before: that copy means it already covers the 'python' section. Co-authored-by: Isaac --- .../python/telemetry-experimental/output.txt | 3 ++- acceptance/bundle/python/telemetry/output.txt | 3 ++- bundle/bundle.go | 3 +++ .../config/mutator/python/python_mutator.go | 24 ++++++++++++++++++ .../mutator/python/python_mutator_test.go | 25 +++++++++++++++++++ bundle/phases/telemetry.go | 14 +++++------ bundle/phases/telemetry_test.go | 9 +++---- libs/telemetry/protos/bundle_deploy.go | 4 +++ libs/telemetry/protos/enum.go | 9 +++++++ 9 files changed, 79 insertions(+), 15 deletions(-) diff --git a/acceptance/bundle/python/telemetry-experimental/output.txt b/acceptance/bundle/python/telemetry-experimental/output.txt index d35cb6927a2..c113be6c1f5 100644 --- a/acceptance/bundle/python/telemetry-experimental/output.txt +++ b/acceptance/bundle/python/telemetry-experimental/output.txt @@ -27,7 +27,8 @@ Resources: 4 created, 0 changed, 0 deleted, 0 unchanged "added_count": 1, "updated_count": 0 } - ] + ], + "config_section": "EXPERIMENTAL_PYTHON" }, "experimental": { "python_added_resources_count": 2, diff --git a/acceptance/bundle/python/telemetry/output.txt b/acceptance/bundle/python/telemetry/output.txt index d35cb6927a2..0ababc04a77 100644 --- a/acceptance/bundle/python/telemetry/output.txt +++ b/acceptance/bundle/python/telemetry/output.txt @@ -27,7 +27,8 @@ Resources: 4 created, 0 changed, 0 deleted, 0 unchanged "added_count": 1, "updated_count": 0 } - ] + ], + "config_section": "PYTHON" }, "experimental": { "python_added_resources_count": 2, diff --git a/bundle/bundle.go b/bundle/bundle.go index 348604e5a81..6cee3beade6 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -82,6 +82,9 @@ type Metrics struct { PythonAddedResources map[string]int64 PythonUpdatedResources map[string]int64 + // Configuration section Python support is declared in. + PythonConfigSection protos.PydabsConfigSection + // StateEngine is the engine that ran (or would have run) the deploy. Set to the // requested engine as soon as it is resolved, then refined to the state's engine // once the state is pulled, so deploy telemetry reports it even when the deploy diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index ba6da49a676..665c8d2828a 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -32,6 +32,7 @@ import ( "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/databricks/cli/libs/process" + "github.com/databricks/cli/libs/telemetry/protos" ) type phase string @@ -162,6 +163,23 @@ func getOpts(b *bundle.Bundle, phase phase) (opts, error) { } } +// pythonConfigSection reports which section the user declared Python support in. +func pythonConfigSection(b *bundle.Bundle) protos.PydabsConfigSection { + pythonEnabled := !reflect.DeepEqual(b.Config.Python, config.Python{}) + experimentalPythonEnabled := b.Config.Experimental != nil && !reflect.DeepEqual(b.Config.Experimental.Python, config.Python{}) + + switch { + case pythonEnabled && experimentalPythonEnabled: + return protos.PydabsConfigSectionBoth + case pythonEnabled: + return protos.PydabsConfigSectionPython + case experimentalPythonEnabled: + return protos.PydabsConfigSectionExperimentalPython + default: + return protos.PydabsConfigSectionUnspecified + } +} + // applyBackwardsCompatibilityFixes applies fixes to bundle configuration // so that older version of databricks-bundles Python library continue to see // 'experimental.python' section even if bundle configuration uses 'python' section. @@ -210,6 +228,12 @@ func applyBackwardsCompatibilityFixes(b *bundle.Bundle) error { } func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + // Record before applyBackwardsCompatibilityFixes copies 'python' into 'experimental/python', + // which makes the sections indistinguishable. The load_resources phase runs first. + if m.phase == PythonMutatorPhaseLoadResources { + b.Metrics.PythonConfigSection = pythonConfigSection(b) + } + err := applyBackwardsCompatibilityFixes(b) if err != nil { return diag.FromErr(fmt.Errorf("failed to apply backwards compatibility fixes: %w", err)) diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index 94d9487c794..a7ae661fe99 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -23,6 +23,7 @@ import ( "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/process" + "github.com/databricks/cli/libs/telemetry/protos" "github.com/stretchr/testify/assert" ) @@ -158,6 +159,7 @@ workspace: { current_user: { userName: test }}`) assert.Equal(t, map[string]int64{"jobs": 1, "pipelines": 1}, b.Metrics.PythonAddedResources) assert.Nil(t, b.Metrics.PythonUpdatedResources) + assert.Equal(t, protos.PydabsConfigSectionExperimentalPython, b.Metrics.PythonConfigSection) assert.Len(t, diags, 1) assert.Equal(t, "job doesn't have any tasks", diags[0].Summary) @@ -239,6 +241,29 @@ resources: assert.Nil(t, b.Metrics.PythonAddedResources) assert.Equal(t, map[string]int64{"jobs": 1}, b.Metrics.PythonUpdatedResources) + + // Recorded only in the load_resources phase. + assert.Empty(t, b.Metrics.PythonConfigSection) +} + +func TestPythonConfigSection(t *testing.T) { + tests := []struct { + name string + yaml string + expected protos.PydabsConfigSection + }{ + {"none", `bundle: {name: test}`, protos.PydabsConfigSectionUnspecified}, + {"python", `python: {resources: ["resources:load"]}`, protos.PydabsConfigSectionPython}, + {"experimental/python", `experimental: {python: {resources: ["resources:load"]}}`, protos.PydabsConfigSectionExperimentalPython}, + {"both", ` +python: {resources: ["resources:load"]} +experimental: {python: {resources: ["resources:load"]}}`, protos.PydabsConfigSectionBoth}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, pythonConfigSection(loadYaml("databricks.yml", tc.yaml))) + }) + } } func TestPythonMutator_badOutput(t *testing.T) { diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 0dd65b4ca79..ece4710b392 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -197,12 +197,11 @@ func aiRuntimeTaskMetrics(jobs map[string]*resources.Job) (present, scheduled, m } // pydabsMetrics reports usage of Python support. Loaders and mutators are read from -// 'python', falling back to 'experimental/python'; the Python mutator rejects -// configs where both are set and differ. +// 'experimental/python' only: the Python mutator copies 'python' there. func pydabsMetrics(b *bundle.Bundle) *protos.BundleDeployPydabs { - pythonConfig := b.Config.Python - if reflect.DeepEqual(pythonConfig, config.Python{}) && b.Config.Experimental != nil { - pythonConfig = b.Config.Experimental.Python + experimentalConfig := b.Config.Experimental + if experimentalConfig == nil { + experimentalConfig = &config.Experimental{} } added := b.Metrics.PythonAddedResources @@ -213,8 +212,9 @@ func pydabsMetrics(b *bundle.Bundle) *protos.BundleDeployPydabs { types = slices.Compact(types) out := &protos.BundleDeployPydabs{ - ResourceLoadersCount: int64(len(pythonConfig.Resources)), - ResourceMutatorsCount: int64(len(pythonConfig.Mutators)), + ResourceLoadersCount: int64(len(experimentalConfig.Python.Resources)), + ResourceMutatorsCount: int64(len(experimentalConfig.Python.Mutators)), + ConfigSection: b.Metrics.PythonConfigSection, } for _, t := range types { out.AddedResourcesCount += added[t] diff --git a/bundle/phases/telemetry_test.go b/bundle/phases/telemetry_test.go index 7971fc70f90..66280b5322e 100644 --- a/bundle/phases/telemetry_test.go +++ b/bundle/phases/telemetry_test.go @@ -186,6 +186,7 @@ func TestPydabsMetrics(t *testing.T) { metrics := bundle.Metrics{ PythonAddedResources: map[string]int64{"jobs": 2, "pipelines": 1}, PythonUpdatedResources: map[string]int64{"jobs": 3, "apps": 1}, + PythonConfigSection: protos.PydabsConfigSectionPython, } expected := &protos.BundleDeployPydabs{ ResourceLoadersCount: 1, @@ -197,14 +198,10 @@ func TestPydabsMetrics(t *testing.T) { {ResourceType: "jobs", AddedCount: 2, UpdatedCount: 3}, {ResourceType: "pipelines", AddedCount: 1, UpdatedCount: 0}, }, + ConfigSection: protos.PydabsConfigSectionPython, } - t.Run("python", func(t *testing.T) { - b := &bundle.Bundle{Config: config.Root{Python: pythonConfig}, Metrics: metrics} - assert.Equal(t, expected, pydabsMetrics(b)) - }) - - t.Run("experimental/python", func(t *testing.T) { + t.Run("used", func(t *testing.T) { b := &bundle.Bundle{Config: config.Root{Experimental: &config.Experimental{Python: pythonConfig}}, Metrics: metrics} assert.Equal(t, expected, pydabsMetrics(b)) }) diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index 0002e9211da..a9a77e3474e 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -176,6 +176,10 @@ type BundleDeployPydabs struct { // One entry per resource type added or updated by Python, ordered by type name. Resources []PydabsResourceTypeCount `json:"resources,omitempty"` + + // Configuration section the user declared Python support in: 'python', + // 'experimental/python', or both. + ConfigSection PydabsConfigSection `json:"config_section,omitempty"` } // PydabsResourceTypeCount holds PyDABs counts for a single resource type. diff --git a/libs/telemetry/protos/enum.go b/libs/telemetry/protos/enum.go index 7f6780cb657..a0c873063fa 100644 --- a/libs/telemetry/protos/enum.go +++ b/libs/telemetry/protos/enum.go @@ -24,3 +24,12 @@ const ( BundleDeployArtifactPathTypeWorkspace BundleDeployArtifactPathType = "WORKSPACE_FILE_SYSTEM" BundleDeployArtifactPathTypeVolume BundleDeployArtifactPathType = "UC_VOLUME" ) + +type PydabsConfigSection string + +const ( + PydabsConfigSectionUnspecified PydabsConfigSection = "TYPE_UNSPECIFIED" + PydabsConfigSectionPython PydabsConfigSection = "PYTHON" + PydabsConfigSectionExperimentalPython PydabsConfigSection = "EXPERIMENTAL_PYTHON" + PydabsConfigSectionBoth PydabsConfigSection = "BOTH" +) From 685ea7ab7207941f42949b028c72d16c3620eecc Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Mon, 19 Oct 2026 09:10:34 +0000 Subject: [PATCH 3/8] Drop unit tests covered by the PyDABs telemetry acceptance tests acceptance/bundle/python/telemetry{,-experimental} assert per-type counts and config_section for both sections, and acceptance/bundle/telemetry/deploy asserts the pydabs section is omitted without Python. Co-authored-by: Isaac --- bundle/config/mutator/python/python_mutator_test.go | 12 ------------ bundle/phases/telemetry_test.go | 11 ++--------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index 3f186fb9fe7..f3378b1bd51 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -158,10 +158,6 @@ workspace: { current_user: { userName: test }}`) }) assert.NoError(t, err) - assert.Equal(t, map[string]int64{"jobs": 1, "pipelines": 1}, b.Metrics.PythonAddedResources) - assert.Nil(t, b.Metrics.PythonUpdatedResources) - assert.Equal(t, protos.PydabsConfigSectionExperimentalPython, b.Metrics.PythonConfigSection) - assert.Len(t, diags, 1) assert.Equal(t, "job doesn't have any tasks", diags[0].Summary) assert.Equal(t, []diag.Location{ @@ -239,12 +235,6 @@ resources: return v, nil }) assert.NoError(t, err) - - assert.Nil(t, b.Metrics.PythonAddedResources) - assert.Equal(t, map[string]int64{"jobs": 1}, b.Metrics.PythonUpdatedResources) - - // Recorded only in the load_resources phase. - assert.Empty(t, b.Metrics.PythonConfigSection) } func TestPythonConfigSection(t *testing.T) { @@ -254,8 +244,6 @@ func TestPythonConfigSection(t *testing.T) { expected protos.PydabsConfigSection }{ {"none", `bundle: {name: test}`, protos.PydabsConfigSectionUnspecified}, - {"python", `python: {resources: ["resources:load"]}`, protos.PydabsConfigSectionPython}, - {"experimental/python", `experimental: {python: {resources: ["resources:load"]}}`, protos.PydabsConfigSectionExperimentalPython}, {"both", ` python: {resources: ["resources:load"]} experimental: {python: {resources: ["resources:load"]}}`, protos.PydabsConfigSectionBoth}, diff --git a/bundle/phases/telemetry_test.go b/bundle/phases/telemetry_test.go index ffdd8197d95..1436ecb7e84 100644 --- a/bundle/phases/telemetry_test.go +++ b/bundle/phases/telemetry_test.go @@ -201,13 +201,6 @@ func TestPydabsMetrics(t *testing.T) { ConfigSection: protos.PydabsConfigSectionPython, } - t.Run("used", func(t *testing.T) { - b := &bundle.Bundle{Config: config.Root{Experimental: &config.Experimental{Python: pythonConfig}}, Metrics: metrics} - assert.Equal(t, expected, pydabsMetrics(b)) - }) - - t.Run("not used", func(t *testing.T) { - b := &bundle.Bundle{} - assert.Equal(t, &protos.BundleDeployPydabs{}, pydabsMetrics(b)) - }) + b := &bundle.Bundle{Config: config.Root{Experimental: &config.Experimental{Python: pythonConfig}}, Metrics: metrics} + assert.Equal(t, expected, pydabsMetrics(b)) } From 53f52150d4db2fe664c8c7ea79fbd060ea174516 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Tue, 20 Oct 2026 13:04:37 +0000 Subject: [PATCH 4/8] Fix SA1019 lint on deprecated experimental.python_* telemetry fields Group the deprecation under one comment, as for the Resource*Count fields, and mark the remaining use with nolint while both are populated. Co-authored-by: Isaac --- bundle/phases/telemetry.go | 2 +- libs/telemetry/protos/bundle_deploy.go | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 8297fa9466b..99ee3b3c95e 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -433,7 +433,7 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { WorkspaceArtifactPathType: artifactPathType, BoolValues: b.Metrics.BoolValues, LocalCacheMeasurementsMs: b.Metrics.LocalCacheMeasurementsMs, - PythonAddedResourcesCount: pydabs.AddedResourcesCount, + PythonAddedResourcesCount: pydabs.AddedResourcesCount, //nolint:staticcheck // SA1019: populated alongside Pydabs during the transition PythonUpdatedResourcesCount: pydabs.UpdatedResourcesCount, PythonResourceLoadersCount: pydabs.ResourceLoadersCount, PythonResourceMutatorsCount: pydabs.ResourceMutatorsCount, diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index 92e4a1a4809..3daf8f731cf 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -79,16 +79,11 @@ type BundleDeployExperimental struct { // Execution time per mutator for a selected subset of mutators. BundleMutatorExecutionTimeMs []IntMapEntry `json:"bundle_mutator_execution_time_ms,omitempty"` - // Deprecated: superseded by Pydabs.AddedResourcesCount. - PythonAddedResourcesCount int64 `json:"python_added_resources_count,omitempty"` - - // Deprecated: superseded by Pydabs.UpdatedResourcesCount. + // Deprecated: superseded by the matching Pydabs fields. Both are + // populated during the transition. + PythonAddedResourcesCount int64 `json:"python_added_resources_count,omitempty"` PythonUpdatedResourcesCount int64 `json:"python_updated_resources_count,omitempty"` - - // Deprecated: superseded by Pydabs.ResourceLoadersCount. - PythonResourceLoadersCount int64 `json:"python_resource_loaders_count,omitempty"` - - // Deprecated: superseded by Pydabs.ResourceMutatorsCount. + PythonResourceLoadersCount int64 `json:"python_resource_loaders_count,omitempty"` PythonResourceMutatorsCount int64 `json:"python_resource_mutators_count,omitempty"` // Number of files in the synced source tree considered for upload: the full From 3da2069ac490c259846664b5429138cb4b1f43d5 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Tue, 20 Oct 2026 13:08:07 +0000 Subject: [PATCH 5/8] Merge PyDABs telemetry acceptance tests into one Deploy once with 'python' and once with 'experimental/python' in the same test instead of two near-identical directories, and run only the current databricks-bundles version: the telemetry doesn't depend on it. Co-authored-by: Isaac --- .../telemetry-experimental/databricks.yml | 20 --------- .../python/telemetry-experimental/mutators.py | 11 ----- .../telemetry-experimental/out.test.toml | 4 -- .../python/telemetry-experimental/output.txt | 44 ------------------- .../telemetry-experimental/resources.py | 9 ---- .../python/telemetry-experimental/script | 13 ------ .../python/telemetry-experimental/test.toml | 4 -- .../bundle/python/telemetry/databricks.yml | 7 +-- .../bundle/python/telemetry/out.test.toml | 2 +- acceptance/bundle/python/telemetry/output.txt | 44 +++++++++++++++++-- acceptance/bundle/python/telemetry/script | 26 +++++++---- acceptance/bundle/python/telemetry/test.toml | 2 + 12 files changed, 63 insertions(+), 123 deletions(-) delete mode 100644 acceptance/bundle/python/telemetry-experimental/databricks.yml delete mode 100644 acceptance/bundle/python/telemetry-experimental/mutators.py delete mode 100644 acceptance/bundle/python/telemetry-experimental/out.test.toml delete mode 100644 acceptance/bundle/python/telemetry-experimental/output.txt delete mode 100644 acceptance/bundle/python/telemetry-experimental/resources.py delete mode 100644 acceptance/bundle/python/telemetry-experimental/script delete mode 100644 acceptance/bundle/python/telemetry-experimental/test.toml diff --git a/acceptance/bundle/python/telemetry-experimental/databricks.yml b/acceptance/bundle/python/telemetry-experimental/databricks.yml deleted file mode 100644 index f6deb34e0e8..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/databricks.yml +++ /dev/null @@ -1,20 +0,0 @@ -bundle: - name: my_project - -sync: {paths: []} # don't need to copy files - -experimental: - python: - resources: - - "resources:load_resources" - mutators: - - "mutators:update_job" - -resources: - jobs: - yaml_job: - name: "YAML Job" - schemas: - yaml_schema: - catalog_name: main - name: yaml_schema diff --git a/acceptance/bundle/python/telemetry-experimental/mutators.py b/acceptance/bundle/python/telemetry-experimental/mutators.py deleted file mode 100644 index 9335dd63f19..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/mutators.py +++ /dev/null @@ -1,11 +0,0 @@ -from dataclasses import replace - -from databricks.bundles.core import job_mutator -from databricks.bundles.jobs import Job - - -@job_mutator -def update_job(job: Job) -> Job: - assert isinstance(job.name, str) - - return replace(job, name=f"{job.name} (updated)") diff --git a/acceptance/bundle/python/telemetry-experimental/out.test.toml b/acceptance/bundle/python/telemetry-experimental/out.test.toml deleted file mode 100644 index d8dc0230d83..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/out.test.toml +++ /dev/null @@ -1,4 +0,0 @@ -Cloud = false -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.DMS = [""] -EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/telemetry-experimental/output.txt b/acceptance/bundle/python/telemetry-experimental/output.txt deleted file mode 100644 index c113be6c1f5..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/output.txt +++ /dev/null @@ -1,44 +0,0 @@ - ->>> uv run [UV_ARGS] -q [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/my_project/default/files... -Created jobs.python_job -Created jobs.yaml_job -Created pipelines.python_pipeline -Created schemas.yaml_schema -Files: 0 uploaded, 0 deleted -Resources: 4 created, 0 changed, 0 deleted, 0 unchanged - -=== PyDABs deploy telemetry ->>> cat [OUTPUT_DIR]/out.requests.txt -{ - "pydabs": { - "resource_loaders_count": 1, - "resource_mutators_count": 1, - "added_resources_count": 2, - "updated_resources_count": 2, - "resources": [ - { - "resource_type": "jobs", - "added_count": 1, - "updated_count": 2 - }, - { - "resource_type": "pipelines", - "added_count": 1, - "updated_count": 0 - } - ], - "config_section": "EXPERIMENTAL_PYTHON" - }, - "experimental": { - "python_added_resources_count": 2, - "python_updated_resources_count": 2, - "python_resource_loaders_count": 1, - "python_resource_mutators_count": 1 - }, - "all_resource_types": [ - "jobs", - "pipelines", - "schemas" - ] -} diff --git a/acceptance/bundle/python/telemetry-experimental/resources.py b/acceptance/bundle/python/telemetry-experimental/resources.py deleted file mode 100644 index 9f9555ae9e0..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/resources.py +++ /dev/null @@ -1,9 +0,0 @@ -from databricks.bundles.core import Resources - - -def load_resources() -> Resources: - resources = Resources() - resources.add_job("python_job", {"name": "Python Job"}) - resources.add_pipeline("python_pipeline", {"name": "Python Pipeline"}) - - return resources diff --git a/acceptance/bundle/python/telemetry-experimental/script b/acceptance/bundle/python/telemetry-experimental/script deleted file mode 100644 index 1026f0f013e..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/script +++ /dev/null @@ -1,13 +0,0 @@ -trace uv run $UV_ARGS -q $CLI bundle deploy - -# pydabs covers only resources defined or modified in Python (jobs, pipelines), -# while resources_metadata covers every resource in the bundle (including schemas). -title "PyDABs deploy telemetry" -trace cat "$OUT_REQUESTS" | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { - pydabs, - experimental: .experimental | with_entries(select(.key | startswith("python_"))), - all_resource_types: [.resources_metadata.resources[].resource_type] -}' - -rm -fr .databricks __pycache__ -rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/python/telemetry-experimental/test.toml b/acceptance/bundle/python/telemetry-experimental/test.toml deleted file mode 100644 index 99957b14edb..00000000000 --- a/acceptance/bundle/python/telemetry-experimental/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -RecordRequests = true - -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] -EnvMatrix.DMS = [""] diff --git a/acceptance/bundle/python/telemetry/databricks.yml b/acceptance/bundle/python/telemetry/databricks.yml index 9bf94c12524..ba73d97729e 100644 --- a/acceptance/bundle/python/telemetry/databricks.yml +++ b/acceptance/bundle/python/telemetry/databricks.yml @@ -3,11 +3,8 @@ bundle: sync: {paths: []} # don't need to copy files -python: - resources: - - "resources:load_resources" - mutators: - - "mutators:update_job" +# Single line so the script can switch it to 'experimental/python'. +python: {resources: ["resources:load_resources"], mutators: ["mutators:update_job"]} resources: jobs: diff --git a/acceptance/bundle/python/telemetry/out.test.toml b/acceptance/bundle/python/telemetry/out.test.toml index d8dc0230d83..ec15e2ef448 100644 --- a/acceptance/bundle/python/telemetry/out.test.toml +++ b/acceptance/bundle/python/telemetry/out.test.toml @@ -1,4 +1,4 @@ Cloud = false EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] EnvMatrix.DMS = [""] -EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/acceptance/bundle/python/telemetry/output.txt b/acceptance/bundle/python/telemetry/output.txt index 0ababc04a77..797748841a6 100644 --- a/acceptance/bundle/python/telemetry/output.txt +++ b/acceptance/bundle/python/telemetry/output.txt @@ -1,4 +1,5 @@ +=== Deploy with 'python' >>> uv run [UV_ARGS] -q [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/my_project/default/files... Created jobs.python_job @@ -7,9 +8,6 @@ Created pipelines.python_pipeline Created schemas.yaml_schema Files: 0 uploaded, 0 deleted Resources: 4 created, 0 changed, 0 deleted, 0 unchanged - -=== PyDABs deploy telemetry ->>> cat [OUTPUT_DIR]/out.requests.txt { "pydabs": { "resource_loaders_count": 1, @@ -42,3 +40,43 @@ Resources: 4 created, 0 changed, 0 deleted, 0 unchanged "schemas" ] } + +=== Deploy with 'experimental/python' +>>> update_file.py databricks.yml python: {resources: ["resources:load_resources"], mutators: ["mutators:update_job"]} experimental: {python: {resources: ["resources:load_resources"], mutators: ["mutators:update_job"]}} + +>>> uv run [UV_ARGS] -q [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/my_project/default/files... +Files: 0 uploaded, 0 deleted +Resources: 0 created, 0 changed, 0 deleted, 4 unchanged +{ + "pydabs": { + "resource_loaders_count": 1, + "resource_mutators_count": 1, + "added_resources_count": 2, + "updated_resources_count": 2, + "resources": [ + { + "resource_type": "jobs", + "added_count": 1, + "updated_count": 2 + }, + { + "resource_type": "pipelines", + "added_count": 1, + "updated_count": 0 + } + ], + "config_section": "EXPERIMENTAL_PYTHON" + }, + "experimental": { + "python_added_resources_count": 2, + "python_updated_resources_count": 2, + "python_resource_loaders_count": 1, + "python_resource_mutators_count": 1 + }, + "all_resource_types": [ + "jobs", + "pipelines", + "schemas" + ] +} diff --git a/acceptance/bundle/python/telemetry/script b/acceptance/bundle/python/telemetry/script index 1026f0f013e..7722ab6ff62 100644 --- a/acceptance/bundle/python/telemetry/script +++ b/acceptance/bundle/python/telemetry/script @@ -1,13 +1,21 @@ -trace uv run $UV_ARGS -q $CLI bundle deploy - # pydabs covers only resources defined or modified in Python (jobs, pipelines), # while resources_metadata covers every resource in the bundle (including schemas). -title "PyDABs deploy telemetry" -trace cat "$OUT_REQUESTS" | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { - pydabs, - experimental: .experimental | with_entries(select(.key | startswith("python_"))), - all_resource_types: [.resources_metadata.resources[].resource_type] -}' +print_pydabs_telemetry() { + jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { + pydabs, + experimental: .experimental | with_entries(select(.key | startswith("python_"))), + all_resource_types: [.resources_metadata.resources[].resource_type] + }' "$OUT_REQUESTS" + rm "$OUT_REQUESTS" +} + +title "Deploy with 'python'" +trace uv run $UV_ARGS -q $CLI bundle deploy +print_pydabs_telemetry + +title "Deploy with 'experimental/python'" +trace update_file.py databricks.yml 'python: {resources: ["resources:load_resources"], mutators: ["mutators:update_job"]}' 'experimental: {python: {resources: ["resources:load_resources"], mutators: ["mutators:update_job"]}}' +trace uv run $UV_ARGS -q $CLI bundle deploy +print_pydabs_telemetry rm -fr .databricks __pycache__ -rm "$OUT_REQUESTS" diff --git a/acceptance/bundle/python/telemetry/test.toml b/acceptance/bundle/python/telemetry/test.toml index 99957b14edb..d8a076d9590 100644 --- a/acceptance/bundle/python/telemetry/test.toml +++ b/acceptance/bundle/python/telemetry/test.toml @@ -2,3 +2,5 @@ RecordRequests = true EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] EnvMatrix.DMS = [""] +# Telemetry doesn't depend on the databricks-bundles version. +EnvMatrix.PYDAB_VERSION = ["current"] From c84081b780c65dd57bd47473a59fbfc06bfc3e47 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Fri, 23 Oct 2026 12:40:38 +0000 Subject: [PATCH 6/8] Build pydabs per-type counts with two loops Fill one entry per type from the added and updated maps and sort once, instead of concatenating, sorting and compacting the key lists. Co-authored-by: Isaac --- bundle/phases/telemetry.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 99ee3b3c95e..cf67e2b6ed8 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -204,26 +204,27 @@ func pydabsMetrics(b *bundle.Bundle) *protos.BundleDeployPydabs { experimentalConfig = &config.Experimental{} } - added := b.Metrics.PythonAddedResources - updated := b.Metrics.PythonUpdatedResources - - types := slices.Concat(slices.Collect(maps.Keys(added)), slices.Collect(maps.Keys(updated))) - slices.Sort(types) - types = slices.Compact(types) - out := &protos.BundleDeployPydabs{ ResourceLoadersCount: int64(len(experimentalConfig.Python.Resources)), ResourceMutatorsCount: int64(len(experimentalConfig.Python.Mutators)), ConfigSection: b.Metrics.PythonConfigSection, } - for _, t := range types { - out.AddedResourcesCount += added[t] - out.UpdatedResourcesCount += updated[t] - out.Resources = append(out.Resources, protos.PydabsResourceTypeCount{ - ResourceType: t, - AddedCount: added[t], - UpdatedCount: updated[t], - }) + + byType := make(map[string]*protos.PydabsResourceTypeCount) + for t, n := range b.Metrics.PythonAddedResources { + byType[t] = &protos.PydabsResourceTypeCount{ResourceType: t, AddedCount: n} + out.AddedResourcesCount += n + } + for t, n := range b.Metrics.PythonUpdatedResources { + if byType[t] == nil { + byType[t] = &protos.PydabsResourceTypeCount{ResourceType: t} + } + byType[t].UpdatedCount = n + out.UpdatedResourcesCount += n + } + + for _, t := range slices.Sorted(maps.Keys(byType)) { + out.Resources = append(out.Resources, *byType[t]) } return out } From 3509dd39ad8c7ce3d377f3406651e172331ad156 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Fri, 23 Oct 2026 13:14:01 +0000 Subject: [PATCH 7/8] Drop the engine matrix from the PyDABs telemetry test The direct engine is now the only engine, and the acceptance harness rejects EnvMatrix.DATABRICKS_BUNDLE_ENGINE. Co-authored-by: Isaac --- acceptance/bundle/python/telemetry/out.test.toml | 1 - acceptance/bundle/python/telemetry/test.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/acceptance/bundle/python/telemetry/out.test.toml b/acceptance/bundle/python/telemetry/out.test.toml index ec15e2ef448..d2d80ecfd50 100644 --- a/acceptance/bundle/python/telemetry/out.test.toml +++ b/acceptance/bundle/python/telemetry/out.test.toml @@ -1,4 +1,3 @@ Cloud = false -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] EnvMatrix.DMS = [""] EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/acceptance/bundle/python/telemetry/test.toml b/acceptance/bundle/python/telemetry/test.toml index d8a076d9590..355ba35fa71 100644 --- a/acceptance/bundle/python/telemetry/test.toml +++ b/acceptance/bundle/python/telemetry/test.toml @@ -1,6 +1,5 @@ RecordRequests = true -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] EnvMatrix.DMS = [""] # Telemetry doesn't depend on the databricks-bundles version. EnvMatrix.PYDAB_VERSION = ["current"] From 35c13f8d08a37fb338161e3a5918f5214e1ac399 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Fri, 23 Oct 2026 13:48:01 +0000 Subject: [PATCH 8/8] Stop sending experimental.python_* deploy telemetry The pydabs section carries the same counts. Consumers read pydabs for new CLIs and fall back to experimental.python_* for older ones. Co-authored-by: Isaac --- acceptance/bundle/python/telemetry/output.txt | 12 ------------ acceptance/bundle/python/telemetry/script | 1 - bundle/phases/telemetry.go | 4 ---- libs/telemetry/protos/bundle_deploy.go | 7 ------- 4 files changed, 24 deletions(-) diff --git a/acceptance/bundle/python/telemetry/output.txt b/acceptance/bundle/python/telemetry/output.txt index 797748841a6..1b6ba81e813 100644 --- a/acceptance/bundle/python/telemetry/output.txt +++ b/acceptance/bundle/python/telemetry/output.txt @@ -28,12 +28,6 @@ Resources: 4 created, 0 changed, 0 deleted, 0 unchanged ], "config_section": "PYTHON" }, - "experimental": { - "python_added_resources_count": 2, - "python_updated_resources_count": 2, - "python_resource_loaders_count": 1, - "python_resource_mutators_count": 1 - }, "all_resource_types": [ "jobs", "pipelines", @@ -68,12 +62,6 @@ Resources: 0 created, 0 changed, 0 deleted, 4 unchanged ], "config_section": "EXPERIMENTAL_PYTHON" }, - "experimental": { - "python_added_resources_count": 2, - "python_updated_resources_count": 2, - "python_resource_loaders_count": 1, - "python_resource_mutators_count": 1 - }, "all_resource_types": [ "jobs", "pipelines", diff --git a/acceptance/bundle/python/telemetry/script b/acceptance/bundle/python/telemetry/script index 7722ab6ff62..311998c3e9c 100644 --- a/acceptance/bundle/python/telemetry/script +++ b/acceptance/bundle/python/telemetry/script @@ -3,7 +3,6 @@ print_pydabs_telemetry() { jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event | { pydabs, - experimental: .experimental | with_entries(select(.key | startswith("python_"))), all_resource_types: [.resources_metadata.resources[].resource_type] }' "$OUT_REQUESTS" rm "$OUT_REQUESTS" diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index cf67e2b6ed8..c4f6caf5473 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -434,10 +434,6 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { WorkspaceArtifactPathType: artifactPathType, BoolValues: b.Metrics.BoolValues, LocalCacheMeasurementsMs: b.Metrics.LocalCacheMeasurementsMs, - PythonAddedResourcesCount: pydabs.AddedResourcesCount, //nolint:staticcheck // SA1019: populated alongside Pydabs during the transition - PythonUpdatedResourcesCount: pydabs.UpdatedResourcesCount, - PythonResourceLoadersCount: pydabs.ResourceLoadersCount, - PythonResourceMutatorsCount: pydabs.ResourceMutatorsCount, VariableCount: int64(variableCount), ComplexVariableCount: complexVariableCount, LookupVariableCount: lookupVariableCount, diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index 3daf8f731cf..4464f36bca4 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -79,13 +79,6 @@ type BundleDeployExperimental struct { // Execution time per mutator for a selected subset of mutators. BundleMutatorExecutionTimeMs []IntMapEntry `json:"bundle_mutator_execution_time_ms,omitempty"` - // Deprecated: superseded by the matching Pydabs fields. Both are - // populated during the transition. - PythonAddedResourcesCount int64 `json:"python_added_resources_count,omitempty"` - PythonUpdatedResourcesCount int64 `json:"python_updated_resources_count,omitempty"` - PythonResourceLoadersCount int64 `json:"python_resource_loaders_count,omitempty"` - PythonResourceMutatorsCount int64 `json:"python_resource_mutators_count,omitempty"` - // Number of files in the synced source tree considered for upload: the full // set, not the incremental delta actually transferred (an incremental deploy // reports the full count even when nothing changed). The set is whatever the