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

io.hyperfoil.core.impl.SimulationRunnerImpl Maven / Gradle / Ivy

There is a newer version: 0.27.1
Show newest version
package io.hyperfoil.core.impl;

import io.hyperfoil.api.config.Benchmark;
import io.hyperfoil.api.session.SharedData;
import io.hyperfoil.api.statistics.SessionStatistics;
import io.hyperfoil.core.client.netty.HttpDestinationTableImpl;
import io.hyperfoil.core.session.SharedDataImpl;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.hyperfoil.api.config.Http;
import io.hyperfoil.api.config.Phase;
import io.hyperfoil.api.connection.HttpClientPool;
import io.hyperfoil.api.connection.HttpConnection;
import io.hyperfoil.api.connection.HttpConnectionPool;
import io.hyperfoil.api.session.PhaseChangeHandler;
import io.hyperfoil.api.session.Session;
import io.hyperfoil.api.statistics.Statistics;
import io.hyperfoil.api.session.PhaseInstance;
import io.hyperfoil.core.api.SimulationRunner;
import io.hyperfoil.core.client.netty.HttpClientPoolImpl;
import io.hyperfoil.core.session.SessionFactory;
import io.vertx.core.AsyncResult;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.net.ssl.SSLException;

/**
 * @author Julien Viet
 * @author John O'Hara
 */
public class SimulationRunnerImpl implements SimulationRunner {
    protected static final Logger log = LoggerFactory.getLogger(SimulationRunner.class);

    protected final Benchmark benchmark;
    protected final Map instances = new HashMap<>();
    protected final List sessions = new ArrayList<>();
    private final Map sharedResources = new HashMap<>();
    protected final EventLoopGroup eventLoopGroup;
    protected final Map httpClientPools = new HashMap<>();
    protected final Map httpDestinations = new HashMap<>();

    public SimulationRunnerImpl(Benchmark benchmark) {
        this.eventLoopGroup = new NioEventLoopGroup(benchmark.threads());
        this.benchmark = benchmark;
        Map> httpConnectionPools = new HashMap<>();
        for (Map.Entry http : benchmark.http().entrySet()) {
            try {
                HttpClientPool httpClientPool = new HttpClientPoolImpl(eventLoopGroup, http.getValue());
                httpClientPools.put(http.getKey(), httpClientPool);
                if (http.getValue().isDefault()) {
                    httpClientPools.put(null, httpClientPool);
                }
                for (EventExecutor executor : eventLoopGroup) {
                    HttpConnectionPool httpConnectionPool = httpClientPool.connectionPool(executor);
                    Map pools = httpConnectionPools.computeIfAbsent(executor, e -> new HashMap<>());
                    pools.put(http.getKey(), httpConnectionPool);
                    if (http.getValue().isDefault()) {
                        pools.put(null, httpConnectionPool);
                    }
                }
            } catch (SSLException e) {
                throw new IllegalStateException("Failed creating connection pool to " + http.getValue().baseUrl(), e);
            }
        }
        for (Map.Entry> entry : httpConnectionPools.entrySet()) {
            httpDestinations.put(entry.getKey(), new HttpDestinationTableImpl(entry.getValue()));
        }
    }

    @Override
    public void init(PhaseChangeHandler phaseChangeHandler, Handler> handler) {
        //Initialise HttpClientPool
        ArrayList futures = new ArrayList<>();
        for (Map.Entry entry : httpClientPools.entrySet()) {
            // default client pool is initialized by name
            if (entry.getKey() != null) {
                Future f = Future.future();
                futures.add(f);
                entry.getValue().start(f);
            }
        }

        for (Phase def : benchmark.phases()) {
            SharedResources sharedResources;
            if (def.sharedResources == null) {
                // Noop phases don't use any resources
                sharedResources = SharedResources.NONE;
            } else if ((sharedResources = this.sharedResources.get(def.sharedResources)) == null) {
                sharedResources = new SharedResources(eventLoopGroup, def.scenario.sequences().length);
                List phaseSessions = sharedResources.sessions = new ArrayList<>();
                Map statistics = sharedResources.statistics;
                Map data = sharedResources.data;
                Supplier sessionSupplier = () -> {
                    Session session;
                    synchronized (this.sessions) {
                        session = SessionFactory.create(def.scenario, this.sessions.size());
                        this.sessions.add(session);
                    }
                    // We probably don't need to synchronize
                    synchronized (phaseSessions) {
                        phaseSessions.add(session);
                    }
                    EventLoop eventLoop = eventLoopGroup.next();
                    session.attach(eventLoop, data.get(eventLoop), httpDestinations.get(eventLoop), statistics.get(eventLoop));
                    session.reserve(def.scenario);
                    return session;
                };
                sharedResources.sessionPool = new ElasticPoolImpl<>(sessionSupplier, () -> {
                    log.warn("Pool depleted, allocating new sessions!");
                    return sessionSupplier.get();
                });
                this.sharedResources.put(def.sharedResources, sharedResources);
            }
            PhaseInstance phase = PhaseInstanceImpl.newInstance(def);
            instances.put(def.name(), phase);
            phase.setComponents(sharedResources.sessionPool, sharedResources.sessions, sharedResources.allStatistics(), phaseChangeHandler);
            phase.reserveSessions();
            // at this point all session resources should be reserved
        }

        CompositeFuture composite = CompositeFuture.join(futures);
        composite.setHandler(result -> handler.handle(result.mapEmpty()));
    }

    @Override
    public void shutdown() {
        for (HttpClientPool pool : httpClientPools.values()) {
            pool.shutdown();
        }
    }

    public void visitSessions(Consumer consumer) {
        synchronized (sessions) {
            for (int i = 0; i < sessions.size(); i++) {
                Session session = sessions.get(i);
                consumer.accept(session);
            }
        }
    }

    public void visitStatistics(Consumer consumer) {
        for (SharedResources sharedResources : this.sharedResources.values()) {
            if (sharedResources.currentPhase == null) {
                // Phase(s) with these resources have not been started yet
                continue;
            }
            for (SessionStatistics statistics : sharedResources.statistics.values()) {
                consumer.accept(statistics);
            }
        }
    }

    public void visitStatistics(Phase phase, Consumer consumer) {
        SharedResources sharedResources = this.sharedResources.get(phase.sharedResources);
        if (sharedResources == null || sharedResources.statistics == null) {
            return;
        }
        for (SessionStatistics statistics : sharedResources.statistics.values()) {
            consumer.accept(statistics);
        }
    }

    public void visitSessionPoolStats(SessionStatsConsumer consumer) {
       for (SharedResources sharedResources : this.sharedResources.values()) {
          if (sharedResources.currentPhase == null) {
             // Phase(s) with these resources have not been started yet
             continue;
          }
          int minUsed = sharedResources.sessionPool.minUsed();
          int maxUsed = sharedResources.sessionPool.maxUsed();
          sharedResources.sessionPool.resetStats();
          if (minUsed < maxUsed) {
              consumer.accept(sharedResources.currentPhase.definition().name(), minUsed, maxUsed);
          }
       }
    }

    public void visitSessionPoolStats(Phase phase, SessionStatsConsumer consumer) {
       SharedResources sharedResources = this.sharedResources.get(phase.sharedResources);
       if (sharedResources != null) {
          int minUsed = sharedResources.sessionPool.minUsed();
          int maxUsed = sharedResources.sessionPool.maxUsed();
          sharedResources.sessionPool.resetStats();
          if (minUsed < maxUsed) {
              consumer.accept(phase.name(), minUsed, maxUsed);
          }
       }
    }

    @Override
    public void startPhase(String phase) {
        PhaseInstance phaseInstance = instances.get(phase);
        SharedResources sharedResources = this.sharedResources.get(phaseInstance.definition().sharedResources);
        if (sharedResources != null) {
            // Avoid NPE in noop phases
            sharedResources.currentPhase = phaseInstance;
        }
        phaseInstance.start(eventLoopGroup);
    }

    @Override
    public void finishPhase(String phase) {
        instances.get(phase).finish();
    }

    @Override
    public void tryTerminatePhase(String phase) {
        instances.get(phase).tryTerminate();
    }

    @Override
    public void terminatePhase(String phase) {
        instances.get(phase).terminate();
    }

    public List listConnections() {
        ArrayList list = new ArrayList<>();
        // Connection pools should be accessed only from the executor, but since we're only publishing stats...
        for (HttpDestinationTableImpl destinations : httpDestinations.values()) {
            for (Map.Entry entry : destinations.iterable()) {
                if (entry.getKey() == null) {
                    // Ignore default pool: it's there twice
                    continue;
                }
                HttpConnectionPool pool = entry.getValue();
                Collection connections = pool.connections();
                int available = 0;
                int inFlight = 0;
                for (HttpConnection conn : connections) {
                    if (conn.isAvailable()) {
                        available++;
                    }
                    inFlight += conn.inFlight();
                }
                list.add(String.format("%s: %d/%d available, %d in-flight requests, %d waiting sessions (estimate)", entry.getKey(), available, connections.size(), inFlight, pool.waitingSessions()));
            }
        }
        return list;
    }

    private static class SharedResources {
        static final SharedResources NONE = new SharedResources(null, 0);

        PhaseInstance currentPhase;
        ElasticPoolImpl sessionPool;
        List sessions;
        Map statistics = new HashMap<>();
        Map data = new HashMap<>();

        SharedResources(EventExecutorGroup executors, int sequences) {
            if (executors != null) {
                for (EventExecutor executor : executors) {
                    this.statistics.put(executor, new SessionStatistics());
                    this.data.put(executor, new SharedDataImpl());
                }
            }
        }

        Iterable allStatistics() {
            if (statistics.isEmpty()) {
                return Collections.emptyList();
            }
            return () -> new FlattenIterator<>(statistics.values().iterator());
        }
    }

    private static class FlattenIterator implements Iterator {
        private final Iterator> it1;
        private Iterator it2;

        public FlattenIterator(Iterator> iterator) {
            it1 = iterator;
        }

        @Override
        public boolean hasNext() {
            if (it2 != null && it2.hasNext()) {
                return true;
            } else if (it1.hasNext()) {
                boolean it2HasNext;
                do {
                    it2 = it1.next().iterator();
                } while (!(it2HasNext = it2.hasNext()) && it1.hasNext());
                return it2HasNext;
            } else {
                return false;
            }
        }

        @Override
        public T next() {
            if (it2 != null && it2.hasNext()) {
                return it2.next();
            } else if (it1.hasNext()) {
                boolean it2HasNext;
                do {
                    it2 = it1.next().iterator();
                } while (!(it2HasNext = it2.hasNext()) && it1.hasNext());
                if (it2HasNext) {
                    return it2.next();
                }
            }
            throw new NoSuchElementException();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy