Skip to content

Commit

Permalink
Add ability for users to assign JFR config name or file
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Quinn <[email protected]>
  • Loading branch information
tjquinno committed Jan 11, 2025
1 parent 48c5b6b commit 26f0ecb
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 9 deletions.
30 changes: 22 additions & 8 deletions docs/src/main/asciidoc/includes/guides/metrics.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -405,22 +405,36 @@ endif::se-flavor[]
[[observing-vthreads]]
==== Observing Virtual Threads Behavior
:vthreads-prefix: vthreads
Helidon maintains several {metrics} related to virtual threads as summarized in the next table.
Helidon maintains several {metrics} related to virtual threads as summarized in the next table. Helidon might rely on Java Flight Recorder (JFR) events and JMX MXBeans in computing the {metric} values. Be aware that limitations or changes in the values provided by these sources are outside the control of Helidon.
.{metrics_uc} for Virtual Threads
[cols="2,5,1"]
[cols="2,5,3,1"]
|===
| {metric_uc} name | Usage | Reported by default
| {metric_uc} name | Usage | Source | Reported by default
| `{vthreads-prefix}.count` | Current number of active virtual threads. | no
| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | yes
| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | yes
| `{vthreads-prefix}.started` | Number of virtual threads started. | no
| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | yes
| `{vthreads-prefix}.count` | Current number of active virtual threads. | JFR `jdk.virtualThreadStart` and `jdk.virtualThreadEnd` events| no
| `{vthreads-prefix}.pinned` | Number of times virtual threads have been pinned. | JFR `jdk.virtualThreadPinned` event | yes
| `{vthreads-prefix}.recentPinned` | Distribution of the duration of thread pinning. ^1^ | JFR `jdk.virtualThreadPinned` event | yes
| `{vthreads-prefix}.started` | Number of virtual threads started. | JFR `jdk.virtualThreadStart` event | no
| `{vthreads-prefix}.submitFailed` | Number of times submissions of a virtual thread to a platform carrier thread failed. | JFR `jdk.virtualThreadSubmitFailed` event | yes
|===
^1^ Distribution summaries can discard stale data, so the `recentPinned` summary might not reflect all thread pinning activity.
// tag::virtualThreadsMetricsConfig[]
==== Configuring Virtual Threads Metrics
===== Specifying Java Flight Recorder Configuration
Helidon relies on Java Flight Recorder (JFR) events to implement the virtual threads {metrics}. The JDK includes some predefined JFR configurations and users can provide their own, customized configurations.
By default, Helidon uses the predefined JFR configuration named `default` when subscribing to JFR events.
To change that assign the optional `metrics.virtual-threads.configuration` configuration setting to either:
* the _name_ of a predefined JFR configuration, or
* the _file path_ of a custom JFR configuration file.
Specifying a JFR configuration name or path that Helidon cannot resolve or load causes the server start-up to fail.
===== Controlling Virtual Thread Counts
For performance reasons Helidon does not by default report the {metrics} related to the count of virtual threads.
Enable these {metrics} using configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.helidon.metrics.api;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -210,6 +211,16 @@ static List<Tag> createTags(String pairs) {
@Option.DefaultBoolean(false)
boolean virtualThreadCountEnabled();

/**
* Java Flight Recorded configuration, either a predefined configuration name or a file spec to a custom file, for use in
* monitoring Java Flight Recorder events for virtual thread meters.
*
* @return the JFR configuration name or file path
*/
@Option.Configured("virtual-threads.configuration")
@Option.Default("default")
String virtualThreadsConfig();

/**
* Metrics configuration node.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
package io.helidon.metrics.systemmeters;

import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand All @@ -25,10 +29,12 @@
import io.helidon.metrics.api.Gauge;
import io.helidon.metrics.api.Meter;
import io.helidon.metrics.api.Metrics;
import io.helidon.metrics.api.MetricsConfig;
import io.helidon.metrics.api.MetricsFactory;
import io.helidon.metrics.api.Timer;
import io.helidon.metrics.spi.MetersProvider;

import jdk.jfr.Configuration;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingStream;

Expand Down Expand Up @@ -73,7 +79,7 @@ public VThreadSystemMetersProvider() {
@Override
public Collection<Meter.Builder<?, ?>> meterBuilders(MetricsFactory metricsFactory) {

var rs = new RecordingStream();
var rs = new RecordingStream(effectiveConfiguration(metricsFactory.metricsConfig()));

List<Meter.Builder<?, ?>> meterBuilders = new ArrayList<>(List.of(
Gauge.builder(METER_NAME_PREFIX + SUBMIT_FAILURES, () -> virtualThreadSubmitFails)
Expand Down Expand Up @@ -145,4 +151,25 @@ private void recordThreadPin(RecordedEvent event) {
pinnedVirtualThreads++;
recentPinnedVirtualThreads.get().record(event.getDuration());
}

private static Configuration effectiveConfiguration(MetricsConfig metricsConfig) {
/*
Interpret the VFR configuration as the name of a predefined configuration first. If that fails, then treat it as a Path.
*/
String vfrConfigFromMetricsConfig = metricsConfig.virtualThreadsConfig();
Configuration result;
try {
try {
result = Configuration.getConfiguration(vfrConfigFromMetricsConfig);
} catch (NoSuchFileException e) {
result = Configuration.create(Path.of(vfrConfigFromMetricsConfig));
}
return result;
} catch (IOException | ParseException e) {
throw new RuntimeException("Unable to use specified Java Flight Recorder configuration '"
+ vfrConfigFromMetricsConfig
+ "'",
e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* 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.helidon.metrics.systemmeters;

import java.util.Map;

import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.metrics.api.MetricsFactory;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;

class TestVirtualThreadsMetersConfigs {

@Test
void checkExceptionWithBadConfigValue() {
Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.configuration", "badValue")));
MetricsFactory metricsFactory = MetricsFactory.getInstance(config);
VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider();
assertThrows(RuntimeException.class, () -> provider.meterBuilders(metricsFactory));
}

@Test void checkCustomFilePath() {
Config config = Config.just(ConfigSources.create(Map.of("virtual-threads.configuration",
"src/test/resources/metrics-test.jfc")));
MetricsFactory metricsFactory = MetricsFactory.getInstance(config);
VThreadSystemMetersProvider provider = new VThreadSystemMetersProvider();
provider.meterBuilders(metricsFactory);
}

}

0 comments on commit 26f0ecb

Please sign in to comment.