All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hyperfoil.api.config.BenchmarkBuilder Maven / Gradle / Ivy

There is a newer version: 0.27
Show newest version
/*
 * Copyright 2018 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package io.hyperfoil.api.config;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.hyperfoil.impl.FutureSupplier;

/**
 * @author Ståle Pedersen
 */
public class BenchmarkBuilder {

   private final String originalSource;
   private final BenchmarkData data;
   private String name;
   private Map defaultAgentProperties = Collections.emptyMap();
   private Collection agents = new ArrayList<>();
   private ErgonomicsBuilder ergonomics = new ErgonomicsBuilder(this);
   private HttpBuilder defaultHttp;
   private List httpList = new ArrayList<>();
   private int threads = 1;
   private Map> phaseBuilders = new HashMap<>();
   private long statisticsCollectionPeriod = 1000;
   private List preHooks = new ArrayList<>();
   private List postHooks = new ArrayList<>();

   public BenchmarkBuilder(String originalSource, BenchmarkData data) {
      this.originalSource = originalSource;
      this.data = data;
   }

   public static BenchmarkBuilder builder() {
      return new BenchmarkBuilder(null, BenchmarkData.EMPTY);
   }

   public BenchmarkBuilder name(String name) {
      this.name = name;
      return this;
   }

   public BenchmarkBuilder addAgent(String name, String inlineConfig, Map properties) {
      Agent agent = new Agent(name, inlineConfig, properties);
      if (agents.stream().anyMatch(a -> a.name.equals(agent.name))) {
         throw new BenchmarkDefinitionException("Benchmark already contains agent '" + agent.name + "'");
      }
      agents.add(agent);
      return this;
   }

   int numAgents() {
      return agents.size();
   }

   public ErgonomicsBuilder ergonomics() {
      return ergonomics;
   }

   public HttpBuilder http() {
      if (defaultHttp == null) {
         defaultHttp = new HttpBuilder(this);
      }
      return defaultHttp;
   }

   public HttpBuilder http(String host) {
      HttpBuilder builder = new HttpBuilder(this).host(host);
      httpList.add(builder);
      return builder;
   }

   public BenchmarkBuilder threads(int threads) {
      this.threads = threads;
      return this;
   }

   public PhaseBuilder.Catalog addPhase(String name) {
      return new PhaseBuilder.Catalog(this, name);
   }

   public PhaseBuilder.ConstantRate singleConstantRatePhase() {
      if (phaseBuilders.isEmpty()) {
         return new PhaseBuilder.Catalog(this, "main").constantRate(0);
      }
      PhaseBuilder builder = phaseBuilders.get("main");
      if (!(builder instanceof PhaseBuilder.ConstantRate)) {
         throw new BenchmarkDefinitionException("Benchmark already has defined phases; cannot use single-phase definition");
      }
      return (PhaseBuilder.ConstantRate) builder;
   }

   public BenchmarkBuilder addPreHook(RunHook runHook) {
      preHooks.add(runHook);
      return this;
   }

   public BenchmarkBuilder addPostHook(RunHook runHook) {
      postHooks.add(runHook);
      return this;
   }

   public void prepareBuild() {
      if (defaultHttp == null) {
         if (httpList.isEmpty()) {
            // may be removed in the future when we define more than HTTP connections
            throw new BenchmarkDefinitionException("No default HTTP target set!");
         } else if (httpList.size() == 1) {
            defaultHttp = httpList.iterator().next();
         }
      } else {
         if (httpList.stream().anyMatch(http -> http.authority().equals(defaultHttp.authority()))) {
            throw new BenchmarkDefinitionException("Ambiguous HTTP definition for "
                  + defaultHttp.authority() + ": defined both as default and non-default");
         }
         httpList.add(defaultHttp);
      }
      HashSet authorities = new HashSet<>();
      for (HttpBuilder http : httpList) {
         if (!authorities.add(http.authority())) {
            throw new BenchmarkDefinitionException("Duplicit HTTP definition for " + http.authority());
         }
      }
      httpList.forEach(HttpBuilder::prepareBuild);
      phaseBuilders.values().forEach(PhaseBuilder::prepareBuild);
   }

   public Benchmark build() {
      prepareBuild();
      FutureSupplier bs = new FutureSupplier<>();
      Map httpMap = httpList.stream()
            .collect(Collectors.toMap(HttpBuilder::authority, http -> http.build(http == defaultHttp)));

      AtomicInteger phaseIdCounter = new AtomicInteger(0);
      Map phases = phaseBuilders.values().stream()
            .flatMap(builder -> builder.build(bs, phaseIdCounter).stream()).collect(Collectors.toMap(Phase::name, p -> p));
      for (Phase phase : phases.values()) {
         checkDependencies(phase, phase.startAfter, phases);
         checkDependencies(phase, phase.startAfterStrict, phases);
         checkDependencies(phase, phase.terminateAfterStrict, phases);
         checkDeadlock(phase, phases);
      }
      Map tags = new HashMap<>();
      if (defaultHttp != null) {
         Http defaultHttp = this.defaultHttp.build(true);
         tags.put("url", defaultHttp.protocol().scheme + "://" + defaultHttp.host() + ":" + defaultHttp.port());
         tags.put("protocol", defaultHttp.protocol().scheme);
      }
      tags.put("threads", threads);

      // It is important to gather files only after all other potentially file-reading builders
      // are done.
      Map files = data.files();

      Agent[] agents = this.agents.stream().map(a -> {
         HashMap properties = new HashMap<>(defaultAgentProperties);
         properties.putAll(a.properties);
         return new Agent(a.name, a.inlineConfig, properties);
      }).toArray(Agent[]::new);
      Benchmark benchmark = new Benchmark(name, originalSource, files, agents, threads, ergonomics.build(),
            httpMap, new ArrayList<>(phases.values()), tags, statisticsCollectionPeriod, preHooks, postHooks);
      bs.set(benchmark);
      return benchmark;
   }

   private void checkDeadlock(Phase phase, Map phases) {
      // prevent deadlock
      Map deps = new HashMap<>();
      Queue toProcess = new ArrayDeque<>();
      toProcess.add(phase);
      while (!toProcess.isEmpty()) {
         Phase p = toProcess.poll();
         Stream.concat(p.startAfter.stream(), p.startAfterStrict.stream()).forEach(name -> {
            Phase p2 = phases.get(name);
            if (p2 == phase) {
               StringBuilder sb = new StringBuilder("Phase dependencies contain cycle: ").append(name).append(" > ");
               Phase p3 = p;
               do {
                  sb.append(p3.name).append(" > ");
                  p3 = deps.get(p3);
                  assert p3 != null;
               } while (p3 != phase);
               throw new BenchmarkDefinitionException(sb.append(name).toString());
            }
            if (deps.putIfAbsent(p2, p) == null) {
               toProcess.add(p2);
            }
         });
      }
   }

   private void checkDependencies(Phase phase, Collection references, Map phases) {
      for (String dep : references) {
         if (!phases.containsKey(dep)) {
            String suggestion = phases.keySet().stream()
                  .filter(name -> name.toLowerCase().startsWith(dep)).findAny()
                  .map(name -> " Did you mean " + name + "?").orElse("");
            throw new BenchmarkDefinitionException("Phase " + dep + " referenced from " + phase.name() + " is not defined." + suggestion);
         }
      }
   }

   void addPhase(String name, PhaseBuilder phaseBuilder) {
      if (phaseBuilders.containsKey(name)) {
         throw new IllegalArgumentException("Phase '" + name + "' already defined.");
      }
      phaseBuilders.put(name, phaseBuilder);
   }

   public BenchmarkBuilder statisticsCollectionPeriod(long statisticsCollectionPeriod) {
      this.statisticsCollectionPeriod = statisticsCollectionPeriod;
      return this;
   }

   Collection> phases() {
      return phaseBuilders.values();
   }

   public boolean validateAuthority(String authority) {
      return authority == null && defaultHttp != null || httpList.stream().anyMatch(http -> http.authority().equals(authority));
   }

   public HttpBuilder decoupledHttp() {
      return new HttpBuilder(this);
   }

   public void addHttp(HttpBuilder builder) {
      if (builder.authority() == null) {
         throw new BenchmarkDefinitionException("Missing hostname!");
      }
      httpList.add(builder);
   }

   public BenchmarkData data() {
      return data;
   }

   public BenchmarkBuilder setDefaultAgentProperties(Map properties) {
      this.defaultAgentProperties = properties;
      return this;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy