Merged in development (pull request #9)
Support for configuring extensions.
This commit is contained in:
commit
39d3127437
|
@ -19,6 +19,7 @@ subprojects {
|
|||
|
||||
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
|
||||
implementation "org.slf4j:slf4j-simple:${slf4jVersion}"
|
||||
implementation 'org.tomlj:tomlj:1.0.0'
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -37,11 +37,12 @@ def projectName = "sysmon-client"
|
|||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass.set('sysmon.client.Application')
|
||||
applicationDefaultJvmArgs = [ "-server", "-XX:+UseG1GC", "-Xmx32m" ]
|
||||
applicationDefaultJvmArgs = [ "-server", "-Xms16m", "-Xmx32m", "-XX:+UseG1GC" ]
|
||||
}
|
||||
|
||||
run {
|
||||
systemProperty 'pf4j.pluginsDir', '../plugins/output/'
|
||||
systemProperty 'sysmon.cfgFile', 'doc/sysmon-client.toml'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
|
|
@ -4,7 +4,7 @@ Works on IBM Power VIO (Virtual IO) servers, as well as regular IBM Power AIX in
|
|||
|
||||
## Installation
|
||||
|
||||
We require Java 8, which should already be installed.
|
||||
We require Java 8, which should already be installed on AIX, or is available to install.
|
||||
The RPM packages are *"noarch"* Java bytecode, so we can use the **--ignoreos** option to install:
|
||||
|
||||
```shell
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
Description=Sysmon Client Service
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
TimeoutSec=20
|
||||
Restart=on-failure
|
||||
ExecStart=/opt/sysmon/client/bin/client -s http://10.20.30.40:9925/metrics
|
||||
|
||||
[Install]
|
||||
|
|
10
client/doc/sysmon-client.toml
Normal file
10
client/doc/sysmon-client.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Configuration for sysmon-client
|
||||
|
||||
[extension.base_disk]
|
||||
enabled = false
|
||||
|
||||
[extension.base_process]
|
||||
enabled = true
|
||||
include = [ "java", "influxd", "grafana-server" ]
|
||||
|
||||
|
|
@ -4,10 +4,9 @@
|
|||
package sysmon.client;
|
||||
|
||||
import org.apache.camel.main.Main;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
|
@ -17,8 +16,6 @@ import java.util.concurrent.Callable;
|
|||
@CommandLine.Command(name = "sysmon-client", mixinStandardHelpOptions = true)
|
||||
public class Application implements Callable<Integer> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Application.class);
|
||||
|
||||
@CommandLine.Option(names = { "-s", "--server-url" }, description = "Server URL (default: ${DEFAULT-VALUE}).", defaultValue = "http://127.0.0.1:9925/metrics", paramLabel = "<url>")
|
||||
private URL serverUrl;
|
||||
|
||||
|
@ -28,6 +25,9 @@ public class Application implements Callable<Integer> {
|
|||
@CommandLine.Option(names = { "-p", "--plugin-dir" }, description = "Plugin jar path (default: ${DEFAULT-VALUE}).", paramLabel = "<path>", defaultValue = "/opt/sysmon/plugins")
|
||||
private String pluginPath;
|
||||
|
||||
@CommandLine.Option(names = { "-c", "--conf" }, description = "Configuration file [default: '/etc/sysmon-client.toml'].", paramLabel = "<file>", defaultValue = "/etc/sysmon-client.toml")
|
||||
private File configurationFile;
|
||||
|
||||
public static void main(String... args) {
|
||||
int exitCode = new CommandLine(new Application()).execute(args);
|
||||
System.exit(exitCode);
|
||||
|
@ -37,6 +37,8 @@ public class Application implements Callable<Integer> {
|
|||
@Override
|
||||
public Integer call() throws IOException {
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
|
||||
if(hostname == null || hostname.isEmpty()) {
|
||||
try {
|
||||
hostname = InetAddress.getLocalHost().getHostName();
|
||||
|
@ -51,10 +53,26 @@ public class Application implements Callable<Integer> {
|
|||
pluginPath = pf4jPluginsDir;
|
||||
}
|
||||
|
||||
String sysmonCfgFile = System.getProperty("sysmon.cfgFile");
|
||||
if(sysmonCfgFile != null) {
|
||||
configurationFile = new File(sysmonCfgFile);
|
||||
}
|
||||
|
||||
|
||||
if(configurationFile.exists()) {
|
||||
try {
|
||||
configuration.parse(configurationFile.toPath());
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Main main = new Main();
|
||||
main.bind("pluginPath", pluginPath);
|
||||
main.bind("myServerUrl", serverUrl.toString());
|
||||
main.bind("myHostname", hostname);
|
||||
main.bind("configuration", configuration);
|
||||
main.configure().addRoutesBuilder(ClientRouteBuilder.class);
|
||||
|
||||
// now keep the application running until the JVM is terminated (ctrl + c or sigterm)
|
||||
|
@ -62,6 +80,7 @@ public class Application implements Callable<Integer> {
|
|||
main.run();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -24,6 +24,7 @@ public class ClientRouteBuilder extends RouteBuilder {
|
|||
public void configure() {
|
||||
|
||||
Registry registry = getContext().getRegistry();
|
||||
Configuration configuration = (Configuration) registry.lookupByName("configuration");
|
||||
|
||||
Path[] pluginpaths = { Paths.get(registry.lookupByNameAndType("pluginPath", String.class)) };
|
||||
PluginManager pluginManager = new JarPluginManager(pluginpaths);
|
||||
|
@ -34,9 +35,18 @@ public class ClientRouteBuilder extends RouteBuilder {
|
|||
List<MetricExtension> metricExtensions = pluginManager.getExtensions(MetricExtension.class);
|
||||
for (MetricExtension ext : metricExtensions) {
|
||||
|
||||
if(ext.isSupported()) {
|
||||
final String name = ext.getName();
|
||||
final String provides = ext.getProvides();
|
||||
|
||||
String provides = ext.getProvides();
|
||||
// Load configuration if available
|
||||
if(configuration.isForExtension(name)) {
|
||||
log.info(">>> Loading configuring for extension: " + ext.getDescription());
|
||||
ext.setConfiguration(configuration.getForExtension(name));
|
||||
}
|
||||
|
||||
if(ext.isSupported() && ext.isEnabled()) {
|
||||
|
||||
// Check that another extension has not already been loaded - TODO: Is this required ?
|
||||
if(providers.contains(provides)) {
|
||||
log.warn("Skipping extension (already provided): " + ext.getName());
|
||||
continue;
|
||||
|
@ -46,7 +56,7 @@ public class ClientRouteBuilder extends RouteBuilder {
|
|||
providers.add(provides);
|
||||
|
||||
|
||||
// TODO: Make timer thread configurable
|
||||
// TODO: Make timer thread configurable ?
|
||||
|
||||
// Setup Camel route for this extension
|
||||
// a unique timer name gives the timer it's own thread, otherwise it's a shared thread for other timers with same name.
|
||||
|
@ -62,7 +72,7 @@ public class ClientRouteBuilder extends RouteBuilder {
|
|||
.otherwise()
|
||||
.to("seda:metrics?discardWhenFull=true");
|
||||
} else {
|
||||
log.info(">>> Skipping extension (not supported here): " + ext.getDescription());
|
||||
log.info(">>> Skipping extension (not supported or disabled): " + ext.getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
64
client/src/main/java/sysmon/client/Configuration.java
Normal file
64
client/src/main/java/sysmon/client/Configuration.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package sysmon.client;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tomlj.Toml;
|
||||
import org.tomlj.TomlParseResult;
|
||||
import org.tomlj.TomlTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Configuration {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(Configuration.class);
|
||||
|
||||
private TomlParseResult result;
|
||||
|
||||
void parse(Path configurationFile) throws IOException {
|
||||
log.info("Parsing configuration file: " + configurationFile);
|
||||
result = Toml.parse(configurationFile);
|
||||
result.errors().forEach(error -> log.error(error.toString()));
|
||||
}
|
||||
|
||||
|
||||
boolean isForExtension(String extName) {
|
||||
if(result == null) {
|
||||
return false;
|
||||
}
|
||||
String key = String.format("extension.%s", extName);
|
||||
return result.contains(key);
|
||||
}
|
||||
|
||||
|
||||
Map<String, Object> getForExtension(String extName) {
|
||||
|
||||
if(result == null) {
|
||||
log.debug("No configuration file loaded ...");
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
String key = String.format("extension.%s", extName);
|
||||
TomlTable table = result.getTableOrEmpty(key);
|
||||
table.keySet().forEach( k -> {
|
||||
if(table.isString(k)) {
|
||||
map.put(k, table.getString(k));
|
||||
} else if(table.isBoolean(k)) {
|
||||
map.put(k, table.getBoolean(k));
|
||||
} else if(table.isDouble(k)) {
|
||||
map.put(k, table.getDouble(k));
|
||||
} else if(table.isArray(k)) {
|
||||
map.put(k, Objects.requireNonNull(table.getArray(k)).toList());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,17 +10,11 @@
|
|||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "gauge",
|
||||
"name": "Gauge",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "8.0.6"
|
||||
"version": "8.1.2"
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
|
@ -56,6 +50,12 @@
|
|||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
|
@ -65,7 +65,7 @@
|
|||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1631013505736,
|
||||
"iteration": 1631256755587,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
|
@ -120,7 +120,7 @@
|
|||
"showHeader": true,
|
||||
"sortBy": []
|
||||
},
|
||||
"pluginVersion": "8.0.6",
|
||||
"pluginVersion": "8.1.2",
|
||||
"targets": [
|
||||
{
|
||||
"groupBy": [
|
||||
|
@ -286,7 +286,7 @@
|
|||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.6",
|
||||
"pluginVersion": "8.1.2",
|
||||
"targets": [
|
||||
{
|
||||
"groupBy": [
|
||||
|
@ -377,7 +377,7 @@
|
|||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
|
@ -426,6 +426,10 @@
|
|||
},
|
||||
"id": 26,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
|
@ -433,11 +437,10 @@
|
|||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": true,
|
||||
"text": {}
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.6",
|
||||
"pluginVersion": "8.1.2",
|
||||
"targets": [
|
||||
{
|
||||
"groupBy": [
|
||||
|
@ -471,10 +474,6 @@
|
|||
"params": [],
|
||||
"type": "sum"
|
||||
},
|
||||
{
|
||||
"params": [],
|
||||
"type": "non_negative_difference"
|
||||
},
|
||||
{
|
||||
"params": [
|
||||
"read"
|
||||
|
@ -493,10 +492,6 @@
|
|||
"params": [],
|
||||
"type": "sum"
|
||||
},
|
||||
{
|
||||
"params": [],
|
||||
"type": "non_negative_difference"
|
||||
},
|
||||
{
|
||||
"params": [
|
||||
"write"
|
||||
|
@ -529,7 +524,7 @@
|
|||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Disk Metrics",
|
||||
"type": "gauge"
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_INFLUXDB-SYSMON}",
|
||||
|
@ -890,7 +885,7 @@
|
|||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"sort": 5,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
|
@ -913,14 +908,14 @@
|
|||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"sort": 5,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": "",
|
||||
"allValue": null,
|
||||
"current": {},
|
||||
"datasource": "${DS_INFLUXDB-SYSMON}",
|
||||
"definition": "SHOW TAG VALUES FROM \"base_process\" WITH KEY = \"pid\" WHERE hostname =~ /$hostname/AND \"name\" =~ /$process/ AND time > now() - 60m",
|
||||
"definition": "SELECT DISTINCT(\"pid\") FROM (SELECT * FROM \"base_process\" WHERE time > now() - 60m AND \"hostname\" =~ /$hostname/ AND \"name\" =~ /$process/)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
|
@ -929,17 +924,17 @@
|
|||
"multi": false,
|
||||
"name": "pid",
|
||||
"options": [],
|
||||
"query": "SHOW TAG VALUES FROM \"base_process\" WITH KEY = \"pid\" WHERE hostname =~ /$hostname/AND \"name\" =~ /$process/ AND time > now() - 60m",
|
||||
"refresh": 1,
|
||||
"query": "SELECT DISTINCT(\"pid\") FROM (SELECT * FROM \"base_process\" WHERE time > now() - 60m AND \"hostname\" =~ /$hostname/ AND \"name\" =~ /$process/)",
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"sort": 3,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-3h",
|
||||
"from": "now-6h",
|
||||
"to": "now-30s"
|
||||
},
|
||||
"timepicker": {
|
||||
|
@ -958,5 +953,5 @@
|
|||
"timezone": "",
|
||||
"title": "Sysmon - Process Explorer",
|
||||
"uid": "Vjut5mS7k",
|
||||
"version": 11
|
||||
"version": 15
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
version=0.0.9
|
||||
version=0.0.10
|
||||
pf4jVersion=3.6.0
|
||||
slf4jVersion=1.7.32
|
||||
camelVersion=3.11.1
|
||||
|
|
|
@ -19,6 +19,20 @@ public class AixNetstatExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(AixNetstatExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "aix_network_netstat";
|
||||
private final String provides = "network_netstat";
|
||||
private final String description = "AIX Netstat Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
|
@ -37,17 +51,24 @@ public class AixNetstatExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aix_network_netstat";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "network_netstat";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "AIX Netstat Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +84,7 @@ public class AixNetstatExtension implements MetricExtension {
|
|||
}
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AixNetstatParser {
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import sysmon.shared.PluginHelper;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Extension
|
||||
|
@ -19,6 +18,20 @@ public class AixProcessorExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(AixProcessorExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "aix_processor";
|
||||
private final String provides = "lpar_processor";
|
||||
private final String description = "AIX Processor Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
|
@ -38,17 +51,24 @@ public class AixProcessorExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aix_processor";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "processor_lpar";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "AIX Processor Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +83,7 @@ public class AixProcessorExtension implements MetricExtension {
|
|||
fieldsMap = processorStat.getFields();
|
||||
}
|
||||
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -86,7 +85,7 @@ public class AixProcessorStat {
|
|||
}
|
||||
|
||||
//String lparstat = lines.get(lines.size() -1);
|
||||
String[] splitStr = lastLine.trim().split("\\s+");
|
||||
String[] splitStr = Objects.requireNonNull(lastLine).trim().split("\\s+");
|
||||
if(type.equalsIgnoreCase("shared") && splitStr.length < 9 ||
|
||||
type.equalsIgnoreCase("dedicated") && splitStr.length < 8) {
|
||||
throw new UnsupportedOperationException("lparstat string error: " + lastLine);
|
||||
|
|
|
@ -36,3 +36,30 @@ Metrics reported are:
|
|||
- **writes** - The total number of bytes written.
|
||||
- **iotime** - Time spent on IO in milliseconds.
|
||||
- **queue** - Lenght of IO queue.
|
||||
|
||||
## Process Extension
|
||||
|
||||
Reports metrics on one or more running processes.
|
||||
|
||||
- **mem_rss** - Resident set memory in bytes.
|
||||
- **mem_vsz** - Virtual memory in bytes.
|
||||
- **kernel_time** - Time spent (in milliseconds) in kernel space.
|
||||
- **user_time** - Time used (in milliseconds) in user space.
|
||||
- **read_bytes** - Bytes read by process.
|
||||
- **write_bytes** - Bytes written by process.
|
||||
- **files** - Files currently open by process.
|
||||
- **threads** - Running threads.
|
||||
- **user** - User running the process.
|
||||
- **group** - Group running the process
|
||||
- **prio** - Process priority.
|
||||
|
||||
|
||||
### Configuration
|
||||
|
||||
The **include** option let's you specify what processes to report for.
|
||||
|
||||
```toml
|
||||
[extension.base_process]
|
||||
enabled = true # true or false
|
||||
include = [ "java", "influxd", "grafana-server" ]
|
||||
```
|
|
@ -3,7 +3,6 @@ package sysmon.plugins.os_base;
|
|||
import org.pf4j.Extension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HWDiskStore;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import sysmon.shared.Measurement;
|
||||
|
@ -19,9 +18,22 @@ public class BaseDiskExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseDiskExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "base_disk";
|
||||
private final String provides = "disk";
|
||||
private final String description = "Base Disk Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
private HardwareAbstractionLayer hardwareAbstractionLayer;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
hardwareAbstractionLayer = BasePlugin.getHardwareAbstractionLayer();
|
||||
|
@ -30,19 +42,25 @@ public class BaseDiskExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "base_disk";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "disk";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Base Disk Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricResult getMetrics() {
|
||||
|
@ -58,7 +76,7 @@ public class BaseDiskExtension implements MetricExtension {
|
|||
List<HWDiskStore> diskStores = hardwareAbstractionLayer.getDiskStores();
|
||||
for(HWDiskStore store : diskStores) {
|
||||
String name = store.getName();
|
||||
if (name.matches("hdisk[0-9]+") || name.matches("/dev/x?[sv]d[a-z]{1}") || name.matches("/dev/nvme[0-9]n[0-9]")) {
|
||||
if (name.matches("hdisk[0-9]+") || name.matches("/dev/x?[sv]d[a-z]") || name.matches("/dev/nvme[0-9]n[0-9]")) {
|
||||
log.debug("Using device: " + name);
|
||||
writeBytes += store.getWriteBytes();
|
||||
readBytes += store.getReadBytes();
|
||||
|
@ -73,7 +91,7 @@ public class BaseDiskExtension implements MetricExtension {
|
|||
fieldsMap.put("queue", queueLength);
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,6 @@ package sysmon.plugins.os_base;
|
|||
import org.pf4j.Extension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import sysmon.shared.Measurement;
|
||||
import sysmon.shared.MetricExtension;
|
||||
|
@ -17,8 +16,22 @@ public class BaseMemoryExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseMemoryExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "base_memory";
|
||||
private final String provides = "memory";
|
||||
private final String description = "Base Memory Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
private HardwareAbstractionLayer hardwareAbstractionLayer;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
hardwareAbstractionLayer = BasePlugin.getHardwareAbstractionLayer();
|
||||
|
@ -27,17 +40,24 @@ public class BaseMemoryExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "base_memory";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "memory";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Base Memory Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,7 +77,7 @@ public class BaseMemoryExtension implements MetricExtension {
|
|||
fieldsMap.put("virtual", hardwareAbstractionLayer.getMemory().getVirtualMemory().getVirtualInUse());
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package sysmon.plugins.os_base;
|
|||
import org.pf4j.Extension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import sysmon.shared.Measurement;
|
||||
|
@ -19,8 +18,22 @@ public class BaseNetworkExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseNetworkExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "base_network";
|
||||
private final String provides = "network";
|
||||
private final String description = "Base Network Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
private HardwareAbstractionLayer hardwareAbstractionLayer;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
hardwareAbstractionLayer = BasePlugin.getHardwareAbstractionLayer();
|
||||
|
@ -29,17 +42,24 @@ public class BaseNetworkExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "base_network";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "network";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Base Network Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,7 +95,7 @@ public class BaseNetworkExtension implements MetricExtension {
|
|||
fieldsMap.put("txErrors", txErrs);
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,29 +9,31 @@ import sysmon.shared.Measurement;
|
|||
import sysmon.shared.MetricExtension;
|
||||
import sysmon.shared.MetricResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@Extension
|
||||
public class BaseProcessExtension implements MetricExtension {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseProcessorExtension.class);
|
||||
|
||||
// TODO: configurable include-list and/or exclude-list of process names
|
||||
private final List<String> includeList = new ArrayList<String>() {{
|
||||
// Extension details
|
||||
private final String name = "base_process";
|
||||
private final String provides = "process";
|
||||
private final String description = "Base Process Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
private List<?> includeList = new ArrayList<Object>() {{
|
||||
add("java");
|
||||
add("nginx");
|
||||
add("influxd");
|
||||
add("dockerd");
|
||||
add("containerd");
|
||||
add("mysqld");
|
||||
add("postgres");
|
||||
add("grafana-server");
|
||||
}};
|
||||
|
||||
private SystemInfo systemInfo;
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
systemInfo = BasePlugin.getSystemInfo();
|
||||
|
@ -40,19 +42,29 @@ public class BaseProcessExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "base_process";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "process";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Base Process Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if(map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
if(map.containsKey("include")) {
|
||||
includeList = (List<?>) map.get("include");
|
||||
}
|
||||
log.info(includeList.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricResult getMetrics() {
|
||||
|
@ -71,7 +83,7 @@ public class BaseProcessExtension implements MetricExtension {
|
|||
if(!includeList.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
log.debug("pid: " + p.getProcessID() + ", name: " + name + ", virt: " + p.getVirtualSize() + " rss: " + p.getResidentSetSize() + " cmd: " + p.getCommandLine());
|
||||
log.debug("pid: " + p.getProcessID() + ", name: " + name + ", virt: " + p.getVirtualSize() + " rss: " + p.getResidentSetSize());
|
||||
|
||||
HashMap<String, String> tagsMap = new HashMap<>();
|
||||
HashMap<String, Object> fieldsMap = new HashMap<>();
|
||||
|
@ -95,7 +107,7 @@ public class BaseProcessExtension implements MetricExtension {
|
|||
}
|
||||
|
||||
//log.info("Size of measurements: " + measurementList.size());
|
||||
return new MetricResult(getName(), measurementList);
|
||||
return new MetricResult(name, measurementList);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,20 +3,13 @@ package sysmon.plugins.os_base;
|
|||
import org.pf4j.Extension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import sysmon.shared.Measurement;
|
||||
import sysmon.shared.MetricExtension;
|
||||
import sysmon.shared.MetricResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Extension
|
||||
|
@ -24,9 +17,22 @@ public class BaseProcessorExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(BaseProcessorExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "base_processor";
|
||||
private final String provides = "processor";
|
||||
private final String description = "Base Processor Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
private HardwareAbstractionLayer hardwareAbstractionLayer;
|
||||
private long[] oldTicks;
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
hardwareAbstractionLayer = BasePlugin.getHardwareAbstractionLayer();
|
||||
|
@ -35,19 +41,25 @@ public class BaseProcessorExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "base_processor";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "processor";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Base Processor Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricResult getMetrics() {
|
||||
|
@ -86,7 +98,7 @@ public class BaseProcessorExtension implements MetricExtension {
|
|||
|
||||
oldTicks = ticks;
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import sysmon.shared.MetricExtension;
|
|||
import sysmon.shared.MetricResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
// Disable for now...
|
||||
//@Extension
|
||||
|
@ -15,9 +16,23 @@ public class TestExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "ibmi_test";
|
||||
private final String provides = "ibmi_test";
|
||||
private final String description = "IBM i Test Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
private AS400 as400;
|
||||
private SystemStatus systemStatus;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
|
@ -47,17 +62,24 @@ public class TestExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ibmi-test";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "test";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "IBM i Test Extension";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,20 @@ public class LinuxNetstatExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(LinuxNetstatExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "linux_network_netstat";
|
||||
private final String provides = "network_netstat";
|
||||
private final String description = "Linux Netstat Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
|
@ -37,17 +51,24 @@ public class LinuxNetstatExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "linux_network_netstat";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "network_netstat";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Linux Netstat Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +84,7 @@ public class LinuxNetstatExtension implements MetricExtension {
|
|||
}
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LinuxNetstatParser {
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
|
@ -17,6 +17,20 @@ public class LinuxSockstatExtension implements MetricExtension {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(LinuxSockstatExtension.class);
|
||||
|
||||
// Extension details
|
||||
private final String name = "linux_network_sockets";
|
||||
private final String provides = "network_sockets";
|
||||
private final String description = "Linux Network Socket Metrics";
|
||||
|
||||
// Configuration / Options
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
|
@ -30,17 +44,24 @@ public class LinuxSockstatExtension implements MetricExtension {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "linux_network_sockets";
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProvides() {
|
||||
return "network_sockets";
|
||||
return provides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Linux Network Socket Metrics";
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Map<String, Object> map) {
|
||||
if (map.containsKey("enabled")) {
|
||||
enabled = (boolean) map.get("enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,7 +73,7 @@ public class LinuxSockstatExtension implements MetricExtension {
|
|||
HashMap<String, Object> fieldsMap = sockStat.getFields();
|
||||
|
||||
log.debug(fieldsMap.toString());
|
||||
return new MetricResult(getName(), new Measurement(tagsMap, fieldsMap));
|
||||
return new MetricResult(name, new Measurement(tagsMap, fieldsMap));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ def projectName = "sysmon-server"
|
|||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass.set('sysmon.server.Application')
|
||||
applicationDefaultJvmArgs = [ "-server", "-XX:+UseG1GC", "-Xmx128m" ]
|
||||
applicationDefaultJvmArgs = [ "-server", "-Xms64m", "-Xmx128m", "-XX:+UseG1GC" ]
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
Description=Sysmon Server Service
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
#User=nobody
|
||||
#Group=nobody
|
||||
TimeoutSec=20
|
||||
Restart=on-failure
|
||||
ExecStart=/opt/sysmon/server/bin/server
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -2,9 +2,7 @@ package sysmon.server;
|
|||
|
||||
import org.apache.camel.main.Main;
|
||||
import org.influxdb.InfluxDB;
|
||||
import org.influxdb.InfluxDBException;
|
||||
import org.influxdb.InfluxDBFactory;
|
||||
import org.influxdb.dto.Query;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -2,8 +2,6 @@ package sysmon.server;
|
|||
|
||||
import org.apache.camel.Exchange;
|
||||
import org.apache.camel.builder.RouteBuilder;
|
||||
import org.apache.camel.component.influxdb.InfluxDbConstants;
|
||||
import org.apache.camel.component.jackson.JacksonDataFormat;
|
||||
import org.apache.camel.model.rest.RestBindingMode;
|
||||
import org.apache.camel.spi.Registry;
|
||||
import sysmon.shared.MetricResult;
|
||||
|
@ -39,7 +37,7 @@ public class ServerRouteBuilder extends RouteBuilder {
|
|||
.route()
|
||||
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(202))
|
||||
.setHeader("Content-Type", constant("application/x-www-form-urlencoded"))
|
||||
.to("seda:inbound")
|
||||
.to("seda:inbound?discardWhenFull=true")
|
||||
.endRest();
|
||||
|
||||
fromF("seda:inbound?concurrentConsumers=%s", threads)
|
||||
|
|
|
@ -2,14 +2,18 @@ package sysmon.shared;
|
|||
|
||||
import org.pf4j.ExtensionPoint;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface MetricExtension extends ExtensionPoint {
|
||||
|
||||
boolean isEnabled();
|
||||
boolean isSupported();
|
||||
|
||||
String getName();
|
||||
String getProvides();
|
||||
String getDescription();
|
||||
|
||||
void setConfiguration(Map<String, Object> map);
|
||||
|
||||
MetricResult getMetrics() throws Exception;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package sysmon.shared;
|
|||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MetricResult implements Serializable {
|
||||
|
|
Loading…
Reference in a new issue