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

io.ebeaninternal.server.autotune.service.DefaultAutoTuneService Maven / Gradle / Ivy

There is a newer version: 15.8.1
Show newest version
package io.ebeaninternal.server.autotune.service;

import io.ebean.config.AutoTuneConfig;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.autotune.AutoTuneCollection;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.model.Autotune;
import io.ebeaninternal.server.autotune.model.Origin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;

/**
 * Implementation of the AutoTuneService which is comprised of profiling and query tuning.
 */
public class DefaultAutoTuneService implements AutoTuneService {

  private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);

  private final SpiEbeanServer server;

  private final long defaultGarbageCollectionWait;

  private final boolean skipGarbageCollectionOnShutdown;

  private final boolean skipProfileReportingOnShutdown;

  private final BaseQueryTuner queryTuner;

  private final ProfileManager profileManager;

  private final boolean profiling;

  private final boolean queryTuning;

  private final String tuningFile;

  private final String profilingFile;

  private final String serverName;

  private final int profilingUpdateFrequency;

  private long runtimeChangeCount;

  public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) {

    AutoTuneConfig config = serverConfig.getAutoTuneConfig();

    this.server = server;
    this.queryTuning = config.isQueryTuning();
    this.profiling = config.isProfiling();
    this.tuningFile = config.getQueryTuningFile();
    this.profilingFile = config.getProfilingFile();
    this.profilingUpdateFrequency = config.getProfilingUpdateFrequency();
    this.serverName = server.getName();
    this.profileManager = new ProfileManager(config, server);
    this.queryTuner = new BaseQueryTuner(config, server, profileManager);
    this.skipGarbageCollectionOnShutdown = config.isSkipGarbageCollectionOnShutdown();
    this.skipProfileReportingOnShutdown = config.isSkipProfileReportingOnShutdown();
    this.defaultGarbageCollectionWait = config.getGarbageCollectionWait();
  }

  /**
   * Load the query tuning information from it's data store.
   */
  @Override
  public void startup() {

    if (queryTuning) {
      loadTuningFile();
      if (isRuntimeTuningUpdates()) {
        // periodically gather and update query tuning
        server.getBackgroundExecutor().executePeriodically(new ProfilingUpdate(), profilingUpdateFrequency, TimeUnit.SECONDS);
      }
    }
  }


  /**
   * Return true if the tuning should update periodically at runtime.
   */
  private boolean isRuntimeTuningUpdates() {
    return profilingUpdateFrequency > 0;
  }

  private class ProfilingUpdate implements Runnable {

    @Override
    public void run() {
      runtimeTuningUpdate();
    }
  }

  /**
   * Load tuning information from an existing tuning file.
   */
  private void loadTuningFile() {
    File file = new File(tuningFile);
    if (file.exists()) {
      loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
    } else {
      // look for autotune as a resource
      try (InputStream stream = getClass().getResourceAsStream("/" + tuningFile)) {
        if (stream != null) {
          loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
        } else {
          logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
        }
      } catch (IOException e) {
        throw new IllegalStateException("Error on auto close of " + tuningFile, e);
      }
    }
  }

  private void loadAutoTuneProfiling(Autotune profiling) {
    logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
    for (Origin origin : profiling.getOrigin()) {
      queryTuner.put(origin);
    }
  }

  /**
   * Collect profiling, check for new/diff to existing tuning and apply changes.
   */
  private void runtimeTuningUpdate() {

    synchronized (this) {
      try {
        long start = System.currentTimeMillis();

        AutoTuneCollection profiling = profileManager.profilingCollection(false);

        AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, true);
        event.process();
        if (event.isEmpty()) {
          long exeMillis = System.currentTimeMillis() - start;
          logger.debug("No query tuning updates for server:{} executionMillis:{}", serverName, exeMillis);

        } else {
          // report the query tuning changes that have been made
          runtimeChangeCount += event.getChangeCount();
          event.writeFile(profilingFile + "-" + serverName + "-update");
          long exeMillis = System.currentTimeMillis() - start;
          logger.info("query tuning updates - new:{} diff:{} for server:{} executionMillis:{}", event.getNewCount(), event.getDiffCount(), serverName, exeMillis);
        }
      } catch (Throwable e) {
        logger.error("Error collecting or applying automatic query tuning", e);
      }
    }
  }

  private void saveProfilingOnShutdown(boolean reset) {

    synchronized (this) {
      if (isRuntimeTuningUpdates()) {
        runtimeTuningUpdate();
        outputAllTuning();

      } else {

        AutoTuneCollection profiling = profileManager.profilingCollection(reset);

        AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, false);
        event.process();
        if (event.isEmpty()) {
          logger.info("No new or diff entries for profiling server:{}", serverName);

        } else {
          event.writeFile(profilingFile + "-" + serverName);
          logger.info("writing new:{} diff:{} profiling entries for server:{}", event.getNewCount(), event.getDiffCount(), serverName);
        }
      }
    }
  }

  /**
   * Output all the query tuning (the "all" file).
   * 

* This is the originally loaded tuning plus any tuning changes picked up and applied at runtime. *

*

* This "all" file can be used as the next "ebean-autotune.xml" file. *

*/ private void outputAllTuning() { if (runtimeChangeCount == 0) { logger.info("no runtime query tuning changes for server:{}", serverName); } else { AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner); int size = event.size(); File existingTuning = new File(tuningFile); if (existingTuning.exists()) { // rename the existing autotune.xml file (appending 'now') if (!existingTuning.renameTo(new File(tuningFile + "." + AutoTuneXmlWriter.now()))) { logger.warn("Failed to rename autotune file [{}]", tuningFile); } } event.writeFile(tuningFile, false); logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName); } } /** * Shutdown the listener. *

* We should try to collect the usage statistics by calling a System.gc(). * This is necessary for use with short lived applications where garbage * collection may not otherwise occur at all. *

*/ @Override public void shutdown() { if (profiling) { if (!skipGarbageCollectionOnShutdown && !skipProfileReportingOnShutdown) { // trigger GC to update profiling information on recently executed queries collectProfiling(-1); } if (!skipProfileReportingOnShutdown) { saveProfilingOnShutdown(false); } } } /** * Output the profiling. *

* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined. * When profiling is not applied at runtime then this reports the diff report with new and diff entries relative * to the existing tuning. *

*/ @Override public void reportProfiling() { saveProfilingOnShutdown(false); } /** * Ask for a System.gc() so that we gather node usage information. *

* Really only want to do this sparingly but useful just prior to shutdown * for short run application where garbage collection may otherwise not * occur at all. *

*

* waitMillis will do a thread sleep to give the garbage collection a little * time to do its thing assuming we are shutting down the VM. *

*

* If waitMillis is -1 then the defaultGarbageCollectionWait is used which * defaults to 100 milliseconds. *

*/ @Override public void collectProfiling() { collectProfiling(-1); } public void collectProfiling(long waitMillis) { System.gc(); try { if (waitMillis < 0) { waitMillis = defaultGarbageCollectionWait; } Thread.sleep(waitMillis); } catch (InterruptedException e) { // restore the interrupted status Thread.currentThread().interrupt(); logger.warn("Error while sleeping after System.gc() request.", e); } } /** * Auto tune the query and enable profiling. */ @Override public boolean tuneQuery(SpiQuery query) { return queryTuner.tuneQuery(query); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy