This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# binjr-adapter-jfr
[![Maven Central](https://img.shields.io/maven-central/v/eu.binjr/binjr-adapter-jfr.svg?label=Maven%20Central&style=flat-square)](https://search.maven.org/search?q=g:%22eu.binjr%22%20AND%20a:%22binjr-adapter-jfr%22)
This module implements a DataAdapter capable of consuming data from JDK Flight Recorder files.
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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.
*/
dependencies {
compileOnly project(':binjr-core')
}
jar {
manifest {
attributes(
'Specification-Title': project.name,
'Specification-Version': project.version,
'Implementation-Title': project.name,
'Implementation-Version': project.version,
'Build-Number': BINJR_BUILD_NUMBER
)
}
}
@@ -0,0 +1,199 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import com.google.gson.Gson;
import eu.binjr.common.function.CheckedLambdas;
import eu.binjr.common.io.FileSystemBrowser;
import eu.binjr.common.io.IOUtils;
import eu.binjr.common.javafx.controls.TimeRange;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.BaseDataAdapter;
import eu.binjr.core.data.adapters.DataAdapter;
import eu.binjr.core.data.adapters.ReloadPolicy;
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.data.exceptions.InvalidAdapterParameterException;
import eu.binjr.core.data.indexes.Index;
import eu.binjr.core.data.indexes.Indexes;
import eu.binjr.core.data.indexes.IndexingStatus;
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
import eu.binjr.core.data.workspace.TimeSeriesInfo;
import javafx.beans.property.LongProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.facet.FacetField;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import static eu.binjr.core.data.indexes.parser.capture.CaptureGroup.SEVERITY;
/**
* A {@link DataAdapter} implementation to retrieve data from a JDK Flight Recorder file.
*
* @author Frederic Thevenet
*/
public abstract class BaseJfrDataAdapter<T> extends BaseDataAdapter<T> {
private static final Logger logger = Logger.create(BaseJfrDataAdapter.class);
protected static final Gson gson = new Gson();
protected static final Property<IndexingStatus> INDEXING_OK = new SimpleObjectProperty<>(IndexingStatus.OK);
protected static final String ZONE_ID = "zoneId";
protected static final String ENCODING = "encoding";
protected static final String PARSING_PROFILE = "parsingProfile";
protected static final String PATH = "jfrPath";
protected JfrEventFormat eventFormat;
protected Path jfrFilePath;
protected ZoneId zoneId;
protected String encoding;
protected Index index;
protected FileSystemBrowser fileBrowser;
public BaseJfrDataAdapter(Path jfrPath, ZoneId zoneId) throws DataAdapterException {
super();
initParams(zoneId, jfrPath, "utf-8");
}
@Override
public String getEncoding() {
return encoding;
}
@Override
public ZoneId getTimeZoneId() {
return zoneId;
}
@Override
public Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put(ZONE_ID, zoneId.toString());
params.put(ENCODING, encoding);
params.put(PATH, jfrFilePath.toString());
return params;
}
@Override
public void loadParams(Map<String, String> params) throws DataAdapterException {
if (params == null) {
throw new InvalidAdapterParameterException("Could not find parameter list for adapter " + getSourceName());
}
initParams(validateParameter(params, ZONE_ID,
s -> {
if (s == null) {
throw new InvalidAdapterParameterException("Parameter '" + ZONE_ID + "' is missing in adapter " + getSourceName());
}
return ZoneId.of(s);
}),
Paths.get(validateParameterNullity(params, PATH)),
validateParameterNullity(params, ENCODING));
}
private void initParams(ZoneId zoneId,
Path jfrPath,
String encoding) {
this.zoneId = zoneId;
this.jfrFilePath = jfrPath;
this.encoding = encoding;
this.eventFormat = new JfrEventFormat(zoneId, Charset.forName(encoding));
}
@Override
public void onStart() throws DataAdapterException {
super.onStart();
try {
this.fileBrowser = FileSystemBrowser.of(jfrFilePath.getParent());
this.index = Indexes.LOG_FILES.acquire();
} catch (IOException e) {
throw new CannotInitializeDataAdapterException("An error occurred during the data adapter initialization", e);
}
}
@Override
public void close() {
try {
Indexes.LOG_FILES.release();
} catch (Exception e) {
logger.error("An error occurred while releasing index " + Indexes.LOG_FILES.name() + ": " + e.getMessage());
logger.debug("Stack Trace:", e);
}
IOUtils.close(fileBrowser);
super.close();
}
public synchronized void ensureIndexed(Set<String> sources, ReloadPolicy reloadPolicy) throws IOException {
if (reloadPolicy == ReloadPolicy.ALL) {
sources.forEach(index.getIndexedFiles()::remove);
}
final LongProperty charRead = new SimpleLongProperty(0);
var isCommitNecessary = false;
var filterMap = new HashMap<Path, HashSet<String>>();
for (var binding : sources) {
if (!index.getIndexedFiles().containsKey(binding)) {
var a = binding.split("\\|");
var filePath = Path.of(a[0].replace(BuiltInParsingProfile.NONE.getProfileId() + "/", ""));
var eventType = a[1];
filterMap.computeIfAbsent(filePath, p -> new HashSet<>()).add(eventType);
isCommitNecessary = true;
index.getIndexedFiles().put(binding, IndexingStatus.OK);
}
}
for (Map.Entry<Path, HashSet<String>> entry : filterMap.entrySet()) {
Path path = entry.getKey();
HashSet<String> strings = entry.getValue();
index.add(path.toString(),
new JfrRecordingFilter(path, strings),
false,
eventFormat,
(doc, event) -> {
// Add number fields
event.getNumberFields().forEach((key, value) -> doc.add(new StoredField(key, value.doubleValue())));
// Set HAS_NUM field
doc.add(new StringField(JfrEventFormat.HAS_NUM_FIELDS, event.getNumberFields().size() > 0 ? "true" : "false", Field.Store.NO));
// Add event categories as severity
String severity = event.getTextField(JfrEventFormat.CATEGORIES) == null ? "JFR" :
event.getTextField(JfrEventFormat.CATEGORIES);//.toLowerCase();
doc.add(new FacetField(SEVERITY, severity));
doc.add(new StoredField(SEVERITY, severity));
return doc;
},
charRead,
INDEXING_OK,
(rootPath, parsedEvent) -> BuiltInParsingProfile.NONE.getProfileId() + "/" + rootPath + "|" + parsedEvent.getTextField(JfrEventFormat.EVENT_TYPE_NAME),
(source) -> source.eventTypes().stream().map(type -> BuiltInParsingProfile.NONE.getProfileId() + "/" + source.recordingPath().toString() + "|" + type).toList());
}
if (isCommitNecessary) {
index.commitIndexAndTaxonomy();
}
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import com.google.gson.Gson;
import eu.binjr.common.preferences.ObservablePreference;
import eu.binjr.core.data.adapters.DataAdapter;
import eu.binjr.core.data.adapters.DataAdapterPreferences;
import java.util.Set;
/**
* Defines the preferences associated with the Text files adapter.
*/
public class JfrAdapterPreferences extends DataAdapterPreferences {
private static final Gson gson = new Gson();
/**
* The default text panel font size preference.
*/
public ObservablePreference<Number> defaultTextViewFontSize = integerPreference("defaultTextViewFontSize", 10);
/**
* The filters used when scanning folders in the source filesystem.
*/
public ObservablePreference<String[]> folderFilters = objectPreference(String[].class,
"folderFilters",
new String[]{"*"},
gson::toJson,
s -> gson.fromJson(s, String[].class));
/**
* The filters used to prune file extensions to scan in the source filesystem.
*/
public ObservablePreference<String[]> fileExtensionFilters = objectPreference(String[].class,
"fileExtensionFilters",
new String[]{".jfr"},
gson::toJson,
s -> gson.fromJson(s, String[].class));
/**
* A list of value types for the event payload that should be included
*/
public ObservablePreference<String[]> includedEventsDataTypes = objectPreference(String[].class,
"includedEventsDataTypes",
new String[]{"short", "int", "long", "float", "double"},
gson::toJson,
s -> gson.fromJson(s, String[].class));
/**
* A list of names of events that should be excluded
*/
public ObservablePreference<String[]> excludedEventsNames = objectPreference(String[].class,
"excludedEventsByName",
new String[]{"gcId", "javaThreadId", "osThreadId", "modifiers"},
gson::toJson,
s -> gson.fromJson(s, String[].class));
/**
* Initialize a new instance of the {@link JfrAdapterPreferences} class associated to
* a {@link DataAdapter} instance.
*
* @param dataAdapterClass the associated {@link DataAdapter}
*/
public JfrAdapterPreferences(Class<? extends DataAdapter<?>> dataAdapterClass) {
super(dataAdapterClass);
}
}
@@ -0,0 +1,160 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import eu.binjr.common.javafx.controls.TimeRange;
import eu.binjr.common.javafx.controls.TreeViewUtils;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.*;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.data.indexes.IndexingStatus;
import eu.binjr.core.data.indexes.SearchHit;
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
import eu.binjr.core.data.timeseries.TimeSeriesProcessor;
import eu.binjr.core.data.workspace.TimeSeriesInfo;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordingFile;
import org.eclipse.fx.ui.controls.tree.FilterableTreeItem;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A {@link DataAdapter} implementation to retrieve data from a JDK Flight Recorder file.
*
* @author Frederic Thevenet
*/
public class JfrDataAdapter extends BaseJfrDataAdapter<SearchHit> implements ProgressAdapter<SearchHit> {
private static final Logger logger = Logger.create(JfrDataAdapter.class);
/**
* Initializes a new instance of the {@link JfrDataAdapter} class with a set of default values.
*
* @throws DataAdapterException if the {@link DataAdapter} could not be initializes.
*/
public JfrDataAdapter() throws DataAdapterException {
super(Path.of(""), ZoneId.systemDefault());
}
/**
* Initializes a new instance of the {@link JfrDataAdapter} class with the provided parameters.
*
* @param jfrPath the path to the JFR file.
* @param zoneId the time zone to used.
* @throws DataAdapterException if the {@link DataAdapter} could not be initialized.
*/
public JfrDataAdapter(Path jfrPath,
ZoneId zoneId)
throws DataAdapterException {
super(jfrPath, zoneId);
}
@Override
public TimeRange getInitialTimeRange(String path, List<TimeSeriesInfo<SearchHit>> seriesInfo) throws DataAdapterException {
try {
ensureIndexed(seriesInfo.stream().map(info -> BuiltInParsingProfile.NONE.getProfileId() + "/" + info.getBinding().getPath()).collect(Collectors.toSet()), ReloadPolicy.UNLOADED);
return index.getTimeRangeBoundaries(seriesInfo.stream().map(ts -> BuiltInParsingProfile.NONE.getProfileId() + "/" + ts.getBinding().getPath()).toList(), getTimeZoneId());
} catch (IOException e) {
throw new DataAdapterException("Error retrieving initial time range", e);
}
}
@Override
public FilterableTreeItem<SourceBinding> getBindingTree() throws DataAdapterException {
String rootPath = jfrFilePath.toString() + "|";
FilterableTreeItem<SourceBinding> tree = new FilterableTreeItem<>(new LogFilesBinding.Builder()
.withLabel(getSourceName())
.withPath(jfrFilePath.toString() + "|")
.withAdapter(this)
.build());
try (var recordingFile = new RecordingFile(jfrFilePath)) {
for (EventType eventType : recordingFile.readEventTypes()) {
var branch = tree;
for (var cat : eventType.getCategoryNames()) {
var pos = TreeViewUtils.findFirstInTree(tree, item -> item.getValue().getLabel().equals(cat));
if (pos.isEmpty()) {
var node = new FilterableTreeItem<>((SourceBinding) new LogFilesBinding.Builder()
.withLabel(cat)
.withParent(branch.getValue())
.withPath(branch.getValue().getPath() + "/" + cat)
.withAdapter(this)
.build());
branch.getInternalChildren().add(node);
branch = node;
} else {
branch = (FilterableTreeItem<SourceBinding>) pos.get();
}
}
var leaf = new FilterableTreeItem<>((SourceBinding) new LogFilesBinding.Builder()
.withLabel(eventType.getLabel())
.withLegend(eventType.getLabel())
.withPath(rootPath + eventType.getName())
.withParent(branch.getValue())
.withAdapter(this)
.build());
branch.getInternalChildren().add(leaf);
}
} catch (IOException e) {
throw new DataAdapterException("Error while attempting to read JFR recording: " + e.getMessage(), e);
}
return tree;
}
@Deprecated
@Override
public Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> fetchData(String path,
Instant begin,
Instant end,
List<TimeSeriesInfo<SearchHit>> seriesInfo,
boolean bypassCache) throws DataAdapterException {
return loadSeries(path, seriesInfo, bypassCache ? ReloadPolicy.ALL : ReloadPolicy.UNLOADED, null, INDEXING_OK);
}
@Override
public Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> loadSeries(String path,
List<TimeSeriesInfo<SearchHit>> seriesInfo,
ReloadPolicy reloadPolicy,
DoubleProperty progress,
Property<IndexingStatus> indexingStatus) throws DataAdapterException {
Map<TimeSeriesInfo<SearchHit>, TimeSeriesProcessor<SearchHit>> data = new HashMap<>();
try {
ensureIndexed(seriesInfo.stream().map(info -> BuiltInParsingProfile.NONE.getProfileId() + "/" + info.getBinding().getPath()).collect(Collectors.toSet()), ReloadPolicy.UNLOADED);
} catch (Exception e) {
throw new DataAdapterException("Error fetching logs from " + path, e);
}
return data;
}
@Override
public String getSourceName() {
return new StringBuilder("[JFR: Events] ")
.append(jfrFilePath != null ? jfrFilePath.getFileName() : "???")
.toString();
}
}
@@ -0,0 +1,143 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import com.google.gson.Gson;
import eu.binjr.common.javafx.controls.NodeUtils;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.DataAdapter;
import eu.binjr.core.data.adapters.DataAdapterFactory;
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.data.exceptions.NoAdapterFoundException;
import eu.binjr.core.dialogs.DataAdapterDialog;
import eu.binjr.core.dialogs.Dialogs;
import eu.binjr.sources.jfr.adapters.charts.JfrChartsDataAdapter;
import eu.binjr.sources.jfr.adapters.charts.JfrChartsDataAdapterInfo;
import javafx.scene.Node;
import javafx.stage.FileChooser;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZoneId;
import java.util.Collection;
import java.util.List;
/**
* An implementation of the {@link DataAdapterDialog} class that presents a dialog box to retrieve the parameters specific {@link JfrDataAdapterDialog}
*
* @author Frederic Thevenet
*/
public class JfrDataAdapterDialog extends DataAdapterDialog<Path> {
private static final Logger logger = Logger.create(JfrDataAdapterDialog.class);
// private final TextField extensionFiltersTextField;
private final JfrAdapterPreferences prefs;
private static final Gson gson = new Gson();
/**
* Initializes a new instance of the {@link JfrDataAdapterDialog} class.
*
* @param owner the owner window for the dialog
* @throws NoAdapterFoundException if no adapter could be found to get preferences for.
*/
public JfrDataAdapterDialog(Node owner) throws NoAdapterFoundException {
super(owner, Mode.PATH, "mostRecentJfrFiles", false);
this.prefs = (JfrAdapterPreferences) DataAdapterFactory.getInstance().getAdapterPreferences(JfrDataAdapter.class.getName());
setDialogHeaderText("Add a JFR File");
/* extensionFiltersTextField = new TextField(gson.toJson(prefs.fileExtensionFilters.get()));
var label = new Label("Extensions:");
GridPane.setConstraints(label, 0, 1, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
GridPane.setConstraints(extensionFiltersTextField, 1, 1, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
getParamsGridPane().getChildren().addAll(label, extensionFiltersTextField);*/
}
@Override
protected File displayFileChooser(Node owner) {
try {
/* ContextMenu sourceMenu = new ContextMenu();
MenuItem fileMenuItem = new MenuItem("JFR file");
fileMenuItem.setOnAction(eventHandler -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open JFR File");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("JFR files", "*.jfr"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*", "*"));
Dialogs.getInitialDir(getMostRecentList()).ifPresent(fileChooser::setInitialDirectory);
File selectedFile = fileChooser.showOpenDialog(NodeUtils.getStage(owner));
if (selectedFile != null) {
setSourceUri(selectedFile.getPath());
}
});
sourceMenu.getItems().add(fileMenuItem);
MenuItem menuItem = new MenuItem("Zip file");
menuItem.setOnAction(eventHandler -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open Zip Archive");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Zip archive", "*.zip"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*", "*"));
Dialogs.getInitialDir(getMostRecentList()).ifPresent(fileChooser::setInitialDirectory);
File selectedFile = fileChooser.showOpenDialog(NodeUtils.getStage(owner));
if (selectedFile != null) {
setSourceUri(selectedFile.getPath());
}
});
sourceMenu.getItems().add(menuItem);
MenuItem folderMenuItem = new MenuItem("Folder");
folderMenuItem.setOnAction(eventHandler -> {
DirectoryChooser dirChooser = new DirectoryChooser();
dirChooser.setTitle("Open Folder");
Dialogs.getInitialDir(getMostRecentList()).ifPresent(dirChooser::setInitialDirectory);
File selectedFile = dirChooser.showDialog(NodeUtils.getStage(owner));
if (selectedFile != null) {
setSourceUri(selectedFile.getPath());
}
});
sourceMenu.getItems().add(folderMenuItem);
sourceMenu.show(owner, Side.RIGHT, 0, 0);*/
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open JFR File");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("JFR files", "*.jfr"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files", "*.*", "*"));
Dialogs.getInitialDir(getMostRecentList()).ifPresent(fileChooser::setInitialDirectory);
File selectedFile = fileChooser.showOpenDialog(NodeUtils.getStage(owner));
if (selectedFile != null) {
return selectedFile;
}
} catch (Exception e) {
Dialogs.notifyException("Error while displaying file chooser: " + e.getMessage(), e, owner);
}
return null;
}
@Override
protected Collection<DataAdapter> getDataAdapters() throws DataAdapterException {
Path path = Paths.get(getSourceUri());
if (!Files.exists(path)) {
throw new CannotInitializeDataAdapterException("Cannot find " + getSourceUri());
}
if (!path.isAbsolute()) {
throw new CannotInitializeDataAdapterException("The provided path is not valid.");
}
getMostRecentList().push(path);
// prefs.fileExtensionFilters.set(gson.fromJson(extensionFiltersTextField.getText(), String[].class));
return List.of(new JfrDataAdapter(path, ZoneId.of(getSourceTimezone())),
new JfrChartsDataAdapter(path, ZoneId.of(getSourceTimezone())));
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import eu.binjr.core.data.adapters.AdapterMetadata;
import eu.binjr.core.data.adapters.BaseDataAdapterInfo;
import eu.binjr.core.data.adapters.SourceLocality;
import eu.binjr.core.data.adapters.VisualizationType;
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
import eu.binjr.core.preferences.AppEnvironment;
/**
* Defines the metadata associated with JfrDataAdapter.
*
* @author Frederic Thevenet
*/
@AdapterMetadata(
name = "JFR",
description = "JDK Flight Recorder Files Data Adapter (Events view)",
copyright = AppEnvironment.COPYRIGHT_NOTICE,
license = AppEnvironment.LICENSE,
siteUrl = AppEnvironment.HTTP_WWW_BINJR_EU,
adapterClass = JfrDataAdapter.class,
dialogClass = JfrDataAdapterDialog.class,
preferencesClass = JfrAdapterPreferences.class,
sourceLocality = SourceLocality.LOCAL,
apiLevel = AppEnvironment.PLUGIN_API_LEVEL,
visualizationType = VisualizationType.EVENTS
)
public class JfrDataAdapterInfo extends BaseDataAdapterInfo {
/**
* Initialises a new instance of the {@link JfrDataAdapterInfo} class.
*
* @throws CannotInitializeDataAdapterException if the adapter's initialization failed
*/
public JfrDataAdapterInfo() throws CannotInitializeDataAdapterException {
super(JfrDataAdapterInfo.class);
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.DataAdapterFactory;
import eu.binjr.core.data.exceptions.NoAdapterFoundException;
import eu.binjr.core.data.indexes.parser.EventFormat;
import eu.binjr.core.data.indexes.parser.EventParser;
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
import eu.binjr.core.data.indexes.parser.profile.ParsingProfile;
import jdk.jfr.MemoryAddress;
import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor;
import java.nio.charset.Charset;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Objects;
public class JfrEventFormat implements EventFormat<JfrRecordingFilter> {
public static final String EVENT_TYPE_NAME = "eventTypeName";
public static final String JDK_CPULOAD = "jdk.CPULoad";
public static final String JVM_SYSTEM = "jvmSystem";
public static final String JVM_USER = "jvmUser";
public static final String MACHINE_TOTAL = "machineTotal";
private static final Logger logger = Logger.create(JfrEventParser.class);
public static final String CATEGORIES = "categories";
public static final String HAS_NUM_FIELDS = "hasNumFields";
public static final String GCREF_TYPE_FIELD = "type";
public static final String JDK_GCREFERENCE_STATISTICS = "jdk.GCReferenceStatistics";
public static final String GCREF_COUNT_FIELD = "count";
public static final String JDK_TYPES_THREAD_GROUP = "jdk.types.ThreadGroup";
public static final String GCREF_FINAL_REFERENCE = "Final reference";
public static final String GCREF_SOFT_REFERENCE = "Soft reference";
public static final String GCREF_WEAK_REFERENCE = "Weak reference";
public static final String GCREF_PHANTOM_REFERENCE = "Phantom reference";
public static final String GCREF_TOTAL_COUNT = "Total Count";
public static final String JDK_TYPES_STACK_TRACE = "jdk.types.StackTrace";
public static final String JDK_GARBAGE_COLLECTION = "jdk.GarbageCollection";
private final ZoneId zoneId;
private final Charset encoding;
private static final JfrAdapterPreferences adapterPrefs;
static {
try {
adapterPrefs = (JfrAdapterPreferences) DataAdapterFactory.getInstance().getAdapterPreferences(JfrDataAdapter.class.getName());
} catch (NoAdapterFoundException e) {
throw new RuntimeException(e);
}
}
public JfrEventFormat(ZoneId zoneId, Charset encoding) {
this.zoneId = zoneId;
this.encoding = encoding;
}
@Override
public ParsingProfile getProfile() {
return BuiltInParsingProfile.NONE;
}
@Override
public EventParser parse(JfrRecordingFilter source) {
return new JfrEventParser(this, source);
}
@Override
public Charset getEncoding() {
return encoding;
}
@Override
public ZoneId getZoneId() {
return zoneId;
}
public static boolean includeField(ValueDescriptor field) {
return field.getAnnotation(Timestamp.class) == null &&
field.getAnnotation(MemoryAddress.class) ==null &&
Arrays.stream(adapterPrefs.includedEventsDataTypes.get()).anyMatch(s -> Objects.equals(s, field.getTypeName())) &&
Arrays.stream(adapterPrefs.excludedEventsNames.get()).noneMatch(s -> Objects.equals(s, field.getName()));
}
}
@@ -0,0 +1,139 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import eu.binjr.common.logging.Logger;
import eu.binjr.common.logging.Profiler;
import eu.binjr.core.data.indexes.parser.EventParser;
import eu.binjr.core.data.indexes.parser.ParsedEvent;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordingFile;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public class JfrEventParser implements EventParser {
private static final Logger logger = Logger.create(JfrEventParser.class);
// private final BufferedReader reader;
private final AtomicLong sequence;
private final JfrEventFormat format;
private final JfrEventIterator eventIterator;
private final RecordingFile recordingFile;
private final LongProperty progress = new SimpleLongProperty(0);
private final JfrRecordingFilter eventTypeFilter;
JfrEventParser(JfrEventFormat format, JfrRecordingFilter filter) {
this.sequence = new AtomicLong(0);
this.format = format;
try (var p = Profiler.start("Initialize JFR Recording file", logger::perf)) {
this.eventTypeFilter = filter;
this.recordingFile = new RecordingFile(filter.recordingPath());
this.eventIterator = new JfrEventIterator();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() throws IOException {
recordingFile.close();
}
@Override
public LongProperty progressIndicator() {
return progress;
}
@Override
public Iterator<ParsedEvent> iterator() {
return eventIterator;
}
public class JfrEventIterator implements Iterator<ParsedEvent> {
@Override
public ParsedEvent next() {
ParsedEvent event = null;
while (event == null && recordingFile.hasMoreEvents()) {
event = readNextJrfEvent();
}
return event;
}
private ParsedEvent readNextJrfEvent() {
RecordedEvent jfrEvent = null;
try {
jfrEvent = recordingFile.readEvent();
if (!eventTypeFilter.eventTypes().contains(jfrEvent.getEventType().getName())) {
return null;
}
ZonedDateTime timestamp = ZonedDateTime.ofInstant(jfrEvent.getStartTime(), format.getZoneId());
Map<String, Number> numFields = new LinkedHashMap<>();
var categories = jfrEvent.getEventType().getCategoryNames();
String catFacet = categories.size() > 1 ? categories.get(1) : categories.get(0);
switch (jfrEvent.getEventType().getName()) {
case JfrEventFormat.JDK_GCREFERENCE_STATISTICS ->
numFields.put(String.join(" ", jfrEvent.getValue(JfrEventFormat.GCREF_TYPE_FIELD),
JfrEventFormat.GCREF_TOTAL_COUNT), jfrEvent.getValue(JfrEventFormat.GCREF_COUNT_FIELD));
default -> addField("", jfrEvent, numFields);
}
return new ParsedEvent(
sequence.incrementAndGet(),
timestamp,
jfrEvent.toString(),
Map.of(JfrEventFormat.CATEGORIES, catFacet, JfrEventFormat.EVENT_TYPE_NAME, jfrEvent.getEventType().getName()),
numFields);
} catch (Exception e) {
logger.error("Error parsing JFR event [" + (jfrEvent == null ? "unknown" : jfrEvent.getEventType().getName()) + "]: " + e.getMessage());
logger.debug("Stack trace", e);
return null;
}
}
private void addField(String parentLabel, RecordedObject jfrEvent, Map<String, Number> numFields) {
for (var field : jfrEvent.getFields()) {
if (JfrEventFormat.includeField(field)) {
numFields.put(String.join(" ", parentLabel, field.getLabel()).trim(), jfrEvent.getValue(field.getName()));
}
if (!field.getFields().isEmpty() && jfrEvent.getValue(field.getName()) instanceof RecordedObject nestedEvent) {
addField(field.getLabel(), nestedEvent, numFields);
}
}
}
@Override
public boolean hasNext() {
return recordingFile.hasMoreEvents();
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters;
import java.nio.file.Path;
import java.util.Set;
public record JfrRecordingFilter(Path recordingPath, Set<String> eventTypes) {
}
@@ -0,0 +1,285 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters.charts;
import eu.binjr.common.javafx.controls.TimeRange;
import eu.binjr.common.javafx.controls.TreeViewUtils;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.DataAdapter;
import eu.binjr.core.data.adapters.ReloadPolicy;
import eu.binjr.core.data.adapters.SourceBinding;
import eu.binjr.core.data.adapters.TimeSeriesBinding;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.data.indexes.parser.profile.BuiltInParsingProfile;
import eu.binjr.core.data.timeseries.DoubleTimeSeriesProcessor;
import eu.binjr.core.data.timeseries.TimeSeriesProcessor;
import eu.binjr.core.data.workspace.ChartType;
import eu.binjr.core.data.workspace.TimeSeriesInfo;
import eu.binjr.core.data.workspace.UnitPrefixes;
import eu.binjr.sources.jfr.adapters.BaseJfrDataAdapter;
import eu.binjr.sources.jfr.adapters.JfrEventFormat;
import javafx.scene.paint.Color;
import jdk.jfr.*;
import jdk.jfr.consumer.RecordingFile;
import org.eclipse.fx.ui.controls.tree.FilterableTreeItem;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* A {@link DataAdapter} implementation to retrieve data from a JDK Flight Recorder file.
*
* @author Frederic Thevenet
*/
public class JfrChartsDataAdapter extends BaseJfrDataAdapter<Double> {
private static final Logger logger = Logger.create(JfrChartsDataAdapter.class);
public static final int RECURSE_MAX_DEPTH = 10;
/**
* Initializes a new instance of the {@link JfrChartsDataAdapter} class with a set of default values.
*
* @throws DataAdapterException if the {@link DataAdapter} could not be initializes.
*/
public JfrChartsDataAdapter() throws DataAdapterException {
super(Path.of(""), ZoneId.systemDefault());
}
/**
* Initializes a new instance of the {@link JfrChartsDataAdapter} class with the provided parameters.
*
* @param jfrPath the path to the JFR file.
* @param zoneId the time zone to used.
* @throws DataAdapterException if the {@link DataAdapter} could not be initialized.
*/
public JfrChartsDataAdapter(Path jfrPath,
ZoneId zoneId)
throws DataAdapterException {
super(jfrPath, zoneId);
}
@Override
public TimeRange getInitialTimeRange(String path, List<TimeSeriesInfo<Double>> seriesInfo) throws DataAdapterException {
try {
ensureIndexed(seriesInfo.stream().map(info -> info.getBinding().getPath()).collect(Collectors.toSet()), ReloadPolicy.UNLOADED);
return index.getTimeRangeBoundaries(seriesInfo.stream().map(ts -> ts.getBinding().getPath()).toList(), getTimeZoneId());
} catch (IOException e) {
throw new DataAdapterException("Error retrieving initial time range", e);
}
}
@Override
public FilterableTreeItem<SourceBinding> getBindingTree() throws DataAdapterException {
String rootPath = BuiltInParsingProfile.NONE.getProfileId() + "/" + jfrFilePath.toString() + "|";
FilterableTreeItem<SourceBinding> tree = new FilterableTreeItem<>(new TimeSeriesBinding.Builder()
.withLabel(getSourceName())
.withPath(rootPath)
.withAdapter(this)
.build());
try (var recordingFile = new RecordingFile(jfrFilePath)) {
for (EventType eventType : recordingFile.readEventTypes()) {
var branch = tree;
for (var cat : eventType.getCategoryNames()) {
var pos = TreeViewUtils.findFirstInTree(tree, item -> item.getValue().getLabel().equals(cat));
if (pos.isEmpty()) {
var node = new FilterableTreeItem<>((SourceBinding) new TimeSeriesBinding.Builder()
.withLabel(cat)
.withParent(branch.getValue())
.withPath(branch.getValue().getPath() + "/" + cat)
.withAdapter(this)
.build());
branch.getInternalChildren().add(node);
branch = node;
} else {
branch = (FilterableTreeItem<SourceBinding>) pos.get();
}
}
var leaf = new FilterableTreeItem<>((SourceBinding) new TimeSeriesBinding.Builder()
.withLabel(eventType.getLabel())
.withLegend(eventType.getLabel())
.withPath(rootPath + eventType.getName())
.withParent(branch.getValue())
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build());
branch.getInternalChildren().add(leaf);
switch (eventType.getName()) {
case JfrEventFormat.JDK_GCREFERENCE_STATISTICS -> {
addField(JfrEventFormat.GCREF_FINAL_REFERENCE, eventType.getField(JfrEventFormat.GCREF_COUNT_FIELD), leaf, 1);
addField(JfrEventFormat.GCREF_SOFT_REFERENCE, eventType.getField(JfrEventFormat.GCREF_COUNT_FIELD), leaf, 1);
addField(JfrEventFormat.GCREF_WEAK_REFERENCE, eventType.getField(JfrEventFormat.GCREF_COUNT_FIELD), leaf, 1);
addField(JfrEventFormat.GCREF_PHANTOM_REFERENCE, eventType.getField(JfrEventFormat.GCREF_COUNT_FIELD), leaf, 1);
}
case JfrEventFormat.JDK_CPULOAD ->{
var jvmBranch =new FilterableTreeItem<>((SourceBinding) new TimeSeriesBinding.Builder()
.withLabel("JVM")
.withPath(leaf.getValue().getPath())
.withParent(leaf.getValue())
.withUnitName("%")
.withPrefix(UnitPrefixes.PERCENTAGE)
.withGraphType(ChartType.STACKED)
.withAdapter(this)
.build());
leaf.getInternalChildren().add(jvmBranch);
jvmBranch.getInternalChildren().add(new FilterableTreeItem<>(new TimeSeriesBinding.Builder()
.withLabel(eventType.getField(JfrEventFormat.JVM_SYSTEM).getLabel())
.withPath(jvmBranch.getValue().getPath())
.withParent(jvmBranch.getValue())
.withUnitName("%")
.withPrefix(UnitPrefixes.PERCENTAGE)
.withColor(Color.RED)
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build()));
jvmBranch.getInternalChildren().add(new FilterableTreeItem<>(new TimeSeriesBinding.Builder()
.withLabel(eventType.getField(JfrEventFormat.JVM_USER).getLabel())
.withPath(jvmBranch.getValue().getPath())
.withParent(jvmBranch.getValue())
.withUnitName("%")
.withPrefix(UnitPrefixes.PERCENTAGE)
.withColor(Color.DARKGREEN)
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build()));
var machineBranch =new FilterableTreeItem<>((SourceBinding) new TimeSeriesBinding.Builder()
.withLabel("Machine")
.withPath(leaf.getValue().getPath())
.withParent(leaf.getValue())
.withUnitName("%")
.withPrefix(UnitPrefixes.PERCENTAGE)
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build());
leaf.getInternalChildren().add(machineBranch);
machineBranch.getInternalChildren().add(new FilterableTreeItem<>(new TimeSeriesBinding.Builder()
.withLabel(eventType.getField(JfrEventFormat.MACHINE_TOTAL).getLabel())
.withPath(machineBranch.getValue().getPath())
.withParent(machineBranch.getValue())
.withUnitName("%")
.withPrefix(UnitPrefixes.PERCENTAGE)
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build()));
}
default -> {
for (var field : eventType.getFields()) {
addField("", field, leaf, 1);
}
}
}
}
} catch (Exception e) {
throw new DataAdapterException("Error while attempting to read JFR recording: " + e.getMessage(), e);
}
return tree;
}
private void addField(String parentName, ValueDescriptor field, FilterableTreeItem<SourceBinding> leaf, int depth) {
if (JfrEventFormat.includeField(field)) {
var unit = extractKnownUnit(field);
leaf.getInternalChildren().add(new FilterableTreeItem<>(new TimeSeriesBinding.Builder()
.withLabel(String.join(" ", parentName, field.getLabel()).trim())
.withPath(leaf.getValue().getPath())
.withParent(leaf.getValue())
.withUnitName(unit.name())
.withPrefix(unit.prefix())
.withGraphType(ChartType.LINE)
.withAdapter(this)
.build()));
}
for (var nestedField : field.getFields()) {
if (!field.getTypeName().equals(JfrEventFormat.JDK_TYPES_THREAD_GROUP) &&
!field.getTypeName().equals(JfrEventFormat.JDK_TYPES_STACK_TRACE) &&
depth <= RECURSE_MAX_DEPTH) {
addField(field.getLabel(), nestedField, leaf, depth + 1);
}
}
}
@Override
public Map<TimeSeriesInfo<Double>, TimeSeriesProcessor<Double>> fetchData(String path,
Instant begin,
Instant end,
List<TimeSeriesInfo<Double>> seriesInfo,
boolean bypassCache) throws DataAdapterException {
try {
ensureIndexed(seriesInfo.stream().map(info -> info.getBinding().getPath()).collect(Collectors.toSet()), ReloadPolicy.UNLOADED);
Map<TimeSeriesInfo<Double>, TimeSeriesProcessor<Double>> series = new HashMap<>();
for (TimeSeriesInfo<Double> info : seriesInfo) {
series.put(info, new DoubleTimeSeriesProcessor());
}
var nbHits = index.search(
begin.toEpochMilli(),
end.toEpochMilli(),
series,
getTimeZoneId(),
bypassCache);
logger.debug(() -> "Retrieved " + nbHits + " hits");
return series;
} catch (Exception e) {
throw new DataAdapterException("Error fetching data from " + path, e);
}
}
@Override
public String getSourceName() {
return new StringBuilder("[JFR: Charts] ")
.append(jfrFilePath != null ? jfrFilePath.getFileName() : "???")
.toString();
}
private Unit extractKnownUnit(ValueDescriptor field) {
var timespan = field.getAnnotation(Timespan.class);
if (timespan != null) {
return new Unit(timespan.value(), UnitPrefixes.METRIC);
}
var frequency = field.getAnnotation(Frequency.class);
if (frequency != null) {
return new Unit("Hertz", UnitPrefixes.METRIC);
}
var dataAmount = field.getAnnotation(DataAmount.class);
if (dataAmount != null) {
return new Unit(dataAmount.value(), UnitPrefixes.BINARY);
}
var percentage = field.getAnnotation(Percentage.class);
if (percentage != null) {
return new Unit("%", UnitPrefixes.NONE);
}
return new Unit("-", UnitPrefixes.NONE);
}
private record Unit(String name, UnitPrefixes prefix) {
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jfr.adapters.charts;
import eu.binjr.core.data.adapters.AdapterMetadata;
import eu.binjr.core.data.adapters.BaseDataAdapterInfo;
import eu.binjr.core.data.adapters.SourceLocality;
import eu.binjr.core.data.adapters.VisualizationType;
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
import eu.binjr.core.preferences.AppEnvironment;
import eu.binjr.sources.jfr.adapters.JfrDataAdapterDialog;
/**
* Defines the metadata associated with JfrDataAdapter.
*
* @author Frederic Thevenet
*/
@AdapterMetadata(
name = "JFR - Charts)",
description = "JDK Flight Recorder Charts Data Adapter",
copyright = AppEnvironment.COPYRIGHT_NOTICE,
license = AppEnvironment.LICENSE,
siteUrl = AppEnvironment.HTTP_WWW_BINJR_EU,
adapterClass = JfrChartsDataAdapter.class,
dialogClass = JfrDataAdapterDialog.class,
sourceLocality = SourceLocality.LOCAL,
apiLevel = AppEnvironment.PLUGIN_API_LEVEL,
visualizationType = VisualizationType.CHARTS
)
public class JfrChartsDataAdapterInfo extends BaseDataAdapterInfo {
/**
* Initialises a new instance of the {@link JfrChartsDataAdapterInfo} class.
*
* @throws CannotInitializeDataAdapterException if the adapter's initialization failed
*/
public JfrChartsDataAdapterInfo() throws CannotInitializeDataAdapterException {
super(JfrChartsDataAdapterInfo.class);
}
@Override
public boolean isEnabled() {
return false;
}
}
@@ -0,0 +1,20 @@
#
# Copyright 2023 Frederic Thevenet
#
# 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.
#
# JFR Data adapter service implementation
eu.binjr.sources.jfr.adapters.JfrDataAdapterInfo
eu.binjr.sources.jfr.adapters.charts.JfrChartsDataAdapterInfo