From 3e4d6b252f1ad4540b792ce4516654ade76a18dd Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 18 Feb 2022 16:43:23 -0500 Subject: [PATCH] jsonplan: Improve performance for deep objects When calculating the unknown values for JSON plan output, we would previously recursively call the `unknownAsBool` function on the current sub-tree twice, if any values were unknown. This was wasteful, but not noticeable for normal Terraform resource shapes. However for deeper nested object values, such as Kubernetes manifests, this was a severe performance problem, causing `terraform show -json` to take several hours to render a plan. This commit reuses the already calculated unknown value for the subtree, and adds benchmark coverage to demonstrate the improvement. --- internal/command/jsonplan/plan.go | 2 +- internal/command/jsonplan/plan_test.go | 56 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 53fe8ec01..c68cfe802 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -597,7 +597,7 @@ func unknownAsBool(val cty.Value) cty.Value { // Omit all of the "false"s for known values for more compact // serialization if !vAsBool.RawEquals(cty.False) { - vals[k.AsString()] = unknownAsBool(v) + vals[k.AsString()] = vAsBool } } // The above transform may have changed the types of some of the diff --git a/internal/command/jsonplan/plan_test.go b/internal/command/jsonplan/plan_test.go index cf23187e5..3c640cf6d 100644 --- a/internal/command/jsonplan/plan_test.go +++ b/internal/command/jsonplan/plan_test.go @@ -304,3 +304,59 @@ func TestEncodePaths(t *testing.T) { }) } } + +func deepObjectValue(depth int) cty.Value { + v := cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.NumberIntVal(2), + "c": cty.True, + "d": cty.UnknownVal(cty.String), + }) + + result := v + + for i := 0; i < depth; i++ { + result = cty.ObjectVal(map[string]cty.Value{ + "a": result, + "b": result, + "c": result, + }) + } + + return result +} + +func BenchmarkUnknownAsBool_2(b *testing.B) { + value := deepObjectValue(2) + for n := 0; n < b.N; n++ { + unknownAsBool(value) + } +} + +func BenchmarkUnknownAsBool_3(b *testing.B) { + value := deepObjectValue(3) + for n := 0; n < b.N; n++ { + unknownAsBool(value) + } +} + +func BenchmarkUnknownAsBool_5(b *testing.B) { + value := deepObjectValue(5) + for n := 0; n < b.N; n++ { + unknownAsBool(value) + } +} + +func BenchmarkUnknownAsBool_7(b *testing.B) { + value := deepObjectValue(7) + for n := 0; n < b.N; n++ { + unknownAsBool(value) + } +} + +func BenchmarkUnknownAsBool_9(b *testing.B) { + value := deepObjectValue(9) + for n := 0; n < b.N; n++ { + unknownAsBool(value) + } +}