Move metrics package to org.springframework.core

See gh-24878
This commit is contained in:
Juergen Hoeller
2020-07-28 11:28:10 +02:00
parent 9f211de113
commit adce6593af
18 changed files with 60 additions and 46 deletions

View File

@@ -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.core.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
*/
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);
}

View File

@@ -0,0 +1,95 @@
/*
* 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.core.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();
}
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.core.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
*/
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();
}
}

View File

@@ -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.core.metrics.jfr;
import java.util.ArrayDeque;
import java.util.Deque;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.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.getFirst(), committedStep -> this.currentSteps.removeFirst());
this.currentSteps.offerFirst(this.currentSequenceId);
return step;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.core.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.core.metrics.StartupStep.Tags} are serialized
* as a single {@code String}, since Flight Recorder events do not support complex types.
*
* @author Brian Clozel
* @since 5.3
*/
@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;
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.core.metrics.jfr;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.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;
}
}
}

View File

@@ -0,0 +1,9 @@
/**
* Support package for recording startup metrics using Java Flight Recorder.
*/
@NonNullApi
@NonNullFields
package org.springframework.core.metrics.jfr;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -0,0 +1,9 @@
/**
* Support package for recording metrics during application startup.
*/
@NonNullApi
@NonNullFields
package org.springframework.core.metrics;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;