001package io.ebeaninternal.server.autotune.service;
002
003import io.ebean.bean.NodeUsageCollector;
004import io.ebean.bean.ObjectGraphNode;
005import io.ebean.bean.ObjectGraphOrigin;
006import io.ebean.config.AutoTuneConfig;
007import io.ebeaninternal.api.SpiEbeanServer;
008import io.ebeaninternal.api.SpiQuery;
009import io.ebeaninternal.server.autotune.ProfilingListener;
010import io.ebeaninternal.server.deploy.BeanDescriptor;
011
012import java.util.Map;
013import java.util.concurrent.ConcurrentHashMap;
014import java.util.concurrent.locks.ReentrantLock;
015
016/**
017 * Manages the collection of object graph usage profiling.
018 */
019public class ProfileManager implements ProfilingListener {
020
021  private final ReentrantLock lock = new ReentrantLock();
022
023  private final boolean queryTuningAddVersion;
024
025  /**
026   * Converted from a 0-100 int to a double. Effectively a percentage rate at
027   * which to collect profiling information.
028   */
029  private final double profilingRate;
030
031  private final int profilingBase;
032
033  /**
034   * Map of the usage and query statistics gathered.
035   */
036  private final Map<String, ProfileOrigin> profileMap = new ConcurrentHashMap<>();
037
038  private final SpiEbeanServer server;
039
040  public ProfileManager(AutoTuneConfig config, SpiEbeanServer server) {
041    this.server = server;
042    this.profilingRate = config.getProfilingRate();
043    this.profilingBase = config.getProfilingBase();
044    this.queryTuningAddVersion = config.isQueryTuningAddVersion();
045  }
046
047  @Override
048  public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query) {
049
050    ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey());
051    if (profileOrigin == null) {
052      profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query));
053      return true;
054    } else {
055      return profileOrigin.isProfile();
056    }
057  }
058
059  /**
060   * Create the profile origin noting the query detail currently being used.
061   * <p>
062   * For new profiling entries it is useful to compare the profiling against the current
063   * query detail that is specified in the code (as the query might already be manually optimised).
064   * </p>
065   */
066  private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery<?> query) {
067    ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate);
068    // set the current query detail (fetch group) so that we can compare against profiling for new entries
069    profileOrigin.setOriginalQuery(query.getDetail().toString());
070    return profileOrigin;
071  }
072
073  /**
074   * Gather query execution statistics. This could either be the originating
075   * query in which case the parentNode will be null, or a lazy loading query
076   * resulting from traversal of the object graph.
077   */
078  @Override
079  public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) {
080
081    if (node != null) {
082      ObjectGraphOrigin origin = node.getOriginQueryPoint();
083      if (origin != null) {
084        ProfileOrigin stats = getProfileOrigin(origin);
085        stats.collectQueryInfo(node, beans, micros);
086      }
087    }
088  }
089
090  /**
091   * Collect usage statistics from a node in the object graph.
092   * <p>
093   * This is sent to use from a EntityBeanIntercept when the finalise method
094   * is called on the bean.
095   * </p>
096   */
097  @Override
098  public void collectNodeUsage(NodeUsageCollector usageCollector) {
099
100    ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint());
101    profileOrigin.collectUsageInfo(usageCollector);
102  }
103
104  private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) {
105    lock.lock();
106    try {
107      return profileMap.computeIfAbsent(originQueryPoint.getKey(), k -> new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate));
108    } finally {
109      lock.unlock();
110    }
111  }
112
113  /**
114   * Collect all the profiling information.
115   */
116  public AutoTuneCollection profilingCollection(boolean reset) {
117
118    AutoTuneCollection req = new AutoTuneCollection();
119
120    for (ProfileOrigin origin : profileMap.values()) {
121      BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
122      if (desc != null) {
123        origin.profilingCollection(desc, req, reset);
124      }
125    }
126
127    return req;
128  }
129
130}