View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.impl.model;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.maven.api.Session;
26  import org.apache.maven.api.model.Dependency;
27  import org.apache.maven.api.model.DependencyManagement;
28  import org.apache.maven.api.model.Model;
29  import org.apache.maven.api.model.Plugin;
30  import org.apache.maven.api.services.model.ModelValidator;
31  import org.apache.maven.impl.model.profile.SimpleProblemCollector;
32  import org.apache.maven.impl.standalone.ApiRunner;
33  import org.openjdk.jmh.annotations.Benchmark;
34  import org.openjdk.jmh.annotations.BenchmarkMode;
35  import org.openjdk.jmh.annotations.Level;
36  import org.openjdk.jmh.annotations.Measurement;
37  import org.openjdk.jmh.annotations.Mode;
38  import org.openjdk.jmh.annotations.OutputTimeUnit;
39  import org.openjdk.jmh.annotations.Param;
40  import org.openjdk.jmh.annotations.Scope;
41  import org.openjdk.jmh.annotations.Setup;
42  import org.openjdk.jmh.annotations.State;
43  import org.openjdk.jmh.annotations.Warmup;
44  import org.openjdk.jmh.runner.Runner;
45  import org.openjdk.jmh.runner.RunnerException;
46  import org.openjdk.jmh.runner.options.Options;
47  import org.openjdk.jmh.runner.options.OptionsBuilder;
48  
49  /**
50   * JMH Benchmark for measuring the performance gains from PR #2518:
51   * Optimize validation performance with lazy SourceHint evaluation.
52   *
53   * This benchmark measures the performance difference between validating
54   * models with different numbers of dependencies (1, 10, 100) to demonstrate
55   * how the lazy evaluation optimization scales with project complexity.
56   */
57  @BenchmarkMode(Mode.AverageTime)
58  @OutputTimeUnit(TimeUnit.MICROSECONDS)
59  @Warmup(iterations = 3, time = 2)
60  @Measurement(iterations = 5, time = 3)
61  @State(Scope.Benchmark)
62  public class ModelValidationBenchmark {
63  
64      @Param({"1", "10", "100"})
65      private int dependencyCount;
66  
67      private Session session;
68      private ModelValidator validator;
69      private Model validModel;
70      private Model invalidModel;
71      private SimpleProblemCollector problemCollector;
72  
73      @Setup(Level.Trial)
74      public void setup() {
75          session = ApiRunner.createSession();
76          validator = new DefaultModelValidator();
77  
78          // Create models with different numbers of dependencies
79          validModel = createValidModel(dependencyCount);
80          invalidModel = createInvalidModel(dependencyCount);
81      }
82  
83      @Setup(Level.Invocation)
84      public void setupInvocation() {
85          problemCollector = new SimpleProblemCollector();
86      }
87  
88      /**
89       * Benchmark validation of a valid model (no validation errors).
90       * This is the common case where lazy evaluation provides the most benefit
91       * since SourceHint strings are never computed.
92       */
93      @Benchmark
94      public void validateValidModel() {
95          validator.validateEffectiveModel(session, validModel, ModelValidator.VALIDATION_LEVEL_STRICT, problemCollector);
96      }
97  
98      /**
99       * Benchmark validation of an invalid model (with validation errors).
100      * This tests the case where SourceHint strings are actually computed
101      * and used in error messages.
102      */
103     @Benchmark
104     public void validateInvalidModel() {
105         validator.validateEffectiveModel(
106                 session, invalidModel, ModelValidator.VALIDATION_LEVEL_STRICT, problemCollector);
107     }
108 
109     /**
110      * Benchmark raw model validation (before inheritance and interpolation).
111      * This tests the validation of the raw model as read from the POM file.
112      */
113     @Benchmark
114     public void validateRawModel() {
115         validator.validateRawModel(session, validModel, ModelValidator.VALIDATION_LEVEL_STRICT, problemCollector);
116     }
117 
118     /**
119      * Benchmark validation with minimal validation level.
120      * This tests performance with reduced validation checks.
121      */
122     @Benchmark
123     public void validateMinimalLevel() {
124         validator.validateEffectiveModel(
125                 session, validModel, ModelValidator.VALIDATION_LEVEL_MINIMAL, problemCollector);
126     }
127 
128     /**
129      * Benchmark validation focusing on dependency management.
130      * This creates a model with many managed dependencies to stress-test
131      * the SourceHint.dependencyManagementKey() optimization.
132      */
133     @Benchmark
134     public void validateDependencyManagement() {
135         Model modelWithManyManagedDeps = createModelWithManyManagedDependencies(dependencyCount);
136         validator.validateEffectiveModel(
137                 session, modelWithManyManagedDeps, ModelValidator.VALIDATION_LEVEL_STRICT, problemCollector);
138     }
139 
140     /**
141      * Creates a valid model with the specified number of dependencies.
142      * Includes dependency management and plugins to simulate real-world complexity.
143      */
144     private Model createValidModel(int dependencyCount) {
145         List<Dependency> dependencies = new ArrayList<>();
146         List<Dependency> managedDependencies = new ArrayList<>();
147         List<Plugin> plugins = new ArrayList<>();
148 
149         // Create regular dependencies
150         for (int i = 0; i < dependencyCount; i++) {
151             dependencies.add(Dependency.newBuilder()
152                     .groupId("org.example.group" + i)
153                     .artifactId("artifact" + i)
154                     .version("1.0.0")
155                     .type("jar")
156                     .scope("compile")
157                     .build());
158         }
159 
160         // Create managed dependencies (typically fewer than regular dependencies)
161         int managedCount = Math.max(1, dependencyCount / 3);
162         for (int i = 0; i < managedCount; i++) {
163             managedDependencies.add(Dependency.newBuilder()
164                     .groupId("org.managed.group" + i)
165                     .artifactId("managed-artifact" + i)
166                     .version("2.0.0")
167                     .type("jar")
168                     .scope("compile")
169                     .build());
170         }
171 
172         // Create plugins (typically fewer than dependencies)
173         int pluginCount = Math.max(1, dependencyCount / 5);
174         for (int i = 0; i < pluginCount; i++) {
175             plugins.add(Plugin.newBuilder()
176                     .groupId("org.apache.maven.plugins")
177                     .artifactId("maven-plugin-" + i)
178                     .version("3.0.0")
179                     .build());
180         }
181 
182         return Model.newBuilder()
183                 .modelVersion("4.0.0")
184                 .groupId("org.apache.maven.benchmark")
185                 .artifactId("validation-benchmark")
186                 .version("1.0.0")
187                 .packaging("jar")
188                 .dependencies(dependencies)
189                 .dependencyManagement(DependencyManagement.newBuilder()
190                         .dependencies(managedDependencies)
191                         .build())
192                 .build();
193     }
194 
195     /**
196      * Creates an invalid model with the specified number of dependencies.
197      * Some dependencies will have missing required fields to trigger validation errors
198      * and exercise the SourceHint generation code paths.
199      */
200     private Model createInvalidModel(int dependencyCount) {
201         List<Dependency> dependencies = new ArrayList<>();
202         List<Dependency> managedDependencies = new ArrayList<>();
203 
204         // Create dependencies with various validation errors
205         for (int i = 0; i < dependencyCount; i++) {
206             if (i % 4 == 0) {
207                 // Missing version (triggers SourceHint.dependencyManagementKey)
208                 dependencies.add(Dependency.newBuilder()
209                         .groupId("org.example.group" + i)
210                         .artifactId("artifact" + i)
211                         .type("jar")
212                         .scope("compile")
213                         .build());
214             } else if (i % 4 == 1) {
215                 // Missing groupId (triggers validation error)
216                 dependencies.add(Dependency.newBuilder()
217                         .artifactId("artifact" + i)
218                         .version("1.0.0")
219                         .type("jar")
220                         .scope("compile")
221                         .build());
222             } else if (i % 4 == 2) {
223                 // Missing artifactId (triggers validation error)
224                 dependencies.add(Dependency.newBuilder()
225                         .groupId("org.example.group" + i)
226                         .version("1.0.0")
227                         .type("jar")
228                         .scope("compile")
229                         .build());
230             } else {
231                 // Valid dependency (some should be valid to test mixed scenarios)
232                 dependencies.add(Dependency.newBuilder()
233                         .groupId("org.example.group" + i)
234                         .artifactId("artifact" + i)
235                         .version("1.0.0")
236                         .type("jar")
237                         .scope("compile")
238                         .build());
239             }
240         }
241 
242         // Add some invalid managed dependencies too
243         int managedCount = Math.max(1, dependencyCount / 3);
244         for (int i = 0; i < managedCount; i++) {
245             if (i % 2 == 0) {
246                 // Missing version in dependency management
247                 managedDependencies.add(Dependency.newBuilder()
248                         .groupId("org.managed.group" + i)
249                         .artifactId("managed-artifact" + i)
250                         .type("jar")
251                         .build());
252             } else {
253                 // Valid managed dependency
254                 managedDependencies.add(Dependency.newBuilder()
255                         .groupId("org.managed.group" + i)
256                         .artifactId("managed-artifact" + i)
257                         .version("2.0.0")
258                         .type("jar")
259                         .build());
260             }
261         }
262 
263         return Model.newBuilder()
264                 .modelVersion("4.0.0")
265                 .groupId("org.apache.maven.benchmark")
266                 .artifactId("validation-benchmark")
267                 .version("1.0.0")
268                 .packaging("jar")
269                 .dependencies(dependencies)
270                 .dependencyManagement(DependencyManagement.newBuilder()
271                         .dependencies(managedDependencies)
272                         .build())
273                 .build();
274     }
275 
276     /**
277      * Creates a model with many managed dependencies to stress-test
278      * the SourceHint.dependencyManagementKey() optimization.
279      */
280     private Model createModelWithManyManagedDependencies(int dependencyCount) {
281         List<Dependency> managedDependencies = new ArrayList<>();
282 
283         // Create many managed dependencies with different classifiers and types
284         for (int i = 0; i < dependencyCount; i++) {
285             String classifier = (i % 3 == 0) ? "sources" : (i % 3 == 1) ? "javadoc" : null;
286             String type = (i % 4 == 0) ? "jar" : (i % 4 == 1) ? "war" : (i % 4 == 2) ? "pom" : "ejb";
287 
288             managedDependencies.add(Dependency.newBuilder()
289                     .groupId("org.managed.group" + i)
290                     .artifactId("managed-artifact" + i)
291                     .version("2.0.0")
292                     .type(type)
293                     .classifier(classifier)
294                     .scope("compile")
295                     .build());
296         }
297 
298         return Model.newBuilder()
299                 .modelVersion("4.0.0")
300                 .groupId("org.apache.maven.benchmark")
301                 .artifactId("dependency-management-benchmark")
302                 .version("1.0.0")
303                 .packaging("pom")
304                 .dependencyManagement(DependencyManagement.newBuilder()
305                         .dependencies(managedDependencies)
306                         .build())
307                 .build();
308     }
309 
310     /**
311      * Getter for dependencyCount (required for test access).
312      */
313     public int getDependencyCount() {
314         return dependencyCount;
315     }
316 
317     /**
318      * Main method to run the benchmark.
319      */
320     public static void main(String[] args) throws RunnerException {
321         Options opts = new OptionsBuilder()
322                 .include(ModelValidationBenchmark.class.getSimpleName())
323                 .forks(1)
324                 .build();
325         new Runner(opts).run();
326     }
327 }