LCOV - code coverage report
Current view: top level - server/notedb - ChangeNotesCache.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 122 125 97.6 %
Date: 2022-11-19 15:00:39 Functions: 31 31 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2016 The Android Open Source Project
       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 com.google.gerrit.server.notedb;
      16             : 
      17             : import com.google.auto.value.AutoValue;
      18             : import com.google.common.annotations.VisibleForTesting;
      19             : import com.google.common.cache.Cache;
      20             : import com.google.common.collect.Table;
      21             : import com.google.common.flogger.FluentLogger;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.entities.Change;
      24             : import com.google.gerrit.entities.Project;
      25             : import com.google.gerrit.entities.RefNames;
      26             : import com.google.gerrit.proto.Protos;
      27             : import com.google.gerrit.server.ReviewerByEmailSet;
      28             : import com.google.gerrit.server.ReviewerSet;
      29             : import com.google.gerrit.server.account.externalids.ExternalIdCache;
      30             : import com.google.gerrit.server.cache.CacheModule;
      31             : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesKeyProto;
      32             : import com.google.gerrit.server.cache.serialize.CacheSerializer;
      33             : import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
      34             : import com.google.gerrit.server.notedb.AbstractChangeNotes.Args;
      35             : import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
      36             : import com.google.inject.Inject;
      37             : import com.google.inject.Module;
      38             : import com.google.inject.Singleton;
      39             : import com.google.inject.name.Named;
      40             : import java.io.IOException;
      41             : import java.util.List;
      42             : import java.util.Map;
      43             : import java.util.Set;
      44             : import java.util.concurrent.Callable;
      45             : import java.util.concurrent.ExecutionException;
      46             : import java.util.function.Supplier;
      47             : import org.eclipse.jgit.errors.ConfigInvalidException;
      48             : import org.eclipse.jgit.lib.ObjectId;
      49             : 
      50             : @Singleton
      51             : public class ChangeNotesCache {
      52         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      53             : 
      54             :   @VisibleForTesting static final String CACHE_NAME = "change_notes";
      55             : 
      56             :   public static Module module() {
      57         152 :     return new CacheModule() {
      58             :       @Override
      59             :       protected void configure() {
      60         152 :         bind(ChangeNotesCache.class);
      61         152 :         persist(CACHE_NAME, Key.class, ChangeNotesState.class)
      62         152 :             .weigher(Weigher.class)
      63         152 :             .maximumWeight(10 << 20)
      64         152 :             .diskLimit(-1)
      65         152 :             .version(5)
      66         152 :             .keySerializer(Key.Serializer.INSTANCE)
      67         152 :             .valueSerializer(ChangeNotesState.Serializer.INSTANCE);
      68         152 :       }
      69             :     };
      70             :   }
      71             : 
      72             :   @AutoValue
      73         103 :   public abstract static class Key {
      74             :     static Key create(Project.NameKey project, Change.Id changeId, ObjectId id) {
      75         103 :       return new AutoValue_ChangeNotesCache_Key(project, changeId, id.copy());
      76             :     }
      77             : 
      78             :     abstract Project.NameKey project();
      79             : 
      80             :     abstract Change.Id changeId();
      81             : 
      82             :     abstract ObjectId id();
      83             : 
      84         152 :     @VisibleForTesting
      85             :     enum Serializer implements CacheSerializer<Key> {
      86         152 :       INSTANCE;
      87             : 
      88             :       @Override
      89             :       public byte[] serialize(Key object) {
      90           1 :         return Protos.toByteArray(
      91           1 :             ChangeNotesKeyProto.newBuilder()
      92           1 :                 .setProject(object.project().get())
      93           1 :                 .setChangeId(object.changeId().get())
      94           1 :                 .setId(ObjectIdConverter.create().toByteString(object.id()))
      95           1 :                 .build());
      96             :       }
      97             : 
      98             :       @Override
      99             :       public Key deserialize(byte[] in) {
     100           1 :         ChangeNotesKeyProto proto = Protos.parseUnchecked(ChangeNotesKeyProto.parser(), in);
     101           1 :         return Key.create(
     102           1 :             Project.nameKey(proto.getProject()),
     103           1 :             Change.id(proto.getChangeId()),
     104           1 :             ObjectIdConverter.create().fromByteString(proto.getId()));
     105             :       }
     106             :     }
     107             :   }
     108             : 
     109         152 :   public static class Weigher implements com.google.common.cache.Weigher<Key, ChangeNotesState> {
     110             :     // Single object overhead.
     111             :     private static final int O = 16;
     112             : 
     113             :     // Single pointer overhead.
     114             :     private static final int P = 8;
     115             : 
     116             :     // Single int overhead.
     117             :     private static final int I = 4;
     118             : 
     119             :     // Single IntKey overhead.
     120             :     private static final int K = O + I;
     121             : 
     122             :     // Single Timestamp overhead.
     123             :     private static final int T = O + 8;
     124             : 
     125             :     /**
     126             :      * {@inheritDoc}
     127             :      *
     128             :      * <p>Take all columns and all collection sizes into account, but use estimated average element
     129             :      * sizes rather than iterating over collections. Numbers are largely hand-wavy based on
     130             :      * http://stackoverflow.com/questions/258120/what-is-the-memory-consumption-of-an-object-in-java
     131             :      *
     132             :      * <p>Should be kept up to date with {@link ChangeNotesState}. Please, keep weights listed in
     133             :      * the same order as fields.
     134             :      */
     135             :     @Override
     136             :     public int weigh(Key key, ChangeNotesState state) {
     137         103 :       return P
     138             :           + O
     139             :           + 20 // metaId
     140             :           + K // changeId
     141         103 :           + str(40) // changeKey
     142             :           + T // createdOn
     143             :           + T // lastUpdatedOn
     144             :           + P
     145             :           + K // owner
     146             :           + P
     147         103 :           + str(state.columns().branch())
     148             :           + P // status
     149             :           + P
     150         103 :           + patchSetId() // currentPatchSetId
     151             :           + P
     152         103 :           + str(state.columns().subject())
     153             :           + P
     154         103 :           + str(state.columns().topic())
     155             :           + P
     156         103 :           + str(state.columns().originalSubject())
     157             :           + P
     158         103 :           + str(state.columns().submissionId())
     159             :           + 1 // isPrivate
     160             :           + 1 // workInProgress
     161             :           + 1 // reviewStarted
     162             :           + P
     163             :           + K // revertOf
     164             :           + P
     165         103 :           + patchSetId() // cherryPickOf
     166             :           + P
     167         103 :           + set(state.hashtags(), str(10))
     168         103 :           + str(state.serverId()) // serverId
     169             :           + P
     170         103 :           + list(state.patchSets(), patchSet())
     171             :           + P
     172         103 :           + reviewerSet(state.reviewers(), 2) // REVIEWER or CC
     173             :           + P
     174         103 :           + reviewerSet(state.reviewersByEmail(), 2) // REVIEWER or CC
     175             :           + P
     176         103 :           + reviewerSet(state.pendingReviewers(), 3) // includes REMOVED
     177             :           + P
     178         103 :           + reviewerSet(state.pendingReviewersByEmail(), 3) // includes REMOVED
     179             :           + P
     180         103 :           + list(state.allPastReviewers(), approval())
     181             :           + P
     182         103 :           + list(state.reviewerUpdates(), 4 * O + K + K + P)
     183             :           + P
     184         103 :           + list(state.assigneeUpdates(), 4 * O + K + K)
     185             :           + P
     186         103 :           + set(state.attentionSet(), 4 * O + K + I + str(15))
     187             :           + P
     188         103 :           + list(state.allAttentionSetUpdates(), 4 * O + K + I + str(15))
     189             :           + P
     190         103 :           + list(state.submitRecords(), P + list(2, str(4) + P + K) + P)
     191             :           + P
     192         103 :           + list(state.changeMessages(), changeMessage())
     193             :           + P
     194         103 :           + map(state.publishedComments().asMap(), comment())
     195             :           + I // updateCount
     196             :           + T; // mergedOn
     197             :     }
     198             : 
     199             :     private static int str(String s) {
     200         103 :       if (s == null) {
     201         103 :         return P;
     202             :       }
     203         103 :       return str(s.length());
     204             :     }
     205             : 
     206             :     private static int str(int n) {
     207         103 :       return 8 + 24 + 2 * n;
     208             :     }
     209             : 
     210             :     private static int patchSetId() {
     211         103 :       return O + 4 + O + 4;
     212             :     }
     213             : 
     214             :     private static int set(Set<?> set, int elemSize) {
     215         103 :       if (set == null) {
     216           0 :         return P;
     217             :       }
     218         103 :       return hashtable(set.size(), elemSize);
     219             :     }
     220             : 
     221             :     private static int map(Map<?, ?> map, int elemSize) {
     222         103 :       if (map == null) {
     223           0 :         return P;
     224             :       }
     225         103 :       return hashtable(map.size(), elemSize);
     226             :     }
     227             : 
     228             :     private static int hashtable(int n, int elemSize) {
     229             :       // Made up numbers.
     230         103 :       int overhead = 32;
     231         103 :       int elemOverhead = O + 32;
     232         103 :       return overhead + elemOverhead * n * elemSize;
     233             :     }
     234             : 
     235             :     private static int list(List<?> list, int elemSize) {
     236         103 :       if (list == null) {
     237           0 :         return P;
     238             :       }
     239         103 :       return list(list.size(), elemSize);
     240             :     }
     241             : 
     242             :     private static int list(int n, int elemSize) {
     243         103 :       return O + O + n * (P + elemSize);
     244             :     }
     245             : 
     246             :     private static int hashBasedTable(
     247             :         Table<?, ?, ?> table, int numRows, int rowKey, int columnKey, int elemSize) {
     248         103 :       return O
     249         103 :           + hashtable(numRows, rowKey + hashtable(0, 0))
     250         103 :           + hashtable(table.size(), columnKey + elemSize);
     251             :     }
     252             : 
     253             :     private static int reviewerSet(ReviewerSet reviewers, int numRows) {
     254         103 :       final int rowKey = 1; // ReviewerStateInternal
     255         103 :       final int columnKey = K; // Account.Id
     256         103 :       final int cellValue = T; // Timestamp
     257         103 :       return hashBasedTable(reviewers.asTable(), numRows, rowKey, columnKey, cellValue);
     258             :     }
     259             : 
     260             :     private static int reviewerSet(ReviewerByEmailSet reviewers, int numRows) {
     261         103 :       final int rowKey = 1; // ReviewerStateInternal
     262         103 :       final int columnKey = P + 2 * str(20); // name and email, just a guess
     263         103 :       final int cellValue = T; // Timestamp
     264         103 :       return hashBasedTable(reviewers.asTable(), numRows, rowKey, columnKey, cellValue);
     265             :     }
     266             : 
     267             :     private static int patchSet() {
     268         103 :       return O
     269             :           + P
     270         103 :           + patchSetId()
     271         103 :           + str(40) // revision
     272             :           + P
     273             :           + K // uploader
     274             :           + P
     275             :           + T // createdOn
     276             :           + 1 // draft
     277         103 :           + str(40) // groups
     278             :           + P; // pushCertificate
     279             :     }
     280             : 
     281             :     private static int approval() {
     282         103 :       return O
     283             :           + P
     284         103 :           + patchSetId()
     285             :           + P
     286             :           + K
     287             :           + P
     288             :           + O
     289         103 :           + str(10)
     290             :           + 2 // value
     291             :           + P
     292             :           + T // granted
     293             :           + P // tag
     294             :           + P; // realAccountId
     295             :     }
     296             : 
     297             :     private static int changeMessage() {
     298         103 :       int key = K + str(20);
     299         103 :       return O
     300             :           + P
     301             :           + key
     302             :           + P
     303             :           + K // author
     304             :           + P
     305             :           + T // writtenON
     306         103 :           + str(64) // message
     307             :           + P
     308         103 :           + patchSetId()
     309             :           + P
     310             :           + P; // realAuthor
     311             :     }
     312             : 
     313             :     private static int comment() {
     314         103 :       int key = P + str(20) + P + str(32) + 4;
     315         103 :       int ident = O + 4;
     316         103 :       return O
     317             :           + P
     318             :           + key
     319             :           + 4 // lineNbr
     320             :           + P
     321             :           + ident // author
     322             :           + P
     323             :           + ident // realAuthor
     324             :           + P
     325             :           + T // writtenOn
     326             :           + 2 // side
     327         103 :           + str(32) // message
     328         103 :           + str(10) // parentUuid
     329             :           + (P + O + 4 + 4 + 4 + 4) / 2 // range on 50% of comments
     330             :           + P // tag
     331             :           + P
     332         103 :           + str(40) // revId
     333             :           + P
     334         103 :           + str(36); // serverId
     335             :     }
     336             :   }
     337             : 
     338             :   @AutoValue
     339         103 :   abstract static class Value {
     340             :     abstract ChangeNotesState state();
     341             : 
     342             :     /**
     343             :      * The {@link RevisionNoteMap} produced while parsing this change.
     344             :      *
     345             :      * <p>These instances are mutable and non-threadsafe, so it is only safe to return it to the
     346             :      * caller that actually incurred the cache miss. It is only used as an optimization; {@link
     347             :      * ChangeNotes} is capable of lazily loading it as necessary.
     348             :      */
     349             :     @Nullable
     350             :     abstract RevisionNoteMap<ChangeRevisionNote> revisionNoteMap();
     351             :   }
     352             : 
     353             :   private class Loader implements Callable<ChangeNotesState> {
     354             :     private final Key key;
     355             :     private final Supplier<ChangeNotesRevWalk> walkSupplier;
     356             : 
     357             :     private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
     358             : 
     359         103 :     private Loader(Key key, Supplier<ChangeNotesRevWalk> walkSupplier) {
     360         103 :       this.key = key;
     361         103 :       this.walkSupplier = walkSupplier;
     362         103 :     }
     363             : 
     364             :     @Override
     365             :     public ChangeNotesState call() throws ConfigInvalidException, IOException {
     366         103 :       logger.atFine().log(
     367         103 :           "Load change notes for change %s of project %s", key.changeId(), key.project());
     368         103 :       ChangeNotesParser parser =
     369             :           new ChangeNotesParser(
     370         103 :               key.changeId(),
     371         103 :               key.id(),
     372         103 :               walkSupplier.get(),
     373             :               args.changeNoteJson,
     374             :               args.metrics,
     375             :               args.serverId,
     376             :               externalIdCache);
     377         103 :       ChangeNotesState result = parser.parseAll();
     378             :       // This assignment only happens if call() was actually called, which only
     379             :       // happens when Cache#get(K, Callable<V>) incurs a cache miss.
     380         103 :       revisionNoteMap = parser.getRevisionNoteMap();
     381         103 :       return result;
     382             :     }
     383             :   }
     384             : 
     385             :   private final Cache<Key, ChangeNotesState> cache;
     386             :   private final Args args;
     387             :   private final ExternalIdCache externalIdCache;
     388             : 
     389             :   @Inject
     390             :   ChangeNotesCache(
     391             :       @Named(CACHE_NAME) Cache<Key, ChangeNotesState> cache,
     392             :       Args args,
     393         146 :       ExternalIdCache externalIdCache) {
     394         146 :     this.cache = cache;
     395         146 :     this.args = args;
     396         146 :     this.externalIdCache = externalIdCache;
     397         146 :   }
     398             : 
     399             :   Value get(
     400             :       Project.NameKey project,
     401             :       Change.Id changeId,
     402             :       ObjectId metaId,
     403             :       Supplier<ChangeNotesRevWalk> walkSupplier)
     404             :       throws IOException {
     405             :     try {
     406         103 :       Key key = Key.create(project, changeId, metaId);
     407         103 :       Loader loader = new Loader(key, walkSupplier);
     408         103 :       ChangeNotesState s = cache.get(key, loader);
     409         103 :       return new AutoValue_ChangeNotesCache_Value(s, loader.revisionNoteMap);
     410           1 :     } catch (ExecutionException e) {
     411           1 :       throw new IOException(
     412           1 :           String.format(
     413             :               "Error loading %s in %s at %s",
     414           1 :               RefNames.changeMetaRef(changeId), project, metaId.name()),
     415             :           e);
     416             :     }
     417             :   }
     418             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750