go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/cvtesting/benchmarks/cl_1k_test.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package benchmarks 16 17 import ( 18 "context" 19 "fmt" 20 "runtime" 21 "testing" 22 "time" 23 24 "github.com/klauspost/compress/zstd" 25 26 "google.golang.org/protobuf/proto" 27 "google.golang.org/protobuf/types/known/timestamppb" 28 29 "go.chromium.org/luci/common/clock/testclock" 30 gerritpb "go.chromium.org/luci/common/proto/gerrit" 31 "go.chromium.org/luci/gae/impl/memory" 32 "go.chromium.org/luci/gae/service/datastore" 33 34 cfgpb "go.chromium.org/luci/cv/api/config/v2" 35 "go.chromium.org/luci/cv/internal/changelist" 36 "go.chromium.org/luci/cv/internal/common" 37 "go.chromium.org/luci/cv/internal/gerrit" 38 gf "go.chromium.org/luci/cv/internal/gerrit/gerritfake" 39 "go.chromium.org/luci/cv/internal/gerrit/trigger" 40 "go.chromium.org/luci/cv/internal/prjmanager/prjpb" 41 ) 42 43 var epoch = testclock.TestRecentTimeUTC 44 45 // clidOffset emulates realistic datastore-generated CLIDs. 46 const clidOffset = 4683683202072576 47 48 func benchmarkRAMper1KObjects(b *testing.B, objectKind string, work func()) (before, after runtime.MemStats) { 49 runtime.GC() 50 runtime.ReadMemStats(&before) 51 52 b.ReportAllocs() 53 work() 54 55 runtime.ReadMemStats(&after) 56 obj1 := float64(after.HeapAlloc-before.HeapAlloc) / 1024 / 1024 / float64(b.N) 57 b.ReportMetric(obj1*1000, "MB-heap/1K-"+objectKind) 58 return 59 } 60 61 // BenchmarkLoadCLsUsage1K estimates how much RAM keeping 1K CL objects will take. 62 func BenchmarkLoadCLsUsage1K(b *testing.B) { 63 if b.N > 100000 { 64 b.Skip("Specify lower `-benchtime=XX`") 65 } 66 ctx := putCLs(b.N) 67 cls := initCLObjects(b.N) 68 69 benchmarkRAMper1KObjects(b, "CL", func() { 70 if err := datastore.Get(ctx, cls); err != nil { 71 panic(err) 72 } 73 }) 74 } 75 76 // BenchmarkPMStatePCLRAMUsage1K estimates how much RAM keeping 1K PM.PState.PCL 77 // objects will take. 78 func BenchmarkPMStatePCLRAMUsage1K(b *testing.B) { 79 if b.N > 100000 { 80 b.Skip("Specify lower `-benchtime=XX`") 81 } 82 cis := makeCIs(b.N) 83 cls := make([]*changelist.CL, b.N) 84 for i, ci := range cis { 85 cls[i] = makeCL(ci) 86 } 87 88 out := make([]*prjpb.PCL, b.N) 89 90 benchmarkRAMper1KObjects(b, "prjpb.PCL", func() { 91 for i, cl := range cls { 92 out[i] = makePCL(cl) 93 } 94 }) 95 96 bs, _ := proto.Marshal(&prjpb.PState{Pcls: out}) 97 v := float64(len(bs)) / 1024 / 1024 / float64(b.N) 98 b.ReportMetric(v*1000, "MB-pb/1K-"+"prjpb.PCL") 99 100 var z zstd.Encoder 101 compressed := z.EncodeAll(bs, nil) 102 v = float64(len(compressed)) / 1024 / 1024 / float64(b.N) 103 b.ReportMetric(v*1000, "MB-pb-zstd/1K-"+"prjpb.PCL") 104 } 105 106 func putCLs(N int) context.Context { 107 ctx := memory.Use(context.Background()) 108 cis := makeCIs(N) 109 for _, ci := range cis { 110 cl := makeCL(ci) 111 if err := datastore.Put(ctx, cl); err != nil { 112 panic(err) 113 } 114 } 115 return ctx 116 } 117 118 func initCLObjects(N int) []*changelist.CL { 119 out := make([]*changelist.CL, N) 120 for i := range out { 121 out[i] = &changelist.CL{ID: common.CLID(i + 1 + clidOffset)} 122 } 123 return out 124 } 125 126 func makePCL(cl *changelist.CL) *prjpb.PCL { 127 // Copy deps for realism. 128 deps := make([]*changelist.Dep, len(cl.Snapshot.GetDeps())) 129 copy(deps, cl.Snapshot.GetDeps()) 130 131 trs := trigger.Find(&trigger.FindInput{ 132 ChangeInfo: cl.Snapshot.GetGerrit().GetInfo(), 133 ConfigGroup: &cfgpb.ConfigGroup{}}) 134 // Copy trigger email string for realism of allocations. 135 tr := trs.GetCqVoteTrigger() 136 tr.Email = tr.Email + " " 137 return &prjpb.PCL{ 138 Clid: int64(cl.ID), 139 Eversion: cl.EVersion, 140 Status: prjpb.PCL_OK, 141 Submitted: false, 142 ConfigGroupIndexes: []int32{0}, 143 Deps: deps, 144 Triggers: trs, 145 } 146 } 147 148 func makeCL(ci *gerritpb.ChangeInfo) *changelist.CL { 149 now := epoch.Add(time.Hour * 1000) 150 mps, ps, err := gerrit.EquivalentPatchsetRange(ci) 151 if err != nil { 152 panic(err) 153 } 154 num := clidOffset + ci.GetNumber() 155 // The deps aren't supposed to make sense, only to resemble what actual 156 // CL stores. 157 return &changelist.CL{ 158 ID: common.CLID(num), 159 ExternalID: changelist.MustGobID("host", ci.GetNumber()), 160 Snapshot: &changelist.Snapshot{ 161 Kind: &changelist.Snapshot_Gerrit{Gerrit: &changelist.Gerrit{ 162 Info: ci, 163 Files: []string{"a.cpp"}, 164 Host: "host", 165 GitDeps: []*changelist.GerritGitDep{ 166 {Change: num - 1, Immediate: true}, 167 {Change: num - 2, Immediate: false}, 168 }, 169 SoftDeps: []*changelist.GerritSoftDep{ 170 {Change: num + 1, Host: "host-2"}, 171 }, 172 }}, 173 Deps: []*changelist.Dep{ 174 {Clid: num - 2, Kind: changelist.DepKind_SOFT}, 175 {Clid: num - 1, Kind: changelist.DepKind_HARD}, 176 {Clid: num + 1, Kind: changelist.DepKind_SOFT}, // num:host-2 177 }, 178 ExternalUpdateTime: ci.GetUpdated(), 179 LuciProject: "test", 180 MinEquivalentPatchset: int32(mps), 181 Patchset: int32(ps), 182 }, 183 ApplicableConfig: &changelist.ApplicableConfig{ 184 Projects: []*changelist.ApplicableConfig_Project{ 185 {Name: "test", ConfigGroupIds: []string{"main"}}, 186 }, 187 }, 188 Access: &changelist.Access{ 189 ByProject: map[string]*changelist.Access_Project{ 190 "test-2": { 191 NoAccess: true, 192 NoAccessTime: timestamppb.New(now), 193 UpdateTime: timestamppb.New(now), 194 }, 195 }, 196 }, 197 EVersion: 1, 198 UpdateTime: now, 199 IncompleteRuns: common.MakeRunIDs("test/run-001"), 200 } 201 } 202 203 func makeCIs(N int) []*gerritpb.ChangeInfo { 204 cis := make([]*gerritpb.ChangeInfo, N) 205 for i := range cis { 206 cis[i] = makeCI(i) 207 changelist.RemoveUnusedGerritInfo(cis[i]) 208 } 209 return cis 210 } 211 212 func makeCI(i int) *gerritpb.ChangeInfo { 213 di := time.Duration(i) 214 u1 := fmt.Sprintf("user-%d", 2*i+1) 215 u2 := fmt.Sprintf("user-%d", 2*i+2) 216 return gf.CI( 217 i+1, 218 gf.Owner("owner-3"), 219 gf.PS(5), 220 gf.AllRevs(), 221 gf.CQ(+1, epoch.Add(di*time.Minute), u1), 222 gf.Vote("Code-Review", +2, epoch.Add(di*time.Minute), u2), 223 gf.Messages( 224 &gerritpb.ChangeMessageInfo{ 225 Author: gf.U(u1), 226 Date: timestamppb.New(epoch.Add(di * time.Minute)), 227 Message: "this is message", 228 Id: "sha-something-1", 229 }, 230 &gerritpb.ChangeMessageInfo{ 231 Author: gf.U(u2), 232 Date: timestamppb.New(epoch.Add(di * time.Minute)), 233 Message: "this is a better message", 234 Id: "sha-something-2", 235 }, 236 ), 237 ) 238 }