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}