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}