/* * Copyright 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jdev.miniprofiler.ratpack; import io.jdev.miniprofiler.Profiler; import io.jdev.miniprofiler.ProfilerProvider; import ratpack.exec.ExecInitializer; import ratpack.exec.Execution; import ratpack.func.Action; import ratpack.http.Request; import ratpack.http.Response; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; /** * A Ratpack `ExecInitializer` that provides MiniProfiler support. * * <p>It starts a profiling session and binds that to the execution.</p> * * <p> * If the <code>defaultProfilerStoreOption</code> constructor argument is * {@link ProfilerStoreOption#STORE_RESULTS}, the profiler will be saved to storage * at the end of the execution. if that value is set to {@link ProfilerStoreOption#DISCARD_RESULTS}, * the profiler results will not be saved. The default can be overridden by * binding one of those values to the execution during execution, or by * adding one of {@link StoreMiniProfilerHandler} or {@link DiscardMiniProfilerHandler} * to the handler chain. * </p> */ public class MiniProfilerExecInitializer implements ExecInitializer { private final ProfilerProvider provider; private final ProfilerStoreOption defaultProfilerStoreOption; /** * Construct the initializer with the given provider and default storage option. * * @param provider the profiler provider to use * @param defaultProfilerStoreOption the default profiler storage behaviour */ public MiniProfilerExecInitializer(ProfilerProvider provider, ProfilerStoreOption defaultProfilerStoreOption) { this.provider = provider; if (defaultProfilerStoreOption == null) { throw new IllegalArgumentException("defaultProfilerStoreOption cannot be null"); } this.defaultProfilerStoreOption = defaultProfilerStoreOption; } /** * Construct the initializer with the given provider and a default to store all profiler results. * * @param provider the profiler provider to use */ public MiniProfilerExecInitializer(ProfilerProvider provider) { this(provider, ProfilerStoreOption.STORE_RESULTS); } /** * Initialize the given execution and bind a new Profiler object * * <p> * The {@link ProfilerProvider} that this initializer was created with will always get bound to the execution. * </p> * @param execution the execution whose segment is being intercepted */ @Override public void init(Execution execution) { // create a profiler if there isn't one already if (shouldCreateProfilerOnExecutionStart(execution) && !provider.hasCurrentProfiler()) { provider.start(getProfilerName(execution)); } Completion completion = new Completion(execution); execution.onComplete(completion); // try to complete the profiler before the response is sent, if there is one execution.maybeGet(Response.class).ifPresent(r -> r.beforeSend(completion)); } protected void executionComplete(Execution execution) { if (provider.hasCurrentProfiler()) { Profiler profiler = provider.getCurrentProfiler(); ProfilerStoreOption store = execution.maybeGet(ProfilerStoreOption.class).orElse(defaultProfilerStoreOption); profiler.stop(store == ProfilerStoreOption.DISCARD_RESULTS); } } /** * Override to customize the name given to the profiling instance. * * <p>Default implementation looks for a Ratpack {@link Request} and uses the URI on that if * one is found, otherwise the string <code>"Unknown"</code>.</p> * * @param execution the execution whose segment is being intercepted * @return the name to be used for the new profiling session */ protected String getProfilerName(Execution execution) { Optional<Request> maybeReq = execution.maybeGet(Request.class); return maybeReq.isPresent() ? maybeReq.get().getUri() : "Unknown"; } /** * Controls whether this initializer will start a profiler at the start of the execution for the given execution. * * <p>Default is to return false, ie profilers are started by some other execution logic, rather than for all * executions.</p> * * @param execution the execution whose segment is being initialized * @return <code>false</code> for the default implementation */ protected boolean shouldCreateProfilerOnExecutionStart(Execution execution) { return false; } private class Completion implements AutoCloseable, Action<Response> { private final Execution execution; private final AtomicBoolean completed = new AtomicBoolean(false); private Completion(Execution execution) { this.execution = execution; } private void complete() { if(!completed.getAndSet(true)) { executionComplete(execution); } } @Override public void close() throws Exception { complete(); } @Override public void execute(Response response) throws Exception { complete(); } } }