Add application startup metrics support
This commit adds a new `StartupStep` interface and its factory `ApplicationStartup`. Such steps are created, tagged with metadata and thir execution time can be recorded - in order to collect metrics about the application startup. The default implementation is a "no-op" variant and has no side-effect. Other implementations can record and collect events in a dedicated metrics system or profiling tools. We provide here an implementation for recording and storing steps with Java Flight Recorder. This commit also instruments the Spring application context to gather metrics about various phases of the application context, such as: * context refresh phase * bean definition registry post-processing * bean factory post-processing * beans instantiation and post-processing Third part libraries involved in the Spring application context can reuse the same infrastructure to record similar metrics. Closes gh-24878
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.HierarchicalBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.metrics.ApplicationStartup;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
@@ -276,6 +277,20 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
|
||||
@Nullable
|
||||
Scope getRegisteredScope(String scopeName);
|
||||
|
||||
/**
|
||||
* Set the {@code ApplicationStartup} for this bean factory.
|
||||
* <p>This allows the application context to record metrics during application startup.
|
||||
* @param applicationStartup the new application startup
|
||||
* @since 5.3.0
|
||||
*/
|
||||
void setApplicationStartup(ApplicationStartup applicationStartup);
|
||||
|
||||
/**
|
||||
* Return the {@code ApplicationStartup} for this bean factory.
|
||||
* @since 5.3.0
|
||||
*/
|
||||
ApplicationStartup getApplicationStartup();
|
||||
|
||||
/**
|
||||
* Provides a security access control context relevant to this factory.
|
||||
* @return the applicable AccessControlContext (never {@code null})
|
||||
|
||||
@@ -69,6 +69,8 @@ import org.springframework.beans.factory.config.DestructionAwareBeanPostProcesso
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.metrics.ApplicationStartup;
|
||||
import org.springframework.beans.metrics.StartupStep;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
import org.springframework.core.DecoratingClassLoader;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
@@ -178,6 +180,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
|
||||
new NamedThreadLocal<>("Prototype beans currently in creation");
|
||||
|
||||
/** Application startup metrics. **/
|
||||
private ApplicationStartup applicationStartup = ApplicationStartup.getDefault();
|
||||
|
||||
/**
|
||||
* Create a new AbstractBeanFactory.
|
||||
@@ -297,6 +301,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
}
|
||||
|
||||
try {
|
||||
StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
|
||||
.tag("beanName", name);
|
||||
if (requiredType != null) {
|
||||
beanCreation.tag("beanType", requiredType::toString);
|
||||
}
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
checkMergedBeanDefinition(mbd, beanName, args);
|
||||
|
||||
@@ -374,6 +383,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
throw new ScopeNotActiveException(beanName, scopeName, ex);
|
||||
}
|
||||
}
|
||||
beanCreation.end();
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
cleanupAfterBeanCreationFailure(beanName);
|
||||
@@ -1044,6 +1054,17 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
this.securityContextProvider = securityProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationStartup(ApplicationStartup applicationStartup) {
|
||||
Assert.notNull(applicationStartup, "applicationStartup should not be null");
|
||||
this.applicationStartup = applicationStartup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationStartup getApplicationStartup() {
|
||||
return this.applicationStartup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the creation of the access control context to the
|
||||
* {@link #setSecurityContextProvider SecurityContextProvider}.
|
||||
@@ -1380,7 +1401,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
else {
|
||||
throw new NoSuchBeanDefinitionException(parentBeanName,
|
||||
"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
|
||||
"': cannot be resolved without a ConfigurableBeanFactory parent");
|
||||
"': cannot be resolved without a ConfigurableBeanFactory parent");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2068,7 +2089,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
super.replaceAll(operator);
|
||||
beanPostProcessorCache = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,6 +71,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.NamedBeanHolder;
|
||||
import org.springframework.beans.metrics.StartupStep;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
@@ -564,7 +565,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else {
|
||||
if (includeNonSingletons || isNonLazyDecorated ||
|
||||
(allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
@@ -937,6 +938,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
for (String beanName : beanNames) {
|
||||
Object singletonInstance = getSingleton(beanName);
|
||||
if (singletonInstance instanceof SmartInitializingSingleton) {
|
||||
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
|
||||
.tag("beanName", beanName);
|
||||
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
|
||||
if (System.getSecurityManager() != null) {
|
||||
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
@@ -947,6 +950,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
else {
|
||||
smartSingleton.afterSingletonsInstantiated();
|
||||
}
|
||||
smartInitialize.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1672,7 +1676,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (candidatePriority.equals(highestPriority)) {
|
||||
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
|
||||
"Multiple beans found with the same priority ('" + highestPriority +
|
||||
"') among candidates: " + candidates.keySet());
|
||||
"') among candidates: " + candidates.keySet());
|
||||
}
|
||||
else if (candidatePriority < highestPriority) {
|
||||
highestPriorityBeanName = candidateBeanName;
|
||||
@@ -1758,7 +1762,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
|
||||
throw new NoSuchBeanDefinitionException(resolvableType,
|
||||
"expected at least 1 bean which qualifies as autowire candidate. " +
|
||||
"Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations()));
|
||||
"Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1803,6 +1807,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
public boolean isRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
|
||||
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
|
||||
@@ -2019,6 +2024,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
public boolean isRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics;
|
||||
|
||||
/**
|
||||
* Instruments the application startup phase using {@link StartupStep steps}.
|
||||
* <p>The core container and its infrastructure components can use the {@code ApplicationStartup}
|
||||
* to mark steps during the application startup and collect data about the execution context
|
||||
* or their processing time.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public interface ApplicationStartup {
|
||||
|
||||
/**
|
||||
* Return a default "no op" {@code ApplicationStartup} implementation.
|
||||
* <p>This variant is designed for minimal overhead and does not record data.
|
||||
*/
|
||||
static ApplicationStartup getDefault() {
|
||||
return new DefaultApplicationStartup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new step and marks its beginning.
|
||||
* <p>A step name describes the current action or phase. This technical
|
||||
* name should be "." namespaced and can be reused to describe other instances of
|
||||
* the same step during application startup.
|
||||
* @param name the step name
|
||||
*/
|
||||
StartupStep start(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Default "no op" {@code ApplicationStartup} implementation.
|
||||
* <p>This variant is designed for minimal overhead and does not record events.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class DefaultApplicationStartup implements ApplicationStartup {
|
||||
|
||||
@Override
|
||||
public DefaultStartupStep start(String name) {
|
||||
return new DefaultStartupStep();
|
||||
}
|
||||
|
||||
static class DefaultStartupStep implements StartupStep {
|
||||
|
||||
boolean recorded = false;
|
||||
|
||||
private final DefaultTags TAGS = new DefaultTags();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getParentId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tags tags() {
|
||||
return this.TAGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartupStep tag(String key, String value) {
|
||||
if (this.recorded) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartupStep tag(String key, Supplier<String> value) {
|
||||
if (this.recorded) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
this.recorded = true;
|
||||
}
|
||||
|
||||
static class DefaultTags implements StartupStep.Tags {
|
||||
|
||||
@Override
|
||||
public Iterator<StartupStep.Tag> iterator() {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Step recording metrics about a particular phase or action happening during the {@link ApplicationStartup}.
|
||||
* <p>The lifecycle of a {@code StartupStep} goes as follows:
|
||||
* <ol>
|
||||
* <li>the step is created and starts by calling {@link ApplicationStartup#start(String) the application startup}
|
||||
* and is assigned a unique {@link StartupStep#getId() id}.
|
||||
* <li>we can then attach information with {@link Tags} during processing
|
||||
* <li>we then need to mark the {@link #end()} of the step
|
||||
* </ol>
|
||||
* <p>Implementations can track the "execution time" or other metrics for steps.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public interface StartupStep {
|
||||
|
||||
/**
|
||||
* Return the name of the startup step.
|
||||
* <p>A step name describes the current action or phase. This technical
|
||||
* name should be "." namespaced and can be reused to describe other instances of
|
||||
* similar steps during application startup.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the unique id for this step within the application startup.
|
||||
*/
|
||||
long getId();
|
||||
|
||||
/**
|
||||
* Return, if available, the id of the parent step.
|
||||
* <p>The parent step is the step that was started the most recently when the current step was created.
|
||||
*/
|
||||
@Nullable
|
||||
Long getParentId();
|
||||
|
||||
/**
|
||||
* Add a {@link Tag} to the step.
|
||||
* @param key tag key
|
||||
* @param value tag value
|
||||
*/
|
||||
StartupStep tag(String key, String value);
|
||||
|
||||
/**
|
||||
* Add a {@link Tag} to the step.
|
||||
* @param key tag key
|
||||
* @param value {@link Supplier} for the tag value
|
||||
*/
|
||||
StartupStep tag(String key, Supplier<String> value);
|
||||
|
||||
/**
|
||||
* Return the {@link Tag} collection for this step.
|
||||
*/
|
||||
Tags tags();
|
||||
|
||||
/**
|
||||
* Record the state of the step and possibly other metrics like execution time.
|
||||
* <p>Once ended, changes on the step state are not allowed.
|
||||
*/
|
||||
void end();
|
||||
|
||||
/**
|
||||
* Mutable collection of {@link Tag}.
|
||||
*/
|
||||
interface Tags extends Iterable<Tag> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple key/value association for storing step metadata.
|
||||
*/
|
||||
interface Tag {
|
||||
|
||||
/**
|
||||
* Return the {@code Tag} name.
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* Return the {@code Tag} value.
|
||||
*/
|
||||
String getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics.jfr;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.springframework.beans.metrics.ApplicationStartup;
|
||||
import org.springframework.beans.metrics.StartupStep;
|
||||
|
||||
/**
|
||||
* {@link ApplicationStartup} implementation for the Java Flight Recorder.
|
||||
* <p>This variant records {@link StartupStep} as Flight Recorder events; because such events
|
||||
* only support base types, the {@link StartupStep.Tags} are serialized as a single String attribute.
|
||||
* <p>Once this is configured on the application context, you can record data by launching the application
|
||||
* with recording enabled: {@code java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar app.jar}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 5.3
|
||||
*/
|
||||
public class FlightRecorderApplicationStartup implements ApplicationStartup {
|
||||
|
||||
private long currentSequenceId;
|
||||
|
||||
private final Deque<Long> currentSteps;
|
||||
|
||||
public FlightRecorderApplicationStartup() {
|
||||
this.currentSequenceId = 0;
|
||||
this.currentSteps = new ArrayDeque<>();
|
||||
this.currentSteps.offerFirst(0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartupStep start(String name) {
|
||||
FlightRecorderStartupStep step = new FlightRecorderStartupStep(++this.currentSequenceId, name,
|
||||
this.currentSteps.peekFirst(), committedStep -> this.currentSteps.removeFirst());
|
||||
this.currentSteps.offerFirst(this.currentSequenceId);
|
||||
return step;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics.jfr;
|
||||
|
||||
import jdk.jfr.Category;
|
||||
import jdk.jfr.Description;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.Label;
|
||||
|
||||
/**
|
||||
* {@link Event} extension for recording {@link FlightRecorderStartupStep}
|
||||
* in Java Flight Recorder.
|
||||
* <p>{@link org.springframework.beans.metrics.StartupStep.Tags} are serialized as a single {@code String},
|
||||
* since Flight Recorder events do not support complex types.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@Category("Spring Application")
|
||||
@Label("Startup Step")
|
||||
@Description("Spring Application Startup")
|
||||
class FlightRecorderStartupEvent extends Event {
|
||||
|
||||
public final long eventId;
|
||||
|
||||
public final long parentId;
|
||||
|
||||
@Label("Name")
|
||||
public final String name;
|
||||
|
||||
@Label("Tags")
|
||||
String tags = "";
|
||||
|
||||
public FlightRecorderStartupEvent(long eventId, String name, long parentId) {
|
||||
this.name = name;
|
||||
this.eventId = eventId;
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public void setTags(String tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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 org.springframework.beans.metrics.jfr;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.springframework.beans.metrics.StartupStep;
|
||||
|
||||
/**
|
||||
* {@link StartupStep} implementation for the Java Flight Recorder.
|
||||
* <p>This variant delegates to a {@link FlightRecorderStartupEvent JFR event extension}
|
||||
* to collect and record data in Java Flight Recorder.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class FlightRecorderStartupStep implements StartupStep {
|
||||
|
||||
private final FlightRecorderStartupEvent event;
|
||||
|
||||
private final FlightRecorderTags tags = new FlightRecorderTags();
|
||||
|
||||
private final Consumer<FlightRecorderStartupStep> recordingCallback;
|
||||
|
||||
public FlightRecorderStartupStep(long id, String name, long parentId,
|
||||
Consumer<FlightRecorderStartupStep> recordingCallback) {
|
||||
this.event = new FlightRecorderStartupEvent(id, name, parentId);
|
||||
this.event.begin();
|
||||
this.recordingCallback = recordingCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.event.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return this.event.eventId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getParentId() {
|
||||
return this.event.parentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartupStep tag(String key, String value) {
|
||||
this.tags.add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartupStep tag(String key, Supplier<String> value) {
|
||||
this.tags.add(key, value.get());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tags tags() {
|
||||
return this.tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
this.event.end();
|
||||
if (this.event.shouldCommit()) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
this.tags.forEach(tag ->
|
||||
builder.append(tag.getKey()).append('=').append(tag.getValue()).append(',')
|
||||
);
|
||||
this.event.setTags(builder.toString());
|
||||
}
|
||||
this.event.commit();
|
||||
this.recordingCallback.accept(this);
|
||||
}
|
||||
|
||||
protected FlightRecorderStartupEvent getEvent() {
|
||||
return this.event;
|
||||
}
|
||||
|
||||
static class FlightRecorderTags implements Tags {
|
||||
|
||||
private Tag[] tags = new Tag[0];
|
||||
|
||||
public void add(String key, String value) {
|
||||
Tag[] newTags = new Tag[this.tags.length + 1];
|
||||
System.arraycopy(this.tags, 0, newTags, 0, this.tags.length);
|
||||
newTags[newTags.length - 1] = new FlightRecorderTag(key, value);
|
||||
this.tags = newTags;
|
||||
}
|
||||
|
||||
public void add(String key, Supplier<String> value) {
|
||||
add(key, value.get());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<Tag> iterator() {
|
||||
return new TagsIterator();
|
||||
}
|
||||
|
||||
private class TagsIterator implements Iterator<Tag> {
|
||||
|
||||
private int idx = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.idx < tags.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag next() {
|
||||
return tags[this.idx++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("tags are append only");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class FlightRecorderTag implements Tag {
|
||||
|
||||
private final String key;
|
||||
|
||||
private final String value;
|
||||
|
||||
public FlightRecorderTag(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Support package for recording startup metrics using Java Flight Recorder.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.beans.metrics.jfr;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Support package for recording metrics during application startup.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.beans.metrics;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
Reference in New Issue
Block a user