Testing plugin framework.

This commit is contained in:
Mark Nellemann 2021-05-01 14:44:55 +02:00
parent 7f1b714870
commit afbf506749
34 changed files with 851 additions and 570 deletions

View file

@ -20,25 +20,25 @@ dependencies {
testImplementation 'org.codehaus.groovy:groovy:3.0.7' testImplementation 'org.codehaus.groovy:groovy:3.0.7'
testImplementation 'org.spockframework:spock-core:2.0-M4-groovy-3.0' testImplementation 'org.spockframework:spock-core:2.0-M4-groovy-3.0'
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.slf4j:slf4j-api:1.7.30' testImplementation "org.slf4j:slf4j-api:${slf4jVersion}"
testImplementation project(':shared') testImplementation project(':shared')
implementation project(':shared') implementation project(':shared')
implementation 'org.slf4j:slf4j-api:1.7.30' implementation "org.slf4j:slf4j-api:${slf4jVersion}"
implementation 'org.slf4j:slf4j-simple:1.7.30' implementation "org.slf4j:slf4j-simple:${slf4jVersion}"
// https://mvnrepository.com/artifact/org.apache.camel/camel-core annotationProcessor(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}")
implementation group: 'org.apache.camel', name: 'camel-core', version: '3.7.3' implementation group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}"
implementation group: 'org.apache.camel', name: 'camel-main', version: '3.7.3'
implementation group: 'org.apache.camel', name: 'camel-bean', version: '3.7.3'
implementation group: 'org.apache.camel', name: 'camel-timer', version: '3.7.3'
implementation group: 'org.apache.camel', name: 'camel-stream', version: '3.7.3'
} }
application { application {
// Define the main class for the application. // Define the main class for the application.
mainClass = 'org.sysmon.agent.Application' mainClassName = 'org.sysmon.agent.Application'
}
run {
systemProperty 'pf4j.pluginsDir', '../plugins/test/'
} }
tasks.named('test') { tasks.named('test') {

View file

@ -3,24 +3,83 @@
*/ */
package org.sysmon.agent; package org.sysmon.agent;
import org.apache.camel.main.Main; import org.pf4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sysmon.shared.MetricExtension;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class Application { public class Application {
public static void main(String[] args) { private static final Logger log = LoggerFactory.getLogger(Application.class);
// use Camels Main class public static void main(String[] args) throws InterruptedException {
Main main = new Main();
// and add the routes (you can specify multiple classes) // create the plugin manager
main.configure().addRoutesBuilder(MyRouteBuilder.class); PluginManager pluginManager = new SysmonPluginManager(); // or "new ZipPluginManager() / new DefaultPluginManager()"
// now keep the application running until the JVM is terminated (ctrl + c or sigterm) // start and load all plugins of application
try { pluginManager.loadPlugins();
main.run(args); pluginManager.startPlugins();
} catch(Exception e) {
System.err.println(e.getMessage()); /*
final PluginManager pluginManager = new SysmonPluginManager() {
protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
extensionFinder.addServiceProviderExtensionFinder();
return extensionFinder;
}
};
pluginManager.loadPlugins();
pluginManager.startPlugins();
*/
/*
List<PluginWrapper> plugins = pluginManager.getPlugins();
for(PluginWrapper wrapper : plugins) {
log.info(">>> Plugin Description: " + wrapper.getDescriptor().getPluginDescription());
}
*/
List<MetricExtension> metricExtensions = pluginManager.getExtensions(MetricExtension.class);
log.info(String.format("Found %d extensions for extension point '%s':", metricExtensions.size(), MetricExtension.class.getName()));
for (MetricExtension plugin : metricExtensions) {
log.info(">>> " + plugin.getGreeting());
} }
AtomicBoolean keepRunning = new AtomicBoolean(true);
Thread shutdownHook = new Thread(() -> {
keepRunning.set(false);
pluginManager.stopPlugins();
System.out.println("Stopping sysmon, please wait ...");
});
Runtime.getRuntime().addShutdownHook(shutdownHook);
/*
do {
for (MetricExtension plugin : metricExtensions) {
// TODO: Find better way to avoid using plugins not working on runtime OS.
if(plugin.isSupported()) {
System.out.println(">>> " + plugin.getMetrics());
}
}
Thread.sleep(15000);
} while (keepRunning.get());
*/
} }
} }

View file

@ -1,26 +0,0 @@
package org.sysmon.agent;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sysmon.shared.MetricResult;
public class MetricProcessor implements Processor {
private final static Logger log = LoggerFactory.getLogger(MetricProcessor.class);
public void process(Exchange exchange) throws Exception {
MetricResult payload = exchange.getIn().getBody(MetricResult.class);
log.info(payload.toString());
// do something with the payload and/or exchange here
//exchange.getIn().setBody("Changed body");
// do something...
}
}

View file

@ -1,56 +0,0 @@
package org.sysmon.agent;
import org.apache.camel.builder.RouteBuilder;
import org.sysmon.agent.beans.ProcessorBeanAix;
import org.sysmon.agent.beans.ProcessorBeanLinux;
public class MyRouteBuilder extends RouteBuilder {
private static final String osName = System.getProperty("os.name").toLowerCase();
@Override
public void configure() throws Exception {
// TODO: Some smarter way to do this ?
if(osName.contains("linux")) {
// Linux specific beans
from("timer:collect?period=5000")
.bean(new ProcessorBeanLinux(), "getMetrics")
.to("seda:metrics");
} else if(osName.contains("aix")) {
// AIX specific beans
from("timer:collect?period=5000")
.bean(new ProcessorBeanAix(), "getMetrics")
.to("seda:metrics");
} else {
// Unsupported OS
throw new UnsupportedOperationException("OS not implemented: " + osName);
}
// TODO: Discover beans on classpath and setup accordingly ??
// Setup metrics measurement beans
from("timer:collect?period=5000")
.bean("memoryBean", "getMetrics")
.to("seda:metrics");
from("timer:collect?period=5000")
.bean("diskBean", "getMetrics")
.to("seda:metrics");
// TODO: Somehow combine all results in a format suitable for sending to a central REST endpoint
from("seda:metrics").process("metricProcessor");
}
}

View file

@ -0,0 +1,30 @@
package org.sysmon.agent;
import org.pf4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SysmonPluginManager extends DefaultPluginManager {
private static final Logger log = LoggerFactory.getLogger(SysmonPluginManager.class);
public SysmonPluginManager() {
super();
log.warn("SysmonPluginManager()");
}
@Override
protected PluginStatusProvider createPluginStatusProvider() {
log.warn("createPluginStatusProvider()");
return new SysmonPluginStatusProvider();
}
@Override
protected ExtensionFactory createExtensionFactory() {
log.warn("createExtensionFactory()");
return new SingletonExtensionFactory();
}
}

View file

@ -0,0 +1,57 @@
package org.sysmon.agent;
import org.pf4j.PluginStatusProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class SysmonPluginStatusProvider implements PluginStatusProvider {
private static final Logger log = LoggerFactory.getLogger(SysmonPluginStatusProvider.class);
private final List<String> enabledPlugins = new ArrayList<>();
private final List<String> disabledPlugins = new ArrayList<>();
public SysmonPluginStatusProvider() {
log.warn("SysmonPluginManager()");
}
@Override
public boolean isPluginDisabled(String pluginId) {
log.warn("isPluginDisabled() - " + pluginId);
if (disabledPlugins.contains(pluginId)) {
return true;
}
return !enabledPlugins.isEmpty() && !enabledPlugins.contains(pluginId);
}
@Override
public void disablePlugin(String pluginId) {
log.warn("disablePlugin() - " + pluginId);
if (isPluginDisabled(pluginId)) {
// do nothing
return;
}
}
@Override
public void enablePlugin(String pluginId) {
log.warn("enablePlugin() - " + pluginId);
if (!isPluginDisabled(pluginId)) {
// do nothing
return;
}
}
}

View file

@ -1,206 +0,0 @@
package org.sysmon.agent.beans;
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.List;
import org.apache.camel.spi.Configurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sysmon.shared.MetricBean;
import org.sysmon.shared.MetricMeasurement;
import org.sysmon.shared.MetricResult;
@Configurer
public class DiskBean implements MetricBean {
private final static Logger log = LoggerFactory.getLogger(DiskBean.class);
private List<LinuxDiskStat> currentDiskStats;
private List<LinuxDiskStat> previousDiskStats;
@Override
public MetricResult getMetrics() {
MetricResult result = new MetricResult("disk");
try {
copyCurrentValues();
readProcFile();
result.setMeasurementList(calculate());
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private void readProcFile() throws IOException {
currentDiskStats = new ArrayList<>();
List<String> allLines = Files.readAllLines(Paths.get("/proc/diskstats"), StandardCharsets.UTF_8);
for(String line : allLines) {
currentDiskStats.add(new LinuxDiskStat(line));
}
}
private void copyCurrentValues() {
if(currentDiskStats != null && currentDiskStats.size() > 0) {
previousDiskStats = new ArrayList<>(currentDiskStats);
}
}
private List<MetricMeasurement> calculate() {
List<MetricMeasurement> measurementList = new ArrayList<>();
if(previousDiskStats == null || previousDiskStats.size() != currentDiskStats.size()) {
return measurementList;
}
for(int i = 0; i < currentDiskStats.size(); i++) {
LinuxDiskStat curStat = currentDiskStats.get(i);
LinuxDiskStat preStat = previousDiskStats.get(i);
if(curStat.device.startsWith("loop")) {
continue;
}
long timeSpendDoingIo = curStat.timeSpentOnIo - preStat.timeSpentOnIo;
// TODO: Calculate differences for wanted disk io stats
measurementList.add(new MetricMeasurement(curStat.getDevice() + "-iotime", timeSpendDoingIo));
}
return measurementList;
}
public static class LinuxDiskStat {
/*
== ===================================
1 major number
2 minor mumber
3 device name
4 reads completed successfully
5 reads merged
6 sectors read
7 time spent reading (ms)
8 writes completed
9 writes merged
10 sectors written
11 time spent writing (ms)
12 I/Os currently in progress
13 time spent doing I/Os (ms)
14 weighted time spent doing I/Os (ms)
== ===================================
Kernel 4.18+ appends four more fields for discard
tracking putting the total at 18:
== ===================================
15 discards completed successfully
16 discards merged
17 sectors discarded
18 time spent discarding
== ===================================
Kernel 5.5+ appends two more fields for flush requests:
== =====================================
19 flush requests completed successfully
20 time spent flushing
== =====================================
*/
private final int major;
private final int minor;
private final String device; // device name
private final Long readsCompleted; // successfully
private final Long readsMerged;
private final Long sectorsRead; // 512 bytes pr. sector
private final Long timeSpentReading; // ms
private final Long writesCompleted; // successfully
private final Long writesMerged;
private final Long sectorsWritten; // 512 bytes pr. sector
private final Long timeSpentWriting; // ms
private final Long ioInProgress;
private final Long timeSpentOnIo; // ms
private final Long timeSpentOnIoWeighted;
private final Long discardsCompleted; // successfully
private final Long discardsMerged;
private final Long sectorsDiscarded; // 512 bytes pr. sector
private final Long timeSpentDiscarding; // ms
private final Long flushRequestsCompleted;
private final Long timeSpentFlushing; // ms
LinuxDiskStat(String procString) {
String[] splitStr = procString.trim().split("\\s+");
if(splitStr.length < 14) {
throw new UnsupportedOperationException("Linux proc DISK string error: " + procString);
}
this.major = Integer.parseInt(splitStr[0]);
this.minor = Integer.parseInt(splitStr[1]);
this.device = splitStr[2];
this.readsCompleted = Long.parseLong(splitStr[3]);
this.readsMerged = Long.parseLong(splitStr[4]);
this.sectorsRead = Long.parseLong(splitStr[5]);
this.timeSpentReading = Long.parseLong(splitStr[6]);
this.writesCompleted = Long.parseLong(splitStr[7]);
this.writesMerged = Long.parseLong(splitStr[8]);
this.sectorsWritten = Long.parseLong(splitStr[9]);
this.timeSpentWriting = Long.parseLong(splitStr[10]);
this.ioInProgress = Long.parseLong(splitStr[11]);
this.timeSpentOnIo = Long.parseLong(splitStr[12]);
this.timeSpentOnIoWeighted = Long.parseLong(splitStr[13]);
if(splitStr.length >= 18) {
this.discardsCompleted = Long.parseLong(splitStr[10]);
this.discardsMerged = Long.parseLong(splitStr[11]);
this.sectorsDiscarded = Long.parseLong(splitStr[12]);
this.timeSpentDiscarding = Long.parseLong(splitStr[13]);
} else {
this.discardsCompleted = null;
this.discardsMerged = null;
this.sectorsDiscarded = null;
this.timeSpentDiscarding = null;
}
if(splitStr.length == 20) {
this.flushRequestsCompleted = Long.parseLong(splitStr[14]);
this.timeSpentFlushing = Long.parseLong(splitStr[15]);
} else {
this.flushRequestsCompleted = null;
this.timeSpentFlushing = null;
}
}
public String getDevice() {
return device;
}
}
}

View file

@ -1,188 +0,0 @@
package org.sysmon.agent.beans;
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.List;
import org.apache.camel.spi.Configurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sysmon.shared.MetricBean;
import org.sysmon.shared.MetricMeasurement;
import org.sysmon.shared.MetricResult;
@Configurer
public class ProcessorBeanLinux implements MetricBean {
private final static Logger log = LoggerFactory.getLogger(ProcessorBeanLinux.class);
private List<LinuxProcessorStat> currentProcessorStats;
private List<LinuxProcessorStat> previousProcessorStats;
@Override
public MetricResult getMetrics() {
MetricResult result = new MetricResult("processor");
try {
copyCurrentValues();
readProcFile();
result.setMeasurementList(calculate());
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private void readProcFile() throws IOException {
currentProcessorStats = new ArrayList<>();
List<String> allLines = Files.readAllLines(Paths.get("/proc/stat"), StandardCharsets.UTF_8);
for(String line : allLines) {
if(line.startsWith("cpu")) {
log.debug(line);
currentProcessorStats.add(new LinuxProcessorStat(line));
}
}
}
private void copyCurrentValues() {
if(currentProcessorStats != null && currentProcessorStats.size() > 0) {
previousProcessorStats = new ArrayList<>(currentProcessorStats);
}
}
private List<MetricMeasurement> calculate() {
List<MetricMeasurement> measurementList = new ArrayList<>();
if(previousProcessorStats == null || previousProcessorStats.size() != currentProcessorStats.size()) {
return measurementList;
}
for(int i = 0; i < currentProcessorStats.size(); i++) {
LinuxProcessorStat curStat = currentProcessorStats.get(i);
LinuxProcessorStat preStat = previousProcessorStats.get(i);
long workTimeDiff = curStat.getCombinedTime() - preStat.getCombinedTime();
long idleTimeDiff = curStat.getCombinedIdleTime() - preStat.getCombinedIdleTime();
float percentUsage = (float) (workTimeDiff - idleTimeDiff) / workTimeDiff;
Integer pct = (int) (percentUsage * 100);
measurementList.add(new MetricMeasurement(curStat.getCpuName(), pct));
}
return measurementList;
}
public static class LinuxProcessorStat {
private final String cpuName;
private final Long userTime;
private final Long niceTime;
private final Long systemTime;
private final Long idleTime;
private final Long ioWaitTime;
private final Long irqTime;
private final Long softIrqTime;
private final Long stealTime;
private final Long guestTime;
private final Long guestNiceTime;
LinuxProcessorStat(String procString) {
String[] splitStr = procString.trim().split("\\s+");
if(splitStr.length != 11) {
throw new UnsupportedOperationException("Linux proc CPU string error: " + procString);
}
this.cpuName = splitStr[0];
this.userTime = Long.parseLong(splitStr[1]);
this.niceTime = Long.parseLong(splitStr[2]);
this.systemTime = Long.parseLong(splitStr[3]);
this.idleTime = Long.parseLong(splitStr[4]);
this.ioWaitTime = Long.parseLong(splitStr[5]);
this.irqTime = Long.parseLong(splitStr[6]);
this.softIrqTime = Long.parseLong(splitStr[7]);
this.stealTime = Long.parseLong(splitStr[8]);
this.guestTime = Long.parseLong(splitStr[9]);
this.guestNiceTime = Long.parseLong(splitStr[10]);
}
public String getCpuName() {
return cpuName;
}
public Long getUserTime() {
return userTime;
}
public Long getNiceTime() {
return niceTime;
}
public Long getSystemTime() {
return systemTime;
}
public Long getIdleTime() {
return idleTime;
}
public Long getIoWaitTime() {
return ioWaitTime;
}
public Long getIrqTime() {
return irqTime;
}
public Long getSoftIrqTime() {
return softIrqTime;
}
public Long getStealTime() {
return stealTime;
}
public Long getGuestTime() {
return guestTime;
}
public Long getGuestNiceTime() {
return guestNiceTime;
}
public Long getCombinedIdleTime() {
return idleTime + ioWaitTime;
}
public Long getCombinedWorkTime() {
return userTime + niceTime + systemTime + irqTime + softIrqTime + stealTime + guestTime + guestNiceTime;
}
public Long getCombinedTime() {
return getCombinedIdleTime() + getCombinedWorkTime();
}
}
}

View file

@ -1,45 +0,0 @@
## ---------------------------------------------------------------------------
## Licensed to the Apache Software Foundation (ASF) under one or more
## contributor license agreements. See the NOTICE file distributed with
## this work for additional information regarding copyright ownership.
## The ASF licenses this file to You 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.
## ---------------------------------------------------------------------------
# to configure camel main
# here you can configure options on camel main (see MainConfigurationProperties class)
camel.main.name = SysMonAgent
# enable tracing
### camel.main.tracing = true
# bean introspection to log reflection based configuration
camel.main.beanIntrospectionExtendedStatistics=true
camel.main.beanIntrospectionLoggingLevel=INFO
# run in lightweight mode to be tiny as possible
camel.main.lightweight = true
# and eager load classes
#camel.main.eager-classloading = true
# use object pooling to reduce JVM garbage collection
#camel.main.exchange-factory = pooled
#camel.main.exchange-factory-statistics-enabled = true
# can be used to not start the route
# camel.main.auto-startup = false
# configure beans
camel.beans.metricProcessor = #class:org.sysmon.agent.MetricProcessor
camel.beans.diskBean = #class:org.sysmon.agent.beans.DiskBean
camel.beans.memoryBean = #class:org.sysmon.agent.beans.MemoryBean
#camel.beans.processorBean = #class:org.sysmon.agent.beans.ProcessorBean

View file

@ -0,0 +1,6 @@
org.slf4j.simpleLogger.logFile=System.err
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.showShortLogName=true
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS
org.slf4j.simpleLogger.levelInBrackets=true
org.slf4j.simpleLogger.defaultLogLevel=info

14
build.gradle Normal file
View file

@ -0,0 +1,14 @@
subprojects {
apply plugin: 'java'
dependencies {
testImplementation project(':shared')
}
repositories {
mavenLocal()
mavenCentral()
}
}

2
gradle.properties Normal file
View file

@ -0,0 +1,2 @@
pf4jVersion=3.6.0
slf4jVersion=1.7.30

1
plugins/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
test

32
plugins/build.gradle Normal file
View file

@ -0,0 +1,32 @@
subprojects {
apply plugin: 'java-library'
jar {
manifest {
attributes(
'Plugin-Id' : "${pluginId}",
'Plugin-Class' : "${pluginClass}",
'Plugin-Version' : "${pluginVersion}",
'Plugin-Provider' : "${pluginProvider}",
'Plugin-Description': "${pluginDescription}"
)
}
}
task copyJar(type: Copy, dependsOn:[jar]) {
from jar // here it automatically reads jar file produced from jar task
into "../test/"
}
tasks.assemble.dependsOn {
copyJar
}
}
task customCleanUp(type:Delete) {
delete "test"
//delete files("${buildDir}/test/*.jar")
}
tasks.clean.dependsOn(tasks.customCleanUp)

View file

@ -0,0 +1,10 @@
dependencies {
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already!
implementation project(':shared')
implementation(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}") {
exclude(group: "org.slf4j")
}
annotationProcessor(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}")
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
}

View file

@ -0,0 +1,6 @@
pluginId=sysmon-aix
pluginClass=org.sysmon.plugins.sysmon_aix.AixPlugin
pluginVersion=0.0.1
pluginProvider=System Monitor
pluginDependencies=
pluginDescription=Collects AIX OS metrics.

View file

@ -0,0 +1,25 @@
package org.sysmon.plugins.sysmon_aix;
import org.pf4j.Extension;
import org.sysmon.shared.MetricExtension;
import org.sysmon.shared.MetricResult;
@Extension
public class AixDiskExtension implements MetricExtension {
@Override
public boolean isSupported() {
return System.getProperty("os.name").toLowerCase().contains("aix");
}
@Override
public String getGreeting() {
return "Welcome from AIX DiskMetric";
}
@Override
public MetricResult getMetrics() {
return null;
}
}

View file

@ -0,0 +1,36 @@
package org.sysmon.plugins.sysmon_aix;
import org.pf4j.PluginState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
public class AixPlugin extends Plugin {
private static final Logger log = LoggerFactory.getLogger(AixPlugin.class);
public AixPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void start() {
if(!System.getProperty("os.name").toLowerCase().contains("aix")) {
log.warn("start() - Plugin not supported here.");
wrapper.setPluginState(PluginState.DISABLED);
wrapper.getPlugin().stop();
} else {
log.info("start() - Good to go.");
}
}
@Override
public void stop() {
log.debug("stop()");
}
}

View file

@ -1,24 +1,32 @@
package org.sysmon.agent.beans; package org.sysmon.plugins.sysmon_aix;
import org.slf4j.Logger; import org.pf4j.Extension;
import org.slf4j.LoggerFactory; import org.sysmon.shared.MetricExtension;
import org.sysmon.shared.MetricBean;
import org.sysmon.shared.MetricResult; import org.sysmon.shared.MetricResult;
public class ProcessorBeanAix implements MetricBean { @Extension
public class AixProcessorExtension implements MetricExtension {
private final static Logger log = LoggerFactory.getLogger(ProcessorBeanAix.class); @Override
public boolean isSupported() {
return System.getProperty("os.name").toLowerCase().contains("aix");
}
@Override
public String getGreeting() {
return "Welcome from AIX ProcessorMetric";
}
@Override @Override
public MetricResult getMetrics() { public MetricResult getMetrics() {
log.warn("TODO: AIX processor stat.");
return null; return null;
} }
} }
/* /*

View file

@ -0,0 +1,10 @@
dependencies {
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already!
implementation project(':shared')
implementation(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}") {
exclude(group: "org.slf4j")
}
annotationProcessor(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}")
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
}

View file

@ -0,0 +1,7 @@
pluginId=sysmon-linux
pluginClass=org.sysmon.plugins.sysmon_linux.LinuxPlugin
pluginVersion=0.0.1
pluginProvider=System Monitor
pluginDependencies=
pluginDescription=Collects Linux OS metrics.

View file

@ -0,0 +1,96 @@
package org.sysmon.plugins.sysmon_linux;
import org.pf4j.Extension;
import org.sysmon.shared.MetricExtension;
import org.sysmon.shared.MetricMeasurement;
import org.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.List;
@Extension
public class LinuxDiskExtension implements MetricExtension {
private List<LinuxDiskStat> currentDiskStats;
private List<LinuxDiskStat> previousDiskStats;
@Override
public boolean isSupported() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
@Override
public String getGreeting() {
return "Welcome from Linux DiskMetric";
}
@Override
public MetricResult getMetrics() {
MetricResult result = new MetricResult("disk");
try {
copyCurrentValues();
readProcFile();
result.setMeasurementList(calculate());
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private void readProcFile() throws IOException {
currentDiskStats = new ArrayList<>();
List<String> allLines = Files.readAllLines(Paths.get("/proc/diskstats"), StandardCharsets.UTF_8);
for(String line : allLines) {
currentDiskStats.add(new LinuxDiskStat(line));
}
}
private void copyCurrentValues() {
if(currentDiskStats != null && currentDiskStats.size() > 0) {
previousDiskStats = new ArrayList<>(currentDiskStats);
}
}
private List<MetricMeasurement> calculate() {
List<MetricMeasurement> measurementList = new ArrayList<>();
if(previousDiskStats == null || previousDiskStats.size() != currentDiskStats.size()) {
return measurementList;
}
for(int i = 0; i < currentDiskStats.size(); i++) {
LinuxDiskStat curStat = currentDiskStats.get(i);
LinuxDiskStat preStat = previousDiskStats.get(i);
if(curStat.getDevice().startsWith("loop")) {
continue;
}
long timeSpendDoingIo = curStat.getTimeSpentOnIo() - preStat.getTimeSpentOnIo();
// TODO: Calculate differences for wanted disk io stats
measurementList.add(new MetricMeasurement(curStat.getDevice() + "-iotime", timeSpendDoingIo));
}
return measurementList;
}
}

View file

@ -0,0 +1,117 @@
package org.sysmon.plugins.sysmon_linux;
public class LinuxDiskStat {
/*
== ===================================
1 major number
2 minor mumber
3 device name
4 reads completed successfully
5 reads merged
6 sectors read
7 time spent reading (ms)
8 writes completed
9 writes merged
10 sectors written
11 time spent writing (ms)
12 I/Os currently in progress
13 time spent doing I/Os (ms)
14 weighted time spent doing I/Os (ms)
== ===================================
Kernel 4.18+ appends four more fields for discard
tracking putting the total at 18:
== ===================================
15 discards completed successfully
16 discards merged
17 sectors discarded
18 time spent discarding
== ===================================
Kernel 5.5+ appends two more fields for flush requests:
== =====================================
19 flush requests completed successfully
20 time spent flushing
== =====================================
*/
private final int major;
private final int minor;
private final String device; // device name
private final Long readsCompleted; // successfully
private final Long readsMerged;
private final Long sectorsRead; // 512 bytes pr. sector
private final Long timeSpentReading; // ms
private final Long writesCompleted; // successfully
private final Long writesMerged;
private final Long sectorsWritten; // 512 bytes pr. sector
private final Long timeSpentWriting; // ms
private final Long ioInProgress;
private final Long timeSpentOnIo; // ms
private final Long timeSpentOnIoWeighted;
private final Long discardsCompleted; // successfully
private final Long discardsMerged;
private final Long sectorsDiscarded; // 512 bytes pr. sector
private final Long timeSpentDiscarding; // ms
private final Long flushRequestsCompleted;
private final Long timeSpentFlushing; // ms
LinuxDiskStat(String procString) {
String[] splitStr = procString.trim().split("\\s+");
if(splitStr.length < 14) {
throw new UnsupportedOperationException("Linux proc DISK string error: " + procString);
}
this.major = Integer.parseInt(splitStr[0]);
this.minor = Integer.parseInt(splitStr[1]);
this.device = splitStr[2];
this.readsCompleted = Long.parseLong(splitStr[3]);
this.readsMerged = Long.parseLong(splitStr[4]);
this.sectorsRead = Long.parseLong(splitStr[5]);
this.timeSpentReading = Long.parseLong(splitStr[6]);
this.writesCompleted = Long.parseLong(splitStr[7]);
this.writesMerged = Long.parseLong(splitStr[8]);
this.sectorsWritten = Long.parseLong(splitStr[9]);
this.timeSpentWriting = Long.parseLong(splitStr[10]);
this.ioInProgress = Long.parseLong(splitStr[11]);
this.timeSpentOnIo = Long.parseLong(splitStr[12]);
this.timeSpentOnIoWeighted = Long.parseLong(splitStr[13]);
if(splitStr.length >= 18) {
this.discardsCompleted = Long.parseLong(splitStr[10]);
this.discardsMerged = Long.parseLong(splitStr[11]);
this.sectorsDiscarded = Long.parseLong(splitStr[12]);
this.timeSpentDiscarding = Long.parseLong(splitStr[13]);
} else {
this.discardsCompleted = null;
this.discardsMerged = null;
this.sectorsDiscarded = null;
this.timeSpentDiscarding = null;
}
if(splitStr.length == 20) {
this.flushRequestsCompleted = Long.parseLong(splitStr[14]);
this.timeSpentFlushing = Long.parseLong(splitStr[15]);
} else {
this.flushRequestsCompleted = null;
this.timeSpentFlushing = null;
}
}
public String getDevice() {
return device;
}
public Long getTimeSpentOnIo() {
return timeSpentOnIo;
}
}

View file

@ -1,4 +1,9 @@
package org.sysmon.agent.beans; package org.sysmon.plugins.sysmon_linux;
import org.pf4j.Extension;
import org.sysmon.shared.MetricExtension;
import org.sysmon.shared.MetricMeasurement;
import org.sysmon.shared.MetricResult;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -9,23 +14,25 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.camel.spi.Configurer; @Extension
import org.slf4j.Logger; public class LinuxMemoryExtension implements MetricExtension {
import org.slf4j.LoggerFactory;
import org.sysmon.shared.MetricBean;
import org.sysmon.shared.MetricMeasurement;
import org.sysmon.shared.MetricResult;
@Configurer
public class MemoryBean implements MetricBean {
private final static Logger log = LoggerFactory.getLogger(MemoryBean.class);
private final Pattern pattern = Pattern.compile("^([a-zA-Z]+):\\s+(\\d+)\\s+"); private final Pattern pattern = Pattern.compile("^([a-zA-Z]+):\\s+(\\d+)\\s+");
@Override
public boolean isSupported() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
@Override
public String getGreeting() {
return "Welcome from Linux MemoryMetric";
}
@Override @Override
public MetricResult getMetrics() { public MetricResult getMetrics() {
MetricResult result = new MetricResult("memory"); MetricResult result = new MetricResult("memory");
try { try {
result.setMeasurementList(readProcFile()); result.setMeasurementList(readProcFile());
@ -37,17 +44,16 @@ public class MemoryBean implements MetricBean {
} }
private List<MetricMeasurement> readProcFile() throws IOException { private List<MetricMeasurement> readProcFile() throws IOException {
List<MetricMeasurement> measurementList = new ArrayList<>(); List<MetricMeasurement> measurementList = new ArrayList<>();
List<String> allLines = Files.readAllLines(Paths.get("/proc/meminfo"), StandardCharsets.UTF_8); List<String> allLines = Files.readAllLines(Paths.get("/proc/meminfo"), StandardCharsets.UTF_8);
for(String line : allLines) { for (String line : allLines) {
if(line.startsWith("Mem")) { if (line.startsWith("Mem")) {
Matcher matcher = pattern.matcher(line); Matcher matcher = pattern.matcher(line);
if(matcher.find() && matcher.groupCount() == 2) { if (matcher.find() && matcher.groupCount() == 2) {
measurementList.add(new MetricMeasurement(matcher.group(1), matcher.group(2))); measurementList.add(new MetricMeasurement(matcher.group(1), matcher.group(2)));
} }
} }

View file

@ -0,0 +1,35 @@
package org.sysmon.plugins.sysmon_linux;
import org.pf4j.PluginState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
public class LinuxPlugin extends Plugin {
private static final Logger log = LoggerFactory.getLogger(LinuxPlugin.class);
public LinuxPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void start() {
if(!System.getProperty("os.name").toLowerCase().contains("linux")) {
log.warn("start() - Plugin not supported here.");
wrapper.setPluginState(PluginState.DISABLED);
} else {
log.info("start() - Good to go.");
}
}
@Override
public void stop() {
log.debug("stop()");
}
}

View file

@ -0,0 +1,97 @@
package org.sysmon.plugins.sysmon_linux;
import org.pf4j.Extension;
import org.sysmon.shared.MetricExtension;
import org.sysmon.shared.MetricMeasurement;
import org.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.List;
@Extension
public class LinuxProcessorExtension implements MetricExtension {
private List<LinuxProcessorStat> currentProcessorStats;
private List<LinuxProcessorStat> previousProcessorStats;
@Override
public boolean isSupported() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
@Override
public String getGreeting() {
return "Welcome from Linux ProcessorMetric";
}
@Override
public MetricResult getMetrics() {
MetricResult result = new MetricResult("processor");
try {
copyCurrentValues();
readProcFile();
result.setMeasurementList(calculate());
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private void readProcFile() throws IOException {
currentProcessorStats = new ArrayList<>();
List<String> allLines = Files.readAllLines(Paths.get("/proc/stat"), StandardCharsets.UTF_8);
for(String line : allLines) {
if(line.startsWith("cpu")) {
currentProcessorStats.add(new LinuxProcessorStat(line));
}
}
}
private void copyCurrentValues() {
if(currentProcessorStats != null && currentProcessorStats.size() > 0) {
previousProcessorStats = new ArrayList<>(currentProcessorStats);
}
}
private List<MetricMeasurement> calculate() {
List<MetricMeasurement> measurementList = new ArrayList<>();
if(previousProcessorStats == null || previousProcessorStats.size() != currentProcessorStats.size()) {
return measurementList;
}
for(int i = 0; i < currentProcessorStats.size(); i++) {
LinuxProcessorStat curStat = currentProcessorStats.get(i);
LinuxProcessorStat preStat = previousProcessorStats.get(i);
long workTimeDiff = curStat.getCombinedTime() - preStat.getCombinedTime();
long idleTimeDiff = curStat.getCombinedIdleTime() - preStat.getCombinedIdleTime();
float percentUsage = (float) (workTimeDiff - idleTimeDiff) / workTimeDiff;
Integer pct = (int) (percentUsage * 100);
measurementList.add(new MetricMeasurement(curStat.getCpuName(), pct));
}
return measurementList;
}
}

View file

@ -0,0 +1,95 @@
package org.sysmon.plugins.sysmon_linux;
public class LinuxProcessorStat {
private final String cpuName;
private final Long userTime;
private final Long niceTime;
private final Long systemTime;
private final Long idleTime;
private final Long ioWaitTime;
private final Long irqTime;
private final Long softIrqTime;
private final Long stealTime;
private final Long guestTime;
private final Long guestNiceTime;
LinuxProcessorStat(String procString) {
String[] splitStr = procString.trim().split("\\s+");
if(splitStr.length != 11) {
throw new UnsupportedOperationException("Linux proc CPU string error: " + procString);
}
this.cpuName = splitStr[0];
this.userTime = Long.parseLong(splitStr[1]);
this.niceTime = Long.parseLong(splitStr[2]);
this.systemTime = Long.parseLong(splitStr[3]);
this.idleTime = Long.parseLong(splitStr[4]);
this.ioWaitTime = Long.parseLong(splitStr[5]);
this.irqTime = Long.parseLong(splitStr[6]);
this.softIrqTime = Long.parseLong(splitStr[7]);
this.stealTime = Long.parseLong(splitStr[8]);
this.guestTime = Long.parseLong(splitStr[9]);
this.guestNiceTime = Long.parseLong(splitStr[10]);
}
public String getCpuName() {
return cpuName;
}
public Long getUserTime() {
return userTime;
}
public Long getNiceTime() {
return niceTime;
}
public Long getSystemTime() {
return systemTime;
}
public Long getIdleTime() {
return idleTime;
}
public Long getIoWaitTime() {
return ioWaitTime;
}
public Long getIrqTime() {
return irqTime;
}
public Long getSoftIrqTime() {
return softIrqTime;
}
public Long getStealTime() {
return stealTime;
}
public Long getGuestTime() {
return guestTime;
}
public Long getGuestNiceTime() {
return guestNiceTime;
}
public Long getCombinedIdleTime() {
return idleTime + ioWaitTime;
}
public Long getCombinedWorkTime() {
return userTime + niceTime + systemTime + irqTime + softIrqTime + stealTime + guestTime + guestNiceTime;
}
public Long getCombinedTime() {
return getCombinedIdleTime() + getCombinedWorkTime();
}
}

View file

@ -8,4 +8,12 @@
*/ */
rootProject.name = 'sysmon' rootProject.name = 'sysmon'
include('shared', 'agent') include('shared', 'agent', 'plugins')
new File(rootDir, "plugins").listFiles().each {
if (it.directory && new File(it, 'build.gradle').exists()) {
include ":plugins:${it.name}"
}
}

View file

@ -20,10 +20,13 @@ dependencies {
testImplementation 'org.codehaus.groovy:groovy:3.0.7' testImplementation 'org.codehaus.groovy:groovy:3.0.7'
testImplementation 'org.spockframework:spock-core:2.0-M4-groovy-3.0' testImplementation 'org.spockframework:spock-core:2.0-M4-groovy-3.0'
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.slf4j:slf4j-api:1.7.30' testImplementation "org.slf4j:slf4j-api:${slf4jVersion}"
implementation 'org.slf4j:slf4j-api:1.7.30' implementation "org.slf4j:slf4j-api:${slf4jVersion}"
implementation 'org.slf4j:slf4j-simple:1.7.30' implementation "org.slf4j:slf4j-simple:${slf4jVersion}"
//annotationProcessor(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}")
implementation group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}"
} }

View file

@ -0,0 +1,5 @@
package org.sysmon.shared;
public interface AixPlugin {
}

View file

@ -1,5 +1,7 @@
package org.sysmon.shared; package org.sysmon.shared;
import java.util.concurrent.Callable;
public interface MetricBean { public interface MetricBean {
public MetricResult getMetrics(); public MetricResult getMetrics();

View file

@ -0,0 +1,11 @@
package org.sysmon.shared;
import org.pf4j.ExtensionPoint;
public interface MetricExtension extends ExtensionPoint {
boolean isSupported();
String getGreeting();
MetricResult getMetrics();
}

View file

@ -20,7 +20,7 @@ public class MetricResult {
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(name).append("\n"); StringBuilder sb = new StringBuilder(String.format("%s - %s\n", timestamp.toString(), name));
for(MetricMeasurement mm : measurementList) { for(MetricMeasurement mm : measurementList) {
sb.append(mm.toString()).append("\n"); sb.append(mm.toString()).append("\n");
} }

View file

@ -0,0 +1,24 @@
package org.sysmon.shared;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SysmonPlugin extends Plugin {
private static final Logger log = LoggerFactory.getLogger(SysmonPlugin.class);
public SysmonPlugin(PluginWrapper wrapper) {
super(wrapper);
log.warn("SysmonPlugin");
}
@Override
public void start() {
log.warn("start();");
}
}