]> source.dussan.org Git - sonarqube.git/commitdiff
Add module sonar-server-benchmark
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 27 Nov 2014 15:48:13 +0000 (16:48 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 27 Nov 2014 15:49:08 +0000 (16:49 +0100)
server/pom.xml
server/sonar-server-benchmark/pom.xml [new file with mode: 0644]
server/sonar-server-benchmark/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java [new file with mode: 0644]
server/sonar-server-benchmark/src/test/resources/logback-test.xml [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueAuthorizationDao.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueAuthorizationIndexer.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java

index 7a5c9de19d8b7a41521b2a4ecdd6b90c960d81f9..4c5fa98f7dfd218ee6f1854bc1863283e373b657 100644 (file)
@@ -17,5 +17,6 @@
     <module>sonar-server</module>
     <module>sonar-web</module>
     <module>sonar-ws-client</module>
+    <!--<module>sonar-server-benchmark</module>-->
   </modules>
 </project>
diff --git a/server/sonar-server-benchmark/pom.xml b/server/sonar-server-benchmark/pom.xml
new file mode 100644 (file)
index 0000000..5ac1d25
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.codehaus.sonar</groupId>
+    <artifactId>server</artifactId>
+    <version>5.0-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent>
+  <artifactId>sonar-server-benchmark</artifactId>
+  <name>SonarQube :: Server :: Benchmark</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-server</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-search</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-core</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-testing-harness</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>${skipServerTests}</skipTests>
+          <argLine>-Xmx256m</argLine>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/server/sonar-server-benchmark/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java b/server/sonar-server-benchmark/src/test/java/org/sonar/server/benchmark/IssueIndexBenchmarkTest.java
new file mode 100644 (file)
index 0000000..6d1eee4
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.benchmark;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.math.RandomUtils;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.internal.Uuids;
+import org.sonar.server.issue.IssueQuery;
+import org.sonar.server.issue.index.IssueAuthorizationDao;
+import org.sonar.server.issue.index.IssueAuthorizationIndexer;
+import org.sonar.server.issue.index.IssueDoc;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.search.QueryContext;
+import org.sonar.server.search.Result;
+import org.sonar.server.tester.ServerTester;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class IssueIndexBenchmarkTest {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger("benchmarkIssues");
+
+  final static int PROJECTS = 100;
+  final static int FILES_PER_PROJECT = 100;
+  final static int ISSUES_PER_FILE = 100;
+
+  @ClassRule
+  public static ServerTester tester = new ServerTester();
+
+  @Test
+  public void benchmark() throws Exception {
+    // initialization - feed issues/issueAuthorization with projects and hardcoded users
+    indexAuthorizations();
+
+    // index issues
+    benchmarkIssueIndexing();
+
+    // execute some queries
+    benchmarkQueries();
+  }
+
+  private void indexAuthorizations() {
+    IssueAuthorizationIndexer indexer = tester.get(IssueAuthorizationIndexer.class);
+    List<IssueAuthorizationDao.Dto> authorizations = Lists.newArrayList();
+    for (int i = 0; i < PROJECTS; i++) {
+      IssueAuthorizationDao.Dto authorization = new IssueAuthorizationDao.Dto("PROJECT" + i, System.currentTimeMillis());
+      authorization.addGroup("sonar-users");
+      authorization.addUser("admin");
+      authorizations.add(authorization);
+    }
+    indexer.index(authorizations);
+  }
+
+  private void benchmarkIssueIndexing() {
+    LOGGER.info("Indexing issues");
+    IssueIterator issues = new IssueIterator(PROJECTS, FILES_PER_PROJECT, ISSUES_PER_FILE);
+    ProgressTask progressTask = new ProgressTask("issues", issues.count());
+    Timer timer = new Timer("IssuesIndex");
+    timer.schedule(progressTask, ProgressTask.PERIOD_MS, ProgressTask.PERIOD_MS);
+
+    long start = System.currentTimeMillis();
+    tester.get(IssueIndexer.class).index(issues);
+    long end = System.currentTimeMillis();
+
+    timer.cancel();
+    long period = end - start;
+    LOGGER.info(String.format("%d issues indexed in %d ms (%d/second)", issues.count.get(), period, 1000 * issues.count.get() / period));
+  }
+
+  private void benchmarkQueries() {
+    benchmarkQuery("all issues", IssueQuery.builder().build());
+    benchmarkQuery("project issues", IssueQuery.builder().projectUuids(Arrays.asList("PROJECT33")).build());
+    benchmarkQuery("file issues", IssueQuery.builder().componentUuids(Arrays.asList("FILE333")).build());
+    benchmarkQuery("various", IssueQuery.builder()
+      .resolutions(Arrays.asList(Issue.RESOLUTION_FIXED))
+      .assigned(true)
+      .build());
+    // TODO test facets
+  }
+
+  private void benchmarkQuery(String label, IssueQuery query) {
+    IssueIndex index = tester.get(IssueIndex.class);
+    for (int i = 0; i < 10; i++) {
+      long start = System.currentTimeMillis();
+      Result<Issue> result = index.search(query, new QueryContext());
+      long end = System.currentTimeMillis();
+      LOGGER.info("Request (" + label + "): {} docs in {} ms", result.getTotal(), end - start);
+    }
+  }
+
+  private static class IssueIterator implements Iterator<IssueDoc> {
+    private final int nbProjects, nbFilesPerProject, nbIssuesPerFile;
+    private int currentProject = 0, currentFile = 0;
+    private AtomicLong count = new AtomicLong(0L);
+    private final Iterator<String> users = cycleIterator("guy", 200);
+    private Iterator<String> rules = cycleIterator("squid:rule", 1000);
+    private final Iterator<String> severities = Iterables.cycle(Severity.ALL).iterator();
+    private final Iterator<String> statuses = Iterables.cycle(Issue.STATUSES).iterator();
+    private final Iterator<String> resolutions = Iterables.cycle(Issue.RESOLUTIONS).iterator();
+
+    IssueIterator(int nbProjects, int nbFilesPerProject, int nbIssuesPerFile) {
+      this.nbProjects = nbProjects;
+      this.nbFilesPerProject = nbFilesPerProject;
+      this.nbIssuesPerFile = nbIssuesPerFile;
+    }
+
+    public AtomicLong count() {
+      return count;
+    }
+
+    @Override
+    public boolean hasNext() {
+      return count.get() < nbProjects * nbFilesPerProject * nbIssuesPerFile;
+    }
+
+    @Override
+    public IssueDoc next() {
+      IssueDoc issue = new IssueDoc(Maps.<String, Object>newHashMap());
+      issue.setKey(Uuids.create());
+      issue.setFilePath("src/main/java/Foo" + currentFile);
+      issue.setComponentUuid("FILE" + currentFile);
+      issue.setProjectUuid("PROJECT" + currentProject);
+      issue.setActionPlanKey("PLAN" + currentProject);
+      issue.setAssignee(users.next());
+      issue.setAuthorLogin(users.next());
+      issue.setLine(RandomUtils.nextInt());
+      issue.setCreationDate(new Date());
+      issue.setUpdateDate(new Date());
+      issue.setFuncUpdateDate(new Date());
+      issue.setFuncCreationDate(new Date());
+      issue.setFuncCloseDate(null);
+      issue.setAttributes(null);
+      issue.setDebt(1000L);
+      issue.setEffortToFix(3.14);
+      issue.setLanguage("php");
+      issue.setReporter(users.next());
+      issue.setRuleKey(rules.next());
+      issue.setResolution(resolutions.next());
+      issue.setStatus(statuses.next());
+      issue.setSeverity(severities.next());
+      issue.setMessage(RandomUtils.nextLong() + "this is the message. Not too short.");
+      count.incrementAndGet();
+      if (count.get() % nbIssuesPerFile == 0) {
+        currentFile++;
+      }
+      if (count.get() % (nbFilesPerProject * nbIssuesPerFile) == 0) {
+        currentProject++;
+      }
+
+      return issue;
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private static Iterator<String> cycleIterator(String prefix, int size) {
+    List<String> values = Lists.newArrayList();
+    for (int i = 0; i < size; i++) {
+      values.add(String.format("%s%d", prefix, i));
+    }
+    return Iterators.cycle(values);
+  }
+
+  static class ProgressTask extends TimerTask {
+    public static final long PERIOD_MS = 60000L;
+
+    private final String label;
+    private final AtomicLong counter;
+    private long previousCount = 0L;
+    private long previousTime = 0L;
+
+    public ProgressTask(String label, AtomicLong counter) {
+      this.label = label;
+      this.counter = counter;
+      this.previousTime = System.currentTimeMillis();
+    }
+
+    @Override
+    public void run() {
+      long currentCount = counter.get();
+      long now = System.currentTimeMillis();
+      LOGGER.info("{} {} indexed ({} {}/second)",
+        currentCount, label, documentsPerSecond(currentCount - previousCount, now - previousTime), label);
+      this.previousCount = currentCount;
+      this.previousTime = now;
+    }
+  }
+
+  static int documentsPerSecond(long nbIssues, long time) {
+    return (int) Math.round(nbIssues / (time / 1000.0));
+  }
+}
diff --git a/server/sonar-server-benchmark/src/test/resources/logback-test.xml b/server/sonar-server-benchmark/src/test/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..e595060
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+
+  <appender name="STDOUT"
+            class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>
+        %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+      </pattern>
+    </encoder>
+  </appender>
+
+  <logger name="org.hibernate">
+    <level value="WARN"/>
+  </logger>
+
+  <logger name="org.dbunit">
+    <level value="WARN"/>
+  </logger>
+
+  <!-- set to level DEBUG to log SQL requests executed by MyBatis -->
+  <logger name="java.sql">
+    <level value="WARN"/>
+  </logger>
+
+  <!-- required for DryRunDatabaseFactoryTest -->
+  <logger name="org.sonar.core.persistence.DryRunDatabaseFactory">
+    <level value="DEBUG"/>
+  </logger>
+
+  <!-- required for DryRunDatabaseFactoryTest -->
+  <logger name="org.elasticsearch">
+    <level value="WARN"/>
+  </logger>
+
+  <root>
+    <level value="INFO"/>
+    <appender-ref ref="STDOUT"/>
+  </root>
+
+</configuration>
index a59fb23c3886f2116aac5e48e84b80fb0ddfa22c..de0936f372513649f6e556472bf2d26108df8693 100644 (file)
@@ -36,40 +36,40 @@ import java.util.Map;
 /**
  * No streaming because of union of joins -> no need to use ResultSetIterator
  */
-class IssueAuthorizationDao {
+public class IssueAuthorizationDao {
 
-  static final class Dto {
+  public static final class Dto {
     private final String projectUuid;
     private final long updatedAt;
     private List<String> users = Lists.newArrayList();
     private List<String> groups = Lists.newArrayList();
 
-    Dto(String projectUuid, long updatedAt) {
+    public Dto(String projectUuid, long updatedAt) {
       this.projectUuid = projectUuid;
       this.updatedAt = updatedAt;
     }
 
-    String getProjectUuid() {
+    public String getProjectUuid() {
       return projectUuid;
     }
 
-    long getUpdatedAt() {
+    public long getUpdatedAt() {
       return updatedAt;
     }
 
-    List<String> getUsers() {
+    public List<String> getUsers() {
       return users;
     }
 
-    void addUser(String s) {
+    public void addUser(String s) {
       users.add(s);
     }
 
-    void addGroup(String s) {
+    public void addGroup(String s) {
       groups.add(s);
     }
 
-    List<String> getGroups() {
+    public List<String> getGroups() {
       return groups;
     }
 
index b53efd9fdbdac35307923d9970cf5599ec90e198..2003e0889457b42af1388bf0bcd5ace3bb27832a 100644 (file)
@@ -72,7 +72,7 @@ public class IssueAuthorizationIndexer extends BaseIndexer {
   }
 
   @VisibleForTesting
-  void index(Collection<IssueAuthorizationDao.Dto> authorizations) {
+  public void index(Collection<IssueAuthorizationDao.Dto> authorizations) {
     final BulkIndexer bulk = new BulkIndexer(esClient, IssueIndexDefinition.INDEX);
     doIndex(bulk, authorizations);
   }
index d9dcb6cd9ccbc76bb2ce601a5bff737bf052fbe2..04d112d99bc36ec588b48ad6834656ffa77b3522 100644 (file)
@@ -51,7 +51,7 @@ public class IssueIndexer extends BaseIndexer {
     long maxDate;
     try {
       IssueResultSetIterator rowIt = IssueResultSetIterator.create(dbClient, dbConnection, lastUpdatedAt);
-      maxDate = index(bulk, rowIt);
+      maxDate = doIndex(bulk, rowIt);
       rowIt.close();
       return maxDate;
 
@@ -61,7 +61,12 @@ public class IssueIndexer extends BaseIndexer {
     }
   }
 
-  public long index(BulkIndexer bulk, Iterator<IssueDoc> issues) {
+  public void index(Iterator<IssueDoc> issues) {
+    final BulkIndexer bulk = createBulkIndexer(false);
+    doIndex(bulk, issues);
+  }
+
+  long doIndex(BulkIndexer bulk, Iterator<IssueDoc> issues) {
     bulk.start();
     long maxDate = 0L;
     while (issues.hasNext()) {
@@ -79,7 +84,7 @@ public class IssueIndexer extends BaseIndexer {
     QueryBuilder query = QueryBuilders.filteredQuery(
       QueryBuilders.matchAllQuery(),
       FilterBuilders.boolFilter().must(FilterBuilders.termsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, uuid))
-    );
+      );
     esClient.prepareDeleteByQuery(IssueIndexDefinition.INDEX).setQuery(query).get();
     if (refresh) {
       esClient.prepareRefresh(IssueIndexDefinition.INDEX).get();
index c1fff90e33a32bb96ca0cf30e72178b008f81889..70544f861e218ac07f70b51e450ec370526c97de 100644 (file)
@@ -43,7 +43,7 @@ public class IssueIndexerTest {
   @Test
   public void index_nothing() throws Exception {
     IssueIndexer indexer = createIndexer();
-    indexer.index(indexer.createBulkIndexer(false), Iterators.<IssueDoc>emptyIterator());
+    indexer.index(Iterators.<IssueDoc>emptyIterator());
     assertThat(esTester.countDocuments(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_ISSUE)).isEqualTo(0L);
   }