001package io.ebeaninternal.server.autotune.service; 002 003import io.ebean.bean.CallOrigin; 004import io.ebean.bean.ObjectGraphNode; 005import io.ebean.config.AutoTuneConfig; 006import io.ebean.config.AutoTuneMode; 007import io.ebeaninternal.api.SpiEbeanServer; 008import io.ebeaninternal.api.SpiQuery; 009import io.ebeaninternal.server.autotune.ProfilingListener; 010import io.ebeaninternal.server.autotune.model.Origin; 011import io.ebeaninternal.server.querydefn.OrmQueryDetail; 012 013import javax.persistence.PersistenceException; 014import java.util.Collection; 015import java.util.Map; 016import java.util.Set; 017import java.util.concurrent.ConcurrentHashMap; 018 019/** 020 * 021 */ 022public class BaseQueryTuner { 023 024 private final boolean queryTuning; 025 026 private final boolean profiling; 027 028 private final AutoTuneMode mode; 029 030 /** 031 * Map of the tuned query details per profile query point. 032 */ 033 private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<>(); 034 035 private final SpiEbeanServer server; 036 037 private final ProfilingListener profilingListener; 038 039 /** 040 * Flag set true when there is no profiling or query tuning. 041 */ 042 private final boolean skipAll; 043 044 BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) { 045 this.server = server; 046 this.profilingListener = profilingListener; 047 this.mode = config.getMode(); 048 this.queryTuning = config.isQueryTuning(); 049 this.profiling = config.isProfiling(); 050 this.skipAll = !queryTuning && !profiling; 051 } 052 053 /** 054 * Return all the current tuned query entries. 055 */ 056 public Collection<TunedQueryInfo> getAll() { 057 return tunedQueryInfoMap.values(); 058 } 059 060 /** 061 * Put a query tuning entry. 062 */ 063 public void put(Origin origin) { 064 065 tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin)); 066 } 067 068 /** 069 * Load the tuned query information. 070 */ 071 public void load(String key, TunedQueryInfo queryInfo) { 072 tunedQueryInfoMap.put(key, queryInfo); 073 } 074 075 /** 076 * Return the detail currently used for tuning. 077 * This returns null if there is currently no matching tuning. 078 */ 079 public OrmQueryDetail get(String key) { 080 TunedQueryInfo info = tunedQueryInfoMap.get(key); 081 return (info == null) ? null : info.getTunedDetail(); 082 } 083 084 /** 085 * Auto tune the query and enable profiling. 086 */ 087 boolean tuneQuery(SpiQuery<?> query) { 088 089 if (skipAll || !tunableQuery(query)) { 090 return false; 091 } 092 093 if (query.getProfilingListener() != null) { 094 // profiling secondary query 095 return false; 096 } 097 098 if (!useTuning(query)) { 099 if (profiling) { 100 profiling(query, server.createCallOrigin()); 101 } 102 return false; 103 } 104 105 if (query.getParentNode() != null) { 106 // This is a +lazy/+query query with profiling on. 107 // We continue to collect the profiling information. 108 query.setProfilingListener(profilingListener); 109 return true; 110 } 111 112 // create a query point to identify the query 113 CallOrigin callOrigin = server.createCallOrigin(); 114 ObjectGraphNode origin = query.setOrigin(callOrigin); 115 116 if (profiling) { 117 if (profilingListener.isProfileRequest(origin, query)) { 118 // collect more profiling based on profiling rate etc 119 query.setProfilingListener(profilingListener); 120 } 121 } 122 123 if (queryTuning) { 124 // get current "tuned fetch" for this query point 125 TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey()); 126 return tuneInfo != null && tuneInfo.tuneQuery(query); 127 } 128 return false; 129 } 130 131 /** 132 * Return false for row count, find ids, subQuery, delete and Versions queries. 133 * <p> 134 * These queries are not applicable for autoTune in that they don't have a select/fetch (fetch group). 135 * </p> 136 * <p> 137 * We also exclude queries that are explicitly set to load the L2 bean cache as we want full beans 138 * in that case. 139 * </p> 140 */ 141 private boolean tunableQuery(SpiQuery<?> query) { 142 SpiQuery.Type type = query.getType(); 143 switch (type) { 144 case COUNT: 145 case ATTRIBUTE: 146 case ID_LIST: 147 case UPDATE: 148 case DELETE: 149 case SUBQUERY: 150 return false; 151 default: 152 // not using autoTune when explicitly loading the l2 bean cache 153 // or when using Versions query 154 return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode(); 155 } 156 } 157 158 private void profiling(SpiQuery<?> query, CallOrigin call) { 159 160 // create a query point to identify the query 161 ObjectGraphNode origin = query.setOrigin(call); 162 if (profilingListener.isProfileRequest(origin, query)) { 163 // collect more profiling based on profiling rate etc 164 query.setProfilingListener(profilingListener); 165 } 166 } 167 168 /** 169 * Return true if we should try to tune this query. 170 */ 171 private boolean useTuning(SpiQuery<?> query) { 172 173 Boolean autoTune = query.isAutoTune(); 174 if (autoTune != null) { 175 // explicitly set... 176 return autoTune; 177 178 } else { 179 // determine using implicit mode... 180 switch (mode) { 181 case DEFAULT_ON: 182 return true; 183 184 case DEFAULT_OFF: 185 return false; 186 187 case DEFAULT_ONIFEMPTY: 188 return query.isDetailEmpty(); 189 190 default: 191 throw new PersistenceException("Invalid AutoTuneMode " + mode); 192 } 193 } 194 } 195 196 /** 197 * Return the keys as a set. 198 */ 199 public Set<String> keySet() { 200 return tunedQueryInfoMap.keySet(); 201 } 202}