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.text.PathProperties;
007import io.ebean.text.PathProperties.Props;
008import io.ebeaninternal.server.deploy.BeanDescriptor;
009import io.ebeaninternal.server.querydefn.OrmQueryDetail;
010
011import java.util.Collection;
012import java.util.Map;
013import java.util.concurrent.ConcurrentHashMap;
014import java.util.concurrent.atomic.AtomicLong;
015import java.util.concurrent.locks.ReentrantLock;
016
017public class ProfileOrigin {
018
019  private final ReentrantLock lock = new ReentrantLock();
020
021  private static final long RESET_COUNT = -1000000000L;
022
023  private final ObjectGraphOrigin origin;
024
025  private final boolean queryTuningAddVersion;
026
027  private final int profilingBase;
028
029  private final double profilingRate;
030
031  private final Map<String, ProfileOriginQuery> queryStatsMap = new ConcurrentHashMap<>();
032
033  private final Map<String, ProfileOriginNodeUsage> nodeUsageMap = new ConcurrentHashMap<>();
034
035  private final AtomicLong requestCount = new AtomicLong();
036
037  private final AtomicLong profileCount = new AtomicLong();
038
039  private String originalQuery;
040
041  public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion, int profilingBase, double profilingRate) {
042    this.origin = origin;
043    this.queryTuningAddVersion = queryTuningAddVersion;
044    this.profilingBase = profilingBase;
045    this.profilingRate = profilingRate;
046  }
047
048  public String getOriginalQuery() {
049    return originalQuery;
050  }
051
052  public void setOriginalQuery(String originalQuery) {
053    this.originalQuery = originalQuery;
054  }
055
056  /**
057   * Return true if this query should be profiled based on a percentage rate.
058   */
059  public boolean isProfile() {
060
061    long count = requestCount.incrementAndGet();
062    if (count < profilingBase) {
063      return true;
064    }
065    long hits = profileCount.get();
066    if (profilingRate > (double) hits / count) {
067      profileCount.incrementAndGet();
068      return true;
069    } else {
070      return false;
071    }
072  }
073
074  /**
075   * Collect profiling information with the option to reset the underlying profiling detail.
076   */
077  public void profilingCollection(BeanDescriptor<?> rootDesc, AutoTuneCollection req, boolean reset) {
078    lock.lock();
079    try {
080      if (nodeUsageMap.isEmpty()) {
081        return;
082      }
083
084      OrmQueryDetail detail = buildDetail(rootDesc);
085      AutoTuneCollection.Entry entry = req.add(origin, detail, originalQuery);
086
087      Collection<ProfileOriginQuery> values = queryStatsMap.values();
088      for (ProfileOriginQuery queryEntry : values) {
089        entry.addQuery(queryEntry.createEntryQuery(reset));
090      }
091      if (reset) {
092        nodeUsageMap.clear();
093        if (requestCount.get() > RESET_COUNT) {
094          requestCount.set(profilingBase);
095          profileCount.set(0);
096        }
097      }
098    } finally {
099      lock.unlock();
100    }
101  }
102
103  OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
104
105    PathProperties pathProps = new PathProperties();
106    for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
107      statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion);
108    }
109
110    OrmQueryDetail detail = new OrmQueryDetail();
111
112    for (Props props : pathProps.getPathProps()) {
113      if (!props.isEmpty()) {
114        detail.fetch(props.getPath(), props.getPropertiesAsString(), null);
115      }
116    }
117
118    detail.sortFetchPaths(rootDesc);
119    return detail;
120  }
121
122  /**
123   * Return the origin.
124   */
125  public ObjectGraphOrigin getOrigin() {
126    return origin;
127  }
128
129  /**
130   * Collect query execution summary statistics.
131   * <p>
132   * This can give us a quick overview into bad lazy loading areas etc.
133   * </p>
134   */
135  public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
136
137    String key = node.getPath();
138    if (key == null) {
139      key = "";
140    }
141
142    ProfileOriginQuery stats = queryStatsMap.get(key);
143    if (stats == null) {
144      // a race condition but we don't care
145      stats = new ProfileOriginQuery(key);
146      queryStatsMap.put(key, stats);
147    }
148    stats.add(beansLoaded, micros);
149  }
150
151  /**
152   * Collect the usage information for from a instance for this node.
153   */
154  public void collectUsageInfo(NodeUsageCollector profile) {
155    if (!profile.isEmpty()) {
156      getNodeStats(profile.getNode().getPath()).collectUsageInfo(profile);
157    }
158  }
159
160  private ProfileOriginNodeUsage getNodeStats(String path) {
161    lock.lock();
162    try {
163      // handle null paths as using ConcurrentHashMap
164      path = (path == null) ? "" : path;
165      ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path);
166      if (nodeStats == null) {
167        nodeStats = new ProfileOriginNodeUsage(path);
168        nodeUsageMap.put(path, nodeStats);
169      }
170      return nodeStats;
171    } finally {
172      lock.unlock();
173    }
174  }
175
176}