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  }