package ru.vyarus.dropwizard.guice.module.context.debug;
import com.google.common.base.Preconditions;
import com.google.inject.AbstractModule;
import io.dropwizard.Application;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import ru.vyarus.dropwizard.guice.injector.lookup.InjectorLookup;
import ru.vyarus.dropwizard.guice.module.context.debug.report.DiagnosticReporter;
import ru.vyarus.dropwizard.guice.module.context.debug.report.diagnostic.DiagnosticConfig;
import ru.vyarus.dropwizard.guice.module.context.debug.report.diagnostic.DiagnosticRenderer;
import ru.vyarus.dropwizard.guice.module.context.debug.report.option.OptionsConfig;
import ru.vyarus.dropwizard.guice.module.context.debug.report.option.OptionsRenderer;
import ru.vyarus.dropwizard.guice.module.context.debug.report.stat.StatsRenderer;
import ru.vyarus.dropwizard.guice.module.context.debug.report.tree.ContextTreeConfig;
import ru.vyarus.dropwizard.guice.module.context.debug.report.tree.ContextTreeRenderer;
import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBootstrap;
import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle;
/**
* Bundle prints detailed configuration info and startup metrics.
* <p>
* Sections:
* <ul>
* <li>startup stats - collected timers and counters showing how much time were spent on different stages</li>
* <li>options - used options</li>
* <li>diagnostic section - summary of installed bundles, modules, used installers and extensions
* (showing execution order). Shows what was configured.</li>
* <li>context tree - tree showing configuration hierarchy (configuration sources). Shows
* from where configuration parts come from.</li>
* </ul>
* <p>
* Reporting is highly configurable. Default configuration shows most valuable (but not all possible) info.
* To create bundle with custom configuration use builder:
* <pre><code>
* DiagnosticBundle.builder()
* .printStartupStats(true)
* .printConfiguration(new DiagnosticConfig().printAll())
* .build();
* </code></pre>
* <p>
* Actual diagnostic rendering is performed by {@link DiagnosticRenderer}, {@link ContextTreeRenderer},
* {@link OptionsRenderer} and {@link StatsRenderer}. They may be used directly, for example, to show report
* on web page.
* <p>
* Reporting is performed after context startup (pure guicey context (in tests) or entire web context) and so
* does not affect collected statistics.
*
* @author Vyacheslav Rusakov
* @since 21.06.2016
*/
public class DiagnosticBundle implements GuiceyBundle {
private final Boolean statsConfig;
private final OptionsConfig optionsConfig;
private final DiagnosticConfig config;
private final ContextTreeConfig treeConfig;
/**
* Initialize bundle with default diagnostic configuration. Configures most commonly required info.
* Suitable for bundle usage with lookup mechanism.
*/
public DiagnosticBundle() {
this(builder()
.printStartupStats(true)
.printOptions(new OptionsConfig()
.showNotDefinedOptions()
.showNotUsedMarker())
.printConfiguration(new DiagnosticConfig()
.printDefaults())
.printContextTree(new ContextTreeConfig()
.hideNotUsedInstallers()
.hideEmptyBundles()
.hideCommands()));
}
DiagnosticBundle(final Builder builder) {
this.statsConfig = builder.statsConfig;
this.optionsConfig = builder.optionsConfig;
this.config = builder.config;
this.treeConfig = builder.treeConfig;
}
@Override
public void initialize(final GuiceyBootstrap bootstrap) {
// use listener to work properly for both guicey only test and normal app (to show hk stats)
bootstrap.environment().lifecycle().addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() {
@Override
public void lifeCycleStarted(final LifeCycle event) {
report(bootstrap.application());
}
});
bootstrap.modules(new DiagnosticModule());
}
private void report(final Application app) {
final DiagnosticReporter reporter = new DiagnosticReporter();
InjectorLookup.getInjector(app).get().injectMembers(reporter);
reporter.report(statsConfig, optionsConfig, config, treeConfig);
}
/**
* @return builder for bundle configuration
*/
public static Builder builder() {
return new Builder();
}
/**
* Diagnostic bundle builder.
*/
public static class Builder {
private Boolean statsConfig;
private OptionsConfig optionsConfig;
private DiagnosticConfig config;
private ContextTreeConfig treeConfig;
/**
* Enables startup statistic reporting. Stats shows internal guicey timings and some details of configuration
* process.
* <p>
* Enabled automatically if default bundle constructor used.
*
* @param hideSmallTimes true to hide times less then 1 ms, false to show everything
* @return builder instance for chained calls
* @see StatsRenderer
*/
public Builder printStartupStats(final boolean hideSmallTimes) {
this.statsConfig = hideSmallTimes;
return this;
}
/**
* Enables options reporting. Some options could be read lazily and so marked as NOT_USED at reporting time.
*
* @param config options section configuration
* @return builder instance for chained calls
*/
public Builder printOptions(final OptionsConfig config) {
this.optionsConfig = config;
return this;
}
/**
* Enable configuration reporting. Shows configuration items in compact form. Suitable for
* configuration overview.
* <p>
* In most situations default preset is enough:
* <pre><code>
* new DiagnosticConfig().printDefaults();
* </code></pre>
*
* @param config configuration reporting section configuration
* @return builder instance for chained calls
* @see DiagnosticRenderer
*/
public Builder printConfiguration(final DiagnosticConfig config) {
this.config = config;
Preconditions.checkState(!config.isEmptyConfig(),
"Empty config provided. Use at least one print option.");
return this;
}
/**
* Enable context tree printing. Tree provides configuration sources perspective, suitable for better
* understanding of configuration sources.
* <p>
* Note: in contrast to diagnostic config which is empty by default, tree config prints everything by default.
*
* @param treeConfig context tree section configuration
* @return builder instance for chained calls
* @see ContextTreeRenderer
*/
public Builder printContextTree(final ContextTreeConfig treeConfig) {
this.treeConfig = treeConfig;
return this;
}
/**
* @return configured bundle instance
*/
public DiagnosticBundle build() {
return new DiagnosticBundle(this);
}
}
/**
* Guicey configuration diagnostic module.
*/
public static class DiagnosticModule extends AbstractModule {
@Override
protected void configure() {
bind(StatsRenderer.class);
bind(OptionsRenderer.class);
bind(DiagnosticRenderer.class);
bind(ContextTreeRenderer.class);
}
}
}