go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/aggrmetrics/pm.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 aggrmetrics 16 17 import ( 18 "context" 19 20 "go.chromium.org/luci/common/errors" 21 "go.chromium.org/luci/common/logging" 22 "go.chromium.org/luci/common/retry/transient" 23 "go.chromium.org/luci/common/tsmon/field" 24 "go.chromium.org/luci/common/tsmon/metric" 25 "go.chromium.org/luci/common/tsmon/types" 26 "go.chromium.org/luci/gae/service/datastore" 27 28 "go.chromium.org/luci/cv/internal/prjmanager" 29 ) 30 31 var ( 32 metricPMStateCLs = metric.NewInt( 33 "cv/internal/pm/cls", 34 "Number of tracked CLs(# of vertices in the CL graph).", 35 nil, 36 field.String("project"), 37 ) 38 metricPMStateDeps = metric.NewInt( 39 "cv/internal/pm/deps", 40 "Number of tracked CL dependencies (# of edges in the CL graph)", 41 nil, 42 field.String("project"), 43 ) 44 metricPMStateComponents = metric.NewInt( 45 "cv/internal/pm/components", 46 "Number of tracked CL components (~ connected components in the CL graph)", 47 nil, 48 field.String("project"), 49 ) 50 metricPMEntitySize = metric.NewInt( 51 "cv/internal/pm/entity_size", 52 "Approximate size of the Project entity in Datastore", 53 &types.MetricMetadata{Units: types.Bytes}, 54 field.String("project"), 55 ) 56 ) 57 58 // maxProjects is the max number of projects to which the pmReporter will 59 // scale in its current implementation. 60 // 61 // If a bigger amount is desired, batching should be implemented to avoid 62 // high memory usage. 63 const maxProjects = 500 64 65 type pmReporter struct{} 66 67 // metrics implements aggregator interface. 68 func (*pmReporter) metrics() []types.Metric { 69 return []types.Metric{ 70 metricPMStateCLs, 71 metricPMStateDeps, 72 metricPMStateComponents, 73 metricPMEntitySize, 74 } 75 } 76 77 // report implements aggregator interface. 78 func (*pmReporter) report(ctx context.Context, projects []string) error { 79 if len(projects) > maxProjects { 80 logging.Errorf(ctx, "FIXME: too many active projects (>%d) to report metrics for", maxProjects) 81 return errors.New("too many active projects") 82 } 83 84 // We need to load & decode Datastore entities as prjmanager.Project objects 85 // in order to compute metrics like the number of components. 86 // But we also need to figure out the Datastore entities' size in bytes, which 87 // is unfortunately not easy to compute off the prjmanager.Project objects. 88 // 89 // So, load each project entity as both prjmanager.Project and the 90 // projectEntitySizeEstimator objects. Note, that both objects have 91 // datastore's Kind is exactly the same (see `gae:"$kind..."` attribute in 92 // both structs. 93 // 94 // Finally, behind the scenes, datastore library should optimize this to load 95 // only N entities instead of 2*N. 96 eSizes := make([]projectEntitySizeEstimator, len(projects)) 97 entities := make([]prjmanager.Project, len(projects)) 98 i := 0 99 for _, p := range projects { 100 eSizes[i].ID = p 101 entities[i].ID = p 102 i++ 103 } 104 err := datastore.Get(ctx, eSizes, entities) 105 if err != nil { 106 return errors.Annotate(err, "failed to fetch all projects").Tag(transient.Tag).Err() 107 } 108 109 for i, e := range entities { 110 project := eSizes[i].ID 111 metricPMEntitySize.Set(ctx, eSizes[i].bytes, project) 112 metricPMStateCLs.Set(ctx, int64(len(e.State.GetPcls())), project) 113 metricPMStateComponents.Set(ctx, int64(len(e.State.GetComponents())), project) 114 deps := 0 115 for _, pcl := range e.State.GetPcls() { 116 deps += len(pcl.GetDeps()) 117 } 118 metricPMStateDeps.Set(ctx, int64(deps), project) 119 } 120 return nil 121 } 122 123 type projectEntitySizeEstimator struct { 124 // Kind must match the kind of the prjmanager.Project entity. 125 _kind string `gae:"$kind,Project"` 126 ID string `gae:"$id"` 127 bytes int64 128 } 129 130 // Load implements datastore.PropertyLoadSaver. 131 func (e *projectEntitySizeEstimator) Load(m datastore.PropertyMap) error { 132 e.bytes = m.EstimateSize() 133 return nil 134 } 135 136 // Save implements datastore.PropertyLoadSaver. 137 func (e *projectEntitySizeEstimator) Save(withMeta bool) (datastore.PropertyMap, error) { 138 panic("not needed") 139 }