diff --git a/acceptance/bundle/telemetry/deploy/databricks.yml b/acceptance/bundle/telemetry/deploy/databricks.yml index 3a20a11c29..febbf0bce7 100644 --- a/acceptance/bundle/telemetry/deploy/databricks.yml +++ b/acceptance/bundle/telemetry/deploy/databricks.yml @@ -6,6 +6,9 @@ resources: jobs: job_one: name: job one + permissions: + - level: CAN_VIEW + user_name: viewer@example.com job_two: name: job two job_three: diff --git a/acceptance/bundle/telemetry/deploy/out.telemetry.txt b/acceptance/bundle/telemetry/deploy/out.telemetry.txt index f945233dd1..62fe9a3f2a 100644 --- a/acceptance/bundle/telemetry/deploy/out.telemetry.txt +++ b/acceptance/bundle/telemetry/deploy/out.telemetry.txt @@ -85,7 +85,28 @@ ], "bundle_mode": "TYPE_UNSPECIFIED", "workspace_artifact_path_type": "WORKSPACE_FILE_SYSTEM", - "local_cache_measurements_ms": [...redacted...] + "local_cache_measurements_ms": [...redacted...], + "resource_state_size_bytes": [ + 250, + 250, + 282, + 284, + 409 + ], + "resource_action_counts": [ + { + "key": "jobs.create", + "value": 3 + }, + { + "key": "jobs.permissions.create", + "value": 1 + }, + { + "key": "pipelines.create", + "value": 2 + } + ] } } } diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 35c4161eae..b072bf0284 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -208,7 +208,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } - logDeployTelemetry(ctx, b) + logDeployTelemetry(ctx, b, plan) bundle.ApplyContext(ctx, b, scripts.Execute(config.ScriptPostDeploy)) } diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 4584e9fc5e..38bbfb8613 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -7,8 +7,10 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/jsonsaver" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/telemetry" "github.com/databricks/cli/libs/telemetry/protos" @@ -33,7 +35,7 @@ func getExecutionTimes(b *bundle.Bundle) []protos.IntMapEntry { return executionTimes } -func logDeployTelemetry(ctx context.Context, b *bundle.Bundle) { +func logDeployTelemetry(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan) { resourcesCount := int64(0) _, err := dyn.MapByPattern(b.Config.Value(), dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { resourcesCount++ @@ -184,7 +186,74 @@ func logDeployTelemetry(ctx context.Context, b *bundle.Bundle) { ComplexVariableCount: complexVariableCount, LookupVariableCount: lookupVariableCount, BundleMutatorExecutionTimeMs: getExecutionTimes(b), + ResourceStateSizeBytes: computeResourceStateSizes(ctx, b), + ResourceActionCounts: computeActionCounts(plan), }, }, }) } + +// computeActionCounts computes per-resource-type action counts from the deploy plan. +func computeActionCounts(plan *deployplan.Plan) []protos.IntMapEntry { + if plan == nil { + return nil + } + + counts := map[string]int64{} + for key, entry := range plan.Plan { + if entry.Action == deployplan.Skip { + continue + } + + resourceType := config.GetResourceTypeFromKey(key) + if resourceType == "" { + continue + } + + mapKey := resourceType + "." + string(entry.Action) + counts[mapKey]++ + } + + return sortedIntMapEntries(counts) +} + +// computeResourceStateSizes computes per-resource config sizes in bytes from +// the bundle's YAML configuration. Returns one entry per resource instance, +// sorted in ascending order. This is engine-independent since it uses the +// YAML config directly. +func computeResourceStateSizes(ctx context.Context, b *bundle.Bundle) []int64 { + var sizes []int64 + + _, err := dyn.MapByPattern( + b.Config.Value(), + dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + func(p dyn.Path, v dyn.Value) (dyn.Value, error) { + jsonBytes, err := jsonsaver.Marshal(v) + if err != nil { + log.Debugf(ctx, "failed to marshal resource %s: %s", p, err) + return v, nil + } + sizes = append(sizes, int64(len(jsonBytes))) + return v, nil + }, + ) + if err != nil { + log.Debugf(ctx, "failed to compute resource state sizes: %s", err) + return nil + } + + slices.Sort(sizes) + return sizes +} + +// sortedIntMapEntries converts a map to a sorted slice of IntMapEntry. +func sortedIntMapEntries(m map[string]int64) []protos.IntMapEntry { + entries := make([]protos.IntMapEntry, 0, len(m)) + for k, v := range m { + entries = append(entries, protos.IntMapEntry{Key: k, Value: v}) + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].Key < entries[j].Key + }) + return entries +} diff --git a/libs/telemetry/protos/bundle_deploy.go b/libs/telemetry/protos/bundle_deploy.go index ab1b3a46de..0a5c9d6648 100644 --- a/libs/telemetry/protos/bundle_deploy.go +++ b/libs/telemetry/protos/bundle_deploy.go @@ -83,6 +83,15 @@ type BundleDeployExperimental struct { // Local cache measurements in milliseconds (compute duration, potential savings, etc.) LocalCacheMeasurementsMs []IntMapEntry `json:"local_cache_measurements_ms,omitempty"` + + // Per-resource config sizes in bytes, one entry per resource instance. + // Sorted in ascending order. + ResourceStateSizeBytes []int64 `json:"resource_state_size_bytes,omitempty"` + + // Per-resource-type deployment action counts. + // Keys are "{resource_type}.{action}" (e.g., "jobs.create", "pipelines.update"). + // Values are the number of resources with that action. + ResourceActionCounts []IntMapEntry `json:"resource_action_counts,omitempty"` } type BoolMapEntry struct {