Line data Source code
1 : // Copyright (C) 2012 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.git;
16 :
17 : import com.google.common.collect.Sets;
18 : import com.google.common.flogger.FluentLogger;
19 : import com.google.gerrit.common.Nullable;
20 : import com.google.gerrit.common.data.GarbageCollectionResult;
21 : import com.google.gerrit.common.data.GarbageCollectionResult.GcError;
22 : import com.google.gerrit.entities.Project;
23 : import com.google.gerrit.extensions.events.GarbageCollectorListener;
24 : import com.google.gerrit.server.config.GcConfig;
25 : import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
26 : import com.google.gerrit.server.plugincontext.PluginSetContext;
27 : import com.google.inject.Inject;
28 : import java.io.PrintWriter;
29 : import java.util.List;
30 : import java.util.Properties;
31 : import java.util.Set;
32 : import org.eclipse.jgit.api.GarbageCollectCommand;
33 : import org.eclipse.jgit.api.Git;
34 : import org.eclipse.jgit.errors.RepositoryNotFoundException;
35 : import org.eclipse.jgit.lib.Config;
36 : import org.eclipse.jgit.lib.ConfigConstants;
37 : import org.eclipse.jgit.lib.NullProgressMonitor;
38 : import org.eclipse.jgit.lib.Repository;
39 : import org.eclipse.jgit.lib.TextProgressMonitor;
40 : import org.eclipse.jgit.storage.pack.PackConfig;
41 :
42 : /** Serial execution of GC on a list of repositories. */
43 : public class GarbageCollection {
44 4 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
45 :
46 : private final GitRepositoryManager repoManager;
47 : private final GarbageCollectionQueue gcQueue;
48 : private final GcConfig gcConfig;
49 : private final PluginSetContext<GarbageCollectorListener> listeners;
50 :
51 : public interface Factory {
52 : GarbageCollection create();
53 : }
54 :
55 : @Inject
56 : GarbageCollection(
57 : GitRepositoryManager repoManager,
58 : GarbageCollectionQueue gcQueue,
59 : GcConfig config,
60 4 : PluginSetContext<GarbageCollectorListener> listeners) {
61 4 : this.repoManager = repoManager;
62 4 : this.gcQueue = gcQueue;
63 4 : this.gcConfig = config;
64 4 : this.listeners = listeners;
65 4 : }
66 :
67 : public GarbageCollectionResult run(List<Project.NameKey> projectNames) {
68 1 : return run(projectNames, null);
69 : }
70 :
71 : public GarbageCollectionResult run(List<Project.NameKey> projectNames, PrintWriter writer) {
72 1 : return run(projectNames, gcConfig.isAggressive(), writer);
73 : }
74 :
75 : /** Runs GC on the given projects, serially. Progress is written to writer if non-null. */
76 : public GarbageCollectionResult run(
77 : List<Project.NameKey> projectNames, boolean aggressive, @Nullable PrintWriter writer) {
78 4 : GarbageCollectionResult result = new GarbageCollectionResult();
79 4 : Set<Project.NameKey> projectsToGc = gcQueue.addAll(projectNames);
80 : for (Project.NameKey projectName :
81 4 : Sets.difference(Sets.newHashSet(projectNames), projectsToGc)) {
82 1 : result.addError(new GcError(GcError.Type.GC_ALREADY_SCHEDULED, projectName));
83 1 : }
84 4 : for (Project.NameKey p : projectsToGc) {
85 4 : try (Repository repo = repoManager.openRepository(p)) {
86 4 : logGcConfiguration(p, repo, aggressive);
87 4 : print(writer, "collecting garbage for \"" + p + "\":\n");
88 : GarbageCollectCommand gc =
89 4 : Git.wrap(
90 4 : repo instanceof DelegateRepository
91 1 : ? ((DelegateRepository) repo).delegate()
92 3 : : repo)
93 4 : .gc();
94 4 : gc.setAggressive(aggressive);
95 4 : logGcInfo(p, "before:", gc.getStatistics());
96 4 : gc.setProgressMonitor(
97 4 : writer != null ? new TextProgressMonitor(writer) : NullProgressMonitor.INSTANCE);
98 4 : Properties statistics = gc.call();
99 4 : logGcInfo(p, "after: ", statistics);
100 4 : print(writer, "done.\n\n");
101 4 : fire(p, statistics);
102 0 : } catch (RepositoryNotFoundException e) {
103 0 : logGcError(writer, p, e);
104 0 : result.addError(new GcError(GcError.Type.REPOSITORY_NOT_FOUND, p));
105 0 : } catch (Exception e) {
106 0 : logGcError(writer, p, e);
107 0 : result.addError(new GcError(GcError.Type.GC_FAILED, p));
108 : } finally {
109 4 : gcQueue.gcFinished(p);
110 : }
111 4 : }
112 4 : return result;
113 : }
114 :
115 : private void fire(Project.NameKey p, Properties statistics) {
116 4 : if (!listeners.iterator().hasNext()) {
117 4 : return;
118 : }
119 0 : Event event = new Event(p, statistics);
120 0 : listeners.runEach(l -> l.onGarbageCollected(event));
121 0 : }
122 :
123 : private static void logGcInfo(Project.NameKey projectName, String msg) {
124 4 : logGcInfo(projectName, msg, null);
125 4 : }
126 :
127 : private static void logGcInfo(Project.NameKey projectName, String msg, Properties statistics) {
128 4 : StringBuilder b = new StringBuilder();
129 4 : b.append("[").append(projectName.get()).append("] ");
130 4 : b.append(msg);
131 4 : if (statistics != null) {
132 4 : b.append(" ");
133 4 : String s = statistics.toString();
134 4 : if (s.startsWith("{") && s.endsWith("}")) {
135 4 : s = s.substring(1, s.length() - 1);
136 : }
137 4 : b.append(s);
138 : }
139 4 : logger.atInfo().log("%s", b);
140 4 : }
141 :
142 : private static void logGcConfiguration(
143 : Project.NameKey projectName, Repository repo, boolean aggressive) {
144 4 : StringBuilder b = new StringBuilder();
145 4 : Config cfg = repo.getConfig();
146 4 : b.append("gc.aggressive=").append(aggressive).append("; ");
147 4 : b.append(formatConfigValues(cfg, ConfigConstants.CONFIG_GC_SECTION, null));
148 4 : for (String subsection : cfg.getSubsections(ConfigConstants.CONFIG_GC_SECTION)) {
149 0 : b.append(formatConfigValues(cfg, ConfigConstants.CONFIG_GC_SECTION, subsection));
150 0 : }
151 4 : if (b.length() == 0) {
152 0 : b.append("no set");
153 : }
154 :
155 4 : logGcInfo(projectName, "gc config: " + b.toString());
156 4 : logGcInfo(projectName, "pack config: " + new PackConfig(repo).toString());
157 4 : }
158 :
159 : private static String formatConfigValues(Config config, String section, String subsection) {
160 4 : StringBuilder b = new StringBuilder();
161 4 : Set<String> names = config.getNames(section, subsection);
162 4 : for (String name : names) {
163 0 : String value = config.getString(section, subsection, name);
164 0 : b.append(section);
165 0 : if (subsection != null) {
166 0 : b.append(".").append(subsection);
167 : }
168 0 : b.append(".");
169 0 : b.append(name).append("=").append(value);
170 0 : b.append("; ");
171 0 : }
172 4 : return b.toString();
173 : }
174 :
175 : private static void logGcError(PrintWriter writer, Project.NameKey projectName, Exception e) {
176 0 : print(writer, "failed.\n\n");
177 0 : StringBuilder b = new StringBuilder();
178 0 : b.append("[").append(projectName.get()).append("]");
179 0 : logger.atSevere().withCause(e).log("%s", b);
180 0 : }
181 :
182 : private static void print(PrintWriter writer, String message) {
183 4 : if (writer != null) {
184 0 : writer.print(message);
185 : }
186 4 : }
187 :
188 : private static class Event extends AbstractNoNotifyEvent
189 : implements GarbageCollectorListener.Event {
190 : private final Project.NameKey p;
191 : private final Properties statistics;
192 :
193 0 : Event(Project.NameKey p, Properties statistics) {
194 0 : this.p = p;
195 0 : this.statistics = statistics;
196 0 : }
197 :
198 : @Override
199 : public String getProjectName() {
200 0 : return p.get();
201 : }
202 :
203 : @Override
204 : public Properties getStatistics() {
205 0 : return statistics;
206 : }
207 : }
208 : }
|